summaryrefslogtreecommitdiffstats
path: root/src/hid_core/resources
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_core/resources')
-rw-r--r--src/hid_core/resources/applet_resource.cpp6
-rw-r--r--src/hid_core/resources/applet_resource.h8
-rw-r--r--src/hid_core/resources/npad/npad.cpp3
-rw-r--r--src/hid_core/resources/npad/npad.h4
-rw-r--r--src/hid_core/resources/npad/npad_resource.cpp6
-rw-r--r--src/hid_core/resources/touch_screen/gesture.cpp367
-rw-r--r--src/hid_core/resources/touch_screen/gesture.h87
-rw-r--r--src/hid_core/resources/touch_screen/gesture_handler.cpp260
-rw-r--r--src/hid_core/resources/touch_screen/gesture_handler.h55
-rw-r--r--src/hid_core/resources/touch_screen/gesture_types.h77
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen.cpp209
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen.h71
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen_driver.cpp114
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen_driver.h47
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen_resource.cpp579
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen_resource.h126
-rw-r--r--src/hid_core/resources/touch_screen/touch_types.h61
17 files changed, 1446 insertions, 634 deletions
diff --git a/src/hid_core/resources/applet_resource.cpp b/src/hid_core/resources/applet_resource.cpp
index db4134037..243beb1c7 100644
--- a/src/hid_core/resources/applet_resource.cpp
+++ b/src/hid_core/resources/applet_resource.cpp
@@ -118,6 +118,12 @@ void AppletResource::UnregisterAppletResourceUserId(u64 aruid) {
data[index].aruid = 0;
registration_list.flag[index] = RegistrationStatus::PendingDelete;
+
+ for (std::size_t i = 0; i < AruidIndexMax; i++) {
+ if (registration_list.flag[i] == RegistrationStatus::Initialized) {
+ active_aruid = registration_list.aruid[i];
+ }
+ }
}
void AppletResource::FreeAppletResourceId(u64 aruid) {
diff --git a/src/hid_core/resources/applet_resource.h b/src/hid_core/resources/applet_resource.h
index e9710d306..4a5416fb2 100644
--- a/src/hid_core/resources/applet_resource.h
+++ b/src/hid_core/resources/applet_resource.h
@@ -13,11 +13,12 @@
namespace Core {
class System;
-}
+} // namespace Core
namespace Kernel {
+class KEvent;
class KSharedMemory;
-}
+} // namespace Kernel
namespace Service::HID {
struct SharedMemoryFormat;
@@ -73,7 +74,8 @@ struct AppletResourceHolder {
std::recursive_mutex* shared_mutex{nullptr};
NPadResource* shared_npad_resource{nullptr};
std::shared_ptr<HandheldConfig> handheld_config{nullptr};
- long* handle_1;
+ Kernel::KEvent* input_event{nullptr};
+ std::mutex* input_mutex{nullptr};
};
class AppletResource {
diff --git a/src/hid_core/resources/npad/npad.cpp b/src/hid_core/resources/npad/npad.cpp
index 2823be348..1a58eff4a 100644
--- a/src/hid_core/resources/npad/npad.cpp
+++ b/src/hid_core/resources/npad/npad.cpp
@@ -1070,11 +1070,14 @@ void NPad::UnregisterAppletResourceUserId(u64 aruid) {
void NPad::SetNpadExternals(std::shared_ptr<AppletResource> resource,
std::recursive_mutex* shared_mutex,
std::shared_ptr<HandheldConfig> handheld_config,
+ Kernel::KEvent* input_event, std::mutex* input_mutex,
std::shared_ptr<Service::Set::ISystemSettingsServer> settings) {
applet_resource_holder.applet_resource = resource;
applet_resource_holder.shared_mutex = shared_mutex;
applet_resource_holder.shared_npad_resource = &npad_resource;
applet_resource_holder.handheld_config = handheld_config;
+ applet_resource_holder.input_event = input_event;
+ applet_resource_holder.input_mutex = input_mutex;
vibration_handler.SetSettingsService(settings);
diff --git a/src/hid_core/resources/npad/npad.h b/src/hid_core/resources/npad/npad.h
index 3b1a69e7f..4e26ed2e8 100644
--- a/src/hid_core/resources/npad/npad.h
+++ b/src/hid_core/resources/npad/npad.h
@@ -131,6 +131,7 @@ public:
void SetNpadExternals(std::shared_ptr<AppletResource> resource,
std::recursive_mutex* shared_mutex,
std::shared_ptr<HandheldConfig> handheld_config,
+ Kernel::KEvent* input_event, std::mutex* input_mutex,
std::shared_ptr<Service::Set::ISystemSettingsServer> settings);
AppletDetailedUiType GetAppletDetailedUiType(Core::HID::NpadIdType npad_id);
@@ -204,9 +205,6 @@ private:
std::array<AbstractPad, MaxSupportedNpadIdTypes> abstracted_pads;
NpadVibration vibration_handler{};
- Kernel::KEvent* input_event{nullptr};
- std::mutex* input_mutex{nullptr};
-
std::atomic<u64> press_state{};
std::array<std::array<NpadControllerData, MaxSupportedNpadIdTypes>, AruidIndexMax>
controller_data{};
diff --git a/src/hid_core/resources/npad/npad_resource.cpp b/src/hid_core/resources/npad/npad_resource.cpp
index ea9fc14ed..8dd86b58e 100644
--- a/src/hid_core/resources/npad/npad_resource.cpp
+++ b/src/hid_core/resources/npad/npad_resource.cpp
@@ -72,6 +72,12 @@ void NPadResource::UnregisterAppletResourceUserId(u64 aruid) {
state[aruid_index] = {};
registration_list.flag[aruid_index] = RegistrationStatus::PendingDelete;
}
+
+ for (std::size_t i = 0; i < AruidIndexMax; i++) {
+ if (registration_list.flag[i] == RegistrationStatus::Initialized) {
+ active_data_aruid = registration_list.aruid[i];
+ }
+ }
}
void NPadResource::DestroyStyleSetUpdateEvents(u64 aruid) {
diff --git a/src/hid_core/resources/touch_screen/gesture.cpp b/src/hid_core/resources/touch_screen/gesture.cpp
index 0ecc0941f..eaa0cc7d0 100644
--- a/src/hid_core/resources/touch_screen/gesture.cpp
+++ b/src/hid_core/resources/touch_screen/gesture.cpp
@@ -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
diff --git a/src/hid_core/resources/touch_screen/gesture.h b/src/hid_core/resources/touch_screen/gesture.h
index 32e9a8690..d92912bb6 100644
--- a/src/hid_core/resources/touch_screen/gesture.h
+++ b/src/hid_core/resources/touch_screen/gesture.h
@@ -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
diff --git a/src/hid_core/resources/touch_screen/gesture_handler.cpp b/src/hid_core/resources/touch_screen/gesture_handler.cpp
new file mode 100644
index 000000000..4fcaf6ecf
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/gesture_handler.cpp
@@ -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
diff --git a/src/hid_core/resources/touch_screen/gesture_handler.h b/src/hid_core/resources/touch_screen/gesture_handler.h
new file mode 100644
index 000000000..fda2040c9
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/gesture_handler.h
@@ -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
diff --git a/src/hid_core/resources/touch_screen/gesture_types.h b/src/hid_core/resources/touch_screen/gesture_types.h
deleted file mode 100644
index b4f034cd3..000000000
--- a/src/hid_core/resources/touch_screen/gesture_types.h
+++ /dev/null
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_screen.cpp b/src/hid_core/resources/touch_screen/touch_screen.cpp
index 48d956c51..35efb1786 100644
--- a/src/hid_core/resources/touch_screen/touch_screen.cpp
+++ b/src/hid_core/resources/touch_screen/touch_screen.cpp
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_screen.h b/src/hid_core/resources/touch_screen/touch_screen.h
index 4b3824742..2fcb6247f 100644
--- a/src/hid_core/resources/touch_screen/touch_screen.h
+++ b/src/hid_core/resources/touch_screen/touch_screen.h
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_screen_driver.cpp b/src/hid_core/resources/touch_screen/touch_screen_driver.cpp
new file mode 100644
index 000000000..6a64c75b3
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_screen_driver.cpp
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_screen_driver.h b/src/hid_core/resources/touch_screen/touch_screen_driver.h
new file mode 100644
index 000000000..ca7e4970e
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_screen_driver.h
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_screen_resource.cpp b/src/hid_core/resources/touch_screen/touch_screen_resource.cpp
new file mode 100644
index 000000000..56e8e8e51
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_screen_resource.cpp
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_screen_resource.h b/src/hid_core/resources/touch_screen/touch_screen_resource.h
new file mode 100644
index 000000000..095cddd76
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_screen_resource.h
@@ -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
diff --git a/src/hid_core/resources/touch_screen/touch_types.h b/src/hid_core/resources/touch_screen/touch_types.h
index 97ee847da..362088939 100644
--- a/src/hid_core/resources/touch_screen/touch_types.h
+++ b/src/hid_core/resources/touch_screen/touch_types.h
@@ -13,8 +13,20 @@
#include "hid_core/hid_types.h"
namespace Service::HID {
-static constexpr std::size_t MAX_FINGERS = 16;
-static constexpr size_t MAX_POINTS = 4;
+constexpr std::size_t MaxFingers = 16;
+constexpr std::size_t MaxPoints = 4;
+constexpr u32 TouchSensorWidth = 1280;
+constexpr u32 TouchSensorHeight = 720;
+constexpr s32 MaxRotationAngle = 270;
+constexpr u32 MaxTouchDiameter = 30;
+constexpr u32 TouchBorders = 15;
+
+// HW is around 700, value is set to 400 to make it easier to trigger with mouse
+constexpr f32 SwipeThreshold = 400.0f; // Threshold in pixels/s
+constexpr f32 AngleThreshold = 0.015f; // Threshold in radians
+constexpr f32 PinchThreshold = 0.5f; // Threshold in pixels
+constexpr f32 PressDelay = 0.5f; // Time in seconds
+constexpr f32 DoubleTapDelay = 0.35f; // Time in seconds
// This is nn::hid::GestureType
enum class GestureType : u32 {
@@ -28,6 +40,7 @@ enum class GestureType : u32 {
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
+ GestureTypeMax = Rotate,
};
// This is nn::hid::GestureDirection
@@ -69,7 +82,7 @@ struct GestureState {
static_assert(sizeof(GestureState) == 0x60, "GestureState is an invalid size");
struct GestureProperties {
- std::array<Common::Point<s32>, MAX_POINTS> points{};
+ std::array<Common::Point<s32>, MaxPoints> points{};
std::size_t active_points{};
Common::Point<s32> mid_point{};
s64 detection_count{};
@@ -78,13 +91,53 @@ struct GestureProperties {
f32 angle{};
};
+// This is nn::hid::TouchState
+struct TouchState {
+ u64 delta_time{};
+ Core::HID::TouchAttribute attribute{};
+ u32 finger{};
+ Common::Point<u32> position{};
+ u32 diameter_x{};
+ u32 diameter_y{};
+ s32 rotation_angle{};
+};
+static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+
// This is nn::hid::TouchScreenState
struct TouchScreenState {
s64 sampling_number{};
s32 entry_count{};
INSERT_PADDING_BYTES(4); // Reserved
- std::array<Core::HID::TouchState, MAX_FINGERS> states{};
+ std::array<TouchState, MaxFingers> states{};
};
static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
+struct TouchFingerMap {
+ s32 finger_count{};
+ Core::HID::TouchScreenModeForNx touch_mode;
+ INSERT_PADDING_BYTES(3);
+ std::array<u32, MaxFingers> finger_ids{};
+};
+static_assert(sizeof(TouchFingerMap) == 0x48, "TouchFingerMap is an invalid size");
+
+struct TouchAruidData {
+ u64 aruid;
+ u32 basic_gesture_id;
+ u64 used_1;
+ u64 used_2;
+ u64 used_3;
+ u64 used_4;
+ GestureType gesture_type;
+ u16 resolution_width;
+ u16 resolution_height;
+ TouchFingerMap finger_map;
+};
+static_assert(sizeof(TouchAruidData) == 0x80, "TouchAruidData is an invalid size");
+
+struct AutoPilotState {
+ u64 count;
+ std::array<TouchState, 16> state;
+};
+static_assert(sizeof(AutoPilotState) == 0x288, "AutoPilotState is an invalid size");
+
} // namespace Service::HID