summaryrefslogtreecommitdiffstats
path: root/src/hid_core/resources/touch_screen
diff options
context:
space:
mode:
Diffstat (limited to 'src/hid_core/resources/touch_screen')
-rw-r--r--src/hid_core/resources/touch_screen/gesture.cpp366
-rw-r--r--src/hid_core/resources/touch_screen/gesture.h87
-rw-r--r--src/hid_core/resources/touch_screen/gesture_types.h77
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen.cpp132
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen.h43
-rw-r--r--src/hid_core/resources/touch_screen/touch_types.h90
6 files changed, 795 insertions, 0 deletions
diff --git a/src/hid_core/resources/touch_screen/gesture.cpp b/src/hid_core/resources/touch_screen/gesture.cpp
new file mode 100644
index 000000000..0ecc0941f
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/gesture.cpp
@@ -0,0 +1,366 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.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"
+
+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(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();
+
+ // 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;
+ }
+ }
+
+ // 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;
+ }
+
+ // Promote to press type
+ if (last_entry.type == GestureType::Touch) {
+ type = GestureType::Press;
+ }
+}
+
+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++;
+ }
+}
+
+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);
+ }
+}
+
+void Gesture::UpdatePanEvent(GestureProperties& gesture, GestureProperties& last_gesture_props,
+ GestureType& type, f32 time_difference) {
+ const auto& last_entry = GetLastGestureEntry();
+
+ 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;
+ }
+
+ 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;
+ }
+}
+
+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;
+}
+
+} // namespace Service::HID
diff --git a/src/hid_core/resources/touch_screen/gesture.h b/src/hid_core/resources/touch_screen/gesture.h
new file mode 100644
index 000000000..32e9a8690
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/gesture.h
@@ -0,0 +1,87 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#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;
+}
+
+namespace Service::HID {
+struct GestureSharedMemoryFormat;
+
+class Gesture final : public ControllerBase {
+public:
+ explicit Gesture(Core::HID::HIDCore& hid_core_);
+ ~Gesture() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+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};
+};
+} // 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
new file mode 100644
index 000000000..b4f034cd3
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/gesture_types.h
@@ -0,0 +1,77 @@
+// 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
new file mode 100644
index 000000000..48d956c51
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_screen.cpp
@@ -0,0 +1,132 @@
+// 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"
+#include "hid_core/resources/touch_screen/touch_screen.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() = default;
+
+void TouchScreen::OnInit() {}
+
+void TouchScreen::OnRelease() {}
+
+void TouchScreen::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ const u64 aruid = applet_resource->GetActiveAruid();
+ auto* data = applet_resource->GetAruidData(aruid);
+
+ if (data == nullptr || !data->flag.is_assigned) {
+ return;
+ }
+
+ TouchScreenSharedMemoryFormat& shared_memory = data->shared_memory_format->touch_screen;
+ shared_memory.touch_screen_lifo.timestamp = core_timing.GetGlobalTimeNs().count();
+
+ if (!IsControllerActivated()) {
+ shared_memory.touch_screen_lifo.buffer_count = 0;
+ shared_memory.touch_screen_lifo.buffer_tail = 0;
+ return;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ shared_memory.touch_screen_lifo.WriteNextEntry(next_state);
+}
+
+void TouchScreen::SetTouchscreenDimensions(u32 width, u32 height) {
+ touchscreen_width = width;
+ touchscreen_height = height;
+}
+
+} // 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
new file mode 100644
index 000000000..4b3824742
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_screen.h
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "hid_core/hid_types.h"
+#include "hid_core/resources/controller_base.h"
+#include "hid_core/resources/touch_screen/touch_types.h"
+
+namespace Core::HID {
+class EmulatedConsole;
+} // namespace Core::HID
+
+namespace Service::HID {
+struct TouchScreenSharedMemoryFormat;
+
+class TouchScreen final : public ControllerBase {
+public:
+ explicit TouchScreen(Core::HID::HIDCore& hid_core_);
+ ~TouchScreen() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+ void SetTouchscreenDimensions(u32 width, u32 height);
+
+private:
+ TouchScreenState next_state{};
+ Core::HID::EmulatedConsole* console = nullptr;
+
+ std::array<Core::HID::TouchFinger, MAX_FINGERS> fingers{};
+ u32 touchscreen_width;
+ u32 touchscreen_height;
+};
+} // 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
new file mode 100644
index 000000000..97ee847da
--- /dev/null
+++ b/src/hid_core/resources/touch_screen/touch_types.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include <array>
+#include "common/bit_field.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/point.h"
+#include "hid_core/hid_types.h"
+
+namespace Service::HID {
+static constexpr std::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{};
+};
+
+// 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{};
+};
+static_assert(sizeof(TouchScreenState) == 0x290, "TouchScreenState is an invalid size");
+
+} // namespace Service::HID