committed by
german77
30 changed files with 1882 additions and 739 deletions
-
5src/core/hle/service/hid/hid.cpp
-
196src/core/hle/service/hid/hid_debug_server.cpp
-
15src/core/hle/service/hid/hid_debug_server.h
-
24src/core/hle/service/hid/hid_server.cpp
-
64src/core/hle/service/hid/hid_system_server.cpp
-
3src/core/hle/service/hid/hid_system_server.h
-
4src/core/hle/service/set/setting_formats/system_settings.h
-
37src/core/hle/service/set/system_settings_server.cpp
-
4src/core/hle/service/set/system_settings_server.h
-
8src/frontend_common/config.cpp
-
7src/hid_core/CMakeLists.txt
-
8src/hid_core/hid_result.h
-
28src/hid_core/hid_types.h
-
61src/hid_core/resource_manager.cpp
-
89src/hid_core/resource_manager.h
-
8src/hid_core/resources/applet_resource.h
-
3src/hid_core/resources/npad/npad.cpp
-
4src/hid_core/resources/npad/npad.h
-
367src/hid_core/resources/touch_screen/gesture.cpp
-
87src/hid_core/resources/touch_screen/gesture.h
-
260src/hid_core/resources/touch_screen/gesture_handler.cpp
-
55src/hid_core/resources/touch_screen/gesture_handler.h
-
77src/hid_core/resources/touch_screen/gesture_types.h
-
209src/hid_core/resources/touch_screen/touch_screen.cpp
-
71src/hid_core/resources/touch_screen/touch_screen.h
-
114src/hid_core/resources/touch_screen/touch_screen_driver.cpp
-
47src/hid_core/resources/touch_screen/touch_screen_driver.h
-
579src/hid_core/resources/touch_screen/touch_screen_resource.cpp
-
126src/hid_core/resources/touch_screen/touch_screen_resource.h
-
61src/hid_core/resources/touch_screen/touch_types.h
@ -1,366 +1,53 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include "common/math_util.h"
|
|||
#include "common/settings.h"
|
|||
#include "core/frontend/emu_window.h"
|
|||
#include "hid_core/frontend/emulated_console.h"
|
|||
#include "hid_core/hid_core.h"
|
|||
#include "hid_core/resources/applet_resource.h"
|
|||
#include "hid_core/resources/shared_memory_format.h"
|
|||
#include "hid_core/resources/touch_screen/gesture.h"
|
|||
#include "hid_core/resources/touch_screen/touch_screen_resource.h"
|
|||
|
|||
namespace Service::HID { |
|||
// HW is around 700, value is set to 400 to make it easier to trigger with mouse
|
|||
constexpr f32 swipe_threshold = 400.0f; // Threshold in pixels/s
|
|||
constexpr f32 angle_threshold = 0.015f; // Threshold in radians
|
|||
constexpr f32 pinch_threshold = 0.5f; // Threshold in pixels
|
|||
constexpr f32 press_delay = 0.5f; // Time in seconds
|
|||
constexpr f32 double_tap_delay = 0.35f; // Time in seconds
|
|||
|
|||
constexpr f32 Square(s32 num) { |
|||
return static_cast<f32>(num * num); |
|||
} |
|||
Gesture::Gesture(std::shared_ptr<TouchResource> resource) : touch_resource{resource} {} |
|||
|
|||
Gesture::Gesture(Core::HID::HIDCore& hid_core_) : ControllerBase(hid_core_) { |
|||
console = hid_core.GetEmulatedConsole(); |
|||
} |
|||
Gesture::~Gesture() = default; |
|||
|
|||
void Gesture::OnInit() { |
|||
std::scoped_lock shared_lock{*shared_mutex}; |
|||
const u64 aruid = applet_resource->GetActiveAruid(); |
|||
auto* data = applet_resource->GetAruidData(aruid); |
|||
|
|||
if (data == nullptr || !data->flag.is_assigned) { |
|||
return; |
|||
} |
|||
|
|||
shared_memory = &data->shared_memory_format->gesture; |
|||
shared_memory->gesture_lifo.buffer_count = 0; |
|||
shared_memory->gesture_lifo.buffer_tail = 0; |
|||
force_update = true; |
|||
} |
|||
|
|||
void Gesture::OnRelease() {} |
|||
|
|||
void Gesture::OnUpdate(const Core::Timing::CoreTiming& core_timing) { |
|||
std::scoped_lock shared_lock{*shared_mutex}; |
|||
const u64 aruid = applet_resource->GetActiveAruid(); |
|||
auto* data = applet_resource->GetAruidData(aruid); |
|||
|
|||
if (data == nullptr || !data->flag.is_assigned) { |
|||
return; |
|||
} |
|||
|
|||
shared_memory = &data->shared_memory_format->gesture; |
|||
|
|||
if (!IsControllerActivated()) { |
|||
shared_memory->gesture_lifo.buffer_count = 0; |
|||
shared_memory->gesture_lifo.buffer_tail = 0; |
|||
return; |
|||
} |
|||
|
|||
ReadTouchInput(); |
|||
|
|||
GestureProperties gesture = GetGestureProperties(); |
|||
f32 time_difference = |
|||
static_cast<f32>(shared_memory->gesture_lifo.timestamp - last_update_timestamp) / |
|||
(1000 * 1000 * 1000); |
|||
|
|||
// Only update if necessary
|
|||
if (!ShouldUpdateGesture(gesture, time_difference)) { |
|||
return; |
|||
} |
|||
|
|||
last_update_timestamp = shared_memory->gesture_lifo.timestamp; |
|||
UpdateGestureSharedMemory(gesture, time_difference); |
|||
} |
|||
|
|||
void Gesture::ReadTouchInput() { |
|||
if (!Settings::values.touchscreen.enabled) { |
|||
fingers = {}; |
|||
return; |
|||
} |
|||
|
|||
const auto touch_status = console->GetTouch(); |
|||
for (std::size_t id = 0; id < fingers.size(); ++id) { |
|||
fingers[id] = touch_status[id]; |
|||
} |
|||
} |
|||
|
|||
bool Gesture::ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
if (force_update) { |
|||
force_update = false; |
|||
return true; |
|||
} |
|||
|
|||
// Update if coordinates change
|
|||
for (size_t id = 0; id < MAX_POINTS; id++) { |
|||
if (gesture.points[id] != last_gesture.points[id]) { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
// Update on press and hold event after 0.5 seconds
|
|||
if (last_entry.type == GestureType::Touch && last_entry.point_count == 1 && |
|||
time_difference > press_delay) { |
|||
return enable_press_and_tap; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
void Gesture::UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference) { |
|||
GestureType type = GestureType::Idle; |
|||
GestureAttribute attributes{}; |
|||
|
|||
const auto& last_entry = shared_memory->gesture_lifo.ReadCurrentEntry().state; |
|||
|
|||
// Reset next state to default
|
|||
next_state.sampling_number = last_entry.sampling_number + 1; |
|||
next_state.delta = {}; |
|||
next_state.vel_x = 0; |
|||
next_state.vel_y = 0; |
|||
next_state.direction = GestureDirection::None; |
|||
next_state.rotation_angle = 0; |
|||
next_state.scale = 0; |
|||
|
|||
if (gesture.active_points > 0) { |
|||
if (last_gesture.active_points == 0) { |
|||
NewGesture(gesture, type, attributes); |
|||
} else { |
|||
UpdateExistingGesture(gesture, type, time_difference); |
|||
} |
|||
} else { |
|||
EndGesture(gesture, last_gesture, type, attributes, time_difference); |
|||
} |
|||
|
|||
// Apply attributes
|
|||
next_state.detection_count = gesture.detection_count; |
|||
next_state.type = type; |
|||
next_state.attributes = attributes; |
|||
next_state.pos = gesture.mid_point; |
|||
next_state.point_count = static_cast<s32>(gesture.active_points); |
|||
next_state.points = gesture.points; |
|||
last_gesture = gesture; |
|||
|
|||
shared_memory->gesture_lifo.WriteNextEntry(next_state); |
|||
} |
|||
|
|||
void Gesture::NewGesture(GestureProperties& gesture, GestureType& type, |
|||
GestureAttribute& attributes) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
|
|||
gesture.detection_count++; |
|||
type = GestureType::Touch; |
|||
|
|||
// New touch after cancel is not considered new
|
|||
if (last_entry.type != GestureType::Cancel) { |
|||
attributes.is_new_touch.Assign(1); |
|||
enable_press_and_tap = true; |
|||
} |
|||
} |
|||
|
|||
void Gesture::UpdateExistingGesture(GestureProperties& gesture, GestureType& type, |
|||
f32 time_difference) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
Result Gesture::Activate() { |
|||
std::scoped_lock lock{mutex}; |
|||
|
|||
// Promote to pan type if touch moved
|
|||
for (size_t id = 0; id < MAX_POINTS; id++) { |
|||
if (gesture.points[id] != last_gesture.points[id]) { |
|||
type = GestureType::Pan; |
|||
break; |
|||
} |
|||
// TODO: Result result = CreateThread();
|
|||
Result result = ResultSuccess; |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
// Number of fingers changed cancel the last event and clear data
|
|||
if (gesture.active_points != last_gesture.active_points) { |
|||
type = GestureType::Cancel; |
|||
enable_press_and_tap = false; |
|||
gesture.active_points = 0; |
|||
gesture.mid_point = {}; |
|||
gesture.points.fill({}); |
|||
return; |
|||
} |
|||
|
|||
// Calculate extra parameters of panning
|
|||
if (type == GestureType::Pan) { |
|||
UpdatePanEvent(gesture, last_gesture, type, time_difference); |
|||
return; |
|||
} |
|||
result = touch_resource->ActivateGesture(); |
|||
|
|||
// Promote to press type
|
|||
if (last_entry.type == GestureType::Touch) { |
|||
type = GestureType::Press; |
|||
if (result.IsError()) { |
|||
// TODO: StopThread();
|
|||
} |
|||
} |
|||
|
|||
void Gesture::EndGesture(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, GestureAttribute& attributes, f32 time_difference) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
|
|||
if (last_gesture_props.active_points != 0) { |
|||
switch (last_entry.type) { |
|||
case GestureType::Touch: |
|||
if (enable_press_and_tap) { |
|||
SetTapEvent(gesture, last_gesture_props, type, attributes); |
|||
return; |
|||
} |
|||
type = GestureType::Cancel; |
|||
force_update = true; |
|||
break; |
|||
case GestureType::Press: |
|||
case GestureType::Tap: |
|||
case GestureType::Swipe: |
|||
case GestureType::Pinch: |
|||
case GestureType::Rotate: |
|||
type = GestureType::Complete; |
|||
force_update = true; |
|||
break; |
|||
case GestureType::Pan: |
|||
EndPanEvent(gesture, last_gesture_props, type, time_difference); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
return; |
|||
} |
|||
if (last_entry.type == GestureType::Complete || last_entry.type == GestureType::Cancel) { |
|||
gesture.detection_count++; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
void Gesture::SetTapEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, GestureAttribute& attributes) { |
|||
type = GestureType::Tap; |
|||
gesture = last_gesture_props; |
|||
force_update = true; |
|||
f32 tap_time_difference = |
|||
static_cast<f32>(last_update_timestamp - last_tap_timestamp) / (1000 * 1000 * 1000); |
|||
last_tap_timestamp = last_update_timestamp; |
|||
if (tap_time_difference < double_tap_delay) { |
|||
attributes.is_double_tap.Assign(1); |
|||
} |
|||
Result Gesture::Activate(u64 aruid, u32 basic_gesture_id) { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->ActivateGesture(aruid, basic_gesture_id); |
|||
} |
|||
|
|||
void Gesture::UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, f32 time_difference) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
Result Gesture::Deactivate() { |
|||
std::scoped_lock lock{mutex}; |
|||
const auto result = touch_resource->DeactivateGesture(); |
|||
|
|||
next_state.delta = gesture.mid_point - last_entry.pos; |
|||
next_state.vel_x = static_cast<f32>(next_state.delta.x) / time_difference; |
|||
next_state.vel_y = static_cast<f32>(next_state.delta.y) / time_difference; |
|||
last_pan_time_difference = time_difference; |
|||
|
|||
// Promote to pinch type
|
|||
if (std::abs(gesture.average_distance - last_gesture_props.average_distance) > |
|||
pinch_threshold) { |
|||
type = GestureType::Pinch; |
|||
next_state.scale = gesture.average_distance / last_gesture_props.average_distance; |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
const f32 angle_between_two_lines = std::atan((gesture.angle - last_gesture_props.angle) / |
|||
(1 + (gesture.angle * last_gesture_props.angle))); |
|||
// Promote to rotate type
|
|||
if (std::abs(angle_between_two_lines) > angle_threshold) { |
|||
type = GestureType::Rotate; |
|||
next_state.scale = 0; |
|||
next_state.rotation_angle = angle_between_two_lines * 180.0f / Common::PI; |
|||
} |
|||
// TODO: return StopThread();
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
void Gesture::EndPanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, f32 time_difference) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
next_state.vel_x = |
|||
static_cast<f32>(last_entry.delta.x) / (last_pan_time_difference + time_difference); |
|||
next_state.vel_y = |
|||
static_cast<f32>(last_entry.delta.y) / (last_pan_time_difference + time_difference); |
|||
const f32 curr_vel = |
|||
std::sqrt((next_state.vel_x * next_state.vel_x) + (next_state.vel_y * next_state.vel_y)); |
|||
|
|||
// Set swipe event with parameters
|
|||
if (curr_vel > swipe_threshold) { |
|||
SetSwipeEvent(gesture, last_gesture_props, type); |
|||
return; |
|||
} |
|||
|
|||
// End panning without swipe
|
|||
type = GestureType::Complete; |
|||
next_state.vel_x = 0; |
|||
next_state.vel_y = 0; |
|||
force_update = true; |
|||
} |
|||
|
|||
void Gesture::SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type) { |
|||
const auto& last_entry = GetLastGestureEntry(); |
|||
|
|||
type = GestureType::Swipe; |
|||
gesture = last_gesture_props; |
|||
force_update = true; |
|||
next_state.delta = last_entry.delta; |
|||
|
|||
if (std::abs(next_state.delta.x) > std::abs(next_state.delta.y)) { |
|||
if (next_state.delta.x > 0) { |
|||
next_state.direction = GestureDirection::Right; |
|||
return; |
|||
} |
|||
next_state.direction = GestureDirection::Left; |
|||
return; |
|||
} |
|||
if (next_state.delta.y > 0) { |
|||
next_state.direction = GestureDirection::Down; |
|||
return; |
|||
} |
|||
next_state.direction = GestureDirection::Up; |
|||
} |
|||
|
|||
const GestureState& Gesture::GetLastGestureEntry() const { |
|||
return shared_memory->gesture_lifo.ReadCurrentEntry().state; |
|||
} |
|||
|
|||
GestureProperties Gesture::GetGestureProperties() { |
|||
GestureProperties gesture; |
|||
std::array<Core::HID::TouchFinger, MAX_POINTS> active_fingers; |
|||
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), |
|||
[](const auto& finger) { return finger.pressed; }); |
|||
gesture.active_points = |
|||
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter)); |
|||
|
|||
for (size_t id = 0; id < gesture.active_points; ++id) { |
|||
const auto& [active_x, active_y] = active_fingers[id].position; |
|||
gesture.points[id] = { |
|||
.x = static_cast<s32>(active_x * Layout::ScreenUndocked::Width), |
|||
.y = static_cast<s32>(active_y * Layout::ScreenUndocked::Height), |
|||
}; |
|||
|
|||
// Hack: There is no touch in docked but games still allow it
|
|||
if (Settings::IsDockedMode()) { |
|||
gesture.points[id] = { |
|||
.x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), |
|||
.y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), |
|||
}; |
|||
} |
|||
|
|||
gesture.mid_point.x += static_cast<s32>(gesture.points[id].x / gesture.active_points); |
|||
gesture.mid_point.y += static_cast<s32>(gesture.points[id].y / gesture.active_points); |
|||
} |
|||
|
|||
for (size_t id = 0; id < gesture.active_points; ++id) { |
|||
const f32 distance = std::sqrt(Square(gesture.mid_point.x - gesture.points[id].x) + |
|||
Square(gesture.mid_point.y - gesture.points[id].y)); |
|||
gesture.average_distance += distance / static_cast<f32>(gesture.active_points); |
|||
} |
|||
|
|||
gesture.angle = std::atan2(static_cast<f32>(gesture.mid_point.y - gesture.points[0].y), |
|||
static_cast<f32>(gesture.mid_point.x - gesture.points[0].x)); |
|||
|
|||
gesture.detection_count = last_gesture.detection_count; |
|||
|
|||
return gesture; |
|||
Result Gesture::IsActive(bool& out_is_active) const { |
|||
out_is_active = touch_resource->IsGestureActive(); |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
} // namespace Service::HID
|
|||
@ -1,87 +1,32 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <mutex> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "hid_core/resources/controller_base.h" |
|||
#include "hid_core/resources/touch_screen/touch_types.h" |
|||
|
|||
namespace Core::HID { |
|||
class EmulatedConsole; |
|||
} |
|||
#include "core/hle/result.h" |
|||
|
|||
namespace Service::HID { |
|||
struct GestureSharedMemoryFormat; |
|||
class TouchResource; |
|||
|
|||
class Gesture final : public ControllerBase { |
|||
/// Handles gesture request from HID interfaces |
|||
class Gesture { |
|||
public: |
|||
explicit Gesture(Core::HID::HIDCore& hid_core_); |
|||
~Gesture() override; |
|||
Gesture(std::shared_ptr<TouchResource> resource); |
|||
~Gesture(); |
|||
|
|||
// Called when the controller is initialized |
|||
void OnInit() override; |
|||
Result Activate(); |
|||
Result Activate(u64 aruid, u32 basic_gesture_id); |
|||
|
|||
// When the controller is released |
|||
void OnRelease() override; |
|||
Result Deactivate(); |
|||
|
|||
// When the controller is requesting an update for the shared memory |
|||
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; |
|||
Result IsActive(bool& out_is_active) const; |
|||
|
|||
private: |
|||
// Reads input from all available input engines |
|||
void ReadTouchInput(); |
|||
|
|||
// Returns true if gesture state needs to be updated |
|||
bool ShouldUpdateGesture(const GestureProperties& gesture, f32 time_difference); |
|||
|
|||
// Updates the shared memory to the next state |
|||
void UpdateGestureSharedMemory(GestureProperties& gesture, f32 time_difference); |
|||
|
|||
// Initializes new gesture |
|||
void NewGesture(GestureProperties& gesture, GestureType& type, GestureAttribute& attributes); |
|||
|
|||
// Updates existing gesture state |
|||
void UpdateExistingGesture(GestureProperties& gesture, GestureType& type, f32 time_difference); |
|||
|
|||
// Terminates exiting gesture |
|||
void EndGesture(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, GestureAttribute& attributes, f32 time_difference); |
|||
|
|||
// Set current event to a tap event |
|||
void SetTapEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, GestureAttribute& attributes); |
|||
|
|||
// Calculates and set the extra parameters related to a pan event |
|||
void UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, f32 time_difference); |
|||
|
|||
// Terminates the pan event |
|||
void EndPanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type, f32 time_difference); |
|||
|
|||
// Set current event to a swipe event |
|||
void SetSwipeEvent(GestureProperties& gesture, GestureProperties& last_gesture_props, |
|||
GestureType& type); |
|||
|
|||
// Retrieves the last gesture entry, as indicated by shared memory indices. |
|||
[[nodiscard]] const GestureState& GetLastGestureEntry() const; |
|||
|
|||
// Returns the average distance, angle and middle point of the active fingers |
|||
GestureProperties GetGestureProperties(); |
|||
|
|||
GestureState next_state{}; |
|||
GestureSharedMemoryFormat* shared_memory; |
|||
Core::HID::EmulatedConsole* console = nullptr; |
|||
|
|||
std::array<Core::HID::TouchFinger, MAX_POINTS> fingers{}; |
|||
GestureProperties last_gesture{}; |
|||
s64 last_update_timestamp{}; |
|||
s64 last_tap_timestamp{}; |
|||
f32 last_pan_time_difference{}; |
|||
bool force_update{false}; |
|||
bool enable_press_and_tap{false}; |
|||
mutable std::mutex mutex; |
|||
std::shared_ptr<TouchResource> touch_resource; |
|||
}; |
|||
|
|||
} // namespace Service::HID |
|||
@ -0,0 +1,260 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include "common/math_util.h"
|
|||
#include "hid_core/resources/touch_screen/gesture_handler.h"
|
|||
|
|||
namespace Service::HID { |
|||
|
|||
constexpr f32 Square(s32 num) { |
|||
return static_cast<f32>(num * num); |
|||
} |
|||
|
|||
GestureHandler::GestureHandler() {} |
|||
|
|||
GestureHandler::~GestureHandler() {} |
|||
|
|||
void GestureHandler::SetTouchState(std::span<TouchState> touch_state, u32 count, s64 timestamp) { |
|||
gesture = {}; |
|||
gesture.active_points = std::min(MaxPoints, static_cast<std::size_t>(count)); |
|||
|
|||
for (size_t id = 0; id < gesture.active_points; ++id) { |
|||
const auto& [active_x, active_y] = touch_state[id].position; |
|||
gesture.points[id] = { |
|||
.x = static_cast<s32>(active_x), |
|||
.y = static_cast<s32>(active_y), |
|||
}; |
|||
|
|||
gesture.mid_point.x += static_cast<s32>(gesture.points[id].x / gesture.active_points); |
|||
gesture.mid_point.y += static_cast<s32>(gesture.points[id].y / gesture.active_points); |
|||
} |
|||
|
|||
for (size_t id = 0; id < gesture.active_points; ++id) { |
|||
const f32 distance = std::sqrt(Square(gesture.mid_point.x - gesture.points[id].x) + |
|||
Square(gesture.mid_point.y - gesture.points[id].y)); |
|||
gesture.average_distance += distance / static_cast<f32>(gesture.active_points); |
|||
} |
|||
|
|||
gesture.angle = std::atan2(static_cast<f32>(gesture.mid_point.y - gesture.points[0].y), |
|||
static_cast<f32>(gesture.mid_point.x - gesture.points[0].x)); |
|||
|
|||
gesture.detection_count = last_gesture.detection_count; |
|||
|
|||
if (last_update_timestamp > timestamp) { |
|||
timestamp = last_tap_timestamp; |
|||
} |
|||
|
|||
time_difference = static_cast<f32>(timestamp - last_update_timestamp) / (1000 * 1000 * 1000); |
|||
} |
|||
|
|||
bool GestureHandler::NeedsUpdate() { |
|||
if (force_update) { |
|||
force_update = false; |
|||
return true; |
|||
} |
|||
|
|||
// Update if coordinates change
|
|||
for (size_t id = 0; id < MaxPoints; id++) { |
|||
if (gesture.points[id] != last_gesture.points[id]) { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
// Update on press and hold event after 0.5 seconds
|
|||
if (last_gesture_state.type == GestureType::Touch && last_gesture_state.point_count == 1 && |
|||
time_difference > PressDelay) { |
|||
return enable_press_and_tap; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
void GestureHandler::UpdateGestureState(GestureState& next_state, s64 timestamp) { |
|||
last_update_timestamp = timestamp; |
|||
|
|||
GestureType type = GestureType::Idle; |
|||
GestureAttribute attributes{}; |
|||
|
|||
// Reset next state to default
|
|||
next_state.sampling_number = last_gesture_state.sampling_number + 1; |
|||
next_state.delta = {}; |
|||
next_state.vel_x = 0; |
|||
next_state.vel_y = 0; |
|||
next_state.direction = GestureDirection::None; |
|||
next_state.rotation_angle = 0; |
|||
next_state.scale = 0; |
|||
|
|||
if (gesture.active_points > 0) { |
|||
if (last_gesture.active_points == 0) { |
|||
NewGesture(type, attributes); |
|||
} else { |
|||
UpdateExistingGesture(next_state, type); |
|||
} |
|||
} else { |
|||
EndGesture(next_state, type, attributes); |
|||
} |
|||
|
|||
// Apply attributes
|
|||
next_state.detection_count = gesture.detection_count; |
|||
next_state.type = type; |
|||
next_state.attributes = attributes; |
|||
next_state.pos = gesture.mid_point; |
|||
next_state.point_count = static_cast<s32>(gesture.active_points); |
|||
next_state.points = gesture.points; |
|||
last_gesture = gesture; |
|||
last_gesture_state = next_state; |
|||
} |
|||
|
|||
void GestureHandler::NewGesture(GestureType& type, GestureAttribute& attributes) { |
|||
gesture.detection_count++; |
|||
type = GestureType::Touch; |
|||
|
|||
// New touch after cancel is not considered new
|
|||
if (last_gesture_state.type != GestureType::Cancel) { |
|||
attributes.is_new_touch.Assign(1); |
|||
enable_press_and_tap = true; |
|||
} |
|||
} |
|||
|
|||
void GestureHandler::UpdateExistingGesture(GestureState& next_state, GestureType& type) { |
|||
// Promote to pan type if touch moved
|
|||
for (size_t id = 0; id < MaxPoints; id++) { |
|||
if (gesture.points[id] != last_gesture.points[id]) { |
|||
type = GestureType::Pan; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Number of fingers changed cancel the last event and clear data
|
|||
if (gesture.active_points != last_gesture.active_points) { |
|||
type = GestureType::Cancel; |
|||
enable_press_and_tap = false; |
|||
gesture.active_points = 0; |
|||
gesture.mid_point = {}; |
|||
gesture.points.fill({}); |
|||
return; |
|||
} |
|||
|
|||
// Calculate extra parameters of panning
|
|||
if (type == GestureType::Pan) { |
|||
UpdatePanEvent(next_state, type); |
|||
return; |
|||
} |
|||
|
|||
// Promote to press type
|
|||
if (last_gesture_state.type == GestureType::Touch) { |
|||
type = GestureType::Press; |
|||
} |
|||
} |
|||
|
|||
void GestureHandler::EndGesture(GestureState& next_state, GestureType& type, |
|||
GestureAttribute& attributes) { |
|||
if (last_gesture.active_points != 0) { |
|||
switch (last_gesture_state.type) { |
|||
case GestureType::Touch: |
|||
if (enable_press_and_tap) { |
|||
SetTapEvent(type, attributes); |
|||
return; |
|||
} |
|||
type = GestureType::Cancel; |
|||
force_update = true; |
|||
break; |
|||
case GestureType::Press: |
|||
case GestureType::Tap: |
|||
case GestureType::Swipe: |
|||
case GestureType::Pinch: |
|||
case GestureType::Rotate: |
|||
type = GestureType::Complete; |
|||
force_update = true; |
|||
break; |
|||
case GestureType::Pan: |
|||
EndPanEvent(next_state, type); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
return; |
|||
} |
|||
if (last_gesture_state.type == GestureType::Complete || |
|||
last_gesture_state.type == GestureType::Cancel) { |
|||
gesture.detection_count++; |
|||
} |
|||
} |
|||
|
|||
void GestureHandler::SetTapEvent(GestureType& type, GestureAttribute& attributes) { |
|||
type = GestureType::Tap; |
|||
gesture = last_gesture; |
|||
force_update = true; |
|||
f32 tap_time_difference = |
|||
static_cast<f32>(last_update_timestamp - last_tap_timestamp) / (1000 * 1000 * 1000); |
|||
last_tap_timestamp = last_update_timestamp; |
|||
if (tap_time_difference < DoubleTapDelay) { |
|||
attributes.is_double_tap.Assign(1); |
|||
} |
|||
} |
|||
|
|||
void GestureHandler::UpdatePanEvent(GestureState& next_state, GestureType& type) { |
|||
next_state.delta = gesture.mid_point - last_gesture_state.pos; |
|||
next_state.vel_x = static_cast<f32>(next_state.delta.x) / time_difference; |
|||
next_state.vel_y = static_cast<f32>(next_state.delta.y) / time_difference; |
|||
last_pan_time_difference = time_difference; |
|||
|
|||
// Promote to pinch type
|
|||
if (std::abs(gesture.average_distance - last_gesture.average_distance) > PinchThreshold) { |
|||
type = GestureType::Pinch; |
|||
next_state.scale = gesture.average_distance / last_gesture.average_distance; |
|||
} |
|||
|
|||
const f32 angle_between_two_lines = std::atan((gesture.angle - last_gesture.angle) / |
|||
(1 + (gesture.angle * last_gesture.angle))); |
|||
// Promote to rotate type
|
|||
if (std::abs(angle_between_two_lines) > AngleThreshold) { |
|||
type = GestureType::Rotate; |
|||
next_state.scale = 0; |
|||
next_state.rotation_angle = angle_between_two_lines * 180.0f / Common::PI; |
|||
} |
|||
} |
|||
|
|||
void GestureHandler::EndPanEvent(GestureState& next_state, GestureType& type) { |
|||
next_state.vel_x = |
|||
static_cast<f32>(last_gesture_state.delta.x) / (last_pan_time_difference + time_difference); |
|||
next_state.vel_y = |
|||
static_cast<f32>(last_gesture_state.delta.y) / (last_pan_time_difference + time_difference); |
|||
const f32 curr_vel = |
|||
std::sqrt((next_state.vel_x * next_state.vel_x) + (next_state.vel_y * next_state.vel_y)); |
|||
|
|||
// Set swipe event with parameters
|
|||
if (curr_vel > SwipeThreshold) { |
|||
SetSwipeEvent(next_state, type); |
|||
return; |
|||
} |
|||
|
|||
// End panning without swipe
|
|||
type = GestureType::Complete; |
|||
next_state.vel_x = 0; |
|||
next_state.vel_y = 0; |
|||
force_update = true; |
|||
} |
|||
|
|||
void GestureHandler::SetSwipeEvent(GestureState& next_state, GestureType& type) { |
|||
type = GestureType::Swipe; |
|||
gesture = last_gesture; |
|||
force_update = true; |
|||
next_state.delta = last_gesture_state.delta; |
|||
|
|||
if (std::abs(next_state.delta.x) > std::abs(next_state.delta.y)) { |
|||
if (next_state.delta.x > 0) { |
|||
next_state.direction = GestureDirection::Right; |
|||
return; |
|||
} |
|||
next_state.direction = GestureDirection::Left; |
|||
return; |
|||
} |
|||
if (next_state.delta.y > 0) { |
|||
next_state.direction = GestureDirection::Down; |
|||
return; |
|||
} |
|||
next_state.direction = GestureDirection::Up; |
|||
} |
|||
|
|||
} // namespace Service::HID
|
|||
@ -0,0 +1,55 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <span> |
|||
|
|||
#include "hid_core/resources/touch_screen/touch_types.h" |
|||
|
|||
namespace Service::HID { |
|||
|
|||
class GestureHandler { |
|||
public: |
|||
GestureHandler(); |
|||
~GestureHandler(); |
|||
|
|||
void SetTouchState(std::span<TouchState> touch_state, u32 count, s64 timestamp); |
|||
|
|||
bool NeedsUpdate(); |
|||
void UpdateGestureState(GestureState& next_state, s64 timestamp); |
|||
|
|||
private: |
|||
// Initializes new gesture |
|||
void NewGesture(GestureType& type, GestureAttribute& attributes); |
|||
|
|||
// Updates existing gesture state |
|||
void UpdateExistingGesture(GestureState& next_state, GestureType& type); |
|||
|
|||
// Terminates exiting gesture |
|||
void EndGesture(GestureState& next_state, GestureType& type, GestureAttribute& attributes); |
|||
|
|||
// Set current event to a tap event |
|||
void SetTapEvent(GestureType& type, GestureAttribute& attributes); |
|||
|
|||
// Calculates and set the extra parameters related to a pan event |
|||
void UpdatePanEvent(GestureState& next_state, GestureType& type); |
|||
|
|||
// Terminates the pan event |
|||
void EndPanEvent(GestureState& next_state, GestureType& type); |
|||
|
|||
// Set current event to a swipe event |
|||
void SetSwipeEvent(GestureState& next_state, GestureType& type); |
|||
|
|||
GestureProperties gesture{}; |
|||
GestureProperties last_gesture{}; |
|||
GestureState last_gesture_state{}; |
|||
s64 last_update_timestamp{}; |
|||
s64 last_tap_timestamp{}; |
|||
f32 last_pan_time_difference{}; |
|||
f32 time_difference{}; |
|||
bool force_update{true}; |
|||
bool enable_press_and_tap{false}; |
|||
}; |
|||
|
|||
} // namespace Service::HID |
|||
@ -1,77 +0,0 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include "common/bit_field.h" |
|||
#include "common/common_types.h" |
|||
#include "common/point.h" |
|||
|
|||
namespace Service::HID { |
|||
static constexpr size_t MAX_FINGERS = 16; |
|||
static constexpr size_t MAX_POINTS = 4; |
|||
|
|||
// This is nn::hid::GestureType |
|||
enum class GestureType : u32 { |
|||
Idle, // Nothing touching the screen |
|||
Complete, // Set at the end of a touch event |
|||
Cancel, // Set when the number of fingers change |
|||
Touch, // A finger just touched the screen |
|||
Press, // Set if last type is touch and the finger hasn't moved |
|||
Tap, // Fast press then release |
|||
Pan, // All points moving together across the screen |
|||
Swipe, // Fast press movement and release of a single point |
|||
Pinch, // All points moving away/closer to the midpoint |
|||
Rotate, // All points rotating from the midpoint |
|||
}; |
|||
|
|||
// This is nn::hid::GestureDirection |
|||
enum class GestureDirection : u32 { |
|||
None, |
|||
Left, |
|||
Up, |
|||
Right, |
|||
Down, |
|||
}; |
|||
|
|||
// This is nn::hid::GestureAttribute |
|||
struct GestureAttribute { |
|||
union { |
|||
u32 raw{}; |
|||
|
|||
BitField<4, 1, u32> is_new_touch; |
|||
BitField<8, 1, u32> is_double_tap; |
|||
}; |
|||
}; |
|||
static_assert(sizeof(GestureAttribute) == 4, "GestureAttribute is an invalid size"); |
|||
|
|||
// This is nn::hid::GestureState |
|||
struct GestureState { |
|||
s64 sampling_number{}; |
|||
s64 detection_count{}; |
|||
GestureType type{GestureType::Idle}; |
|||
GestureDirection direction{GestureDirection::None}; |
|||
Common::Point<s32> pos{}; |
|||
Common::Point<s32> delta{}; |
|||
f32 vel_x{}; |
|||
f32 vel_y{}; |
|||
GestureAttribute attributes{}; |
|||
f32 scale{}; |
|||
f32 rotation_angle{}; |
|||
s32 point_count{}; |
|||
std::array<Common::Point<s32>, 4> points{}; |
|||
}; |
|||
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size"); |
|||
|
|||
struct GestureProperties { |
|||
std::array<Common::Point<s32>, MAX_POINTS> points{}; |
|||
std::size_t active_points{}; |
|||
Common::Point<s32> mid_point{}; |
|||
s64 detection_count{}; |
|||
u64 delta_time{}; |
|||
f32 average_distance{}; |
|||
f32 angle{}; |
|||
}; |
|||
|
|||
} // namespace Service::HID |
|||
@ -1,132 +1,119 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include <algorithm>
|
|||
#include "common/common_types.h"
|
|||
#include "common/settings.h"
|
|||
#include "core/core_timing.h"
|
|||
#include "core/frontend/emu_window.h"
|
|||
#include "hid_core/frontend/emulated_console.h"
|
|||
#include "hid_core/hid_core.h"
|
|||
#include "hid_core/resources/applet_resource.h"
|
|||
#include "hid_core/resources/shared_memory_format.h"
|
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include "hid_core/hid_types.h"
|
|||
#include "hid_core/resources/touch_screen/touch_screen.h"
|
|||
#include "hid_core/resources/touch_screen/touch_screen_resource.h"
|
|||
|
|||
namespace Service::HID { |
|||
|
|||
TouchScreen::TouchScreen(Core::HID::HIDCore& hid_core_) |
|||
: ControllerBase{hid_core_}, touchscreen_width(Layout::ScreenUndocked::Width), |
|||
touchscreen_height(Layout::ScreenUndocked::Height) { |
|||
console = hid_core.GetEmulatedConsole(); |
|||
} |
|||
TouchScreen::TouchScreen(std::shared_ptr<TouchResource> resource) : touch_resource{resource} {} |
|||
|
|||
TouchScreen::~TouchScreen() = default; |
|||
|
|||
void TouchScreen::OnInit() {} |
|||
Result TouchScreen::Activate() { |
|||
std::scoped_lock lock{mutex}; |
|||
|
|||
void TouchScreen::OnRelease() {} |
|||
|
|||
void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) { |
|||
const u64 aruid = applet_resource->GetActiveAruid(); |
|||
auto* data = applet_resource->GetAruidData(aruid); |
|||
// TODO: Result result = CreateThread();
|
|||
Result result = ResultSuccess; |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
if (data == nullptr || !data->flag.is_assigned) { |
|||
return; |
|||
result = touch_resource->ActivateTouch(); |
|||
if (result.IsError()) { |
|||
// TODO: StopThread();
|
|||
} |
|||
|
|||
TouchScreenSharedMemoryFormat& shared_memory = data->shared_memory_format->touch_screen; |
|||
shared_memory.touch_screen_lifo.timestamp = core_timing.GetGlobalTimeNs().count(); |
|||
return result; |
|||
} |
|||
|
|||
if (!IsControllerActivated()) { |
|||
shared_memory.touch_screen_lifo.buffer_count = 0; |
|||
shared_memory.touch_screen_lifo.buffer_tail = 0; |
|||
return; |
|||
} |
|||
Result TouchScreen::Activate(u64 aruid) { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->ActivateTouch(aruid); |
|||
} |
|||
|
|||
const auto touch_status = console->GetTouch(); |
|||
for (std::size_t id = 0; id < MAX_FINGERS; id++) { |
|||
const auto& current_touch = touch_status[id]; |
|||
auto& finger = fingers[id]; |
|||
finger.id = current_touch.id; |
|||
|
|||
if (finger.attribute.start_touch) { |
|||
finger.attribute.raw = 0; |
|||
continue; |
|||
} |
|||
|
|||
if (finger.attribute.end_touch) { |
|||
finger.attribute.raw = 0; |
|||
finger.pressed = false; |
|||
continue; |
|||
} |
|||
|
|||
if (!finger.pressed && current_touch.pressed) { |
|||
// Ignore all touch fingers if disabled
|
|||
if (!Settings::values.touchscreen.enabled) { |
|||
continue; |
|||
} |
|||
|
|||
finger.attribute.start_touch.Assign(1); |
|||
finger.pressed = true; |
|||
finger.position = current_touch.position; |
|||
continue; |
|||
} |
|||
|
|||
if (finger.pressed && !current_touch.pressed) { |
|||
finger.attribute.raw = 0; |
|||
finger.attribute.end_touch.Assign(1); |
|||
continue; |
|||
} |
|||
|
|||
// Only update position if touch is not on a special frame
|
|||
finger.position = current_touch.position; |
|||
} |
|||
Result TouchScreen::Deactivate() { |
|||
std::scoped_lock lock{mutex}; |
|||
const auto result = touch_resource->DeactivateTouch(); |
|||
|
|||
std::array<Core::HID::TouchFinger, MAX_FINGERS> active_fingers; |
|||
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), |
|||
[](const auto& finger) { return finger.pressed; }); |
|||
const auto active_fingers_count = |
|||
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter)); |
|||
|
|||
const u64 timestamp = static_cast<u64>(core_timing.GetGlobalTimeNs().count()); |
|||
const auto& last_entry = shared_memory.touch_screen_lifo.ReadCurrentEntry().state; |
|||
|
|||
next_state.sampling_number = last_entry.sampling_number + 1; |
|||
next_state.entry_count = static_cast<s32>(active_fingers_count); |
|||
|
|||
for (std::size_t id = 0; id < MAX_FINGERS; ++id) { |
|||
auto& touch_entry = next_state.states[id]; |
|||
if (id < active_fingers_count) { |
|||
const auto& [active_x, active_y] = active_fingers[id].position; |
|||
touch_entry.position = { |
|||
.x = static_cast<u16>(active_x * static_cast<float>(touchscreen_width)), |
|||
.y = static_cast<u16>(active_y * static_cast<float>(touchscreen_height)), |
|||
}; |
|||
touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; |
|||
touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; |
|||
touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; |
|||
touch_entry.delta_time = timestamp - active_fingers[id].last_touch; |
|||
fingers[active_fingers[id].id].last_touch = timestamp; |
|||
touch_entry.finger = active_fingers[id].id; |
|||
touch_entry.attribute.raw = active_fingers[id].attribute.raw; |
|||
} else { |
|||
// Clear touch entry
|
|||
touch_entry.attribute.raw = 0; |
|||
touch_entry.position = {}; |
|||
touch_entry.diameter_x = 0; |
|||
touch_entry.diameter_y = 0; |
|||
touch_entry.rotation_angle = 0; |
|||
touch_entry.delta_time = 0; |
|||
touch_entry.finger = 0; |
|||
} |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
shared_memory.touch_screen_lifo.WriteNextEntry(next_state); |
|||
// TODO: return StopThread();
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchScreen::IsActive(bool& out_is_active) const { |
|||
out_is_active = touch_resource->IsTouchActive(); |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchScreen::SetTouchScreenAutoPilotState(const AutoPilotState& auto_pilot_state) { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->SetTouchScreenAutoPilotState(auto_pilot_state); |
|||
} |
|||
|
|||
Result TouchScreen::UnsetTouchScreenAutoPilotState() { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->UnsetTouchScreenAutoPilotState(); |
|||
} |
|||
|
|||
Result TouchScreen::RequestNextTouchInput() { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->RequestNextTouchInput(); |
|||
} |
|||
|
|||
Result TouchScreen::RequestNextDummyInput() { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->RequestNextDummyInput(); |
|||
} |
|||
|
|||
Result TouchScreen::ProcessTouchScreenAutoTune() { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->ProcessTouchScreenAutoTune(); |
|||
} |
|||
|
|||
Result TouchScreen::SetTouchScreenMagnification(f32 point1_x, f32 point1_y, f32 point2_x, |
|||
f32 point2_y) { |
|||
std::scoped_lock lock{mutex}; |
|||
touch_resource->SetTouchScreenMagnification(point1_x, point1_y, point2_x, point2_y); |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchScreen::SetTouchScreenResolution(u32 width, u32 height, u64 aruid) { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->SetTouchScreenResolution(width, height, aruid); |
|||
} |
|||
|
|||
Result TouchScreen::SetTouchScreenConfiguration( |
|||
const Core::HID::TouchScreenConfigurationForNx& mode, u64 aruid) { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->SetTouchScreenConfiguration(mode, aruid); |
|||
} |
|||
|
|||
Result TouchScreen::GetTouchScreenConfiguration(Core::HID::TouchScreenConfigurationForNx& out_mode, |
|||
u64 aruid) const { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->GetTouchScreenConfiguration(out_mode, aruid); |
|||
} |
|||
|
|||
Result TouchScreen::SetTouchScreenDefaultConfiguration( |
|||
const Core::HID::TouchScreenConfigurationForNx& mode) { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->SetTouchScreenDefaultConfiguration(mode); |
|||
} |
|||
|
|||
Result TouchScreen::GetTouchScreenDefaultConfiguration( |
|||
Core::HID::TouchScreenConfigurationForNx& out_mode) const { |
|||
std::scoped_lock lock{mutex}; |
|||
return touch_resource->GetTouchScreenDefaultConfiguration(out_mode); |
|||
} |
|||
|
|||
void TouchScreen::SetTouchscreenDimensions(u32 width, u32 height) { |
|||
touchscreen_width = width; |
|||
touchscreen_height = height; |
|||
void TouchScreen::OnTouchUpdate(u64 timestamp) { |
|||
std::scoped_lock lock{mutex}; |
|||
touch_resource->OnTouchUpdate(timestamp); |
|||
} |
|||
|
|||
} // namespace Service::HID
|
|||
@ -1,43 +1,64 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <mutex> |
|||
|
|||
#include "hid_core/hid_types.h" |
|||
#include "hid_core/resources/controller_base.h" |
|||
#include "hid_core/resources/touch_screen/touch_types.h" |
|||
#include "common/common_types.h" |
|||
#include "core/hle/result.h" |
|||
|
|||
namespace Core::HID { |
|||
class EmulatedConsole; |
|||
} // namespace Core::HID |
|||
struct TouchScreenConfigurationForNx; |
|||
} |
|||
|
|||
namespace Core::Timing { |
|||
struct EventType; |
|||
} |
|||
|
|||
namespace Service::HID { |
|||
struct TouchScreenSharedMemoryFormat; |
|||
class TouchResource; |
|||
struct AutoPilotState; |
|||
|
|||
class TouchScreen final : public ControllerBase { |
|||
/// Handles touch request from HID interfaces |
|||
class TouchScreen { |
|||
public: |
|||
explicit TouchScreen(Core::HID::HIDCore& hid_core_); |
|||
~TouchScreen() override; |
|||
TouchScreen(std::shared_ptr<TouchResource> resource); |
|||
~TouchScreen(); |
|||
|
|||
// Called when the controller is initialized |
|||
void OnInit() override; |
|||
Result Activate(); |
|||
Result Activate(u64 aruid); |
|||
|
|||
// When the controller is released |
|||
void OnRelease() override; |
|||
Result Deactivate(); |
|||
|
|||
// When the controller is requesting an update for the shared memory |
|||
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override; |
|||
Result IsActive(bool& out_is_active) const; |
|||
|
|||
void SetTouchscreenDimensions(u32 width, u32 height); |
|||
Result SetTouchScreenAutoPilotState(const AutoPilotState& auto_pilot_state); |
|||
Result UnsetTouchScreenAutoPilotState(); |
|||
|
|||
private: |
|||
TouchScreenState next_state{}; |
|||
Core::HID::EmulatedConsole* console = nullptr; |
|||
Result RequestNextTouchInput(); |
|||
Result RequestNextDummyInput(); |
|||
|
|||
Result ProcessTouchScreenAutoTune(); |
|||
|
|||
Result SetTouchScreenMagnification(f32 point1_x, f32 point1_y, f32 point2_x, f32 point2_y); |
|||
Result SetTouchScreenResolution(u32 width, u32 height, u64 aruid); |
|||
|
|||
Result SetTouchScreenConfiguration(const Core::HID::TouchScreenConfigurationForNx& mode, |
|||
u64 aruid); |
|||
Result GetTouchScreenConfiguration(Core::HID::TouchScreenConfigurationForNx& out_mode, |
|||
u64 aruid) const; |
|||
|
|||
std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{}; |
|||
u32 touchscreen_width; |
|||
u32 touchscreen_height; |
|||
Result SetTouchScreenDefaultConfiguration(const Core::HID::TouchScreenConfigurationForNx& mode); |
|||
Result GetTouchScreenDefaultConfiguration( |
|||
Core::HID::TouchScreenConfigurationForNx& out_mode) const; |
|||
|
|||
void OnTouchUpdate(u64 timestamp); |
|||
|
|||
private: |
|||
mutable std::mutex mutex; |
|||
std::shared_ptr<TouchResource> touch_resource; |
|||
std::shared_ptr<Core::Timing::EventType> touch_update_event; |
|||
}; |
|||
|
|||
} // namespace Service::HID |
|||
@ -0,0 +1,114 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include <algorithm>
|
|||
#include "common/settings.h"
|
|||
#include "core/frontend/emu_window.h"
|
|||
#include "hid_core/hid_core.h"
|
|||
#include "hid_core/resources/touch_screen/touch_screen_driver.h"
|
|||
|
|||
namespace Service::HID { |
|||
|
|||
TouchDriver::TouchDriver(Core::HID::HIDCore& hid_core) { |
|||
console = hid_core.GetEmulatedConsole(); |
|||
} |
|||
|
|||
TouchDriver::~TouchDriver() = default; |
|||
|
|||
Result TouchDriver::StartTouchSensor() { |
|||
is_running = true; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchDriver::StopTouchSensor() { |
|||
is_running = false; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
bool TouchDriver::IsRunning() const { |
|||
return is_running; |
|||
} |
|||
|
|||
void TouchDriver::ProcessTouchScreenAutoTune() const { |
|||
// TODO
|
|||
} |
|||
|
|||
Result TouchDriver::WaitForDummyInput() { |
|||
touch_status = {}; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchDriver::WaitForInput() { |
|||
touch_status = {}; |
|||
const auto touch_input = console->GetTouch(); |
|||
for (std::size_t id = 0; id < touch_status.states.size(); id++) { |
|||
const auto& current_touch = touch_input[id]; |
|||
auto& finger = fingers[id]; |
|||
finger.id = current_touch.id; |
|||
|
|||
if (finger.attribute.start_touch) { |
|||
finger.attribute.raw = 0; |
|||
continue; |
|||
} |
|||
|
|||
if (finger.attribute.end_touch) { |
|||
finger.attribute.raw = 0; |
|||
finger.pressed = false; |
|||
continue; |
|||
} |
|||
|
|||
if (!finger.pressed && current_touch.pressed) { |
|||
finger.attribute.start_touch.Assign(1); |
|||
finger.pressed = true; |
|||
finger.position = current_touch.position; |
|||
continue; |
|||
} |
|||
|
|||
if (finger.pressed && !current_touch.pressed) { |
|||
finger.attribute.raw = 0; |
|||
finger.attribute.end_touch.Assign(1); |
|||
continue; |
|||
} |
|||
|
|||
// Only update position if touch is not on a special frame
|
|||
finger.position = current_touch.position; |
|||
} |
|||
|
|||
std::array<Core::HID::TouchFinger, MaxFingers> active_fingers; |
|||
const auto end_iter = std::copy_if(fingers.begin(), fingers.end(), active_fingers.begin(), |
|||
[](const auto& finger) { return finger.pressed; }); |
|||
const auto active_fingers_count = |
|||
static_cast<std::size_t>(std::distance(active_fingers.begin(), end_iter)); |
|||
|
|||
touch_status.entry_count = static_cast<s32>(active_fingers_count); |
|||
for (std::size_t id = 0; id < MaxFingers; ++id) { |
|||
auto& touch_entry = touch_status.states[id]; |
|||
if (id < active_fingers_count) { |
|||
const auto& [active_x, active_y] = active_fingers[id].position; |
|||
touch_entry.position = { |
|||
.x = static_cast<u16>(active_x * TouchSensorWidth), |
|||
.y = static_cast<u16>(active_y * TouchSensorHeight), |
|||
}; |
|||
touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; |
|||
touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; |
|||
touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; |
|||
touch_entry.finger = active_fingers[id].id; |
|||
touch_entry.attribute.raw = active_fingers[id].attribute.raw; |
|||
} |
|||
} |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
void TouchDriver::GetNextTouchState(TouchScreenState& out_state) const { |
|||
out_state = touch_status; |
|||
} |
|||
|
|||
void TouchDriver::SetTouchMode(Core::HID::TouchScreenModeForNx mode) { |
|||
touch_mode = mode; |
|||
} |
|||
|
|||
Core::HID::TouchScreenModeForNx TouchDriver::GetTouchMode() const { |
|||
return touch_mode; |
|||
} |
|||
|
|||
} // namespace Service::HID
|
|||
@ -0,0 +1,47 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/hle/result.h" |
|||
#include "hid_core/frontend/emulated_console.h" |
|||
#include "hid_core/hid_types.h" |
|||
#include "hid_core/resources/touch_screen/touch_types.h" |
|||
|
|||
namespace Core::HID { |
|||
class HIDCore; |
|||
} // namespace Core::HID |
|||
|
|||
namespace Service::HID { |
|||
|
|||
/// This handles all request to Ftm3bd56(TouchPanel) hardware |
|||
class TouchDriver { |
|||
public: |
|||
explicit TouchDriver(Core::HID::HIDCore& hid_core); |
|||
~TouchDriver(); |
|||
|
|||
Result StartTouchSensor(); |
|||
Result StopTouchSensor(); |
|||
bool IsRunning() const; |
|||
|
|||
void ProcessTouchScreenAutoTune() const; |
|||
|
|||
Result WaitForDummyInput(); |
|||
Result WaitForInput(); |
|||
|
|||
void GetNextTouchState(TouchScreenState& out_state) const; |
|||
|
|||
void SetTouchMode(Core::HID::TouchScreenModeForNx mode); |
|||
Core::HID::TouchScreenModeForNx GetTouchMode() const; |
|||
|
|||
private: |
|||
bool is_running{}; |
|||
TouchScreenState touch_status{}; |
|||
Core::HID::TouchFingerState fingers{}; |
|||
Core::HID::TouchScreenModeForNx touch_mode{}; |
|||
|
|||
Core::HID::EmulatedConsole* console = nullptr; |
|||
}; |
|||
|
|||
} // namespace Service::HID |
|||
@ -0,0 +1,579 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "core/core_timing.h"
|
|||
#include "core/hle/kernel/k_event.h"
|
|||
#include "core/hle/kernel/k_shared_memory.h"
|
|||
#include "core/hle/service/set/system_settings_server.h"
|
|||
#include "core/hle/service/sm/sm.h"
|
|||
#include "hid_core/hid_result.h"
|
|||
#include "hid_core/resources/applet_resource.h"
|
|||
#include "hid_core/resources/shared_memory_format.h"
|
|||
#include "hid_core/resources/touch_screen/touch_screen_driver.h"
|
|||
#include "hid_core/resources/touch_screen/touch_screen_resource.h"
|
|||
|
|||
namespace Service::HID { |
|||
constexpr auto GestureUpdatePeriod = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)
|
|||
|
|||
TouchResource::TouchResource(Core::System& system_) : system{system_} { |
|||
m_set_sys = system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys"); |
|||
} |
|||
|
|||
TouchResource::~TouchResource() { |
|||
Finalize(); |
|||
}; |
|||
|
|||
Result TouchResource::ActivateTouch() { |
|||
if (global_ref_counter == std::numeric_limits<s32>::max() - 1 || |
|||
touch_ref_counter == std::numeric_limits<s32>::max() - 1) { |
|||
return ResultTouchOverflow; |
|||
} |
|||
|
|||
if (global_ref_counter == 0) { |
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
const auto result = touch_driver->StartTouchSensor(); |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
is_initalized = true; |
|||
system.CoreTiming().ScheduleLoopingEvent(GestureUpdatePeriod, GestureUpdatePeriod, |
|||
timer_event); |
|||
current_touch_state = {}; |
|||
ReadTouchInput(); |
|||
gesture_handler.SetTouchState(current_touch_state.states, current_touch_state.entry_count, |
|||
0); |
|||
} |
|||
|
|||
Set::TouchScreenMode touch_mode{Set::TouchScreenMode::Standard}; |
|||
m_set_sys->GetTouchScreenMode(touch_mode); |
|||
default_touch_screen_mode = static_cast<Core::HID::TouchScreenModeForNx>(touch_mode); |
|||
|
|||
global_ref_counter++; |
|||
touch_ref_counter++; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::ActivateTouch(u64 aruid) { |
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { |
|||
auto* applet_data = applet_resource->GetAruidDataByIndex(aruid_index); |
|||
TouchAruidData& touch_data = aruid_data[aruid_index]; |
|||
|
|||
if (!applet_data->flag.is_assigned) { |
|||
touch_data = {}; |
|||
continue; |
|||
} |
|||
|
|||
const u64 aruid_id = applet_data->aruid; |
|||
if (touch_data.aruid != aruid_id) { |
|||
touch_data = {}; |
|||
touch_data.aruid = aruid_id; |
|||
} |
|||
|
|||
if (aruid != aruid_id) { |
|||
continue; |
|||
} |
|||
|
|||
auto& touch_shared = applet_data->shared_memory_format->touch_screen; |
|||
|
|||
if (touch_shared.touch_screen_lifo.buffer_count == 0) { |
|||
StorePreviousTouchState(previous_touch_state, touch_data.finger_map, |
|||
current_touch_state, |
|||
applet_data->flag.enable_touchscreen.Value() != 0); |
|||
touch_shared.touch_screen_lifo.WriteNextEntry(previous_touch_state); |
|||
} |
|||
} |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::ActivateGesture() { |
|||
if (global_ref_counter == std::numeric_limits<s32>::max() - 1 || |
|||
gesture_ref_counter == std::numeric_limits<s32>::max() - 1) { |
|||
return ResultGestureOverflow; |
|||
} |
|||
|
|||
// Initialize first instance
|
|||
if (global_ref_counter == 0) { |
|||
const auto result = touch_driver->StartTouchSensor(); |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
is_initalized = true; |
|||
system.CoreTiming().ScheduleLoopingEvent(GestureUpdatePeriod, GestureUpdatePeriod, |
|||
timer_event); |
|||
current_touch_state = {}; |
|||
ReadTouchInput(); |
|||
gesture_handler.SetTouchState(current_touch_state.states, current_touch_state.entry_count, |
|||
0); |
|||
} |
|||
|
|||
global_ref_counter++; |
|||
gesture_ref_counter++; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::ActivateGesture(u64 aruid, u32 basic_gesture_id) { |
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { |
|||
auto* applet_data = applet_resource->GetAruidDataByIndex(aruid_index); |
|||
TouchAruidData& touch_data = aruid_data[aruid_index]; |
|||
|
|||
if (!applet_data->flag.is_assigned) { |
|||
touch_data = {}; |
|||
continue; |
|||
} |
|||
|
|||
const u64 aruid_id = applet_data->aruid; |
|||
if (touch_data.aruid != aruid_id) { |
|||
touch_data = {}; |
|||
touch_data.aruid = aruid_id; |
|||
} |
|||
|
|||
if (aruid != aruid_id) { |
|||
continue; |
|||
} |
|||
|
|||
auto& gesture_shared = applet_data->shared_memory_format->gesture; |
|||
if (touch_data.basic_gesture_id != basic_gesture_id) { |
|||
gesture_shared.gesture_lifo.buffer_count = 0; |
|||
} |
|||
|
|||
if (gesture_shared.gesture_lifo.buffer_count == 0) { |
|||
touch_data.basic_gesture_id = basic_gesture_id; |
|||
|
|||
gesture_shared.gesture_lifo.WriteNextEntry(gesture_state); |
|||
} |
|||
} |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::DeactivateTouch() { |
|||
if (touch_ref_counter == 0 || global_ref_counter == 0) { |
|||
return ResultTouchNotInitialized; |
|||
} |
|||
|
|||
global_ref_counter--; |
|||
touch_ref_counter--; |
|||
|
|||
if (touch_ref_counter + global_ref_counter != 0) { |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
return Finalize(); |
|||
} |
|||
|
|||
Result TouchResource::DeactivateGesture() { |
|||
if (gesture_ref_counter == 0 || global_ref_counter == 0) { |
|||
return ResultGestureNotInitialized; |
|||
} |
|||
|
|||
global_ref_counter--; |
|||
gesture_ref_counter--; |
|||
|
|||
if (touch_ref_counter + global_ref_counter != 0) { |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
return Finalize(); |
|||
} |
|||
|
|||
bool TouchResource::IsTouchActive() const { |
|||
return touch_ref_counter != 0; |
|||
} |
|||
|
|||
bool TouchResource::IsGestureActive() const { |
|||
return gesture_ref_counter != 0; |
|||
} |
|||
|
|||
void TouchResource::SetTouchDriver(std::shared_ptr<TouchDriver> driver) { |
|||
touch_driver = driver; |
|||
} |
|||
|
|||
void TouchResource::SetAppletResource(std::shared_ptr<AppletResource> shared, |
|||
std::recursive_mutex* mutex) { |
|||
applet_resource = shared; |
|||
shared_mutex = mutex; |
|||
} |
|||
|
|||
void TouchResource::SetInputEvent(Kernel::KEvent* event, std::mutex* mutex) { |
|||
input_event = event; |
|||
input_mutex = mutex; |
|||
} |
|||
|
|||
void TouchResource::SetHandheldConfig(std::shared_ptr<HandheldConfig> config) { |
|||
handheld_config = config; |
|||
} |
|||
|
|||
void TouchResource::SetTimerEvent(std::shared_ptr<Core::Timing::EventType> event) { |
|||
timer_event = event; |
|||
} |
|||
|
|||
Result TouchResource::SetTouchScreenAutoPilotState(const AutoPilotState& auto_pilot_state) { |
|||
if (global_ref_counter == 0) { |
|||
return ResultTouchNotInitialized; |
|||
} |
|||
|
|||
if (!is_auto_pilot_initialized) { |
|||
is_auto_pilot_initialized = true; |
|||
auto_pilot = {}; |
|||
} |
|||
|
|||
TouchScreenState state = { |
|||
.entry_count = static_cast<s32>(auto_pilot_state.count), |
|||
.states = auto_pilot_state.state, |
|||
}; |
|||
|
|||
SanitizeInput(state); |
|||
|
|||
auto_pilot.count = state.entry_count; |
|||
auto_pilot.state = state.states; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::UnsetTouchScreenAutoPilotState() { |
|||
if (global_ref_counter == 0) { |
|||
return ResultTouchNotInitialized; |
|||
} |
|||
|
|||
is_auto_pilot_initialized = false; |
|||
auto_pilot = {}; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::RequestNextTouchInput() { |
|||
if (global_ref_counter == 0) { |
|||
return ResultTouchNotInitialized; |
|||
} |
|||
|
|||
if (handheld_config->is_handheld_hid_enabled) { |
|||
const Result result = touch_driver->WaitForInput(); |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
is_initalized = true; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::RequestNextDummyInput() { |
|||
if (global_ref_counter == 0) { |
|||
return ResultTouchNotInitialized; |
|||
} |
|||
|
|||
if (handheld_config->is_handheld_hid_enabled) { |
|||
const Result result = touch_driver->WaitForDummyInput(); |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
is_initalized = false; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::ProcessTouchScreenAutoTune() { |
|||
touch_driver->ProcessTouchScreenAutoTune(); |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
void TouchResource::SetTouchScreenMagnification(f32 point1_x, f32 point1_y, f32 point2_x, |
|||
f32 point2_y) { |
|||
offset = { |
|||
.x = point1_x, |
|||
.y = point1_y, |
|||
}; |
|||
magnification = { |
|||
.x = point2_x, |
|||
.y = point2_y, |
|||
}; |
|||
} |
|||
|
|||
Result TouchResource::SetTouchScreenResolution(u32 width, u32 height, u64 aruid) { |
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { |
|||
const auto* applet_data = applet_resource->GetAruidDataByIndex(aruid_index); |
|||
TouchAruidData& data = aruid_data[aruid_index]; |
|||
|
|||
if (!applet_data->flag.is_assigned) { |
|||
continue; |
|||
} |
|||
if (aruid != data.aruid) { |
|||
continue; |
|||
} |
|||
data.resolution_width = static_cast<u16>(width); |
|||
data.resolution_height = static_cast<u16>(height); |
|||
} |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::SetTouchScreenConfiguration( |
|||
const Core::HID::TouchScreenConfigurationForNx& touch_configuration, u64 aruid) { |
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { |
|||
const auto* applet_data = applet_resource->GetAruidDataByIndex(aruid_index); |
|||
TouchAruidData& data = aruid_data[aruid_index]; |
|||
|
|||
if (!applet_data->flag.is_assigned) { |
|||
continue; |
|||
} |
|||
if (aruid != data.aruid) { |
|||
continue; |
|||
} |
|||
data.finger_map.touch_mode = touch_configuration.mode; |
|||
} |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::GetTouchScreenConfiguration( |
|||
Core::HID::TouchScreenConfigurationForNx& out_touch_configuration, u64 aruid) const { |
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { |
|||
const auto* applet_data = applet_resource->GetAruidDataByIndex(aruid_index); |
|||
const TouchAruidData& data = aruid_data[aruid_index]; |
|||
|
|||
if (!applet_data->flag.is_assigned) { |
|||
continue; |
|||
} |
|||
if (aruid != data.aruid) { |
|||
continue; |
|||
} |
|||
out_touch_configuration.mode = data.finger_map.touch_mode; |
|||
} |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::SetTouchScreenDefaultConfiguration( |
|||
const Core::HID::TouchScreenConfigurationForNx& touch_configuration) { |
|||
default_touch_screen_mode = touch_configuration.mode; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::GetTouchScreenDefaultConfiguration( |
|||
Core::HID::TouchScreenConfigurationForNx& out_touch_configuration) const { |
|||
out_touch_configuration.mode = default_touch_screen_mode; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result TouchResource::Finalize() { |
|||
is_auto_pilot_initialized = false; |
|||
auto_pilot = {}; |
|||
system.CoreTiming().UnscheduleEvent(timer_event); |
|||
|
|||
const auto result = touch_driver->StopTouchSensor(); |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
is_initalized = false; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
void TouchResource::StorePreviousTouchState(TouchScreenState& out_previous_touch, |
|||
TouchFingerMap& out_finger_map, |
|||
const TouchScreenState& current_touch, |
|||
bool is_touch_enabled) const { |
|||
s32 finger_count{}; |
|||
|
|||
if (is_touch_enabled) { |
|||
finger_count = current_touch.entry_count; |
|||
if (finger_count < 1) { |
|||
out_finger_map.finger_count = 0; |
|||
out_finger_map.finger_ids = {}; |
|||
out_previous_touch.sampling_number = current_touch.sampling_number; |
|||
out_previous_touch.entry_count = 0; |
|||
out_previous_touch.states = {}; |
|||
return; |
|||
} |
|||
for (std::size_t i = 0; i < static_cast<u32>(finger_count); i++) { |
|||
out_finger_map.finger_ids[i] = current_touch.states[i].finger; |
|||
out_previous_touch.states[i] = current_touch.states[i]; |
|||
} |
|||
out_finger_map.finger_count = finger_count; |
|||
return; |
|||
} |
|||
|
|||
if (!is_touch_enabled && out_finger_map.finger_count > 0 && current_touch.entry_count > 0) { |
|||
// TODO
|
|||
} |
|||
|
|||
// Zero out unused entries
|
|||
for (std::size_t i = finger_count; i < MaxFingers; i++) { |
|||
out_finger_map.finger_ids[i] = 0; |
|||
out_previous_touch.states[i] = {}; |
|||
} |
|||
|
|||
out_previous_touch.sampling_number = current_touch.sampling_number; |
|||
out_previous_touch.entry_count = finger_count; |
|||
} |
|||
|
|||
void TouchResource::ReadTouchInput() { |
|||
previous_touch_state = current_touch_state; |
|||
|
|||
if (!is_initalized || !handheld_config->is_handheld_hid_enabled || !touch_driver->IsRunning()) { |
|||
touch_driver->WaitForDummyInput(); |
|||
} else { |
|||
touch_driver->WaitForInput(); |
|||
} |
|||
|
|||
touch_driver->GetNextTouchState(current_touch_state); |
|||
SanitizeInput(current_touch_state); |
|||
current_touch_state.sampling_number = sample_number; |
|||
sample_number++; |
|||
|
|||
if (is_auto_pilot_initialized && current_touch_state.entry_count == 0) { |
|||
const std::size_t finger_count = static_cast<std::size_t>(auto_pilot.count); |
|||
current_touch_state.entry_count = static_cast<s32>(finger_count); |
|||
for (std::size_t i = 0; i < finger_count; i++) { |
|||
current_touch_state.states[i] = auto_pilot.state[i]; |
|||
} |
|||
|
|||
std::size_t index = 0; |
|||
for (std::size_t i = 0; i < finger_count; i++) { |
|||
if (auto_pilot.state[i].attribute.end_touch) { |
|||
continue; |
|||
} |
|||
auto_pilot.state[i].attribute.raw = 0; |
|||
auto_pilot.state[index] = auto_pilot.state[i]; |
|||
index++; |
|||
} |
|||
|
|||
auto_pilot.count = index; |
|||
for (std::size_t i = index; i < auto_pilot.state.size(); i++) { |
|||
auto_pilot.state[i] = {}; |
|||
} |
|||
} |
|||
|
|||
for (std::size_t i = 0; i < static_cast<std::size_t>(current_touch_state.entry_count); i++) { |
|||
auto& state = current_touch_state.states[i]; |
|||
state.position.x = static_cast<u32>((magnification.y * static_cast<f32>(state.position.x)) + |
|||
(offset.x * static_cast<f32>(TouchSensorWidth))); |
|||
state.position.y = static_cast<u32>((magnification.y * static_cast<f32>(state.position.y)) + |
|||
(offset.x * static_cast<f32>(TouchSensorHeight))); |
|||
state.diameter_x = static_cast<u32>(magnification.x * static_cast<f32>(state.diameter_x)); |
|||
state.diameter_y = static_cast<u32>(magnification.y * static_cast<f32>(state.diameter_y)); |
|||
} |
|||
|
|||
std::size_t index = 0; |
|||
for (std::size_t i = 0; i < static_cast<std::size_t>(current_touch_state.entry_count); i++) { |
|||
const auto& old_state = current_touch_state.states[i]; |
|||
auto& state = current_touch_state.states[index]; |
|||
if ((TouchSensorWidth <= old_state.position.x) || |
|||
(TouchSensorHeight <= old_state.position.y)) { |
|||
continue; |
|||
} |
|||
state = old_state; |
|||
index++; |
|||
} |
|||
current_touch_state.entry_count = static_cast<s32>(index); |
|||
|
|||
SanitizeInput(current_touch_state); |
|||
|
|||
std::scoped_lock lock{*input_mutex}; |
|||
if (current_touch_state.entry_count == previous_touch_state.entry_count) { |
|||
if (current_touch_state.entry_count < 1) { |
|||
return; |
|||
} |
|||
bool has_moved = false; |
|||
for (std::size_t i = 0; i < static_cast<std::size_t>(current_touch_state.entry_count); |
|||
i++) { |
|||
s32 delta_x = std::abs(static_cast<s32>(current_touch_state.states[i].position.x) - |
|||
static_cast<s32>(previous_touch_state.states[i].position.x)); |
|||
s32 delta_y = std::abs(static_cast<s32>(current_touch_state.states[i].position.y) - |
|||
static_cast<s32>(previous_touch_state.states[i].position.y)); |
|||
if (delta_x > 1 || delta_y > 1) { |
|||
has_moved = true; |
|||
} |
|||
} |
|||
if (!has_moved) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
input_event->Signal(); |
|||
} |
|||
|
|||
void TouchResource::OnTouchUpdate(s64 timestamp) { |
|||
if (global_ref_counter == 0) { |
|||
return; |
|||
} |
|||
|
|||
ReadTouchInput(); |
|||
gesture_handler.SetTouchState(current_touch_state.states, current_touch_state.entry_count, |
|||
timestamp); |
|||
|
|||
std::scoped_lock lock{*shared_mutex}; |
|||
|
|||
for (std::size_t aruid_index = 0; aruid_index < AruidIndexMax; aruid_index++) { |
|||
const auto* applet_data = applet_resource->GetAruidDataByIndex(aruid_index); |
|||
TouchAruidData& data = aruid_data[aruid_index]; |
|||
|
|||
if (applet_data == nullptr || !applet_data->flag.is_assigned) { |
|||
data = {}; |
|||
continue; |
|||
} |
|||
|
|||
if (data.aruid != applet_data->aruid) { |
|||
data = {}; |
|||
data.aruid = applet_data->aruid; |
|||
} |
|||
|
|||
if (gesture_ref_counter != 0) { |
|||
if (!applet_data->flag.enable_touchscreen) { |
|||
gesture_state = {}; |
|||
} |
|||
if (gesture_handler.NeedsUpdate()) { |
|||
gesture_handler.UpdateGestureState(gesture_state, timestamp); |
|||
auto& gesture_shared = applet_data->shared_memory_format->gesture; |
|||
gesture_shared.gesture_lifo.WriteNextEntry(gesture_state); |
|||
} |
|||
} |
|||
|
|||
if (touch_ref_counter != 0) { |
|||
auto touch_mode = data.finger_map.touch_mode; |
|||
if (touch_mode == Core::HID::TouchScreenModeForNx::UseSystemSetting) { |
|||
touch_mode = default_touch_screen_mode; |
|||
} |
|||
|
|||
if (applet_resource->GetActiveAruid() == applet_data->aruid && |
|||
touch_mode != Core::HID::TouchScreenModeForNx::UseSystemSetting && is_initalized && |
|||
handheld_config->is_handheld_hid_enabled && touch_driver->IsRunning()) { |
|||
touch_driver->SetTouchMode(touch_mode); |
|||
} |
|||
|
|||
auto& touch_shared = applet_data->shared_memory_format->touch_screen; |
|||
StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, |
|||
applet_data->flag.enable_touchscreen.As<bool>()); |
|||
touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void TouchResource::SanitizeInput(TouchScreenState& state) const { |
|||
for (std::size_t i = 0; i < static_cast<std::size_t>(state.entry_count); i++) { |
|||
auto& entry = state.states[i]; |
|||
entry.position.x = |
|||
std::clamp(entry.position.x, TouchBorders, TouchSensorWidth - TouchBorders - 1); |
|||
entry.position.y = |
|||
std::clamp(entry.position.y, TouchBorders, TouchSensorHeight - TouchBorders - 1); |
|||
entry.diameter_x = std::clamp(entry.diameter_x, 0u, TouchSensorWidth - MaxTouchDiameter); |
|||
entry.diameter_y = std::clamp(entry.diameter_y, 0u, TouchSensorHeight - MaxTouchDiameter); |
|||
entry.rotation_angle = |
|||
std::clamp(entry.rotation_angle, -MaxRotationAngle, MaxRotationAngle); |
|||
} |
|||
} |
|||
|
|||
} // namespace Service::HID
|
|||
@ -0,0 +1,126 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <mutex> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "common/point.h" |
|||
#include "core/hle/result.h" |
|||
#include "hid_core/hid_types.h" |
|||
#include "hid_core/resources/touch_screen/gesture_handler.h" |
|||
#include "hid_core/resources/touch_screen/touch_types.h" |
|||
|
|||
namespace Core { |
|||
class System; |
|||
} |
|||
|
|||
namespace Core::Timing { |
|||
struct EventType; |
|||
} |
|||
|
|||
namespace Kernel { |
|||
class KEvent; |
|||
} // namespace Kernel |
|||
|
|||
namespace Service::Set { |
|||
class ISystemSettingsServer; |
|||
} |
|||
|
|||
namespace Service::HID { |
|||
class AppletResource; |
|||
class TouchSharedMemoryManager; |
|||
class TouchDriver; |
|||
struct HandheldConfig; |
|||
|
|||
class TouchResource { |
|||
public: |
|||
TouchResource(Core::System& system_); |
|||
~TouchResource(); |
|||
|
|||
Result ActivateTouch(); |
|||
Result ActivateTouch(u64 aruid); |
|||
|
|||
Result ActivateGesture(); |
|||
Result ActivateGesture(u64 aruid, u32 basic_gesture_id); |
|||
|
|||
Result DeactivateTouch(); |
|||
Result DeactivateGesture(); |
|||
|
|||
bool IsTouchActive() const; |
|||
bool IsGestureActive() const; |
|||
|
|||
void SetTouchDriver(std::shared_ptr<TouchDriver> driver); |
|||
void SetAppletResource(std::shared_ptr<AppletResource> shared, std::recursive_mutex* mutex); |
|||
void SetInputEvent(Kernel::KEvent* event, std::mutex* mutex); |
|||
void SetHandheldConfig(std::shared_ptr<HandheldConfig> config); |
|||
void SetTimerEvent(std::shared_ptr<Core::Timing::EventType> event); |
|||
|
|||
Result SetTouchScreenAutoPilotState(const AutoPilotState& auto_pilot_state); |
|||
Result UnsetTouchScreenAutoPilotState(); |
|||
|
|||
Result RequestNextTouchInput(); |
|||
Result RequestNextDummyInput(); |
|||
|
|||
Result ProcessTouchScreenAutoTune(); |
|||
void SetTouchScreenMagnification(f32 point1_x, f32 point1_y, f32 point2_x, f32 point2_y); |
|||
Result SetTouchScreenResolution(u32 width, u32 height, u64 aruid); |
|||
|
|||
Result SetTouchScreenConfiguration( |
|||
const Core::HID::TouchScreenConfigurationForNx& touch_configuration, u64 aruid); |
|||
Result GetTouchScreenConfiguration( |
|||
Core::HID::TouchScreenConfigurationForNx& out_touch_configuration, u64 aruid) const; |
|||
|
|||
Result SetTouchScreenDefaultConfiguration( |
|||
const Core::HID::TouchScreenConfigurationForNx& touch_configuration); |
|||
Result GetTouchScreenDefaultConfiguration( |
|||
Core::HID::TouchScreenConfigurationForNx& out_touch_configuration) const; |
|||
|
|||
void OnTouchUpdate(s64 timestamp); |
|||
|
|||
private: |
|||
Result Finalize(); |
|||
|
|||
void StorePreviousTouchState(TouchScreenState& out_previous_touch, |
|||
TouchFingerMap& out_finger_map, |
|||
const TouchScreenState& current_touch, |
|||
bool is_touch_enabled) const; |
|||
void ReadTouchInput(); |
|||
|
|||
void SanitizeInput(TouchScreenState& state) const; |
|||
|
|||
s32 global_ref_counter{}; |
|||
s32 gesture_ref_counter{}; |
|||
s32 touch_ref_counter{}; |
|||
bool is_initalized{}; |
|||
u64 sample_number{}; |
|||
|
|||
// External resources |
|||
std::shared_ptr<Core::Timing::EventType> timer_event{nullptr}; |
|||
std::shared_ptr<TouchDriver> touch_driver{nullptr}; |
|||
std::shared_ptr<AppletResource> applet_resource{nullptr}; |
|||
std::recursive_mutex* shared_mutex{nullptr}; |
|||
std::shared_ptr<HandheldConfig> handheld_config{nullptr}; |
|||
Kernel::KEvent* input_event{nullptr}; |
|||
std::mutex* input_mutex{nullptr}; |
|||
|
|||
// Internal state |
|||
TouchScreenState current_touch_state{}; |
|||
TouchScreenState previous_touch_state{}; |
|||
GestureState gesture_state{}; |
|||
bool is_auto_pilot_initialized{}; |
|||
AutoPilotState auto_pilot{}; |
|||
GestureHandler gesture_handler{}; |
|||
std::array<TouchAruidData, 0x20> aruid_data{}; |
|||
Common::Point<f32> magnification{1.0f, 1.0f}; |
|||
Common::Point<f32> offset{0.0f, 0.0f}; |
|||
Core::HID::TouchScreenModeForNx default_touch_screen_mode{ |
|||
Core::HID::TouchScreenModeForNx::Finger}; |
|||
|
|||
Core::System& system; |
|||
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys; |
|||
}; |
|||
|
|||
} // namespace Service::HID |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue