summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
authorGravatar Feng Chen2021-12-18 13:57:14 +0800
committerGravatar GitHub2021-12-18 13:57:14 +0800
commite49184e6069a9d791d2df3c1958f5c4b1187e124 (patch)
treeb776caf722e0be0e680f67b0ad0842628162ef1c /src/input_common
parentImplement convert legacy to generic (diff)
parentMerge pull request #7570 from ameerj/favorites-expanded (diff)
downloadyuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.gz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.xz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.zip
Merge branch 'yuzu-emu:master' into convert_legacy
Diffstat (limited to '')
-rw-r--r--src/core/hid/motion_input.cpp (renamed from src/input_common/motion_input.cpp)57
-rw-r--r--src/core/hid/motion_input.h (renamed from src/input_common/motion_input.h)27
-rw-r--r--src/input_common/CMakeLists.txt60
-rwxr-xr-xsrc/input_common/analog_from_button.cpp238
-rw-r--r--src/input_common/drivers/gc_adapter.cpp (renamed from src/input_common/gcadapter/gc_adapter.cpp)489
-rw-r--r--src/input_common/drivers/gc_adapter.h135
-rw-r--r--src/input_common/drivers/keyboard.cpp112
-rw-r--r--src/input_common/drivers/keyboard.h56
-rw-r--r--src/input_common/drivers/mouse.cpp185
-rw-r--r--src/input_common/drivers/mouse.h81
-rw-r--r--src/input_common/drivers/sdl_driver.cpp926
-rw-r--r--src/input_common/drivers/sdl_driver.h (renamed from src/input_common/sdl/sdl_impl.h)65
-rw-r--r--src/input_common/drivers/tas_input.cpp320
-rw-r--r--src/input_common/drivers/tas_input.h201
-rw-r--r--src/input_common/drivers/touch_screen.cpp53
-rw-r--r--src/input_common/drivers/touch_screen.h44
-rw-r--r--src/input_common/drivers/udp_client.cpp591
-rw-r--r--src/input_common/drivers/udp_client.h (renamed from src/input_common/udp/client.h)128
-rw-r--r--src/input_common/gcadapter/gc_adapter.h168
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp356
-rw-r--r--src/input_common/gcadapter/gc_poller.h78
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp317
-rw-r--r--[-rwxr-xr-x]src/input_common/helpers/stick_from_buttons.h (renamed from src/input_common/analog_from_button.h)7
-rw-r--r--src/input_common/helpers/touch_from_buttons.cpp84
-rw-r--r--src/input_common/helpers/touch_from_buttons.h (renamed from src/input_common/touch_from_button.h)7
-rw-r--r--src/input_common/helpers/udp_protocol.cpp (renamed from src/input_common/udp/protocol.cpp)2
-rw-r--r--src/input_common/helpers/udp_protocol.h (renamed from src/input_common/udp/protocol.h)75
-rw-r--r--src/input_common/input_engine.cpp362
-rw-r--r--src/input_common/input_engine.h229
-rw-r--r--src/input_common/input_mapping.cpp207
-rw-r--r--src/input_common/input_mapping.h83
-rw-r--r--src/input_common/input_poller.cpp970
-rw-r--r--src/input_common/input_poller.h217
-rw-r--r--src/input_common/keyboard.cpp121
-rw-r--r--src/input_common/keyboard.h47
-rw-r--r--src/input_common/main.cpp468
-rw-r--r--src/input_common/main.h152
-rw-r--r--src/input_common/motion_from_button.cpp34
-rw-r--r--src/input_common/motion_from_button.h25
-rw-r--r--src/input_common/mouse/mouse_input.cpp223
-rw-r--r--src/input_common/mouse/mouse_input.h116
-rw-r--r--src/input_common/mouse/mouse_poller.cpp299
-rw-r--r--src/input_common/mouse/mouse_poller.h109
-rw-r--r--src/input_common/sdl/sdl.cpp19
-rw-r--r--src/input_common/sdl/sdl.h51
-rw-r--r--src/input_common/sdl/sdl_impl.cpp1658
-rw-r--r--src/input_common/tas/tas_input.cpp455
-rw-r--r--src/input_common/tas/tas_input.h237
-rw-r--r--src/input_common/tas/tas_poller.cpp101
-rw-r--r--src/input_common/tas/tas_poller.h43
-rw-r--r--src/input_common/touch_from_button.cpp53
-rw-r--r--src/input_common/udp/client.cpp526
-rw-r--r--src/input_common/udp/udp.cpp110
-rw-r--r--src/input_common/udp/udp.h57
54 files changed, 5950 insertions, 5884 deletions
diff --git a/src/input_common/motion_input.cpp b/src/core/hid/motion_input.cpp
index 1c9d561c0..c25fea966 100644
--- a/src/input_common/motion_input.cpp
+++ b/src/core/hid/motion_input.cpp
@@ -2,13 +2,21 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included 3// Refer to the license.txt file included
4 4
5#include <random>
6#include "common/math_util.h" 5#include "common/math_util.h"
7#include "input_common/motion_input.h" 6#include "core/hid/motion_input.h"
8 7
9namespace InputCommon { 8namespace Core::HID {
10 9
11MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {} 10MotionInput::MotionInput() {
11 // Initialize PID constants with default values
12 SetPID(0.3f, 0.005f, 0.0f);
13}
14
15void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {
16 kp = new_kp;
17 ki = new_ki;
18 kd = new_kd;
19}
12 20
13void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { 21void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
14 accel = acceleration; 22 accel = acceleration;
@@ -65,6 +73,8 @@ void MotionInput::UpdateRotation(u64 elapsed_time) {
65 rotations += gyro * sample_period; 73 rotations += gyro * sample_period;
66} 74}
67 75
76// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
77// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
68void MotionInput::UpdateOrientation(u64 elapsed_time) { 78void MotionInput::UpdateOrientation(u64 elapsed_time) {
69 if (!IsCalibrated(0.1f)) { 79 if (!IsCalibrated(0.1f)) {
70 ResetOrientation(); 80 ResetOrientation();
@@ -190,43 +200,6 @@ Common::Vec3f MotionInput::GetRotations() const {
190 return rotations; 200 return rotations;
191} 201}
192 202
193Input::MotionStatus MotionInput::GetMotion() const {
194 const Common::Vec3f gyroscope = GetGyroscope();
195 const Common::Vec3f accelerometer = GetAcceleration();
196 const Common::Vec3f rotation = GetRotations();
197 const std::array<Common::Vec3f, 3> orientation = GetOrientation();
198 const Common::Quaternion<f32> quaternion = GetQuaternion();
199 return {accelerometer, gyroscope, rotation, orientation, quaternion};
200}
201
202Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
203 std::random_device device;
204 std::mt19937 gen(device());
205 std::uniform_int_distribution<s16> distribution(-1000, 1000);
206 const Common::Vec3f gyroscope{
207 static_cast<f32>(distribution(gen)) * 0.001f,
208 static_cast<f32>(distribution(gen)) * 0.001f,
209 static_cast<f32>(distribution(gen)) * 0.001f,
210 };
211 const Common::Vec3f accelerometer{
212 static_cast<f32>(distribution(gen)) * 0.001f,
213 static_cast<f32>(distribution(gen)) * 0.001f,
214 static_cast<f32>(distribution(gen)) * 0.001f,
215 };
216 constexpr Common::Vec3f rotation;
217 constexpr std::array orientation{
218 Common::Vec3f{1.0f, 0.0f, 0.0f},
219 Common::Vec3f{0.0f, 1.0f, 0.0f},
220 Common::Vec3f{0.0f, 0.0f, 1.0f},
221 };
222 constexpr Common::Quaternion<f32> quaternion{
223 {0.0f, 0.0f, 0.0f},
224 1.0f,
225 };
226 return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation,
227 quaternion};
228}
229
230void MotionInput::ResetOrientation() { 203void MotionInput::ResetOrientation() {
231 if (!reset_enabled || only_accelerometer) { 204 if (!reset_enabled || only_accelerometer) {
232 return; 205 return;
@@ -304,4 +277,4 @@ void MotionInput::SetOrientationFromAccelerometer() {
304 quat = quat.Normalized(); 277 quat = quat.Normalized();
305 } 278 }
306} 279}
307} // namespace InputCommon 280} // namespace Core::HID
diff --git a/src/input_common/motion_input.h b/src/core/hid/motion_input.h
index efe74cf19..5b5b420bb 100644
--- a/src/input_common/motion_input.h
+++ b/src/core/hid/motion_input.h
@@ -7,13 +7,12 @@
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/quaternion.h" 8#include "common/quaternion.h"
9#include "common/vector_math.h" 9#include "common/vector_math.h"
10#include "core/frontend/input.h"
11 10
12namespace InputCommon { 11namespace Core::HID {
13 12
14class MotionInput { 13class MotionInput {
15public: 14public:
16 explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); 15 explicit MotionInput();
17 16
18 MotionInput(const MotionInput&) = default; 17 MotionInput(const MotionInput&) = default;
19 MotionInput& operator=(const MotionInput&) = default; 18 MotionInput& operator=(const MotionInput&) = default;
@@ -21,6 +20,7 @@ public:
21 MotionInput(MotionInput&&) = default; 20 MotionInput(MotionInput&&) = default;
22 MotionInput& operator=(MotionInput&&) = default; 21 MotionInput& operator=(MotionInput&&) = default;
23 22
23 void SetPID(f32 new_kp, f32 new_ki, f32 new_kd);
24 void SetAcceleration(const Common::Vec3f& acceleration); 24 void SetAcceleration(const Common::Vec3f& acceleration);
25 void SetGyroscope(const Common::Vec3f& gyroscope); 25 void SetGyroscope(const Common::Vec3f& gyroscope);
26 void SetQuaternion(const Common::Quaternion<f32>& quaternion); 26 void SetQuaternion(const Common::Quaternion<f32>& quaternion);
@@ -38,9 +38,6 @@ public:
38 [[nodiscard]] Common::Vec3f GetGyroscope() const; 38 [[nodiscard]] Common::Vec3f GetGyroscope() const;
39 [[nodiscard]] Common::Vec3f GetRotations() const; 39 [[nodiscard]] Common::Vec3f GetRotations() const;
40 [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const; 40 [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
41 [[nodiscard]] Input::MotionStatus GetMotion() const;
42 [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
43 int gyro_magnitude) const;
44 41
45 [[nodiscard]] bool IsMoving(f32 sensitivity) const; 42 [[nodiscard]] bool IsMoving(f32 sensitivity) const;
46 [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; 43 [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
@@ -59,16 +56,32 @@ private:
59 Common::Vec3f integral_error; 56 Common::Vec3f integral_error;
60 Common::Vec3f derivative_error; 57 Common::Vec3f derivative_error;
61 58
59 // Quaternion containing the device orientation
62 Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f}; 60 Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
61
62 // Number of full rotations in each axis
63 Common::Vec3f rotations; 63 Common::Vec3f rotations;
64
65 // Acceleration vector measurement in G force
64 Common::Vec3f accel; 66 Common::Vec3f accel;
67
68 // Gyroscope vector measurement in radians/s.
65 Common::Vec3f gyro; 69 Common::Vec3f gyro;
70
71 // Vector to be substracted from gyro measurements
66 Common::Vec3f gyro_drift; 72 Common::Vec3f gyro_drift;
67 73
74 // Minimum gyro amplitude to detect if the device is moving
68 f32 gyro_threshold = 0.0f; 75 f32 gyro_threshold = 0.0f;
76
77 // Number of invalid sequential data
69 u32 reset_counter = 0; 78 u32 reset_counter = 0;
79
80 // If the provided data is invalid the device will be autocalibrated
70 bool reset_enabled = true; 81 bool reset_enabled = true;
82
83 // Use accelerometer values to calculate position
71 bool only_accelerometer = true; 84 bool only_accelerometer = true;
72}; 85};
73 86
74} // namespace InputCommon 87} // namespace Core::HID
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index dd13d948f..d4fa69a77 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -1,36 +1,32 @@
1add_library(input_common STATIC 1add_library(input_common STATIC
2 analog_from_button.cpp 2 drivers/gc_adapter.cpp
3 analog_from_button.h 3 drivers/gc_adapter.h
4 keyboard.cpp 4 drivers/keyboard.cpp
5 keyboard.h 5 drivers/keyboard.h
6 drivers/mouse.cpp
7 drivers/mouse.h
8 drivers/sdl_driver.cpp
9 drivers/sdl_driver.h
10 drivers/tas_input.cpp
11 drivers/tas_input.h
12 drivers/touch_screen.cpp
13 drivers/touch_screen.h
14 drivers/udp_client.cpp
15 drivers/udp_client.h
16 helpers/stick_from_buttons.cpp
17 helpers/stick_from_buttons.h
18 helpers/touch_from_buttons.cpp
19 helpers/touch_from_buttons.h
20 helpers/udp_protocol.cpp
21 helpers/udp_protocol.h
22 input_engine.cpp
23 input_engine.h
24 input_mapping.cpp
25 input_mapping.h
26 input_poller.cpp
27 input_poller.h
6 main.cpp 28 main.cpp
7 main.h 29 main.h
8 motion_from_button.cpp
9 motion_from_button.h
10 motion_input.cpp
11 motion_input.h
12 touch_from_button.cpp
13 touch_from_button.h
14 gcadapter/gc_adapter.cpp
15 gcadapter/gc_adapter.h
16 gcadapter/gc_poller.cpp
17 gcadapter/gc_poller.h
18 mouse/mouse_input.cpp
19 mouse/mouse_input.h
20 mouse/mouse_poller.cpp
21 mouse/mouse_poller.h
22 sdl/sdl.cpp
23 sdl/sdl.h
24 tas/tas_input.cpp
25 tas/tas_input.h
26 tas/tas_poller.cpp
27 tas/tas_poller.h
28 udp/client.cpp
29 udp/client.h
30 udp/protocol.cpp
31 udp/protocol.h
32 udp/udp.cpp
33 udp/udp.h
34) 30)
35 31
36if (MSVC) 32if (MSVC)
@@ -57,8 +53,8 @@ endif()
57 53
58if (ENABLE_SDL2) 54if (ENABLE_SDL2)
59 target_sources(input_common PRIVATE 55 target_sources(input_common PRIVATE
60 sdl/sdl_impl.cpp 56 drivers/sdl_driver.cpp
61 sdl/sdl_impl.h 57 drivers/sdl_driver.h
62 ) 58 )
63 target_link_libraries(input_common PRIVATE SDL2) 59 target_link_libraries(input_common PRIVATE SDL2)
64 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 60 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp
deleted file mode 100755
index 2fafd077f..000000000
--- a/src/input_common/analog_from_button.cpp
+++ /dev/null
@@ -1,238 +0,0 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <chrono>
7#include <cmath>
8#include <thread>
9#include "common/math_util.h"
10#include "common/settings.h"
11#include "input_common/analog_from_button.h"
12
13namespace InputCommon {
14
15class Analog final : public Input::AnalogDevice {
16public:
17 using Button = std::unique_ptr<Input::ButtonDevice>;
18
19 Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_,
20 float modifier_scale_, float modifier_angle_)
21 : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
22 right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
23 modifier_angle(modifier_angle_) {
24 Input::InputCallback<bool> callbacks{
25 [this]([[maybe_unused]] bool status) { UpdateStatus(); }};
26 up->SetCallback(callbacks);
27 down->SetCallback(callbacks);
28 left->SetCallback(callbacks);
29 right->SetCallback(callbacks);
30 modifier->SetCallback(callbacks);
31 }
32
33 bool IsAngleGreater(float old_angle, float new_angle) const {
34 constexpr float TAU = Common::PI * 2.0f;
35 // Use wider angle to ease the transition.
36 constexpr float aperture = TAU * 0.15f;
37 const float top_limit = new_angle + aperture;
38 return (old_angle > new_angle && old_angle <= top_limit) ||
39 (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
40 }
41
42 bool IsAngleSmaller(float old_angle, float new_angle) const {
43 constexpr float TAU = Common::PI * 2.0f;
44 // Use wider angle to ease the transition.
45 constexpr float aperture = TAU * 0.15f;
46 const float bottom_limit = new_angle - aperture;
47 return (old_angle >= bottom_limit && old_angle < new_angle) ||
48 (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
49 }
50
51 float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
52 constexpr float TAU = Common::PI * 2.0f;
53 float new_angle = angle;
54
55 auto time_difference = static_cast<float>(
56 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
57 time_difference /= 1000.0f * 1000.0f;
58 if (time_difference > 0.5f) {
59 time_difference = 0.5f;
60 }
61
62 if (IsAngleGreater(new_angle, goal_angle)) {
63 new_angle -= modifier_angle * time_difference;
64 if (new_angle < 0) {
65 new_angle += TAU;
66 }
67 if (!IsAngleGreater(new_angle, goal_angle)) {
68 return goal_angle;
69 }
70 } else if (IsAngleSmaller(new_angle, goal_angle)) {
71 new_angle += modifier_angle * time_difference;
72 if (new_angle >= TAU) {
73 new_angle -= TAU;
74 }
75 if (!IsAngleSmaller(new_angle, goal_angle)) {
76 return goal_angle;
77 }
78 } else {
79 return goal_angle;
80 }
81 return new_angle;
82 }
83
84 void SetGoalAngle(bool r, bool l, bool u, bool d) {
85 // Move to the right
86 if (r && !u && !d) {
87 goal_angle = 0.0f;
88 }
89
90 // Move to the upper right
91 if (r && u && !d) {
92 goal_angle = Common::PI * 0.25f;
93 }
94
95 // Move up
96 if (u && !l && !r) {
97 goal_angle = Common::PI * 0.5f;
98 }
99
100 // Move to the upper left
101 if (l && u && !d) {
102 goal_angle = Common::PI * 0.75f;
103 }
104
105 // Move to the left
106 if (l && !u && !d) {
107 goal_angle = Common::PI;
108 }
109
110 // Move to the bottom left
111 if (l && !u && d) {
112 goal_angle = Common::PI * 1.25f;
113 }
114
115 // Move down
116 if (d && !l && !r) {
117 goal_angle = Common::PI * 1.5f;
118 }
119
120 // Move to the bottom right
121 if (r && !u && d) {
122 goal_angle = Common::PI * 1.75f;
123 }
124 }
125
126 void UpdateStatus() {
127 const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
128
129 bool r = right->GetStatus();
130 bool l = left->GetStatus();
131 bool u = up->GetStatus();
132 bool d = down->GetStatus();
133
134 // Eliminate contradictory movements
135 if (r && l) {
136 r = false;
137 l = false;
138 }
139 if (u && d) {
140 u = false;
141 d = false;
142 }
143
144 // Move if a key is pressed
145 if (r || l || u || d) {
146 amplitude = coef;
147 } else {
148 amplitude = 0;
149 }
150
151 const auto now = std::chrono::steady_clock::now();
152 const auto time_difference = static_cast<u64>(
153 std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
154
155 if (time_difference < 10) {
156 // Disable analog mode if inputs are too fast
157 SetGoalAngle(r, l, u, d);
158 angle = goal_angle;
159 } else {
160 angle = GetAngle(now);
161 SetGoalAngle(r, l, u, d);
162 }
163
164 last_update = now;
165 }
166
167 std::tuple<float, float> GetStatus() const override {
168 if (Settings::values.emulate_analog_keyboard) {
169 const auto now = std::chrono::steady_clock::now();
170 float angle_ = GetAngle(now);
171 return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude);
172 }
173 constexpr float SQRT_HALF = 0.707106781f;
174 int x = 0, y = 0;
175 if (right->GetStatus()) {
176 ++x;
177 }
178 if (left->GetStatus()) {
179 --x;
180 }
181 if (up->GetStatus()) {
182 ++y;
183 }
184 if (down->GetStatus()) {
185 --y;
186 }
187 const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
188 return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
189 static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
190 }
191
192 Input::AnalogProperties GetAnalogProperties() const override {
193 return {modifier_scale, 1.0f, 0.5f};
194 }
195
196 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
197 switch (direction) {
198 case Input::AnalogDirection::RIGHT:
199 return right->GetStatus();
200 case Input::AnalogDirection::LEFT:
201 return left->GetStatus();
202 case Input::AnalogDirection::UP:
203 return up->GetStatus();
204 case Input::AnalogDirection::DOWN:
205 return down->GetStatus();
206 }
207 return false;
208 }
209
210private:
211 Button up;
212 Button down;
213 Button left;
214 Button right;
215 Button modifier;
216 float modifier_scale;
217 float modifier_angle;
218 float angle{};
219 float goal_angle{};
220 float amplitude{};
221 std::chrono::time_point<std::chrono::steady_clock> last_update;
222};
223
224std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) {
225 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
226 auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine));
227 auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine));
228 auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine));
229 auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine));
230 auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine));
231 auto modifier_scale = params.Get("modifier_scale", 0.5f);
232 auto modifier_angle = params.Get("modifier_angle", 5.5f);
233 return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left),
234 std::move(right), std::move(modifier), modifier_scale,
235 modifier_angle);
236}
237
238} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index a2f1bb67c..7ab4540a8 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -2,47 +2,103 @@
2// Licensed under GPLv2+ 2// Licensed under GPLv2+
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <chrono> 5#include <fmt/format.h>
6#include <thread>
7
8#include <libusb.h> 6#include <libusb.h>
9 7
10#include "common/logging/log.h" 8#include "common/logging/log.h"
11#include "common/param_package.h" 9#include "common/param_package.h"
12#include "common/settings_input.h" 10#include "common/settings_input.h"
13#include "input_common/gcadapter/gc_adapter.h" 11#include "common/thread.h"
12#include "input_common/drivers/gc_adapter.h"
13
14namespace InputCommon {
15
16class LibUSBContext {
17public:
18 explicit LibUSBContext() {
19 init_result = libusb_init(&ctx);
20 }
21
22 ~LibUSBContext() {
23 libusb_exit(ctx);
24 }
25
26 LibUSBContext& operator=(const LibUSBContext&) = delete;
27 LibUSBContext(const LibUSBContext&) = delete;
28
29 LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
30 LibUSBContext(LibUSBContext&&) noexcept = delete;
31
32 [[nodiscard]] int InitResult() const noexcept {
33 return init_result;
34 }
35
36 [[nodiscard]] libusb_context* get() noexcept {
37 return ctx;
38 }
39
40private:
41 libusb_context* ctx;
42 int init_result{};
43};
44
45class LibUSBDeviceHandle {
46public:
47 explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
48 handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
49 }
50
51 ~LibUSBDeviceHandle() noexcept {
52 if (handle) {
53 libusb_release_interface(handle, 1);
54 libusb_close(handle);
55 }
56 }
14 57
15namespace GCAdapter { 58 LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
59 LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
16 60
17Adapter::Adapter() { 61 LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
18 if (usb_adapter_handle != nullptr) { 62 LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
63
64 [[nodiscard]] libusb_device_handle* get() noexcept {
65 return handle;
66 }
67
68private:
69 libusb_device_handle* handle{};
70};
71
72GCAdapter::GCAdapter(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
73 if (usb_adapter_handle) {
19 return; 74 return;
20 } 75 }
21 LOG_INFO(Input, "GC Adapter Initialization started"); 76 LOG_DEBUG(Input, "Initialization started");
22 77
23 const int init_res = libusb_init(&libusb_ctx); 78 libusb_ctx = std::make_unique<LibUSBContext>();
79 const int init_res = libusb_ctx->InitResult();
24 if (init_res == LIBUSB_SUCCESS) { 80 if (init_res == LIBUSB_SUCCESS) {
25 adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); 81 adapter_scan_thread =
82 std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
26 } else { 83 } else {
27 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); 84 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
28 } 85 }
29} 86}
30 87
31Adapter::~Adapter() { 88GCAdapter::~GCAdapter() {
32 Reset(); 89 Reset();
33} 90}
34 91
35void Adapter::AdapterInputThread() { 92void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
36 LOG_DEBUG(Input, "GC Adapter input thread started"); 93 LOG_DEBUG(Input, "Input thread started");
94 Common::SetCurrentThreadName("yuzu:input:GCAdapter");
37 s32 payload_size{}; 95 s32 payload_size{};
38 AdapterPayload adapter_payload{}; 96 AdapterPayload adapter_payload{};
39 97
40 if (adapter_scan_thread.joinable()) { 98 adapter_scan_thread = {};
41 adapter_scan_thread.join();
42 }
43 99
44 while (adapter_input_thread_running) { 100 while (!stop_token.stop_requested()) {
45 libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), 101 libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
46 static_cast<s32>(adapter_payload.size()), &payload_size, 16); 102 static_cast<s32>(adapter_payload.size()), &payload_size, 16);
47 if (IsPayloadCorrect(adapter_payload, payload_size)) { 103 if (IsPayloadCorrect(adapter_payload, payload_size)) {
48 UpdateControllers(adapter_payload); 104 UpdateControllers(adapter_payload);
@@ -52,19 +108,20 @@ void Adapter::AdapterInputThread() {
52 } 108 }
53 109
54 if (restart_scan_thread) { 110 if (restart_scan_thread) {
55 adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); 111 adapter_scan_thread =
112 std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
56 restart_scan_thread = false; 113 restart_scan_thread = false;
57 } 114 }
58} 115}
59 116
60bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { 117bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
61 if (payload_size != static_cast<s32>(adapter_payload.size()) || 118 if (payload_size != static_cast<s32>(adapter_payload.size()) ||
62 adapter_payload[0] != LIBUSB_DT_HID) { 119 adapter_payload[0] != LIBUSB_DT_HID) {
63 LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, 120 LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
64 adapter_payload[0]); 121 adapter_payload[0]);
65 if (input_error_counter++ > 20) { 122 if (input_error_counter++ > 20) {
66 LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); 123 LOG_ERROR(Input, "Timeout, Is the adapter connected?");
67 adapter_input_thread_running = false; 124 adapter_input_thread.request_stop();
68 restart_scan_thread = true; 125 restart_scan_thread = true;
69 } 126 }
70 return false; 127 return false;
@@ -74,7 +131,7 @@ bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payloa
74 return true; 131 return true;
75} 132}
76 133
77void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { 134void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) {
78 for (std::size_t port = 0; port < pads.size(); ++port) { 135 for (std::size_t port = 0; port < pads.size(); ++port) {
79 const std::size_t offset = 1 + (9 * port); 136 const std::size_t offset = 1 + (9 * port);
80 const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); 137 const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
@@ -84,23 +141,24 @@ void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
84 const u8 b2 = adapter_payload[offset + 2]; 141 const u8 b2 = adapter_payload[offset + 2];
85 UpdateStateButtons(port, b1, b2); 142 UpdateStateButtons(port, b1, b2);
86 UpdateStateAxes(port, adapter_payload); 143 UpdateStateAxes(port, adapter_payload);
87 if (configuring) {
88 UpdateYuzuSettings(port);
89 }
90 } 144 }
91 } 145 }
92} 146}
93 147
94void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { 148void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
95 if (pads[port].type == pad_type) { 149 if (pads[port].type == pad_type) {
96 return; 150 return;
97 } 151 }
98 // Device changed reset device and set new type 152 // Device changed reset device and set new type
99 ResetDevice(port); 153 pads[port].axis_origin = {};
154 pads[port].reset_origin_counter = {};
155 pads[port].enable_vibration = {};
156 pads[port].rumble_amplitude = {};
100 pads[port].type = pad_type; 157 pads[port].type = pad_type;
101} 158}
102 159
103void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { 160void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
161 [[maybe_unused]] u8 b2) {
104 if (port >= pads.size()) { 162 if (port >= pads.size()) {
105 return; 163 return;
106 } 164 }
@@ -116,25 +174,21 @@ void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
116 PadButton::TriggerR, 174 PadButton::TriggerR,
117 PadButton::TriggerL, 175 PadButton::TriggerL,
118 }; 176 };
119 pads[port].buttons = 0; 177
120 for (std::size_t i = 0; i < b1_buttons.size(); ++i) { 178 for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
121 if ((b1 & (1U << i)) != 0) { 179 const bool button_status = (b1 & (1U << i)) != 0;
122 pads[port].buttons = 180 const int button = static_cast<int>(b1_buttons[i]);
123 static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i])); 181 SetButton(pads[port].identifier, button, button_status);
124 pads[port].last_button = b1_buttons[i];
125 }
126 } 182 }
127 183
128 for (std::size_t j = 0; j < b2_buttons.size(); ++j) { 184 for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
129 if ((b2 & (1U << j)) != 0) { 185 const bool button_status = (b2 & (1U << j)) != 0;
130 pads[port].buttons = 186 const int button = static_cast<int>(b2_buttons[j]);
131 static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j])); 187 SetButton(pads[port].identifier, button, button_status);
132 pads[port].last_button = b2_buttons[j];
133 }
134 } 188 }
135} 189}
136 190
137void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { 191void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
138 if (port >= pads.size()) { 192 if (port >= pads.size()) {
139 return; 193 return;
140 } 194 }
@@ -155,134 +209,63 @@ void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_pa
155 pads[port].axis_origin[index] = axis_value; 209 pads[port].axis_origin[index] = axis_value;
156 pads[port].reset_origin_counter++; 210 pads[port].reset_origin_counter++;
157 } 211 }
158 pads[port].axis_values[index] = 212 const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
159 static_cast<s16>(axis_value - pads[port].axis_origin[index]); 213 SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
160 }
161}
162
163void Adapter::UpdateYuzuSettings(std::size_t port) {
164 if (port >= pads.size()) {
165 return;
166 }
167
168 constexpr u8 axis_threshold = 50;
169 GCPadStatus pad_status = {.port = port};
170
171 if (pads[port].buttons != 0) {
172 pad_status.button = pads[port].last_button;
173 pad_queue.Push(pad_status);
174 }
175
176 // Accounting for a threshold here to ensure an intentional press
177 for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
178 const s16 value = pads[port].axis_values[i];
179
180 if (value > axis_threshold || value < -axis_threshold) {
181 pad_status.axis = static_cast<PadAxes>(i);
182 pad_status.axis_value = value;
183 pad_status.axis_threshold = axis_threshold;
184 pad_queue.Push(pad_status);
185 }
186 }
187}
188
189void Adapter::UpdateVibrations() {
190 // Use 8 states to keep the switching between on/off fast enough for
191 // a human to not notice the difference between switching from on/off
192 // More states = more rumble strengths = slower update time
193 constexpr u8 vibration_states = 8;
194
195 vibration_counter = (vibration_counter + 1) % vibration_states;
196
197 for (GCController& pad : pads) {
198 const bool vibrate = pad.rumble_amplitude > vibration_counter;
199 vibration_changed |= vibrate != pad.enable_vibration;
200 pad.enable_vibration = vibrate;
201 }
202 SendVibrations();
203}
204
205void Adapter::SendVibrations() {
206 if (!rumble_enabled || !vibration_changed) {
207 return;
208 }
209 s32 size{};
210 constexpr u8 rumble_command = 0x11;
211 const u8 p1 = pads[0].enable_vibration;
212 const u8 p2 = pads[1].enable_vibration;
213 const u8 p3 = pads[2].enable_vibration;
214 const u8 p4 = pads[3].enable_vibration;
215 std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
216 const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
217 static_cast<s32>(payload.size()), &size, 16);
218 if (err) {
219 LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
220 if (output_error_counter++ > 5) {
221 LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
222 rumble_enabled = false;
223 }
224 return;
225 } 214 }
226 output_error_counter = 0;
227 vibration_changed = false;
228} 215}
229 216
230bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { 217void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
231 pads[port].rumble_amplitude = amplitude; 218 Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
232 219 usb_adapter_handle = nullptr;
233 return rumble_enabled; 220 pads = {};
234} 221 while (!stop_token.stop_requested() && !Setup()) {
235 222 std::this_thread::sleep_for(std::chrono::seconds(2));
236void Adapter::AdapterScanThread() {
237 adapter_scan_thread_running = true;
238 adapter_input_thread_running = false;
239 if (adapter_input_thread.joinable()) {
240 adapter_input_thread.join();
241 }
242 ClearLibusbHandle();
243 ResetDevices();
244 while (adapter_scan_thread_running && !adapter_input_thread_running) {
245 Setup();
246 std::this_thread::sleep_for(std::chrono::seconds(1));
247 } 223 }
248} 224}
249 225
250void Adapter::Setup() { 226bool GCAdapter::Setup() {
251 usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); 227 constexpr u16 nintendo_vid = 0x057e;
252 228 constexpr u16 gc_adapter_pid = 0x0337;
253 if (usb_adapter_handle == NULL) { 229 usb_adapter_handle =
254 return; 230 std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
231 if (!usb_adapter_handle->get()) {
232 return false;
255 } 233 }
256 if (!CheckDeviceAccess()) { 234 if (!CheckDeviceAccess()) {
257 ClearLibusbHandle(); 235 usb_adapter_handle = nullptr;
258 return; 236 return false;
259 } 237 }
260 238
261 libusb_device* device = libusb_get_device(usb_adapter_handle); 239 libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
262 240
263 LOG_INFO(Input, "GC adapter is now connected"); 241 LOG_INFO(Input, "GC adapter is now connected");
264 // GC Adapter found and accessible, registering it 242 // GC Adapter found and accessible, registering it
265 if (GetGCEndpoint(device)) { 243 if (GetGCEndpoint(device)) {
266 adapter_scan_thread_running = false;
267 adapter_input_thread_running = true;
268 rumble_enabled = true; 244 rumble_enabled = true;
269 input_error_counter = 0; 245 input_error_counter = 0;
270 output_error_counter = 0; 246 output_error_counter = 0;
271 adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
272 }
273}
274 247
275bool Adapter::CheckDeviceAccess() { 248 std::size_t port = 0;
276 // This fixes payload problems from offbrand GCAdapters 249 for (GCController& pad : pads) {
277 const s32 control_transfer_error = 250 pad.identifier = {
278 libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); 251 .guid = Common::UUID{Common::INVALID_UUID},
279 if (control_transfer_error < 0) { 252 .port = port++,
280 LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); 253 .pad = 0,
254 };
255 PreSetController(pad.identifier);
256 }
257
258 adapter_input_thread =
259 std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
260 return true;
281 } 261 }
262 return false;
263}
282 264
283 s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); 265bool GCAdapter::CheckDeviceAccess() {
266 s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
284 if (kernel_driver_error == 1) { 267 if (kernel_driver_error == 1) {
285 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); 268 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0);
286 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { 269 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
287 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", 270 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
288 kernel_driver_error); 271 kernel_driver_error);
@@ -290,23 +273,28 @@ bool Adapter::CheckDeviceAccess() {
290 } 273 }
291 274
292 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { 275 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
293 libusb_close(usb_adapter_handle);
294 usb_adapter_handle = nullptr; 276 usb_adapter_handle = nullptr;
295 return false; 277 return false;
296 } 278 }
297 279
298 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); 280 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
299 if (interface_claim_error) { 281 if (interface_claim_error) {
300 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); 282 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
301 libusb_close(usb_adapter_handle);
302 usb_adapter_handle = nullptr; 283 usb_adapter_handle = nullptr;
303 return false; 284 return false;
304 } 285 }
305 286
287 // This fixes payload problems from offbrand GCAdapters
288 const s32 control_transfer_error =
289 libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
290 if (control_transfer_error < 0) {
291 LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
292 }
293
306 return true; 294 return true;
307} 295}
308 296
309bool Adapter::GetGCEndpoint(libusb_device* device) { 297bool GCAdapter::GetGCEndpoint(libusb_device* device) {
310 libusb_config_descriptor* config = nullptr; 298 libusb_config_descriptor* config = nullptr;
311 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); 299 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
312 if (config_descriptor_return != LIBUSB_SUCCESS) { 300 if (config_descriptor_return != LIBUSB_SUCCESS) {
@@ -332,77 +320,95 @@ bool Adapter::GetGCEndpoint(libusb_device* device) {
332 // This transfer seems to be responsible for clearing the state of the adapter 320 // This transfer seems to be responsible for clearing the state of the adapter
333 // Used to clear the "busy" state of when the device is unexpectedly unplugged 321 // Used to clear the "busy" state of when the device is unexpectedly unplugged
334 unsigned char clear_payload = 0x13; 322 unsigned char clear_payload = 0x13;
335 libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, 323 libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload,
336 sizeof(clear_payload), nullptr, 16); 324 sizeof(clear_payload), nullptr, 16);
337 return true; 325 return true;
338} 326}
339 327
340void Adapter::JoinThreads() { 328Common::Input::VibrationError GCAdapter::SetRumble(
341 restart_scan_thread = false; 329 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
342 adapter_input_thread_running = false; 330 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
343 adapter_scan_thread_running = false; 331 const auto processed_amplitude =
332 static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
344 333
345 if (adapter_scan_thread.joinable()) { 334 pads[identifier.port].rumble_amplitude = processed_amplitude;
346 adapter_scan_thread.join();
347 }
348 335
349 if (adapter_input_thread.joinable()) { 336 if (!rumble_enabled) {
350 adapter_input_thread.join(); 337 return Common::Input::VibrationError::Disabled;
351 } 338 }
339 return Common::Input::VibrationError::None;
352} 340}
353 341
354void Adapter::ClearLibusbHandle() { 342void GCAdapter::UpdateVibrations() {
355 if (usb_adapter_handle) { 343 // Use 8 states to keep the switching between on/off fast enough for
356 libusb_release_interface(usb_adapter_handle, 1); 344 // a human to feel different vibration strenght
357 libusb_close(usb_adapter_handle); 345 // More states == more rumble strengths == slower update time
358 usb_adapter_handle = nullptr; 346 constexpr u8 vibration_states = 8;
347
348 vibration_counter = (vibration_counter + 1) % vibration_states;
349
350 for (GCController& pad : pads) {
351 const bool vibrate = pad.rumble_amplitude > vibration_counter;
352 vibration_changed |= vibrate != pad.enable_vibration;
353 pad.enable_vibration = vibrate;
359 } 354 }
355 SendVibrations();
360} 356}
361 357
362void Adapter::ResetDevices() { 358void GCAdapter::SendVibrations() {
363 for (std::size_t i = 0; i < pads.size(); ++i) { 359 if (!rumble_enabled || !vibration_changed) {
364 ResetDevice(i); 360 return;
361 }
362 s32 size{};
363 constexpr u8 rumble_command = 0x11;
364 const u8 p1 = pads[0].enable_vibration;
365 const u8 p2 = pads[1].enable_vibration;
366 const u8 p3 = pads[2].enable_vibration;
367 const u8 p4 = pads[3].enable_vibration;
368 std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
369 const int err =
370 libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
371 static_cast<s32>(payload.size()), &size, 16);
372 if (err) {
373 LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err));
374 if (output_error_counter++ > 5) {
375 LOG_ERROR(Input, "Output timeout, Rumble disabled");
376 rumble_enabled = false;
377 }
378 return;
365 } 379 }
380 output_error_counter = 0;
381 vibration_changed = false;
366} 382}
367 383
368void Adapter::ResetDevice(std::size_t port) { 384bool GCAdapter::DeviceConnected(std::size_t port) const {
369 pads[port].type = ControllerTypes::None; 385 return pads[port].type != ControllerTypes::None;
370 pads[port].enable_vibration = false;
371 pads[port].rumble_amplitude = 0;
372 pads[port].buttons = 0;
373 pads[port].last_button = PadButton::Undefined;
374 pads[port].axis_values.fill(0);
375 pads[port].reset_origin_counter = 0;
376} 386}
377 387
378void Adapter::Reset() { 388void GCAdapter::Reset() {
379 JoinThreads(); 389 adapter_scan_thread = {};
380 ClearLibusbHandle(); 390 adapter_input_thread = {};
381 ResetDevices(); 391 usb_adapter_handle = nullptr;
382 392 pads = {};
383 if (libusb_ctx) { 393 libusb_ctx = nullptr;
384 libusb_exit(libusb_ctx);
385 }
386} 394}
387 395
388std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { 396std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
389 std::vector<Common::ParamPackage> devices; 397 std::vector<Common::ParamPackage> devices;
390 for (std::size_t port = 0; port < pads.size(); ++port) { 398 for (std::size_t port = 0; port < pads.size(); ++port) {
391 if (!DeviceConnected(port)) { 399 if (!DeviceConnected(port)) {
392 continue; 400 continue;
393 } 401 }
394 std::string name = fmt::format("Gamecube Controller {}", port + 1); 402 Common::ParamPackage identifier{};
395 devices.emplace_back(Common::ParamPackage{ 403 identifier.Set("engine", GetEngineName());
396 {"class", "gcpad"}, 404 identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1));
397 {"display", std::move(name)}, 405 identifier.Set("port", static_cast<int>(port));
398 {"port", std::to_string(port)}, 406 devices.emplace_back(identifier);
399 });
400 } 407 }
401 return devices; 408 return devices;
402} 409}
403 410
404InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( 411ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
405 const Common::ParamPackage& params) const {
406 // This list is missing ZL/ZR since those are not considered buttons. 412 // This list is missing ZL/ZR since those are not considered buttons.
407 // We will add those afterwards 413 // We will add those afterwards
408 // This list also excludes any button that can't be really mapped 414 // This list also excludes any button that can't be really mapped
@@ -425,47 +431,49 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
425 return {}; 431 return {};
426 } 432 }
427 433
428 InputCommon::ButtonMapping mapping{}; 434 ButtonMapping mapping{};
429 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { 435 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
430 Common::ParamPackage button_params({{"engine", "gcpad"}}); 436 Common::ParamPackage button_params{};
437 button_params.Set("engine", GetEngineName());
431 button_params.Set("port", params.Get("port", 0)); 438 button_params.Set("port", params.Get("port", 0));
432 button_params.Set("button", static_cast<int>(gcadapter_button)); 439 button_params.Set("button", static_cast<int>(gcadapter_button));
433 mapping.insert_or_assign(switch_button, std::move(button_params)); 440 mapping.insert_or_assign(switch_button, std::move(button_params));
434 } 441 }
435 442
436 // Add the missing bindings for ZL/ZR 443 // Add the missing bindings for ZL/ZR
437 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2> 444 static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
438 switch_to_gcadapter_axis = { 445 switch_to_gcadapter_axis = {
439 std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, 446 std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
440 {Settings::NativeButton::ZR, PadAxes::TriggerRight}, 447 {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
441 }; 448 };
442 for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) { 449 for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
443 Common::ParamPackage button_params({{"engine", "gcpad"}}); 450 Common::ParamPackage button_params{};
451 button_params.Set("engine", GetEngineName());
444 button_params.Set("port", params.Get("port", 0)); 452 button_params.Set("port", params.Get("port", 0));
445 button_params.Set("button", static_cast<s32>(PadButton::Stick)); 453 button_params.Set("button", static_cast<s32>(gcadapter_buton));
446 button_params.Set("axis", static_cast<s32>(gcadapter_axis)); 454 button_params.Set("axis", static_cast<s32>(gcadapter_axis));
447 button_params.Set("threshold", 0.5f); 455 button_params.Set("threshold", 0.5f);
456 button_params.Set("range", 1.9f);
448 button_params.Set("direction", "+"); 457 button_params.Set("direction", "+");
449 mapping.insert_or_assign(switch_button, std::move(button_params)); 458 mapping.insert_or_assign(switch_button, std::move(button_params));
450 } 459 }
451 return mapping; 460 return mapping;
452} 461}
453 462
454InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( 463AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
455 const Common::ParamPackage& params) const {
456 if (!params.Has("port")) { 464 if (!params.Has("port")) {
457 return {}; 465 return {};
458 } 466 }
459 467
460 InputCommon::AnalogMapping mapping = {}; 468 AnalogMapping mapping = {};
461 Common::ParamPackage left_analog_params; 469 Common::ParamPackage left_analog_params;
462 left_analog_params.Set("engine", "gcpad"); 470 left_analog_params.Set("engine", GetEngineName());
463 left_analog_params.Set("port", params.Get("port", 0)); 471 left_analog_params.Set("port", params.Get("port", 0));
464 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX)); 472 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
465 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); 473 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
466 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); 474 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
467 Common::ParamPackage right_analog_params; 475 Common::ParamPackage right_analog_params;
468 right_analog_params.Set("engine", "gcpad"); 476 right_analog_params.Set("engine", GetEngineName());
469 right_analog_params.Set("port", params.Get("port", 0)); 477 right_analog_params.Set("port", params.Get("port", 0));
470 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); 478 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
471 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); 479 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
@@ -473,34 +481,47 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
473 return mapping; 481 return mapping;
474} 482}
475 483
476bool Adapter::DeviceConnected(std::size_t port) const { 484Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
477 return pads[port].type != ControllerTypes::None; 485 PadButton button = static_cast<PadButton>(params.Get("button", 0));
478} 486 switch (button) {
479 487 case PadButton::ButtonLeft:
480void Adapter::BeginConfiguration() { 488 return Common::Input::ButtonNames::ButtonLeft;
481 pad_queue.Clear(); 489 case PadButton::ButtonRight:
482 configuring = true; 490 return Common::Input::ButtonNames::ButtonRight;
483} 491 case PadButton::ButtonDown:
484 492 return Common::Input::ButtonNames::ButtonDown;
485void Adapter::EndConfiguration() { 493 case PadButton::ButtonUp:
486 pad_queue.Clear(); 494 return Common::Input::ButtonNames::ButtonUp;
487 configuring = false; 495 case PadButton::TriggerZ:
488} 496 return Common::Input::ButtonNames::TriggerZ;
489 497 case PadButton::TriggerR:
490Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() { 498 return Common::Input::ButtonNames::TriggerR;
491 return pad_queue; 499 case PadButton::TriggerL:
492} 500 return Common::Input::ButtonNames::TriggerL;
493 501 case PadButton::ButtonA:
494const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const { 502 return Common::Input::ButtonNames::ButtonA;
495 return pad_queue; 503 case PadButton::ButtonB:
504 return Common::Input::ButtonNames::ButtonB;
505 case PadButton::ButtonX:
506 return Common::Input::ButtonNames::ButtonX;
507 case PadButton::ButtonY:
508 return Common::Input::ButtonNames::ButtonY;
509 case PadButton::ButtonStart:
510 return Common::Input::ButtonNames::ButtonStart;
511 default:
512 return Common::Input::ButtonNames::Undefined;
513 }
496} 514}
497 515
498GCController& Adapter::GetPadState(std::size_t port) { 516Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const {
499 return pads.at(port); 517 if (params.Has("button")) {
500} 518 return GetUIButtonName(params);
519 }
520 if (params.Has("axis")) {
521 return Common::Input::ButtonNames::Value;
522 }
501 523
502const GCController& Adapter::GetPadState(std::size_t port) const { 524 return Common::Input::ButtonNames::Invalid;
503 return pads.at(port);
504} 525}
505 526
506} // namespace GCAdapter 527} // namespace InputCommon
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
new file mode 100644
index 000000000..7ce1912a3
--- /dev/null
+++ b/src/input_common/drivers/gc_adapter.h
@@ -0,0 +1,135 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <mutex>
10#include <stop_token>
11#include <string>
12#include <thread>
13
14#include "input_common/input_engine.h"
15
16struct libusb_context;
17struct libusb_device;
18struct libusb_device_handle;
19
20namespace InputCommon {
21
22class LibUSBContext;
23class LibUSBDeviceHandle;
24
25class GCAdapter : public InputEngine {
26public:
27 explicit GCAdapter(std::string input_engine_);
28 ~GCAdapter() override;
29
30 Common::Input::VibrationError SetRumble(
31 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
32
33 /// Used for automapping features
34 std::vector<Common::ParamPackage> GetInputDevices() const override;
35 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
36 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
37 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
38
39private:
40 enum class PadButton {
41 Undefined = 0x0000,
42 ButtonLeft = 0x0001,
43 ButtonRight = 0x0002,
44 ButtonDown = 0x0004,
45 ButtonUp = 0x0008,
46 TriggerZ = 0x0010,
47 TriggerR = 0x0020,
48 TriggerL = 0x0040,
49 ButtonA = 0x0100,
50 ButtonB = 0x0200,
51 ButtonX = 0x0400,
52 ButtonY = 0x0800,
53 ButtonStart = 0x1000,
54 };
55
56 enum class PadAxes : u8 {
57 StickX,
58 StickY,
59 SubstickX,
60 SubstickY,
61 TriggerLeft,
62 TriggerRight,
63 Undefined,
64 };
65
66 enum class ControllerTypes {
67 None,
68 Wired,
69 Wireless,
70 };
71
72 struct GCController {
73 ControllerTypes type = ControllerTypes::None;
74 PadIdentifier identifier{};
75 bool enable_vibration = false;
76 u8 rumble_amplitude{};
77 std::array<u8, 6> axis_origin{};
78 u8 reset_origin_counter{};
79 };
80
81 using AdapterPayload = std::array<u8, 37>;
82
83 void UpdatePadType(std::size_t port, ControllerTypes pad_type);
84 void UpdateControllers(const AdapterPayload& adapter_payload);
85 void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
86 void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
87
88 void AdapterInputThread(std::stop_token stop_token);
89
90 void AdapterScanThread(std::stop_token stop_token);
91
92 bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
93
94 /// For use in initialization, querying devices to find the adapter
95 bool Setup();
96
97 /// Returns true if we successfully gain access to GC Adapter
98 bool CheckDeviceAccess();
99
100 /// Captures GC Adapter endpoint address
101 /// Returns true if the endpoint was set correctly
102 bool GetGCEndpoint(libusb_device* device);
103
104 /// Returns true if there is a device connected to port
105 bool DeviceConnected(std::size_t port) const;
106
107 /// For shutting down, clear all data, join all threads, release usb
108 void Reset();
109
110 void UpdateVibrations();
111
112 /// Updates vibration state of all controllers
113 void SendVibrations();
114
115 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
116
117 std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
118 std::array<GCController, 4> pads;
119
120 std::jthread adapter_input_thread;
121 std::jthread adapter_scan_thread;
122 bool restart_scan_thread{};
123
124 std::unique_ptr<LibUSBContext> libusb_ctx;
125
126 u8 input_endpoint{0};
127 u8 output_endpoint{0};
128 u8 input_error_counter{0};
129 u8 output_error_counter{0};
130 int vibration_counter{0};
131
132 bool rumble_enabled{true};
133 bool vibration_changed{true};
134};
135} // namespace InputCommon
diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp
new file mode 100644
index 000000000..4c1e5bbec
--- /dev/null
+++ b/src/input_common/drivers/keyboard.cpp
@@ -0,0 +1,112 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/param_package.h"
6#include "common/settings_input.h"
7#include "input_common/drivers/keyboard.h"
8
9namespace InputCommon {
10
11constexpr PadIdentifier key_identifier = {
12 .guid = Common::UUID{Common::INVALID_UUID},
13 .port = 0,
14 .pad = 0,
15};
16constexpr PadIdentifier keyboard_key_identifier = {
17 .guid = Common::UUID{Common::INVALID_UUID},
18 .port = 1,
19 .pad = 0,
20};
21constexpr PadIdentifier keyboard_modifier_identifier = {
22 .guid = Common::UUID{Common::INVALID_UUID},
23 .port = 1,
24 .pad = 1,
25};
26
27Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
28 // Keyboard is broken into 3 diferent sets:
29 // key: Unfiltered intended for controllers.
30 // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation.
31 // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard
32 // emulation.
33 PreSetController(key_identifier);
34 PreSetController(keyboard_key_identifier);
35 PreSetController(keyboard_modifier_identifier);
36}
37
38void Keyboard::PressKey(int key_code) {
39 SetButton(key_identifier, key_code, true);
40}
41
42void Keyboard::ReleaseKey(int key_code) {
43 SetButton(key_identifier, key_code, false);
44}
45
46void Keyboard::PressKeyboardKey(int key_index) {
47 if (key_index == Settings::NativeKeyboard::None) {
48 return;
49 }
50 SetButton(keyboard_key_identifier, key_index, true);
51}
52
53void Keyboard::ReleaseKeyboardKey(int key_index) {
54 if (key_index == Settings::NativeKeyboard::None) {
55 return;
56 }
57 SetButton(keyboard_key_identifier, key_index, false);
58}
59
60void Keyboard::SetKeyboardModifiers(int key_modifiers) {
61 for (int i = 0; i < 32; ++i) {
62 bool key_value = ((key_modifiers >> i) & 0x1) != 0;
63 SetButton(keyboard_modifier_identifier, i, key_value);
64 // Use the modifier to press the key button equivalent
65 switch (i) {
66 case Settings::NativeKeyboard::LeftControl:
67 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value);
68 break;
69 case Settings::NativeKeyboard::LeftShift:
70 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value);
71 break;
72 case Settings::NativeKeyboard::LeftAlt:
73 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value);
74 break;
75 case Settings::NativeKeyboard::LeftMeta:
76 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value);
77 break;
78 case Settings::NativeKeyboard::RightControl:
79 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey,
80 key_value);
81 break;
82 case Settings::NativeKeyboard::RightShift:
83 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value);
84 break;
85 case Settings::NativeKeyboard::RightAlt:
86 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value);
87 break;
88 case Settings::NativeKeyboard::RightMeta:
89 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value);
90 break;
91 default:
92 // Other modifier keys should be pressed with PressKey since they stay enabled until
93 // next press
94 break;
95 }
96 }
97}
98
99void Keyboard::ReleaseAllKeys() {
100 ResetButtonState();
101}
102
103std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const {
104 std::vector<Common::ParamPackage> devices;
105 devices.emplace_back(Common::ParamPackage{
106 {"engine", GetEngineName()},
107 {"display", "Keyboard Only"},
108 });
109 return devices;
110}
111
112} // namespace InputCommon
diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h
new file mode 100644
index 000000000..3856c882c
--- /dev/null
+++ b/src/input_common/drivers/keyboard.h
@@ -0,0 +1,56 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11/**
12 * A button device factory representing a keyboard. It receives keyboard events and forward them
13 * to all button devices it created.
14 */
15class Keyboard final : public InputEngine {
16public:
17 explicit Keyboard(std::string input_engine_);
18
19 /**
20 * Sets the status of all buttons bound with the key to pressed
21 * @param key_code the code of the key to press
22 */
23 void PressKey(int key_code);
24
25 /**
26 * Sets the status of all buttons bound with the key to released
27 * @param key_code the code of the key to release
28 */
29 void ReleaseKey(int key_code);
30
31 /**
32 * Sets the status of the keyboard key to pressed
33 * @param key_index index of the key to press
34 */
35 void PressKeyboardKey(int key_index);
36
37 /**
38 * Sets the status of the keyboard key to released
39 * @param key_index index of the key to release
40 */
41 void ReleaseKeyboardKey(int key_index);
42
43 /**
44 * Sets the status of all keyboard modifier keys
45 * @param key_modifiers the code of the key to release
46 */
47 void SetKeyboardModifiers(int key_modifiers);
48
49 /// Sets all keys to the non pressed state
50 void ReleaseAllKeys();
51
52 /// Used for automapping features
53 std::vector<Common::ParamPackage> GetInputDevices() const override;
54};
55
56} // namespace InputCommon
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp
new file mode 100644
index 000000000..aa69216c8
--- /dev/null
+++ b/src/input_common/drivers/mouse.cpp
@@ -0,0 +1,185 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include <stop_token>
6#include <thread>
7#include <fmt/format.h>
8
9#include "common/param_package.h"
10#include "common/settings.h"
11#include "common/thread.h"
12#include "input_common/drivers/mouse.h"
13
14namespace InputCommon {
15constexpr int mouse_axis_x = 0;
16constexpr int mouse_axis_y = 1;
17constexpr int wheel_axis_x = 2;
18constexpr int wheel_axis_y = 3;
19constexpr int touch_axis_x = 10;
20constexpr int touch_axis_y = 11;
21constexpr PadIdentifier identifier = {
22 .guid = Common::UUID{Common::INVALID_UUID},
23 .port = 0,
24 .pad = 0,
25};
26
27Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
28 PreSetController(identifier);
29 PreSetAxis(identifier, mouse_axis_x);
30 PreSetAxis(identifier, mouse_axis_y);
31 PreSetAxis(identifier, wheel_axis_x);
32 PreSetAxis(identifier, wheel_axis_y);
33 PreSetAxis(identifier, touch_axis_x);
34 PreSetAxis(identifier, touch_axis_x);
35 update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
36}
37
38void Mouse::UpdateThread(std::stop_token stop_token) {
39 Common::SetCurrentThreadName("yuzu:input:Mouse");
40 constexpr int update_time = 10;
41 while (!stop_token.stop_requested()) {
42 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
43 // Slow movement by 4%
44 last_mouse_change *= 0.96f;
45 const float sensitivity =
46 Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f;
47 SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity);
48 SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity);
49 }
50
51 if (mouse_panning_timout++ > 20) {
52 StopPanning();
53 }
54 std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
55 }
56}
57
58void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
59 // If native mouse is enabled just set the screen coordinates
60 if (Settings::values.mouse_enabled) {
61 SetAxis(identifier, mouse_axis_x, touch_x);
62 SetAxis(identifier, mouse_axis_y, touch_y);
63 return;
64 }
65
66 SetAxis(identifier, touch_axis_x, touch_x);
67 SetAxis(identifier, touch_axis_y, touch_y);
68
69 if (Settings::values.mouse_panning) {
70 auto mouse_change =
71 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
72 mouse_panning_timout = 0;
73
74 const auto move_distance = mouse_change.Length();
75 if (move_distance == 0) {
76 return;
77 }
78
79 // Make slow movements at least 3 units on lenght
80 if (move_distance < 3.0f) {
81 // Normalize value
82 mouse_change /= move_distance;
83 mouse_change *= 3.0f;
84 }
85
86 // Average mouse movements
87 last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
88
89 const auto last_move_distance = last_mouse_change.Length();
90
91 // Make fast movements clamp to 8 units on lenght
92 if (last_move_distance > 8.0f) {
93 // Normalize value
94 last_mouse_change /= last_move_distance;
95 last_mouse_change *= 8.0f;
96 }
97
98 // Ignore average if it's less than 1 unit and use current movement value
99 if (last_move_distance < 1.0f) {
100 last_mouse_change = mouse_change / mouse_change.Length();
101 }
102
103 return;
104 }
105
106 if (button_pressed) {
107 const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
108 const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
109 SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
110 SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
111 }
112}
113
114void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
115 SetAxis(identifier, touch_axis_x, touch_x);
116 SetAxis(identifier, touch_axis_y, touch_y);
117 SetButton(identifier, static_cast<int>(button), true);
118 // Set initial analog parameters
119 mouse_origin = {x, y};
120 last_mouse_position = {x, y};
121 button_pressed = true;
122}
123
124void Mouse::ReleaseButton(MouseButton button) {
125 SetButton(identifier, static_cast<int>(button), false);
126
127 if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
128 SetAxis(identifier, mouse_axis_x, 0);
129 SetAxis(identifier, mouse_axis_y, 0);
130 }
131 button_pressed = false;
132}
133
134void Mouse::MouseWheelChange(int x, int y) {
135 wheel_position.x += x;
136 wheel_position.y += y;
137 SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x));
138 SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y));
139}
140
141void Mouse::ReleaseAllButtons() {
142 ResetButtonState();
143 button_pressed = false;
144}
145
146void Mouse::StopPanning() {
147 last_mouse_change = {};
148}
149
150std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
151 std::vector<Common::ParamPackage> devices;
152 devices.emplace_back(Common::ParamPackage{
153 {"engine", GetEngineName()},
154 {"display", "Keyboard/Mouse"},
155 });
156 return devices;
157}
158
159AnalogMapping Mouse::GetAnalogMappingForDevice(
160 [[maybe_unused]] const Common::ParamPackage& params) {
161 // Only overwrite different buttons from default
162 AnalogMapping mapping = {};
163 Common::ParamPackage right_analog_params;
164 right_analog_params.Set("engine", GetEngineName());
165 right_analog_params.Set("axis_x", 0);
166 right_analog_params.Set("axis_y", 1);
167 right_analog_params.Set("threshold", 0.5f);
168 right_analog_params.Set("range", 1.0f);
169 right_analog_params.Set("deadzone", 0.0f);
170 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
171 return mapping;
172}
173
174Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const {
175 if (params.Has("button")) {
176 return Common::Input::ButtonNames::Value;
177 }
178 if (params.Has("axis")) {
179 return Common::Input::ButtonNames::Value;
180 }
181
182 return Common::Input::ButtonNames::Invalid;
183}
184
185} // namespace InputCommon
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h
new file mode 100644
index 000000000..040446178
--- /dev/null
+++ b/src/input_common/drivers/mouse.h
@@ -0,0 +1,81 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include <stop_token>
8#include <thread>
9
10#include "common/vector_math.h"
11#include "input_common/input_engine.h"
12
13namespace InputCommon {
14
15enum class MouseButton {
16 Left,
17 Right,
18 Wheel,
19 Backward,
20 Forward,
21 Task,
22 Extra,
23 Undefined,
24};
25
26/**
27 * A button device factory representing a keyboard. It receives keyboard events and forward them
28 * to all button devices it created.
29 */
30class Mouse final : public InputEngine {
31public:
32 explicit Mouse(std::string input_engine_);
33
34 /**
35 * Signals that mouse has moved.
36 * @param x the x-coordinate of the cursor
37 * @param y the y-coordinate of the cursor
38 * @param center_x the x-coordinate of the middle of the screen
39 * @param center_y the y-coordinate of the middle of the screen
40 */
41 void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
42
43 /**
44 * Sets the status of all buttons bound with the key to pressed
45 * @param key_code the code of the key to press
46 */
47 void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
48
49 /**
50 * Sets the status of all buttons bound with the key to released
51 * @param key_code the code of the key to release
52 */
53 void ReleaseButton(MouseButton button);
54
55 /**
56 * Sets the status of the mouse wheel
57 * @param x delta movement in the x direction
58 * @param y delta movement in the y direction
59 */
60 void MouseWheelChange(int x, int y);
61
62 void ReleaseAllButtons();
63
64 std::vector<Common::ParamPackage> GetInputDevices() const override;
65 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
66 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
67
68private:
69 void UpdateThread(std::stop_token stop_token);
70 void StopPanning();
71
72 Common::Vec2<int> mouse_origin;
73 Common::Vec2<int> last_mouse_position;
74 Common::Vec2<float> last_mouse_change;
75 Common::Vec2<int> wheel_position;
76 bool button_pressed;
77 int mouse_panning_timout{};
78 std::jthread update_thread;
79};
80
81} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
new file mode 100644
index 000000000..0cda9df62
--- /dev/null
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -0,0 +1,926 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/logging/log.h"
6#include "common/math_util.h"
7#include "common/param_package.h"
8#include "common/settings.h"
9#include "common/thread.h"
10#include "common/vector_math.h"
11#include "input_common/drivers/sdl_driver.h"
12
13namespace InputCommon {
14
15namespace {
16std::string GetGUID(SDL_Joystick* joystick) {
17 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
18 char guid_str[33];
19 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
20 return guid_str;
21}
22} // Anonymous namespace
23
24static int SDLEventWatcher(void* user_data, SDL_Event* event) {
25 auto* const sdl_state = static_cast<SDLDriver*>(user_data);
26
27 sdl_state->HandleGameControllerEvent(*event);
28
29 return 0;
30}
31
32class SDLJoystick {
33public:
34 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
35 SDL_GameController* game_controller)
36 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
37 sdl_controller{game_controller, &SDL_GameControllerClose} {
38 EnableMotion();
39 }
40
41 void EnableMotion() {
42 if (sdl_controller) {
43 SDL_GameController* controller = sdl_controller.get();
44 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
45 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
46 has_accel = true;
47 }
48 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
49 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
50 has_gyro = true;
51 }
52 }
53 }
54
55 bool HasGyro() const {
56 return has_gyro;
57 }
58
59 bool HasAccel() const {
60 return has_accel;
61 }
62
63 bool UpdateMotion(SDL_ControllerSensorEvent event) {
64 constexpr float gravity_constant = 9.80665f;
65 std::lock_guard lock{mutex};
66 const u64 time_difference = event.timestamp - last_motion_update;
67 last_motion_update = event.timestamp;
68 switch (event.sensor) {
69 case SDL_SENSOR_ACCEL: {
70 motion.accel_x = -event.data[0] / gravity_constant;
71 motion.accel_y = event.data[2] / gravity_constant;
72 motion.accel_z = -event.data[1] / gravity_constant;
73 break;
74 }
75 case SDL_SENSOR_GYRO: {
76 motion.gyro_x = event.data[0] / (Common::PI * 2);
77 motion.gyro_y = -event.data[2] / (Common::PI * 2);
78 motion.gyro_z = event.data[1] / (Common::PI * 2);
79 break;
80 }
81 }
82
83 // Ignore duplicated timestamps
84 if (time_difference == 0) {
85 return false;
86 }
87 motion.delta_timestamp = time_difference * 1000;
88 return true;
89 }
90
91 const BasicMotion& GetMotion() const {
92 return motion;
93 }
94
95 bool RumblePlay(const Common::Input::VibrationStatus vibration) {
96 constexpr u32 rumble_max_duration_ms = 1000;
97 if (sdl_controller) {
98 return SDL_GameControllerRumble(
99 sdl_controller.get(), static_cast<u16>(vibration.low_amplitude),
100 static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
101 } else if (sdl_joystick) {
102 return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
103 static_cast<u16>(vibration.high_amplitude),
104 rumble_max_duration_ms) != -1;
105 }
106
107 return false;
108 }
109
110 bool HasHDRumble() const {
111 if (sdl_controller) {
112 return (SDL_GameControllerGetType(sdl_controller.get()) ==
113 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO);
114 }
115 return false;
116 }
117 /**
118 * The Pad identifier of the joystick
119 */
120 const PadIdentifier GetPadIdentifier() const {
121 return {
122 .guid = Common::UUID{guid},
123 .port = static_cast<std::size_t>(port),
124 .pad = 0,
125 };
126 }
127
128 /**
129 * The guid of the joystick
130 */
131 const std::string& GetGUID() const {
132 return guid;
133 }
134
135 /**
136 * The number of joystick from the same type that were connected before this joystick
137 */
138 int GetPort() const {
139 return port;
140 }
141
142 SDL_Joystick* GetSDLJoystick() const {
143 return sdl_joystick.get();
144 }
145
146 SDL_GameController* GetSDLGameController() const {
147 return sdl_controller.get();
148 }
149
150 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
151 sdl_joystick.reset(joystick);
152 sdl_controller.reset(controller);
153 }
154
155 bool IsJoyconLeft() const {
156 const std::string controller_name = GetControllerName();
157 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
158 return true;
159 }
160 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
161 return true;
162 }
163 return false;
164 }
165
166 bool IsJoyconRight() const {
167 const std::string controller_name = GetControllerName();
168 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
169 return true;
170 }
171 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
172 return true;
173 }
174 return false;
175 }
176
177 BatteryLevel GetBatteryLevel() {
178 const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
179 switch (level) {
180 case SDL_JOYSTICK_POWER_EMPTY:
181 return BatteryLevel::Empty;
182 case SDL_JOYSTICK_POWER_LOW:
183 return BatteryLevel::Critical;
184 case SDL_JOYSTICK_POWER_MEDIUM:
185 return BatteryLevel::Low;
186 case SDL_JOYSTICK_POWER_FULL:
187 return BatteryLevel::Medium;
188 case SDL_JOYSTICK_POWER_MAX:
189 return BatteryLevel::Full;
190 case SDL_JOYSTICK_POWER_UNKNOWN:
191 case SDL_JOYSTICK_POWER_WIRED:
192 default:
193 return BatteryLevel::Charging;
194 }
195 }
196
197 std::string GetControllerName() const {
198 if (sdl_controller) {
199 switch (SDL_GameControllerGetType(sdl_controller.get())) {
200 case SDL_CONTROLLER_TYPE_XBOX360:
201 return "XBox 360 Controller";
202 case SDL_CONTROLLER_TYPE_XBOXONE:
203 return "XBox One Controller";
204 default:
205 break;
206 }
207 const auto name = SDL_GameControllerName(sdl_controller.get());
208 if (name) {
209 return name;
210 }
211 }
212
213 if (sdl_joystick) {
214 const auto name = SDL_JoystickName(sdl_joystick.get());
215 if (name) {
216 return name;
217 }
218 }
219
220 return "Unknown";
221 }
222
223private:
224 std::string guid;
225 int port;
226 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
227 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
228 mutable std::mutex mutex;
229
230 u64 last_motion_update{};
231 bool has_gyro{false};
232 bool has_accel{false};
233 BasicMotion motion;
234};
235
236std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
237 std::lock_guard lock{joystick_map_mutex};
238 const auto it = joystick_map.find(guid);
239
240 if (it != joystick_map.end()) {
241 while (it->second.size() <= static_cast<std::size_t>(port)) {
242 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
243 nullptr, nullptr);
244 it->second.emplace_back(std::move(joystick));
245 }
246
247 return it->second[static_cast<std::size_t>(port)];
248 }
249
250 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
251
252 return joystick_map[guid].emplace_back(std::move(joystick));
253}
254
255std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
256 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
257 const std::string guid = GetGUID(sdl_joystick);
258
259 std::lock_guard lock{joystick_map_mutex};
260 const auto map_it = joystick_map.find(guid);
261
262 if (map_it == joystick_map.end()) {
263 return nullptr;
264 }
265
266 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
267 [&sdl_joystick](const auto& joystick) {
268 return joystick->GetSDLJoystick() == sdl_joystick;
269 });
270
271 if (vec_it == map_it->second.end()) {
272 return nullptr;
273 }
274
275 return *vec_it;
276}
277
278void SDLDriver::InitJoystick(int joystick_index) {
279 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
280 SDL_GameController* sdl_gamecontroller = nullptr;
281
282 if (SDL_IsGameController(joystick_index)) {
283 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
284 }
285
286 if (!sdl_joystick) {
287 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
288 return;
289 }
290
291 const std::string guid = GetGUID(sdl_joystick);
292
293 std::lock_guard lock{joystick_map_mutex};
294 if (joystick_map.find(guid) == joystick_map.end()) {
295 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
296 PreSetController(joystick->GetPadIdentifier());
297 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
298 joystick_map[guid].emplace_back(std::move(joystick));
299 return;
300 }
301
302 auto& joystick_guid_list = joystick_map[guid];
303 const auto joystick_it =
304 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
305 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
306
307 if (joystick_it != joystick_guid_list.end()) {
308 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
309 return;
310 }
311
312 const int port = static_cast<int>(joystick_guid_list.size());
313 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
314 PreSetController(joystick->GetPadIdentifier());
315 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
316 joystick_guid_list.emplace_back(std::move(joystick));
317}
318
319void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
320 const std::string guid = GetGUID(sdl_joystick);
321
322 std::lock_guard lock{joystick_map_mutex};
323 // This call to guid is safe since the joystick is guaranteed to be in the map
324 const auto& joystick_guid_list = joystick_map[guid];
325 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
326 [&sdl_joystick](const auto& joystick) {
327 return joystick->GetSDLJoystick() == sdl_joystick;
328 });
329
330 if (joystick_it != joystick_guid_list.end()) {
331 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
332 }
333}
334
335void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
336 switch (event.type) {
337 case SDL_JOYBUTTONUP: {
338 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
339 const PadIdentifier identifier = joystick->GetPadIdentifier();
340 SetButton(identifier, event.jbutton.button, false);
341 }
342 break;
343 }
344 case SDL_JOYBUTTONDOWN: {
345 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
346 const PadIdentifier identifier = joystick->GetPadIdentifier();
347 SetButton(identifier, event.jbutton.button, true);
348 }
349 break;
350 }
351 case SDL_JOYHATMOTION: {
352 if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
353 const PadIdentifier identifier = joystick->GetPadIdentifier();
354 SetHatButton(identifier, event.jhat.hat, event.jhat.value);
355 }
356 break;
357 }
358 case SDL_JOYAXISMOTION: {
359 if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
360 const PadIdentifier identifier = joystick->GetPadIdentifier();
361 SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
362 }
363 break;
364 }
365 case SDL_CONTROLLERSENSORUPDATE: {
366 if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
367 if (joystick->UpdateMotion(event.csensor)) {
368 const PadIdentifier identifier = joystick->GetPadIdentifier();
369 SetMotion(identifier, 0, joystick->GetMotion());
370 }
371 }
372 break;
373 }
374 case SDL_JOYDEVICEREMOVED:
375 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
376 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
377 break;
378 case SDL_JOYDEVICEADDED:
379 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
380 InitJoystick(event.jdevice.which);
381 break;
382 }
383}
384
385void SDLDriver::CloseJoysticks() {
386 std::lock_guard lock{joystick_map_mutex};
387 joystick_map.clear();
388}
389
390SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
391 if (!Settings::values.enable_raw_input) {
392 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
393 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
394 }
395
396 // Prevent SDL from adding undesired axis
397 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
398
399 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
400 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
401 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
402 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
403
404 // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
405 // not a generic one
406 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
407
408 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
409 // driver on Linux.
410 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
411
412 // If the frontend is going to manage the event loop, then we don't start one here
413 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
414 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
415 LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
416 return;
417 }
418
419 SDL_AddEventWatch(&SDLEventWatcher, this);
420
421 initialized = true;
422 if (start_thread) {
423 poll_thread = std::thread([this] {
424 Common::SetCurrentThreadName("yuzu:input:SDL");
425 using namespace std::chrono_literals;
426 while (initialized) {
427 SDL_PumpEvents();
428 std::this_thread::sleep_for(1ms);
429 }
430 });
431 }
432 // Because the events for joystick connection happens before we have our event watcher added, we
433 // can just open all the joysticks right here
434 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
435 InitJoystick(i);
436 }
437}
438
439SDLDriver::~SDLDriver() {
440 CloseJoysticks();
441 SDL_DelEventWatch(&SDLEventWatcher, this);
442
443 initialized = false;
444 if (start_thread) {
445 poll_thread.join();
446 SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
447 }
448}
449
450std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
451 std::vector<Common::ParamPackage> devices;
452 std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
453 for (const auto& [key, value] : joystick_map) {
454 for (const auto& joystick : value) {
455 if (!joystick->GetSDLJoystick()) {
456 continue;
457 }
458 const std::string name =
459 fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
460 devices.emplace_back(Common::ParamPackage{
461 {"engine", GetEngineName()},
462 {"display", std::move(name)},
463 {"guid", joystick->GetGUID()},
464 {"port", std::to_string(joystick->GetPort())},
465 });
466 if (joystick->IsJoyconLeft()) {
467 joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
468 }
469 }
470 }
471
472 // Add dual controllers
473 for (const auto& [key, value] : joystick_map) {
474 for (const auto& joystick : value) {
475 if (joystick->IsJoyconRight()) {
476 if (!joycon_pairs.contains(joystick->GetPort())) {
477 continue;
478 }
479 const auto joystick2 = joycon_pairs.at(joystick->GetPort());
480
481 const std::string name =
482 fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
483 devices.emplace_back(Common::ParamPackage{
484 {"engine", GetEngineName()},
485 {"display", std::move(name)},
486 {"guid", joystick->GetGUID()},
487 {"guid2", joystick2->GetGUID()},
488 {"port", std::to_string(joystick->GetPort())},
489 });
490 }
491 }
492 }
493 return devices;
494}
495
496Common::Input::VibrationError SDLDriver::SetRumble(
497 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
498 const auto joystick =
499 GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port));
500 const auto process_amplitude_exp = [](f32 amplitude, f32 factor) {
501 return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
502 };
503
504 // Default exponential curve for rumble
505 f32 factor = 0.35f;
506
507 // If vibration is set as a linear output use a flatter value
508 if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
509 factor = 0.5f;
510 }
511
512 // Amplitude for HD rumble needs no modification
513 if (joystick->HasHDRumble()) {
514 factor = 1.0f;
515 }
516
517 const Common::Input::VibrationStatus new_vibration{
518 .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor),
519 .low_frequency = vibration.low_frequency,
520 .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor),
521 .high_frequency = vibration.high_frequency,
522 .type = Common::Input::VibrationAmplificationType::Exponential,
523 };
524
525 if (!joystick->RumblePlay(new_vibration)) {
526 return Common::Input::VibrationError::Unknown;
527 }
528
529 return Common::Input::VibrationError::None;
530}
531
532Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
533 s32 axis, float value) const {
534 Common::ParamPackage params{};
535 params.Set("engine", GetEngineName());
536 params.Set("port", port);
537 params.Set("guid", std::move(guid));
538 params.Set("axis", axis);
539 params.Set("threshold", "0.5");
540 params.Set("invert", value < 0 ? "-" : "+");
541 return params;
542}
543
544Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid,
545 s32 button) const {
546 Common::ParamPackage params{};
547 params.Set("engine", GetEngineName());
548 params.Set("port", port);
549 params.Set("guid", std::move(guid));
550 params.Set("button", button);
551 return params;
552}
553
554Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
555 u8 value) const {
556 Common::ParamPackage params{};
557 params.Set("engine", GetEngineName());
558 params.Set("port", port);
559 params.Set("guid", std::move(guid));
560 params.Set("hat", hat);
561 params.Set("direction", GetHatButtonName(value));
562 return params;
563}
564
565Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const {
566 Common::ParamPackage params{};
567 params.Set("engine", GetEngineName());
568 params.Set("motion", 0);
569 params.Set("port", port);
570 params.Set("guid", std::move(guid));
571 return params;
572}
573
574Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
575 int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const {
576 switch (binding.bindType) {
577 case SDL_CONTROLLER_BINDTYPE_NONE:
578 break;
579 case SDL_CONTROLLER_BINDTYPE_AXIS:
580 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
581 case SDL_CONTROLLER_BINDTYPE_BUTTON:
582 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
583 case SDL_CONTROLLER_BINDTYPE_HAT:
584 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
585 static_cast<u8>(binding.value.hat.hat_mask));
586 }
587 return {};
588}
589
590Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
591 int axis_y, float offset_x,
592 float offset_y) const {
593 Common::ParamPackage params;
594 params.Set("engine", GetEngineName());
595 params.Set("port", static_cast<int>(identifier.port));
596 params.Set("guid", identifier.guid.Format());
597 params.Set("axis_x", axis_x);
598 params.Set("axis_y", axis_y);
599 params.Set("offset_x", offset_x);
600 params.Set("offset_y", offset_y);
601 params.Set("invert_x", "+");
602 params.Set("invert_y", "+");
603 return params;
604}
605
606ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) {
607 if (!params.Has("guid") || !params.Has("port")) {
608 return {};
609 }
610 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
611
612 auto* controller = joystick->GetSDLGameController();
613 if (controller == nullptr) {
614 return {};
615 }
616
617 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
618 // We will add those afterwards
619 // This list also excludes Screenshot since theres not really a mapping for that
620 ButtonBindings switch_to_sdl_button;
621
622 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
623 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
624 } else {
625 switch_to_sdl_button = GetDefaultButtonBinding();
626 }
627
628 // Add the missing bindings for ZL/ZR
629 static constexpr ZButtonBindings switch_to_sdl_axis{{
630 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
631 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
632 }};
633
634 // Parameters contain two joysticks return dual
635 if (params.Has("guid2")) {
636 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
637
638 if (joystick2->GetSDLGameController() != nullptr) {
639 return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
640 switch_to_sdl_axis);
641 }
642 }
643
644 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
645}
646
647ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
648 return {
649 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
650 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
651 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
652 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
653 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
654 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
655 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
656 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
657 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
658 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
659 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
660 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
661 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
662 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
663 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
664 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
665 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
666 };
667}
668
669ButtonBindings SDLDriver::GetNintendoButtonBinding(
670 const std::shared_ptr<SDLJoystick>& joystick) const {
671 // Default SL/SR mapping for pro controllers
672 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
673 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
674
675 if (joystick->IsJoyconLeft()) {
676 sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
677 sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
678 }
679 if (joystick->IsJoyconRight()) {
680 sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
681 sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
682 }
683
684 return {
685 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
686 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
687 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
688 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
689 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
690 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
691 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
692 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
693 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
694 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
695 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
696 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
697 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
698 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
699 {Settings::NativeButton::SL, sl_button},
700 {Settings::NativeButton::SR, sr_button},
701 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
702 };
703}
704
705ButtonMapping SDLDriver::GetSingleControllerMapping(
706 const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
707 const ZButtonBindings& switch_to_sdl_axis) const {
708 ButtonMapping mapping;
709 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
710 auto* controller = joystick->GetSDLGameController();
711
712 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
713 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
714 mapping.insert_or_assign(
715 switch_button,
716 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
717 }
718 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
719 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
720 mapping.insert_or_assign(
721 switch_button,
722 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
723 }
724
725 return mapping;
726}
727
728ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
729 const std::shared_ptr<SDLJoystick>& joystick2,
730 const ButtonBindings& switch_to_sdl_button,
731 const ZButtonBindings& switch_to_sdl_axis) const {
732 ButtonMapping mapping;
733 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
734 auto* controller = joystick->GetSDLGameController();
735 auto* controller2 = joystick2->GetSDLGameController();
736
737 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
738 if (IsButtonOnLeftSide(switch_button)) {
739 const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
740 mapping.insert_or_assign(
741 switch_button,
742 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
743 continue;
744 }
745 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
746 mapping.insert_or_assign(
747 switch_button,
748 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
749 }
750 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
751 if (IsButtonOnLeftSide(switch_button)) {
752 const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
753 mapping.insert_or_assign(
754 switch_button,
755 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
756 continue;
757 }
758 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
759 mapping.insert_or_assign(
760 switch_button,
761 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
762 }
763
764 return mapping;
765}
766
767bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
768 switch (button) {
769 case Settings::NativeButton::DDown:
770 case Settings::NativeButton::DLeft:
771 case Settings::NativeButton::DRight:
772 case Settings::NativeButton::DUp:
773 case Settings::NativeButton::L:
774 case Settings::NativeButton::LStick:
775 case Settings::NativeButton::Minus:
776 case Settings::NativeButton::Screenshot:
777 case Settings::NativeButton::ZL:
778 return true;
779 default:
780 return false;
781 }
782}
783
784AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
785 if (!params.Has("guid") || !params.Has("port")) {
786 return {};
787 }
788 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
789 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
790 auto* controller = joystick->GetSDLGameController();
791 if (controller == nullptr) {
792 return {};
793 }
794
795 AnalogMapping mapping = {};
796 const auto& binding_left_x =
797 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
798 const auto& binding_left_y =
799 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
800 if (params.Has("guid2")) {
801 const auto identifier = joystick2->GetPadIdentifier();
802 PreSetController(identifier);
803 PreSetAxis(identifier, binding_left_x.value.axis);
804 PreSetAxis(identifier, binding_left_y.value.axis);
805 const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
806 const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
807 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
808 BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
809 binding_left_y.value.axis,
810 left_offset_x, left_offset_y));
811 } else {
812 const auto identifier = joystick->GetPadIdentifier();
813 PreSetController(identifier);
814 PreSetAxis(identifier, binding_left_x.value.axis);
815 PreSetAxis(identifier, binding_left_y.value.axis);
816 const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
817 const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
818 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
819 BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
820 binding_left_y.value.axis,
821 left_offset_x, left_offset_y));
822 }
823 const auto& binding_right_x =
824 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
825 const auto& binding_right_y =
826 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
827 const auto identifier = joystick->GetPadIdentifier();
828 PreSetController(identifier);
829 PreSetAxis(identifier, binding_right_x.value.axis);
830 PreSetAxis(identifier, binding_right_y.value.axis);
831 const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
832 const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis);
833 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
834 BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
835 binding_right_y.value.axis, right_offset_x,
836 right_offset_y));
837 return mapping;
838}
839
840MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) {
841 if (!params.Has("guid") || !params.Has("port")) {
842 return {};
843 }
844 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
845 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
846 auto* controller = joystick->GetSDLGameController();
847 if (controller == nullptr) {
848 return {};
849 }
850
851 MotionMapping mapping = {};
852 joystick->EnableMotion();
853
854 if (joystick->HasGyro() || joystick->HasAccel()) {
855 mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
856 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
857 }
858 if (params.Has("guid2")) {
859 joystick2->EnableMotion();
860 if (joystick2->HasGyro() || joystick2->HasAccel()) {
861 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
862 BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
863 }
864 } else {
865 if (joystick->HasGyro() || joystick->HasAccel()) {
866 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
867 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
868 }
869 }
870
871 return mapping;
872}
873
874Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
875 if (params.Has("button")) {
876 // TODO(German77): Find how to substitue the values for real button names
877 return Common::Input::ButtonNames::Value;
878 }
879 if (params.Has("hat")) {
880 return Common::Input::ButtonNames::Value;
881 }
882 if (params.Has("axis")) {
883 return Common::Input::ButtonNames::Value;
884 }
885 if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
886 return Common::Input::ButtonNames::Value;
887 }
888 if (params.Has("motion")) {
889 return Common::Input::ButtonNames::Engine;
890 }
891
892 return Common::Input::ButtonNames::Invalid;
893}
894
895std::string SDLDriver::GetHatButtonName(u8 direction_value) const {
896 switch (direction_value) {
897 case SDL_HAT_UP:
898 return "up";
899 case SDL_HAT_DOWN:
900 return "down";
901 case SDL_HAT_LEFT:
902 return "left";
903 case SDL_HAT_RIGHT:
904 return "right";
905 default:
906 return {};
907 }
908}
909
910u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
911 Uint8 direction;
912 if (direction_name == "up") {
913 direction = SDL_HAT_UP;
914 } else if (direction_name == "down") {
915 direction = SDL_HAT_DOWN;
916 } else if (direction_name == "left") {
917 direction = SDL_HAT_LEFT;
918 } else if (direction_name == "right") {
919 direction = SDL_HAT_RIGHT;
920 } else {
921 direction = 0;
922 }
923 return direction;
924}
925
926} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/drivers/sdl_driver.h
index 7a9ad6346..e9a5d2e26 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -5,7 +5,6 @@
5#pragma once 5#pragma once
6 6
7#include <atomic> 7#include <atomic>
8#include <memory>
9#include <mutex> 8#include <mutex>
10#include <thread> 9#include <thread>
11#include <unordered_map> 10#include <unordered_map>
@@ -13,34 +12,29 @@
13#include <SDL.h> 12#include <SDL.h>
14 13
15#include "common/common_types.h" 14#include "common/common_types.h"
16#include "common/threadsafe_queue.h" 15#include "input_common/input_engine.h"
17#include "input_common/sdl/sdl.h"
18 16
19union SDL_Event; 17union SDL_Event;
20using SDL_GameController = struct _SDL_GameController; 18using SDL_GameController = struct _SDL_GameController;
21using SDL_Joystick = struct _SDL_Joystick; 19using SDL_Joystick = struct _SDL_Joystick;
22using SDL_JoystickID = s32; 20using SDL_JoystickID = s32;
23 21
22namespace InputCommon {
23
24class SDLJoystick;
25
24using ButtonBindings = 26using ButtonBindings =
25 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; 27 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
26using ZButtonBindings = 28using ZButtonBindings =
27 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; 29 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
28 30
29namespace InputCommon::SDL { 31class SDLDriver : public InputEngine {
30
31class SDLAnalogFactory;
32class SDLButtonFactory;
33class SDLMotionFactory;
34class SDLVibrationFactory;
35class SDLJoystick;
36
37class SDLState : public State {
38public: 32public:
39 /// Initializes and registers SDL device factories 33 /// Initializes and registers SDL device factories
40 SDLState(); 34 explicit SDLDriver(std::string input_engine_);
41 35
42 /// Unregisters SDL device factories and shut them down. 36 /// Unregisters SDL device factories and shut them down.
43 ~SDLState() override; 37 ~SDLDriver() override;
44 38
45 /// Handle SDL_Events for joysticks from SDL_PollEvent 39 /// Handle SDL_Events for joysticks from SDL_PollEvent
46 void HandleGameControllerEvent(const SDL_Event& event); 40 void HandleGameControllerEvent(const SDL_Event& event);
@@ -54,18 +48,18 @@ public:
54 */ 48 */
55 std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); 49 std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
56 50
57 /// Get all DevicePoller that use the SDL backend for a specific device type 51 std::vector<Common::ParamPackage> GetInputDevices() const override;
58 Pollers GetPollers(Polling::DeviceType type) override;
59
60 /// Used by the Pollers during config
61 std::atomic<bool> polling = false;
62 Common::SPSCQueue<SDL_Event> event_queue;
63
64 std::vector<Common::ParamPackage> GetInputDevices() override;
65 52
66 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; 53 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
67 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; 54 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
68 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; 55 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
56 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
57
58 std::string GetHatButtonName(u8 direction_value) const override;
59 u8 GetHatButtonId(const std::string& direction_name) const override;
60
61 Common::Input::VibrationError SetRumble(
62 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
69 63
70private: 64private:
71 void InitJoystick(int joystick_index); 65 void InitJoystick(int joystick_index);
@@ -74,6 +68,23 @@ private:
74 /// Needs to be called before SDL_QuitSubSystem. 68 /// Needs to be called before SDL_QuitSubSystem.
75 void CloseJoysticks(); 69 void CloseJoysticks();
76 70
71 Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
72 float value = 0.1f) const;
73 Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid,
74 s32 button) const;
75
76 Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
77 u8 value) const;
78
79 Common::ParamPackage BuildMotionParam(int port, std::string guid) const;
80
81 Common::ParamPackage BuildParamPackageForBinding(
82 int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const;
83
84 Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
85 int axis_y, float offset_x,
86 float offset_y) const;
87
77 /// Returns the default button bindings list for generic controllers 88 /// Returns the default button bindings list for generic controllers
78 ButtonBindings GetDefaultButtonBinding() const; 89 ButtonBindings GetDefaultButtonBinding() const;
79 90
@@ -94,21 +105,13 @@ private:
94 /// Returns true if the button is on the left joycon 105 /// Returns true if the button is on the left joycon
95 bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; 106 bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
96 107
97 // Set to true if SDL supports game controller subsystem
98 bool has_gamecontroller = false;
99
100 /// Map of GUID of a list of corresponding virtual Joysticks 108 /// Map of GUID of a list of corresponding virtual Joysticks
101 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; 109 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
102 std::mutex joystick_map_mutex; 110 std::mutex joystick_map_mutex;
103 111
104 std::shared_ptr<SDLButtonFactory> button_factory;
105 std::shared_ptr<SDLAnalogFactory> analog_factory;
106 std::shared_ptr<SDLVibrationFactory> vibration_factory;
107 std::shared_ptr<SDLMotionFactory> motion_factory;
108
109 bool start_thread = false; 112 bool start_thread = false;
110 std::atomic<bool> initialized = false; 113 std::atomic<bool> initialized = false;
111 114
112 std::thread poll_thread; 115 std::thread poll_thread;
113}; 116};
114} // namespace InputCommon::SDL 117} // namespace InputCommon
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
new file mode 100644
index 000000000..5bdd5dac3
--- /dev/null
+++ b/src/input_common/drivers/tas_input.cpp
@@ -0,0 +1,320 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <fmt/format.h>
7
8#include "common/fs/file.h"
9#include "common/fs/fs_types.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/drivers/tas_input.h"
14
15namespace InputCommon::TasInput {
16
17enum class Tas::TasAxis : u8 {
18 StickX,
19 StickY,
20 SubstickX,
21 SubstickY,
22 Undefined,
23};
24
25// Supported keywords and buttons from a TAS file
26constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
27 std::pair{"KEY_A", TasButton::BUTTON_A},
28 {"KEY_B", TasButton::BUTTON_B},
29 {"KEY_X", TasButton::BUTTON_X},
30 {"KEY_Y", TasButton::BUTTON_Y},
31 {"KEY_LSTICK", TasButton::STICK_L},
32 {"KEY_RSTICK", TasButton::STICK_R},
33 {"KEY_L", TasButton::TRIGGER_L},
34 {"KEY_R", TasButton::TRIGGER_R},
35 {"KEY_PLUS", TasButton::BUTTON_PLUS},
36 {"KEY_MINUS", TasButton::BUTTON_MINUS},
37 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
38 {"KEY_DUP", TasButton::BUTTON_UP},
39 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
40 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
41 {"KEY_SL", TasButton::BUTTON_SL},
42 {"KEY_SR", TasButton::BUTTON_SR},
43 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
44 {"KEY_HOME", TasButton::BUTTON_HOME},
45 {"KEY_ZL", TasButton::TRIGGER_ZL},
46 {"KEY_ZR", TasButton::TRIGGER_ZR},
47};
48
49Tas::Tas(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
50 for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
51 PadIdentifier identifier{
52 .guid = Common::UUID{},
53 .port = player_index,
54 .pad = 0,
55 };
56 PreSetController(identifier);
57 }
58 ClearInput();
59 if (!Settings::values.tas_enable) {
60 needs_reset = true;
61 return;
62 }
63 LoadTasFiles();
64}
65
66Tas::~Tas() {
67 Stop();
68}
69
70void Tas::LoadTasFiles() {
71 script_length = 0;
72 for (size_t i = 0; i < commands.size(); i++) {
73 LoadTasFile(i, 0);
74 if (commands[i].size() > script_length) {
75 script_length = commands[i].size();
76 }
77 }
78}
79
80void Tas::LoadTasFile(size_t player_index, size_t file_index) {
81 commands[player_index].clear();
82
83 std::string file = Common::FS::ReadStringFromFile(
84 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
85 fmt::format("script{}-{}.txt", file_index, player_index + 1),
86 Common::FS::FileType::BinaryFile);
87 std::istringstream command_line(file);
88 std::string line;
89 int frame_no = 0;
90 while (std::getline(command_line, line, '\n')) {
91 if (line.empty()) {
92 continue;
93 }
94
95 std::vector<std::string> seg_list;
96 {
97 std::istringstream line_stream(line);
98 std::string segment;
99 while (std::getline(line_stream, segment, ' ')) {
100 seg_list.push_back(std::move(segment));
101 }
102 }
103
104 if (seg_list.size() < 4) {
105 continue;
106 }
107
108 const auto num_frames = std::stoi(seg_list[0]);
109 while (frame_no < num_frames) {
110 commands[player_index].emplace_back();
111 frame_no++;
112 }
113
114 TASCommand command = {
115 .buttons = ReadCommandButtons(seg_list[1]),
116 .l_axis = ReadCommandAxis(seg_list[2]),
117 .r_axis = ReadCommandAxis(seg_list[3]),
118 };
119 commands[player_index].push_back(command);
120 frame_no++;
121 }
122 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
123}
124
125void Tas::WriteTasFile(std::u8string_view file_name) {
126 std::string output_text;
127 for (size_t frame = 0; frame < record_commands.size(); frame++) {
128 const TASCommand& line = record_commands[frame];
129 output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
130 WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
131 }
132
133 const auto tas_file_name = Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name;
134 const auto bytes_written =
135 Common::FS::WriteStringToFile(tas_file_name, Common::FS::FileType::TextFile, output_text);
136 if (bytes_written == output_text.size()) {
137 LOG_INFO(Input, "TAS file written to file!");
138 } else {
139 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
140 output_text.size());
141 }
142}
143
144void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
145 last_input = {
146 .buttons = buttons,
147 .l_axis = left_axis,
148 .r_axis = right_axis,
149 };
150}
151
152std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
153 TasState state;
154 if (is_recording) {
155 return {TasState::Recording, 0, record_commands.size()};
156 }
157
158 if (is_running) {
159 state = TasState::Running;
160 } else {
161 state = TasState::Stopped;
162 }
163
164 return {state, current_command, script_length};
165}
166
167void Tas::UpdateThread() {
168 if (!Settings::values.tas_enable) {
169 if (is_running) {
170 Stop();
171 }
172 return;
173 }
174
175 if (is_recording) {
176 record_commands.push_back(last_input);
177 }
178 if (needs_reset) {
179 current_command = 0;
180 needs_reset = false;
181 LoadTasFiles();
182 LOG_DEBUG(Input, "tas_reset done");
183 }
184
185 if (!is_running) {
186 ClearInput();
187 return;
188 }
189 if (current_command < script_length) {
190 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
191 const size_t frame = current_command++;
192 for (size_t player_index = 0; player_index < commands.size(); player_index++) {
193 TASCommand command{};
194 if (frame < commands[player_index].size()) {
195 command = commands[player_index][frame];
196 }
197
198 PadIdentifier identifier{
199 .guid = Common::UUID{},
200 .port = player_index,
201 .pad = 0,
202 };
203 for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
204 const bool button_status = (command.buttons & (1LLU << i)) != 0;
205 const int button = static_cast<int>(i);
206 SetButton(identifier, button, button_status);
207 }
208 SetTasAxis(identifier, TasAxis::StickX, command.l_axis.x);
209 SetTasAxis(identifier, TasAxis::StickY, command.l_axis.y);
210 SetTasAxis(identifier, TasAxis::SubstickX, command.r_axis.x);
211 SetTasAxis(identifier, TasAxis::SubstickY, command.r_axis.y);
212 }
213 } else {
214 is_running = Settings::values.tas_loop.GetValue();
215 LoadTasFiles();
216 current_command = 0;
217 ClearInput();
218 }
219}
220
221void Tas::ClearInput() {
222 ResetButtonState();
223 ResetAnalogState();
224}
225
226TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
227 std::vector<std::string> seg_list;
228 {
229 std::istringstream line_stream(line);
230 std::string segment;
231 while (std::getline(line_stream, segment, ';')) {
232 seg_list.push_back(std::move(segment));
233 }
234 }
235
236 const float x = std::stof(seg_list.at(0)) / 32767.0f;
237 const float y = std::stof(seg_list.at(1)) / 32767.0f;
238
239 return {x, y};
240}
241
242u64 Tas::ReadCommandButtons(const std::string& line) const {
243 std::istringstream button_text(line);
244 std::string button_line;
245 u64 buttons = 0;
246 while (std::getline(button_text, button_line, ';')) {
247 for (const auto& [text, tas_button] : text_to_tas_button) {
248 if (text == button_line) {
249 buttons |= static_cast<u64>(tas_button);
250 break;
251 }
252 }
253 }
254 return buttons;
255}
256
257std::string Tas::WriteCommandButtons(u64 buttons) const {
258 std::string returns;
259 for (const auto& [text_button, tas_button] : text_to_tas_button) {
260 if ((buttons & static_cast<u64>(tas_button)) != 0) {
261 returns += fmt::format("{};", text_button);
262 }
263 }
264 return returns.empty() ? "NONE" : returns;
265}
266
267std::string Tas::WriteCommandAxis(TasAnalog analog) const {
268 return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
269}
270
271void Tas::SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value) {
272 SetAxis(identifier, static_cast<int>(axis), value);
273}
274
275void Tas::StartStop() {
276 if (!Settings::values.tas_enable) {
277 return;
278 }
279 if (is_running) {
280 Stop();
281 } else {
282 is_running = true;
283 }
284}
285
286void Tas::Stop() {
287 is_running = false;
288}
289
290void Tas::Reset() {
291 if (!Settings::values.tas_enable) {
292 return;
293 }
294 needs_reset = true;
295}
296
297bool Tas::Record() {
298 if (!Settings::values.tas_enable) {
299 return true;
300 }
301 is_recording = !is_recording;
302 return is_recording;
303}
304
305void Tas::SaveRecording(bool overwrite_file) {
306 if (is_recording) {
307 return;
308 }
309 if (record_commands.empty()) {
310 return;
311 }
312 WriteTasFile(u8"record.txt");
313 if (overwrite_file) {
314 WriteTasFile(u8"script0-1.txt");
315 }
316 needs_reset = true;
317 record_commands.clear();
318}
319
320} // namespace InputCommon::TasInput
diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h
new file mode 100644
index 000000000..4b4e6c417
--- /dev/null
+++ b/src/input_common/drivers/tas_input.h
@@ -0,0 +1,201 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <string>
9#include <vector>
10
11#include "common/common_types.h"
12#include "input_common/input_engine.h"
13
14/*
15To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
16Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
17for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
18
19A script file has the same format as TAS-nx uses, so final files will look like this:
20
211 KEY_B 0;0 0;0
226 KEY_ZL 0;0 0;0
2341 KEY_ZL;KEY_Y 0;0 0;0
2443 KEY_X;KEY_A 32767;0 0;0
2544 KEY_A 32767;0 0;0
2645 KEY_A 32767;0 0;0
2746 KEY_A 32767;0 0;0
2847 KEY_A 32767;0 0;0
29
30After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
31CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
32has. Playback can be started or stopped using CTRL+F5.
33
34However, for playback to actually work, the correct input device has to be selected: In the Controls
35menu, select TAS from the device list for the controller that the script should be played on.
36
37Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
38connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
39again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
40record.txt.
41
42For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
43P1).
44*/
45
46namespace InputCommon::TasInput {
47
48constexpr size_t PLAYER_NUMBER = 10;
49
50enum class TasButton : u64 {
51 BUTTON_A = 1U << 0,
52 BUTTON_B = 1U << 1,
53 BUTTON_X = 1U << 2,
54 BUTTON_Y = 1U << 3,
55 STICK_L = 1U << 4,
56 STICK_R = 1U << 5,
57 TRIGGER_L = 1U << 6,
58 TRIGGER_R = 1U << 7,
59 TRIGGER_ZL = 1U << 8,
60 TRIGGER_ZR = 1U << 9,
61 BUTTON_PLUS = 1U << 10,
62 BUTTON_MINUS = 1U << 11,
63 BUTTON_LEFT = 1U << 12,
64 BUTTON_UP = 1U << 13,
65 BUTTON_RIGHT = 1U << 14,
66 BUTTON_DOWN = 1U << 15,
67 BUTTON_SL = 1U << 16,
68 BUTTON_SR = 1U << 17,
69 BUTTON_HOME = 1U << 18,
70 BUTTON_CAPTURE = 1U << 19,
71};
72
73struct TasAnalog {
74 float x{};
75 float y{};
76};
77
78enum class TasState {
79 Running,
80 Recording,
81 Stopped,
82};
83
84class Tas final : public InputEngine {
85public:
86 explicit Tas(std::string input_engine_);
87 ~Tas() override;
88
89 /**
90 * Changes the input status that will be stored in each frame
91 * @param buttons Bitfield with the status of the buttons
92 * @param left_axis Value of the left axis
93 * @param right_axis Value of the right axis
94 */
95 void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis);
96
97 // Main loop that records or executes input
98 void UpdateThread();
99
100 // Sets the flag to start or stop the TAS command execution and swaps controllers profiles
101 void StartStop();
102
103 // Stop the TAS and reverts any controller profile
104 void Stop();
105
106 // Sets the flag to reload the file and start from the beginning in the next update
107 void Reset();
108
109 /**
110 * Sets the flag to enable or disable recording of inputs
111 * @returns true if the current recording status is enabled
112 */
113 bool Record();
114
115 /**
116 * Saves contents of record_commands on a file
117 * @param overwrite_file Indicates if player 1 should be overwritten
118 */
119 void SaveRecording(bool overwrite_file);
120
121 /**
122 * Returns the current status values of TAS playback/recording
123 * @returns A Tuple of
124 * TasState indicating the current state out of Running ;
125 * Current playback progress ;
126 * Total length of script file currently loaded or being recorded
127 */
128 std::tuple<TasState, size_t, size_t> GetStatus() const;
129
130private:
131 enum class TasAxis : u8;
132
133 struct TASCommand {
134 u64 buttons{};
135 TasAnalog l_axis{};
136 TasAnalog r_axis{};
137 };
138
139 /// Loads TAS files from all players
140 void LoadTasFiles();
141
142 /**
143 * Loads TAS file from the specified player
144 * @param player_index Player number to save the script
145 * @param file_index Script number of the file
146 */
147 void LoadTasFile(size_t player_index, size_t file_index);
148
149 /**
150 * Writes a TAS file from the recorded commands
151 * @param file_name Name of the file to be written
152 */
153 void WriteTasFile(std::u8string_view file_name);
154
155 /**
156 * Parses a string containing the axis values. X and Y have a range from -32767 to 32767
157 * @param line String containing axis values with the following format "x;y"
158 * @returns A TAS analog object with axis values with range from -1.0 to 1.0
159 */
160 TasAnalog ReadCommandAxis(const std::string& line) const;
161
162 /**
163 * Parses a string containing the button values. Each button is represented by it's text format
164 * specified in text_to_tas_button array
165 * @param line string containing button name with the following format "a;b;c;d..."
166 * @returns A u64 with each bit representing the status of a button
167 */
168 u64 ReadCommandButtons(const std::string& line) const;
169
170 /**
171 * Reset state of all players
172 */
173 void ClearInput();
174
175 /**
176 * Converts an u64 containing the button status into the text equivalent
177 * @param buttons Bitfield with the status of the buttons
178 * @returns A string with the name of the buttons to be written to the file
179 */
180 std::string WriteCommandButtons(u64 buttons) const;
181
182 /**
183 * Converts an TAS analog object containing the axis status into the text equivalent
184 * @param analog Value of the axis
185 * @returns A string with the value of the axis to be written to the file
186 */
187 std::string WriteCommandAxis(TasAnalog analog) const;
188
189 /// Sets an axis for a particular pad to the given value.
190 void SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value);
191
192 size_t script_length{0};
193 bool is_recording{false};
194 bool is_running{false};
195 bool needs_reset{false};
196 std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
197 std::vector<TASCommand> record_commands{};
198 size_t current_command{0};
199 TASCommand last_input{}; // only used for recording
200};
201} // namespace InputCommon::TasInput
diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp
new file mode 100644
index 000000000..880781825
--- /dev/null
+++ b/src/input_common/drivers/touch_screen.cpp
@@ -0,0 +1,53 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/param_package.h"
6#include "input_common/drivers/touch_screen.h"
7
8namespace InputCommon {
9
10constexpr PadIdentifier identifier = {
11 .guid = Common::UUID{Common::INVALID_UUID},
12 .port = 0,
13 .pad = 0,
14};
15
16TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
17 PreSetController(identifier);
18}
19
20void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
21 if (finger >= 16) {
22 return;
23 }
24 TouchPressed(x, y, finger);
25}
26
27void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
28 if (finger >= 16) {
29 return;
30 }
31 SetButton(identifier, static_cast<int>(finger), true);
32 SetAxis(identifier, static_cast<int>(finger * 2), x);
33 SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
34}
35
36void TouchScreen::TouchReleased(std::size_t finger) {
37 if (finger >= 16) {
38 return;
39 }
40 SetButton(identifier, static_cast<int>(finger), false);
41 SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
42 SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
43}
44
45void TouchScreen::ReleaseAllTouch() {
46 for (int index = 0; index < 16; ++index) {
47 SetButton(identifier, index, false);
48 SetAxis(identifier, index * 2, 0.0f);
49 SetAxis(identifier, index * 2 + 1, 0.0f);
50 }
51}
52
53} // namespace InputCommon
diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h
new file mode 100644
index 000000000..bf395c40b
--- /dev/null
+++ b/src/input_common/drivers/touch_screen.h
@@ -0,0 +1,44 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11/**
12 * A button device factory representing a keyboard. It receives keyboard events and forward them
13 * to all button devices it created.
14 */
15class TouchScreen final : public InputEngine {
16public:
17 explicit TouchScreen(std::string input_engine_);
18
19 /**
20 * Signals that mouse has moved.
21 * @param x the x-coordinate of the cursor
22 * @param y the y-coordinate of the cursor
23 * @param center_x the x-coordinate of the middle of the screen
24 * @param center_y the y-coordinate of the middle of the screen
25 */
26 void TouchMoved(float x, float y, std::size_t finger);
27
28 /**
29 * Sets the status of all buttons bound with the key to pressed
30 * @param key_code the code of the key to press
31 */
32 void TouchPressed(float x, float y, std::size_t finger);
33
34 /**
35 * Sets the status of all buttons bound with the key to released
36 * @param key_code the code of the key to release
37 */
38 void TouchReleased(std::size_t finger);
39
40 /// Resets all inputs to their initial value
41 void ReleaseAllTouch();
42};
43
44} // namespace InputCommon
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
new file mode 100644
index 000000000..4ab991a7d
--- /dev/null
+++ b/src/input_common/drivers/udp_client.cpp
@@ -0,0 +1,591 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <random>
6#include <boost/asio.hpp>
7#include <fmt/format.h>
8
9#include "common/logging/log.h"
10#include "common/param_package.h"
11#include "common/settings.h"
12#include "input_common/drivers/udp_client.h"
13#include "input_common/helpers/udp_protocol.h"
14
15using boost::asio::ip::udp;
16
17namespace InputCommon::CemuhookUDP {
18
19struct SocketCallback {
20 std::function<void(Response::Version)> version;
21 std::function<void(Response::PortInfo)> port_info;
22 std::function<void(Response::PadData)> pad_data;
23};
24
25class Socket {
26public:
27 using clock = std::chrono::system_clock;
28
29 explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
30 : callback(std::move(callback_)), timer(io_service),
31 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
32 boost::system::error_code ec{};
33 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
34 if (ec.value() != boost::system::errc::success) {
35 LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
36 ipv4 = boost::asio::ip::address_v4{};
37 }
38
39 send_endpoint = {udp::endpoint(ipv4, port)};
40 }
41
42 void Stop() {
43 io_service.stop();
44 }
45
46 void Loop() {
47 io_service.run();
48 }
49
50 void StartSend(const clock::time_point& from) {
51 timer.expires_at(from + std::chrono::seconds(3));
52 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
53 }
54
55 void StartReceive() {
56 socket.async_receive_from(
57 boost::asio::buffer(receive_buffer), receive_endpoint,
58 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
59 HandleReceive(error, bytes_transferred);
60 });
61 }
62
63private:
64 u32 GenerateRandomClientId() const {
65 std::random_device device;
66 return device();
67 }
68
69 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
70 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
71 switch (*type) {
72 case Type::Version: {
73 Response::Version version;
74 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
75 callback.version(std::move(version));
76 break;
77 }
78 case Type::PortInfo: {
79 Response::PortInfo port_info;
80 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
81 sizeof(Response::PortInfo));
82 callback.port_info(std::move(port_info));
83 break;
84 }
85 case Type::PadData: {
86 Response::PadData pad_data;
87 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
88 callback.pad_data(std::move(pad_data));
89 break;
90 }
91 }
92 }
93 StartReceive();
94 }
95
96 void HandleSend(const boost::system::error_code&) {
97 boost::system::error_code _ignored{};
98 // Send a request for getting port info for the pad
99 const Request::PortInfo port_info{4, {0, 1, 2, 3}};
100 const auto port_message = Request::Create(port_info, client_id);
101 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
102 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
103
104 // Send a request for getting pad data for the pad
105 const Request::PadData pad_data{
106 Request::RegisterFlags::AllPads,
107 0,
108 EMPTY_MAC_ADDRESS,
109 };
110 const auto pad_message = Request::Create(pad_data, client_id);
111 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
112 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
113 StartSend(timer.expiry());
114 }
115
116 SocketCallback callback;
117 boost::asio::io_service io_service;
118 boost::asio::basic_waitable_timer<clock> timer;
119 udp::socket socket;
120
121 const u32 client_id;
122
123 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
124 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
125 std::array<u8, PORT_INFO_SIZE> send_buffer1;
126 std::array<u8, PAD_DATA_SIZE> send_buffer2;
127 udp::endpoint send_endpoint;
128
129 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
130 udp::endpoint receive_endpoint;
131};
132
133static void SocketLoop(Socket* socket) {
134 socket->StartReceive();
135 socket->StartSend(Socket::clock::now());
136 socket->Loop();
137}
138
139UDPClient::UDPClient(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
140 LOG_INFO(Input, "Udp Initialization started");
141 ReloadSockets();
142}
143
144UDPClient::~UDPClient() {
145 Reset();
146}
147
148UDPClient::ClientConnection::ClientConnection() = default;
149
150UDPClient::ClientConnection::~ClientConnection() = default;
151
152void UDPClient::ReloadSockets() {
153 Reset();
154
155 std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
156 std::string server_token;
157 std::size_t client = 0;
158 while (std::getline(servers_ss, server_token, ',')) {
159 if (client == MAX_UDP_CLIENTS) {
160 break;
161 }
162 std::stringstream server_ss(server_token);
163 std::string token;
164 std::getline(server_ss, token, ':');
165 std::string udp_input_address = token;
166 std::getline(server_ss, token, ':');
167 char* temp;
168 const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
169 if (*temp != '\0') {
170 LOG_ERROR(Input, "Port number is not valid {}", token);
171 continue;
172 }
173
174 const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
175 if (client_number != MAX_UDP_CLIENTS) {
176 LOG_ERROR(Input, "Duplicated UDP servers found");
177 continue;
178 }
179 StartCommunication(client++, udp_input_address, udp_input_port);
180 }
181}
182
183std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
184 for (std::size_t client = 0; client < clients.size(); client++) {
185 if (clients[client].active == -1) {
186 continue;
187 }
188 if (clients[client].host == host && clients[client].port == port) {
189 return client;
190 }
191 }
192 return MAX_UDP_CLIENTS;
193}
194
195void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
196 LOG_TRACE(Input, "Version packet received: {}", data.version);
197}
198
199void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
200 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
201}
202
203void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
204 const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
205
206 if (pad_index >= pads.size()) {
207 LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
208 return;
209 }
210
211 LOG_TRACE(Input, "PadData packet received");
212 if (data.packet_counter == pads[pad_index].packet_sequence) {
213 LOG_WARNING(
214 Input,
215 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
216 pads[pad_index].packet_sequence, data.packet_counter);
217 pads[pad_index].connected = false;
218 return;
219 }
220
221 clients[client].active = 1;
222 pads[pad_index].connected = true;
223 pads[pad_index].packet_sequence = data.packet_counter;
224
225 const auto now = std::chrono::steady_clock::now();
226 const auto time_difference = static_cast<u64>(
227 std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
228 .count());
229 pads[pad_index].last_update = now;
230
231 // Gyroscope values are not it the correct scale from better joy.
232 // Dividing by 312 allows us to make one full turn = 1 turn
233 // This must be a configurable valued called sensitivity
234 const float gyro_scale = 1.0f / 312.0f;
235
236 const BasicMotion motion{
237 .gyro_x = data.gyro.pitch * gyro_scale,
238 .gyro_y = data.gyro.roll * gyro_scale,
239 .gyro_z = -data.gyro.yaw * gyro_scale,
240 .accel_x = data.accel.x,
241 .accel_y = -data.accel.z,
242 .accel_z = data.accel.y,
243 .delta_timestamp = time_difference,
244 };
245 const PadIdentifier identifier = GetPadIdentifier(pad_index);
246 SetMotion(identifier, 0, motion);
247
248 for (std::size_t id = 0; id < data.touch.size(); ++id) {
249 const auto touch_pad = data.touch[id];
250 const auto touch_axis_x_id =
251 static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
252 const auto touch_axis_y_id =
253 static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
254 const auto touch_button_id =
255 static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::touch2);
256
257 // TODO: Use custom calibration per device
258 const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
259 const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
260 const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
261 const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
262 const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
263
264 const f32 x =
265 static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
266 static_cast<f32>(max_x - min_x);
267 const f32 y =
268 static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
269 static_cast<f32>(max_y - min_y);
270
271 if (touch_pad.is_active) {
272 SetAxis(identifier, touch_axis_x_id, x);
273 SetAxis(identifier, touch_axis_y_id, y);
274 SetButton(identifier, touch_button_id, true);
275 continue;
276 }
277 SetAxis(identifier, touch_axis_x_id, 0);
278 SetAxis(identifier, touch_axis_y_id, 0);
279 SetButton(identifier, touch_button_id, false);
280 }
281
282 SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX),
283 (data.left_stick_x - 127.0f) / 127.0f);
284 SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
285 (data.left_stick_y - 127.0f) / 127.0f);
286 SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
287 (data.right_stick_x - 127.0f) / 127.0f);
288 SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
289 (data.right_stick_y - 127.0f) / 127.0f);
290
291 static constexpr std::array<PadButton, 16> buttons{
292 PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options,
293 PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left,
294 PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1,
295 PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square};
296
297 for (std::size_t i = 0; i < buttons.size(); ++i) {
298 const bool button_status = (data.digital_button & (1U << i)) != 0;
299 const int button = static_cast<int>(buttons[i]);
300 SetButton(identifier, button, button_status);
301 }
302}
303
304void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
305 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
306 [this](Response::PortInfo info) { OnPortInfo(info); },
307 [this, client](Response::PadData data) { OnPadData(data, client); }};
308 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
309 clients[client].uuid = GetHostUUID(host);
310 clients[client].host = host;
311 clients[client].port = port;
312 clients[client].active = 0;
313 clients[client].socket = std::make_unique<Socket>(host, port, callback);
314 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
315 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
316 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
317 PreSetController(identifier);
318 }
319}
320
321const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
322 const std::size_t client = pad_index / PADS_PER_CLIENT;
323 return {
324 .guid = clients[client].uuid,
325 .port = static_cast<std::size_t>(clients[client].port),
326 .pad = pad_index,
327 };
328}
329
330const Common::UUID UDPClient::GetHostUUID(const std::string host) const {
331 const auto ip = boost::asio::ip::address_v4::from_string(host);
332 const auto hex_host = fmt::format("{:06x}", ip.to_ulong());
333 return Common::UUID{hex_host};
334}
335
336void UDPClient::Reset() {
337 for (auto& client : clients) {
338 if (client.thread.joinable()) {
339 client.active = -1;
340 client.socket->Stop();
341 client.thread.join();
342 }
343 }
344}
345
346std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
347 std::vector<Common::ParamPackage> devices;
348 if (!Settings::values.enable_udp_controller) {
349 return devices;
350 }
351 for (std::size_t client = 0; client < clients.size(); client++) {
352 if (clients[client].active != 1) {
353 continue;
354 }
355 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
356 const std::size_t pad_index = client * PADS_PER_CLIENT + index;
357 if (!pads[pad_index].connected) {
358 continue;
359 }
360 const auto pad_identifier = GetPadIdentifier(pad_index);
361 Common::ParamPackage identifier{};
362 identifier.Set("engine", GetEngineName());
363 identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad));
364 identifier.Set("guid", pad_identifier.guid.Format());
365 identifier.Set("port", static_cast<int>(pad_identifier.port));
366 identifier.Set("pad", static_cast<int>(pad_identifier.pad));
367 devices.emplace_back(identifier);
368 }
369 }
370 return devices;
371}
372
373ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
374 // This list excludes any button that can't be really mapped
375 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 18>
376 switch_to_dsu_button = {
377 std::pair{Settings::NativeButton::A, PadButton::Circle},
378 {Settings::NativeButton::B, PadButton::Cross},
379 {Settings::NativeButton::X, PadButton::Triangle},
380 {Settings::NativeButton::Y, PadButton::Square},
381 {Settings::NativeButton::Plus, PadButton::Options},
382 {Settings::NativeButton::Minus, PadButton::Share},
383 {Settings::NativeButton::DLeft, PadButton::Left},
384 {Settings::NativeButton::DUp, PadButton::Up},
385 {Settings::NativeButton::DRight, PadButton::Right},
386 {Settings::NativeButton::DDown, PadButton::Down},
387 {Settings::NativeButton::L, PadButton::L1},
388 {Settings::NativeButton::R, PadButton::R1},
389 {Settings::NativeButton::ZL, PadButton::L2},
390 {Settings::NativeButton::ZR, PadButton::R2},
391 {Settings::NativeButton::SL, PadButton::L2},
392 {Settings::NativeButton::SR, PadButton::R2},
393 {Settings::NativeButton::LStick, PadButton::L3},
394 {Settings::NativeButton::RStick, PadButton::R3},
395 };
396 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
397 return {};
398 }
399
400 ButtonMapping mapping{};
401 for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) {
402 Common::ParamPackage button_params{};
403 button_params.Set("engine", GetEngineName());
404 button_params.Set("guid", params.Get("guid", ""));
405 button_params.Set("port", params.Get("port", 0));
406 button_params.Set("pad", params.Get("pad", 0));
407 button_params.Set("button", static_cast<int>(dsu_button));
408 mapping.insert_or_assign(switch_button, std::move(button_params));
409 }
410
411 return mapping;
412}
413
414AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
415 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
416 return {};
417 }
418
419 AnalogMapping mapping = {};
420 Common::ParamPackage left_analog_params;
421 left_analog_params.Set("engine", GetEngineName());
422 left_analog_params.Set("guid", params.Get("guid", ""));
423 left_analog_params.Set("port", params.Get("port", 0));
424 left_analog_params.Set("pad", params.Get("pad", 0));
425 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
426 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY));
427 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
428 Common::ParamPackage right_analog_params;
429 right_analog_params.Set("engine", GetEngineName());
430 right_analog_params.Set("guid", params.Get("guid", ""));
431 right_analog_params.Set("port", params.Get("port", 0));
432 right_analog_params.Set("pad", params.Get("pad", 0));
433 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
434 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY));
435 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
436 return mapping;
437}
438
439MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) {
440 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
441 return {};
442 }
443
444 MotionMapping mapping = {};
445 Common::ParamPackage motion_params;
446 motion_params.Set("engine", GetEngineName());
447 motion_params.Set("guid", params.Get("guid", ""));
448 motion_params.Set("port", params.Get("port", 0));
449 motion_params.Set("pad", params.Get("pad", 0));
450 motion_params.Set("motion", 0);
451 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params));
452 mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params));
453 return mapping;
454}
455
456Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const {
457 PadButton button = static_cast<PadButton>(params.Get("button", 0));
458 switch (button) {
459 case PadButton::Left:
460 return Common::Input::ButtonNames::ButtonLeft;
461 case PadButton::Right:
462 return Common::Input::ButtonNames::ButtonRight;
463 case PadButton::Down:
464 return Common::Input::ButtonNames::ButtonDown;
465 case PadButton::Up:
466 return Common::Input::ButtonNames::ButtonUp;
467 case PadButton::L1:
468 return Common::Input::ButtonNames::L1;
469 case PadButton::L2:
470 return Common::Input::ButtonNames::L2;
471 case PadButton::L3:
472 return Common::Input::ButtonNames::L3;
473 case PadButton::R1:
474 return Common::Input::ButtonNames::R1;
475 case PadButton::R2:
476 return Common::Input::ButtonNames::R2;
477 case PadButton::R3:
478 return Common::Input::ButtonNames::R3;
479 case PadButton::Circle:
480 return Common::Input::ButtonNames::Circle;
481 case PadButton::Cross:
482 return Common::Input::ButtonNames::Cross;
483 case PadButton::Square:
484 return Common::Input::ButtonNames::Square;
485 case PadButton::Triangle:
486 return Common::Input::ButtonNames::Triangle;
487 case PadButton::Share:
488 return Common::Input::ButtonNames::Share;
489 case PadButton::Options:
490 return Common::Input::ButtonNames::Options;
491 default:
492 return Common::Input::ButtonNames::Undefined;
493 }
494}
495
496Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const {
497 if (params.Has("button")) {
498 return GetUIButtonName(params);
499 }
500 if (params.Has("axis")) {
501 return Common::Input::ButtonNames::Value;
502 }
503 if (params.Has("motion")) {
504 return Common::Input::ButtonNames::Engine;
505 }
506
507 return Common::Input::ButtonNames::Invalid;
508}
509
510void TestCommunication(const std::string& host, u16 port,
511 const std::function<void()>& success_callback,
512 const std::function<void()>& failure_callback) {
513 std::thread([=] {
514 Common::Event success_event;
515 SocketCallback callback{
516 .version = [](Response::Version) {},
517 .port_info = [](Response::PortInfo) {},
518 .pad_data = [&](Response::PadData) { success_event.Set(); },
519 };
520 Socket socket{host, port, std::move(callback)};
521 std::thread worker_thread{SocketLoop, &socket};
522 const bool result =
523 success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
524 socket.Stop();
525 worker_thread.join();
526 if (result) {
527 success_callback();
528 } else {
529 failure_callback();
530 }
531 }).detach();
532}
533
534CalibrationConfigurationJob::CalibrationConfigurationJob(
535 const std::string& host, u16 port, std::function<void(Status)> status_callback,
536 std::function<void(u16, u16, u16, u16)> data_callback) {
537
538 std::thread([=, this] {
539 Status current_status{Status::Initialized};
540 SocketCallback callback{
541 [](Response::Version) {}, [](Response::PortInfo) {},
542 [&](Response::PadData data) {
543 static constexpr u16 CALIBRATION_THRESHOLD = 100;
544 static constexpr u16 MAX_VALUE = UINT16_MAX;
545
546 if (current_status == Status::Initialized) {
547 // Receiving data means the communication is ready now
548 current_status = Status::Ready;
549 status_callback(current_status);
550 }
551 const auto& touchpad_0 = data.touch[0];
552 if (touchpad_0.is_active == 0) {
553 return;
554 }
555 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
556 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
557 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
558 if (current_status == Status::Ready) {
559 // First touch - min data (min_x/min_y)
560 current_status = Status::Stage1Completed;
561 status_callback(current_status);
562 }
563 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
564 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
565 // Set the current position as max value and finishes configuration
566 const u16 max_x = touchpad_0.x;
567 const u16 max_y = touchpad_0.y;
568 current_status = Status::Completed;
569 data_callback(min_x, min_y, max_x, max_y);
570 status_callback(current_status);
571
572 complete_event.Set();
573 }
574 }};
575 Socket socket{host, port, std::move(callback)};
576 std::thread worker_thread{SocketLoop, &socket};
577 complete_event.Wait();
578 socket.Stop();
579 worker_thread.join();
580 }).detach();
581}
582
583CalibrationConfigurationJob::~CalibrationConfigurationJob() {
584 Stop();
585}
586
587void CalibrationConfigurationJob::Stop() {
588 complete_event.Set();
589}
590
591} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/client.h b/src/input_common/drivers/udp_client.h
index 380f9bb76..1adc947c4 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/drivers/udp_client.h
@@ -4,20 +4,11 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <functional>
8#include <memory>
9#include <mutex>
10#include <optional> 7#include <optional>
11#include <string> 8
12#include <thread>
13#include <tuple>
14#include "common/common_types.h" 9#include "common/common_types.h"
15#include "common/param_package.h"
16#include "common/thread.h" 10#include "common/thread.h"
17#include "common/threadsafe_queue.h" 11#include "input_common/input_engine.h"
18#include "common/vector_math.h"
19#include "core/frontend/input.h"
20#include "input_common/motion_input.h"
21 12
22namespace InputCommon::CemuhookUDP { 13namespace InputCommon::CemuhookUDP {
23 14
@@ -30,16 +21,6 @@ struct TouchPad;
30struct Version; 21struct Version;
31} // namespace Response 22} // namespace Response
32 23
33enum class PadMotion {
34 GyroX,
35 GyroY,
36 GyroZ,
37 AccX,
38 AccY,
39 AccZ,
40 Undefined,
41};
42
43enum class PadTouch { 24enum class PadTouch {
44 Click, 25 Click,
45 Undefined, 26 Undefined,
@@ -49,14 +30,10 @@ struct UDPPadStatus {
49 std::string host{"127.0.0.1"}; 30 std::string host{"127.0.0.1"};
50 u16 port{26760}; 31 u16 port{26760};
51 std::size_t pad_index{}; 32 std::size_t pad_index{};
52 PadMotion motion{PadMotion::Undefined};
53 f32 motion_value{0.0f};
54}; 33};
55 34
56struct DeviceStatus { 35struct DeviceStatus {
57 std::mutex update_mutex; 36 std::mutex update_mutex;
58 Input::MotionStatus motion_status;
59 std::tuple<float, float, bool> touch_status;
60 37
61 // calibration data for scaling the device's touch area to 3ds 38 // calibration data for scaling the device's touch area to 3ds
62 struct CalibrationData { 39 struct CalibrationData {
@@ -68,48 +45,85 @@ struct DeviceStatus {
68 std::optional<CalibrationData> touch_calibration; 45 std::optional<CalibrationData> touch_calibration;
69}; 46};
70 47
71class Client { 48/**
49 * A button device factory representing a keyboard. It receives keyboard events and forward them
50 * to all button devices it created.
51 */
52class UDPClient final : public InputEngine {
72public: 53public:
73 // Initialize the UDP client capture and read sequence 54 explicit UDPClient(std::string input_engine_);
74 Client(); 55 ~UDPClient() override;
75
76 // Close and release the client
77 ~Client();
78
79 // Used for polling
80 void BeginConfiguration();
81 void EndConfiguration();
82
83 std::vector<Common::ParamPackage> GetInputDevices() const;
84 56
85 bool DeviceConnected(std::size_t pad) const;
86 void ReloadSockets(); 57 void ReloadSockets();
87 58
88 Common::SPSCQueue<UDPPadStatus>& GetPadQueue(); 59 /// Used for automapping features
89 const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const; 60 std::vector<Common::ParamPackage> GetInputDevices() const override;
61 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
62 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
63 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
64 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
90 65
91 DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); 66private:
92 const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; 67 enum class PadButton {
68 Undefined = 0x0000,
69 Share = 0x0001,
70 L3 = 0x0002,
71 R3 = 0x0004,
72 Options = 0x0008,
73 Up = 0x0010,
74 Right = 0x0020,
75 Down = 0x0040,
76 Left = 0x0080,
77 L2 = 0x0100,
78 R2 = 0x0200,
79 L1 = 0x0400,
80 R1 = 0x0800,
81 Triangle = 0x1000,
82 Circle = 0x2000,
83 Cross = 0x4000,
84 Square = 0x8000,
85 Touch1 = 0x10000,
86 touch2 = 0x20000,
87 };
93 88
94 Input::TouchStatus& GetTouchState(); 89 enum class PadAxes : u8 {
95 const Input::TouchStatus& GetTouchState() const; 90 LeftStickX,
91 LeftStickY,
92 RightStickX,
93 RightStickY,
94 AnalogLeft,
95 AnalogDown,
96 AnalogRight,
97 AnalogUp,
98 AnalogSquare,
99 AnalogCross,
100 AnalogCircle,
101 AnalogTriangle,
102 AnalogR1,
103 AnalogL1,
104 AnalogR2,
105 AnalogL3,
106 AnalogR3,
107 Touch1X,
108 Touch1Y,
109 Touch2X,
110 Touch2Y,
111 Undefined,
112 };
96 113
97private:
98 struct PadData { 114 struct PadData {
99 std::size_t pad_index{}; 115 std::size_t pad_index{};
100 bool connected{}; 116 bool connected{};
101 DeviceStatus status; 117 DeviceStatus status;
102 u64 packet_sequence{}; 118 u64 packet_sequence{};
103 119
104 // Realtime values
105 // motion is initalized with PID values for drift correction on joycons
106 InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
107 std::chrono::time_point<std::chrono::steady_clock> last_update; 120 std::chrono::time_point<std::chrono::steady_clock> last_update;
108 }; 121 };
109 122
110 struct ClientConnection { 123 struct ClientConnection {
111 ClientConnection(); 124 ClientConnection();
112 ~ClientConnection(); 125 ~ClientConnection();
126 Common::UUID uuid{"7F000001"};
113 std::string host{"127.0.0.1"}; 127 std::string host{"127.0.0.1"};
114 u16 port{26760}; 128 u16 port{26760};
115 s8 active{-1}; 129 s8 active{-1};
@@ -127,28 +141,16 @@ private:
127 void OnPortInfo(Response::PortInfo); 141 void OnPortInfo(Response::PortInfo);
128 void OnPadData(Response::PadData, std::size_t client); 142 void OnPadData(Response::PadData, std::size_t client);
129 void StartCommunication(std::size_t client, const std::string& host, u16 port); 143 void StartCommunication(std::size_t client, const std::string& host, u16 port);
130 void UpdateYuzuSettings(std::size_t client, std::size_t pad_index, 144 const PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
131 const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro); 145 const Common::UUID GetHostUUID(const std::string host) const;
132
133 // Returns an unused finger id, if there is no fingers available std::nullopt will be
134 // returned
135 std::optional<std::size_t> GetUnusedFingerID() const;
136
137 // Merges and updates all touch inputs into the touch_status array
138 void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id);
139 146
140 bool configuring = false; 147 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
141 148
142 // Allocate clients for 8 udp servers 149 // Allocate clients for 8 udp servers
143 static constexpr std::size_t MAX_UDP_CLIENTS = 8; 150 static constexpr std::size_t MAX_UDP_CLIENTS = 8;
144 static constexpr std::size_t PADS_PER_CLIENT = 4; 151 static constexpr std::size_t PADS_PER_CLIENT = 4;
145 // Each client can have up 2 touch inputs
146 static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2;
147 std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{}; 152 std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
148 std::array<ClientConnection, MAX_UDP_CLIENTS> clients{}; 153 std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
149 Common::SPSCQueue<UDPPadStatus> pad_queue{};
150 Input::TouchStatus touch_status{};
151 std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{};
152}; 154};
153 155
154/// An async job allowing configuration of the touchpad calibration. 156/// An async job allowing configuration of the touchpad calibration.
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
deleted file mode 100644
index e5de5e94f..000000000
--- a/src/input_common/gcadapter/gc_adapter.h
+++ /dev/null
@@ -1,168 +0,0 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6#include <algorithm>
7#include <functional>
8#include <mutex>
9#include <thread>
10#include <unordered_map>
11#include "common/common_types.h"
12#include "common/threadsafe_queue.h"
13#include "input_common/main.h"
14
15struct libusb_context;
16struct libusb_device;
17struct libusb_device_handle;
18
19namespace GCAdapter {
20
21enum class PadButton {
22 Undefined = 0x0000,
23 ButtonLeft = 0x0001,
24 ButtonRight = 0x0002,
25 ButtonDown = 0x0004,
26 ButtonUp = 0x0008,
27 TriggerZ = 0x0010,
28 TriggerR = 0x0020,
29 TriggerL = 0x0040,
30 ButtonA = 0x0100,
31 ButtonB = 0x0200,
32 ButtonX = 0x0400,
33 ButtonY = 0x0800,
34 ButtonStart = 0x1000,
35 // Below is for compatibility with "AxisButton" type
36 Stick = 0x2000,
37};
38
39enum class PadAxes : u8 {
40 StickX,
41 StickY,
42 SubstickX,
43 SubstickY,
44 TriggerLeft,
45 TriggerRight,
46 Undefined,
47};
48
49enum class ControllerTypes {
50 None,
51 Wired,
52 Wireless,
53};
54
55struct GCPadStatus {
56 std::size_t port{};
57
58 PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
59
60 PadAxes axis{PadAxes::Undefined};
61 s16 axis_value{};
62 u8 axis_threshold{50};
63};
64
65struct GCController {
66 ControllerTypes type{};
67 bool enable_vibration{};
68 u8 rumble_amplitude{};
69 u16 buttons{};
70 PadButton last_button{};
71 std::array<s16, 6> axis_values{};
72 std::array<u8, 6> axis_origin{};
73 u8 reset_origin_counter{};
74};
75
76class Adapter {
77public:
78 Adapter();
79 ~Adapter();
80
81 /// Request a vibration for a controller
82 bool RumblePlay(std::size_t port, u8 amplitude);
83
84 /// Used for polling
85 void BeginConfiguration();
86 void EndConfiguration();
87
88 Common::SPSCQueue<GCPadStatus>& GetPadQueue();
89 const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
90
91 GCController& GetPadState(std::size_t port);
92 const GCController& GetPadState(std::size_t port) const;
93
94 /// Returns true if there is a device connected to port
95 bool DeviceConnected(std::size_t port) const;
96
97 /// Used for automapping features
98 std::vector<Common::ParamPackage> GetInputDevices() const;
99 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
100 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
101
102private:
103 using AdapterPayload = std::array<u8, 37>;
104
105 void UpdatePadType(std::size_t port, ControllerTypes pad_type);
106 void UpdateControllers(const AdapterPayload& adapter_payload);
107 void UpdateYuzuSettings(std::size_t port);
108 void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
109 void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
110 void UpdateVibrations();
111
112 void AdapterInputThread();
113
114 void AdapterScanThread();
115
116 bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
117
118 // Updates vibration state of all controllers
119 void SendVibrations();
120
121 /// For use in initialization, querying devices to find the adapter
122 void Setup();
123
124 /// Resets status of all GC controller devices to a disconnected state
125 void ResetDevices();
126
127 /// Resets status of device connected to a disconnected state
128 void ResetDevice(std::size_t port);
129
130 /// Returns true if we successfully gain access to GC Adapter
131 bool CheckDeviceAccess();
132
133 /// Captures GC Adapter endpoint address
134 /// Returns true if the endpoint was set correctly
135 bool GetGCEndpoint(libusb_device* device);
136
137 /// For shutting down, clear all data, join all threads, release usb
138 void Reset();
139
140 // Join all threads
141 void JoinThreads();
142
143 // Release usb handles
144 void ClearLibusbHandle();
145
146 libusb_device_handle* usb_adapter_handle = nullptr;
147 std::array<GCController, 4> pads;
148 Common::SPSCQueue<GCPadStatus> pad_queue;
149
150 std::thread adapter_input_thread;
151 std::thread adapter_scan_thread;
152 bool adapter_input_thread_running;
153 bool adapter_scan_thread_running;
154 bool restart_scan_thread;
155
156 libusb_context* libusb_ctx;
157
158 u8 input_endpoint{0};
159 u8 output_endpoint{0};
160 u8 input_error_counter{0};
161 u8 output_error_counter{0};
162 int vibration_counter{0};
163
164 bool configuring{false};
165 bool rumble_enabled{true};
166 bool vibration_changed{true};
167};
168} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
deleted file mode 100644
index 1b6ded8d6..000000000
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ /dev/null
@@ -1,356 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include <utility>
9#include "common/assert.h"
10#include "common/threadsafe_queue.h"
11#include "input_common/gcadapter/gc_adapter.h"
12#include "input_common/gcadapter/gc_poller.h"
13
14namespace InputCommon {
15
16class GCButton final : public Input::ButtonDevice {
17public:
18 explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
19 : port(port_), button(button_), gcadapter(adapter) {}
20
21 ~GCButton() override;
22
23 bool GetStatus() const override {
24 if (gcadapter->DeviceConnected(port)) {
25 return (gcadapter->GetPadState(port).buttons & button) != 0;
26 }
27 return false;
28 }
29
30private:
31 const u32 port;
32 const s32 button;
33 const GCAdapter::Adapter* gcadapter;
34};
35
36class GCAxisButton final : public Input::ButtonDevice {
37public:
38 explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
39 const GCAdapter::Adapter* adapter)
40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
41 gcadapter(adapter) {}
42
43 bool GetStatus() const override {
44 if (gcadapter->DeviceConnected(port)) {
45 const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
46 const float axis_value = current_axis_value / 128.0f;
47 if (trigger_if_greater) {
48 // TODO: Might be worthwile to set a slider for the trigger threshold. It is
49 // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
50 return axis_value > threshold;
51 }
52 return axis_value < -threshold;
53 }
54 return false;
55 }
56
57private:
58 const u32 port;
59 const u32 axis;
60 float threshold;
61 bool trigger_if_greater;
62 const GCAdapter::Adapter* gcadapter;
63};
64
65GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
66 : adapter(std::move(adapter_)) {}
67
68GCButton::~GCButton() = default;
69
70std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
71 const auto button_id = params.Get("button", 0);
72 const auto port = static_cast<u32>(params.Get("port", 0));
73
74 constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
75
76 // button is not an axis/stick button
77 if (button_id != PAD_STICK_ID) {
78 return std::make_unique<GCButton>(port, button_id, adapter.get());
79 }
80
81 // For Axis buttons, used by the binary sticks.
82 if (button_id == PAD_STICK_ID) {
83 const int axis = params.Get("axis", 0);
84 const float threshold = params.Get("threshold", 0.25f);
85 const std::string direction_name = params.Get("direction", "");
86 bool trigger_if_greater;
87 if (direction_name == "+") {
88 trigger_if_greater = true;
89 } else if (direction_name == "-") {
90 trigger_if_greater = false;
91 } else {
92 trigger_if_greater = true;
93 LOG_ERROR(Input, "Unknown direction {}", direction_name);
94 }
95 return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
96 adapter.get());
97 }
98
99 return nullptr;
100}
101
102Common::ParamPackage GCButtonFactory::GetNextInput() const {
103 Common::ParamPackage params;
104 GCAdapter::GCPadStatus pad;
105 auto& queue = adapter->GetPadQueue();
106 while (queue.Pop(pad)) {
107 // This while loop will break on the earliest detected button
108 params.Set("engine", "gcpad");
109 params.Set("port", static_cast<s32>(pad.port));
110 if (pad.button != GCAdapter::PadButton::Undefined) {
111 params.Set("button", static_cast<u16>(pad.button));
112 }
113
114 // For Axis button implementation
115 if (pad.axis != GCAdapter::PadAxes::Undefined) {
116 params.Set("axis", static_cast<u8>(pad.axis));
117 params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
118 params.Set("threshold", "0.25");
119 if (pad.axis_value > 0) {
120 params.Set("direction", "+");
121 } else {
122 params.Set("direction", "-");
123 }
124 break;
125 }
126 }
127 return params;
128}
129
130void GCButtonFactory::BeginConfiguration() {
131 polling = true;
132 adapter->BeginConfiguration();
133}
134
135void GCButtonFactory::EndConfiguration() {
136 polling = false;
137 adapter->EndConfiguration();
138}
139
140class GCAnalog final : public Input::AnalogDevice {
141public:
142 explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
143 float deadzone_, float range_, const GCAdapter::Adapter* adapter)
144 : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
145 deadzone(deadzone_), range(range_), gcadapter(adapter) {}
146
147 float GetAxis(u32 axis) const {
148 if (gcadapter->DeviceConnected(port)) {
149 std::lock_guard lock{mutex};
150 const auto axis_value =
151 static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
152 return (axis_value) / (100.0f * range);
153 }
154 return 0.0f;
155 }
156
157 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
158 float x = GetAxis(analog_axis_x);
159 float y = GetAxis(analog_axis_y);
160 if (invert_x) {
161 x = -x;
162 }
163 if (invert_y) {
164 y = -y;
165 }
166 // Make sure the coordinates are in the unit circle,
167 // otherwise normalize it.
168 float r = x * x + y * y;
169 if (r > 1.0f) {
170 r = std::sqrt(r);
171 x /= r;
172 y /= r;
173 }
174
175 return {x, y};
176 }
177
178 std::tuple<float, float> GetStatus() const override {
179 const auto [x, y] = GetAnalog(axis_x, axis_y);
180 const float r = std::sqrt((x * x) + (y * y));
181 if (r > deadzone) {
182 return {x / r * (r - deadzone) / (1 - deadzone),
183 y / r * (r - deadzone) / (1 - deadzone)};
184 }
185 return {0.0f, 0.0f};
186 }
187
188 std::tuple<float, float> GetRawStatus() const override {
189 const float x = GetAxis(axis_x);
190 const float y = GetAxis(axis_y);
191 return {x, y};
192 }
193
194 Input::AnalogProperties GetAnalogProperties() const override {
195 return {deadzone, range, 0.5f};
196 }
197
198 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
199 const auto [x, y] = GetStatus();
200 const float directional_deadzone = 0.5f;
201 switch (direction) {
202 case Input::AnalogDirection::RIGHT:
203 return x > directional_deadzone;
204 case Input::AnalogDirection::LEFT:
205 return x < -directional_deadzone;
206 case Input::AnalogDirection::UP:
207 return y > directional_deadzone;
208 case Input::AnalogDirection::DOWN:
209 return y < -directional_deadzone;
210 }
211 return false;
212 }
213
214private:
215 const u32 port;
216 const u32 axis_x;
217 const u32 axis_y;
218 const bool invert_x;
219 const bool invert_y;
220 const float deadzone;
221 const float range;
222 const GCAdapter::Adapter* gcadapter;
223 mutable std::mutex mutex;
224};
225
226/// An analog device factory that creates analog devices from GC Adapter
227GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
228 : adapter(std::move(adapter_)) {}
229
230/**
231 * Creates analog device from joystick axes
232 * @param params contains parameters for creating the device:
233 * - "port": the nth gcpad on the adapter
234 * - "axis_x": the index of the axis to be bind as x-axis
235 * - "axis_y": the index of the axis to be bind as y-axis
236 */
237std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
238 const auto port = static_cast<u32>(params.Get("port", 0));
239 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
240 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
241 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
242 const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
243 const std::string invert_x_value = params.Get("invert_x", "+");
244 const std::string invert_y_value = params.Get("invert_y", "+");
245 const bool invert_x = invert_x_value == "-";
246 const bool invert_y = invert_y_value == "-";
247
248 return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
249 adapter.get());
250}
251
252void GCAnalogFactory::BeginConfiguration() {
253 polling = true;
254 adapter->BeginConfiguration();
255}
256
257void GCAnalogFactory::EndConfiguration() {
258 polling = false;
259 adapter->EndConfiguration();
260}
261
262Common::ParamPackage GCAnalogFactory::GetNextInput() {
263 GCAdapter::GCPadStatus pad;
264 Common::ParamPackage params;
265 auto& queue = adapter->GetPadQueue();
266 while (queue.Pop(pad)) {
267 if (pad.button != GCAdapter::PadButton::Undefined) {
268 params.Set("engine", "gcpad");
269 params.Set("port", static_cast<s32>(pad.port));
270 params.Set("button", static_cast<u16>(pad.button));
271 return params;
272 }
273 if (pad.axis == GCAdapter::PadAxes::Undefined ||
274 std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
275 continue;
276 }
277 // An analog device needs two axes, so we need to store the axis for later and wait for
278 // a second input event. The axes also must be from the same joystick.
279 const u8 axis = static_cast<u8>(pad.axis);
280 if (axis == 0 || axis == 1) {
281 analog_x_axis = 0;
282 analog_y_axis = 1;
283 controller_number = static_cast<s32>(pad.port);
284 break;
285 }
286 if (axis == 2 || axis == 3) {
287 analog_x_axis = 2;
288 analog_y_axis = 3;
289 controller_number = static_cast<s32>(pad.port);
290 break;
291 }
292
293 if (analog_x_axis == -1) {
294 analog_x_axis = axis;
295 controller_number = static_cast<s32>(pad.port);
296 } else if (analog_y_axis == -1 && analog_x_axis != axis &&
297 controller_number == static_cast<s32>(pad.port)) {
298 analog_y_axis = axis;
299 break;
300 }
301 }
302 if (analog_x_axis != -1 && analog_y_axis != -1) {
303 params.Set("engine", "gcpad");
304 params.Set("port", controller_number);
305 params.Set("axis_x", analog_x_axis);
306 params.Set("axis_y", analog_y_axis);
307 params.Set("invert_x", "+");
308 params.Set("invert_y", "+");
309 analog_x_axis = -1;
310 analog_y_axis = -1;
311 controller_number = -1;
312 return params;
313 }
314 return params;
315}
316
317class GCVibration final : public Input::VibrationDevice {
318public:
319 explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
320 : port(port_), gcadapter(adapter) {}
321
322 u8 GetStatus() const override {
323 return gcadapter->RumblePlay(port, 0);
324 }
325
326 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
327 [[maybe_unused]] f32 freq_high) const override {
328 const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
329 const auto processed_amplitude =
330 static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
331
332 return gcadapter->RumblePlay(port, processed_amplitude);
333 }
334
335private:
336 const u32 port;
337 GCAdapter::Adapter* gcadapter;
338};
339
340/// An vibration device factory that creates vibration devices from GC Adapter
341GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
342 : adapter(std::move(adapter_)) {}
343
344/**
345 * Creates a vibration device from a joystick
346 * @param params contains parameters for creating the device:
347 * - "port": the nth gcpad on the adapter
348 */
349std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
350 const Common::ParamPackage& params) {
351 const auto port = static_cast<u32>(params.Get("port", 0));
352
353 return std::make_unique<GCVibration>(port, adapter.get());
354}
355
356} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
deleted file mode 100644
index d1271e3ea..000000000
--- a/src/input_common/gcadapter/gc_poller.h
+++ /dev/null
@@ -1,78 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/gcadapter/gc_adapter.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a gcpad. It receives gcpad events and forward them
15 * to all button devices it created.
16 */
17class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28 Common::ParamPackage GetNextInput() const;
29
30 /// For device input configuration/polling
31 void BeginConfiguration();
32 void EndConfiguration();
33
34 bool IsPolling() const {
35 return polling;
36 }
37
38private:
39 std::shared_ptr<GCAdapter::Adapter> adapter;
40 bool polling = false;
41};
42
43/// An analog device factory that creates analog devices from GC Adapter
44class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
45public:
46 explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
47
48 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
49 Common::ParamPackage GetNextInput();
50
51 /// For device input configuration/polling
52 void BeginConfiguration();
53 void EndConfiguration();
54
55 bool IsPolling() const {
56 return polling;
57 }
58
59private:
60 std::shared_ptr<GCAdapter::Adapter> adapter;
61 int analog_x_axis = -1;
62 int analog_y_axis = -1;
63 int controller_number = -1;
64 bool polling = false;
65};
66
67/// A vibration device factory creates vibration devices from GC Adapter
68class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
69public:
70 explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
71
72 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
73
74private:
75 std::shared_ptr<GCAdapter::Adapter> adapter;
76};
77
78} // namespace InputCommon
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
new file mode 100644
index 000000000..e23394f5f
--- /dev/null
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -0,0 +1,317 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cmath>
7#include "common/math_util.h"
8#include "common/settings.h"
9#include "input_common/helpers/stick_from_buttons.h"
10
11namespace InputCommon {
12
13class Stick final : public Common::Input::InputDevice {
14public:
15 using Button = std::unique_ptr<Common::Input::InputDevice>;
16
17 Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
18 float modifier_scale_, float modifier_angle_)
19 : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
20 right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
21 modifier_angle(modifier_angle_) {
22 up->SetCallback({
23 .on_change =
24 [this](const Common::Input::CallbackStatus& callback_) {
25 UpdateUpButtonStatus(callback_);
26 },
27 });
28 down->SetCallback({
29 .on_change =
30 [this](const Common::Input::CallbackStatus& callback_) {
31 UpdateDownButtonStatus(callback_);
32 },
33 });
34 left->SetCallback({
35 .on_change =
36 [this](const Common::Input::CallbackStatus& callback_) {
37 UpdateLeftButtonStatus(callback_);
38 },
39 });
40 right->SetCallback({
41 .on_change =
42 [this](const Common::Input::CallbackStatus& callback_) {
43 UpdateRightButtonStatus(callback_);
44 },
45 });
46 modifier->SetCallback({
47 .on_change =
48 [this](const Common::Input::CallbackStatus& callback_) {
49 UpdateModButtonStatus(callback_);
50 },
51 });
52 last_x_axis_value = 0.0f;
53 last_y_axis_value = 0.0f;
54 }
55
56 bool IsAngleGreater(float old_angle, float new_angle) const {
57 constexpr float TAU = Common::PI * 2.0f;
58 // Use wider angle to ease the transition.
59 constexpr float aperture = TAU * 0.15f;
60 const float top_limit = new_angle + aperture;
61 return (old_angle > new_angle && old_angle <= top_limit) ||
62 (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
63 }
64
65 bool IsAngleSmaller(float old_angle, float new_angle) const {
66 constexpr float TAU = Common::PI * 2.0f;
67 // Use wider angle to ease the transition.
68 constexpr float aperture = TAU * 0.15f;
69 const float bottom_limit = new_angle - aperture;
70 return (old_angle >= bottom_limit && old_angle < new_angle) ||
71 (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
72 }
73
74 float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
75 constexpr float TAU = Common::PI * 2.0f;
76 float new_angle = angle;
77
78 auto time_difference = static_cast<float>(
79 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
80 time_difference /= 1000.0f * 1000.0f;
81 if (time_difference > 0.5f) {
82 time_difference = 0.5f;
83 }
84
85 if (IsAngleGreater(new_angle, goal_angle)) {
86 new_angle -= modifier_angle * time_difference;
87 if (new_angle < 0) {
88 new_angle += TAU;
89 }
90 if (!IsAngleGreater(new_angle, goal_angle)) {
91 return goal_angle;
92 }
93 } else if (IsAngleSmaller(new_angle, goal_angle)) {
94 new_angle += modifier_angle * time_difference;
95 if (new_angle >= TAU) {
96 new_angle -= TAU;
97 }
98 if (!IsAngleSmaller(new_angle, goal_angle)) {
99 return goal_angle;
100 }
101 } else {
102 return goal_angle;
103 }
104 return new_angle;
105 }
106
107 void SetGoalAngle(bool r, bool l, bool u, bool d) {
108 // Move to the right
109 if (r && !u && !d) {
110 goal_angle = 0.0f;
111 }
112
113 // Move to the upper right
114 if (r && u && !d) {
115 goal_angle = Common::PI * 0.25f;
116 }
117
118 // Move up
119 if (u && !l && !r) {
120 goal_angle = Common::PI * 0.5f;
121 }
122
123 // Move to the upper left
124 if (l && u && !d) {
125 goal_angle = Common::PI * 0.75f;
126 }
127
128 // Move to the left
129 if (l && !u && !d) {
130 goal_angle = Common::PI;
131 }
132
133 // Move to the bottom left
134 if (l && !u && d) {
135 goal_angle = Common::PI * 1.25f;
136 }
137
138 // Move down
139 if (d && !l && !r) {
140 goal_angle = Common::PI * 1.5f;
141 }
142
143 // Move to the bottom right
144 if (r && !u && d) {
145 goal_angle = Common::PI * 1.75f;
146 }
147 }
148
149 void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) {
150 up_status = button_callback.button_status.value;
151 UpdateStatus();
152 }
153
154 void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) {
155 down_status = button_callback.button_status.value;
156 UpdateStatus();
157 }
158
159 void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) {
160 left_status = button_callback.button_status.value;
161 UpdateStatus();
162 }
163
164 void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) {
165 right_status = button_callback.button_status.value;
166 UpdateStatus();
167 }
168
169 void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) {
170 modifier_status = button_callback.button_status.value;
171 UpdateStatus();
172 }
173
174 void UpdateStatus() {
175 const float coef = modifier_status ? modifier_scale : 1.0f;
176
177 bool r = right_status;
178 bool l = left_status;
179 bool u = up_status;
180 bool d = down_status;
181
182 // Eliminate contradictory movements
183 if (r && l) {
184 r = false;
185 l = false;
186 }
187 if (u && d) {
188 u = false;
189 d = false;
190 }
191
192 // Move if a key is pressed
193 if (r || l || u || d) {
194 amplitude = coef;
195 } else {
196 amplitude = 0;
197 }
198
199 const auto now = std::chrono::steady_clock::now();
200 const auto time_difference = static_cast<u64>(
201 std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
202
203 if (time_difference < 10) {
204 // Disable analog mode if inputs are too fast
205 SetGoalAngle(r, l, u, d);
206 angle = goal_angle;
207 } else {
208 angle = GetAngle(now);
209 SetGoalAngle(r, l, u, d);
210 }
211
212 last_update = now;
213 Common::Input::CallbackStatus status{
214 .type = Common::Input::InputType::Stick,
215 .stick_status = GetStatus(),
216 };
217 last_x_axis_value = status.stick_status.x.raw_value;
218 last_y_axis_value = status.stick_status.y.raw_value;
219 TriggerOnChange(status);
220 }
221
222 void ForceUpdate() override {
223 up->ForceUpdate();
224 down->ForceUpdate();
225 left->ForceUpdate();
226 right->ForceUpdate();
227 modifier->ForceUpdate();
228 }
229
230 void SoftUpdate() override {
231 Common::Input::CallbackStatus status{
232 .type = Common::Input::InputType::Stick,
233 .stick_status = GetStatus(),
234 };
235 if (last_x_axis_value == status.stick_status.x.raw_value &&
236 last_y_axis_value == status.stick_status.y.raw_value) {
237 return;
238 }
239 last_x_axis_value = status.stick_status.x.raw_value;
240 last_y_axis_value = status.stick_status.y.raw_value;
241 TriggerOnChange(status);
242 }
243
244 Common::Input::StickStatus GetStatus() const {
245 Common::Input::StickStatus status{};
246 status.x.properties = properties;
247 status.y.properties = properties;
248 if (Settings::values.emulate_analog_keyboard) {
249 const auto now = std::chrono::steady_clock::now();
250 float angle_ = GetAngle(now);
251 status.x.raw_value = std::cos(angle_) * amplitude;
252 status.y.raw_value = std::sin(angle_) * amplitude;
253 return status;
254 }
255 constexpr float SQRT_HALF = 0.707106781f;
256 int x = 0, y = 0;
257 if (right_status) {
258 ++x;
259 }
260 if (left_status) {
261 --x;
262 }
263 if (up_status) {
264 ++y;
265 }
266 if (down_status) {
267 --y;
268 }
269 const float coef = modifier_status ? modifier_scale : 1.0f;
270 status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
271 status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
272 return status;
273 }
274
275private:
276 Button up;
277 Button down;
278 Button left;
279 Button right;
280 Button modifier;
281 float modifier_scale{};
282 float modifier_angle{};
283 float angle{};
284 float goal_angle{};
285 float amplitude{};
286 bool up_status{};
287 bool down_status{};
288 bool left_status{};
289 bool right_status{};
290 bool modifier_status{};
291 float last_x_axis_value{};
292 float last_y_axis_value{};
293 const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
294 std::chrono::time_point<std::chrono::steady_clock> last_update;
295};
296
297std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
298 const Common::ParamPackage& params) {
299 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
300 auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
301 params.Get("up", null_engine));
302 auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
303 params.Get("down", null_engine));
304 auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
305 params.Get("left", null_engine));
306 auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
307 params.Get("right", null_engine));
308 auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
309 params.Get("modifier", null_engine));
310 auto modifier_scale = params.Get("modifier_scale", 0.5f);
311 auto modifier_angle = params.Get("modifier_angle", 5.5f);
312 return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
313 std::move(right), std::move(modifier), modifier_scale,
314 modifier_angle);
315}
316
317} // namespace InputCommon
diff --git a/src/input_common/analog_from_button.h b/src/input_common/helpers/stick_from_buttons.h
index bbd583dd9..437ace4f7 100755..100644
--- a/src/input_common/analog_from_button.h
+++ b/src/input_common/helpers/stick_from_buttons.h
@@ -4,8 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include "common/input.h"
8#include "core/frontend/input.h"
9 8
10namespace InputCommon { 9namespace InputCommon {
11 10
@@ -13,7 +12,7 @@ namespace InputCommon {
13 * An analog device factory that takes direction button devices and combines them into a analog 12 * An analog device factory that takes direction button devices and combines them into a analog
14 * device. 13 * device.
15 */ 14 */
16class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> { 15class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
17public: 16public:
18 /** 17 /**
19 * Creates an analog device from direction button devices 18 * Creates an analog device from direction button devices
@@ -25,7 +24,7 @@ public:
25 * - "modifier": a serialized ParamPackage for creating a button device as the modifier 24 * - "modifier": a serialized ParamPackage for creating a button device as the modifier
26 * - "modifier_scale": a float for the multiplier the modifier gives to the position 25 * - "modifier_scale": a float for the multiplier the modifier gives to the position
27 */ 26 */
28 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; 27 std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
29}; 28};
30 29
31} // namespace InputCommon 30} // namespace InputCommon
diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp
new file mode 100644
index 000000000..ece1e3b32
--- /dev/null
+++ b/src/input_common/helpers/touch_from_buttons.cpp
@@ -0,0 +1,84 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include "common/settings.h"
7#include "core/frontend/framebuffer_layout.h"
8#include "input_common/helpers/touch_from_buttons.h"
9
10namespace InputCommon {
11
12class TouchFromButtonDevice final : public Common::Input::InputDevice {
13public:
14 using Button = std::unique_ptr<Common::Input::InputDevice>;
15 TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_)
16 : button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) {
17 last_button_value = false;
18 button->SetCallback({
19 .on_change =
20 [this](const Common::Input::CallbackStatus& callback_) {
21 UpdateButtonStatus(callback_);
22 },
23 });
24 button->ForceUpdate();
25 }
26
27 void ForceUpdate() override {
28 button->ForceUpdate();
29 }
30
31 Common::Input::TouchStatus GetStatus(bool pressed) const {
32 const Common::Input::ButtonStatus button_status{
33 .value = pressed,
34 };
35 Common::Input::TouchStatus status{
36 .pressed = button_status,
37 .x = {},
38 .y = {},
39 .id = touch_id,
40 };
41 status.x.properties = properties;
42 status.y.properties = properties;
43
44 if (!pressed) {
45 return status;
46 }
47
48 status.x.raw_value = x;
49 status.y.raw_value = y;
50 return status;
51 }
52
53 void UpdateButtonStatus(const Common::Input::CallbackStatus& button_callback) {
54 const Common::Input::CallbackStatus status{
55 .type = Common::Input::InputType::Touch,
56 .touch_status = GetStatus(button_callback.button_status.value),
57 };
58 if (last_button_value != button_callback.button_status.value) {
59 last_button_value = button_callback.button_status.value;
60 TriggerOnChange(status);
61 }
62 }
63
64private:
65 Button button;
66 bool last_button_value;
67 const int touch_id;
68 const float x;
69 const float y;
70 const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
71};
72
73std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
74 const Common::ParamPackage& params) {
75 const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
76 auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
77 params.Get("button", null_engine));
78 const auto touch_id = params.Get("touch_id", 0);
79 const float x = params.Get("x", 0.0f) / 1280.0f;
80 const float y = params.Get("y", 0.0f) / 720.0f;
81 return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y);
82}
83
84} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/helpers/touch_from_buttons.h
index 8b4d1aa96..628f18215 100644
--- a/src/input_common/touch_from_button.h
+++ b/src/input_common/helpers/touch_from_buttons.h
@@ -4,20 +4,19 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include "common/input.h"
8#include "core/frontend/input.h"
9 8
10namespace InputCommon { 9namespace InputCommon {
11 10
12/** 11/**
13 * A touch device factory that takes a list of button devices and combines them into a touch device. 12 * A touch device factory that takes a list of button devices and combines them into a touch device.
14 */ 13 */
15class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { 14class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> {
16public: 15public:
17 /** 16 /**
18 * Creates a touch device from a list of button devices 17 * Creates a touch device from a list of button devices
19 */ 18 */
20 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; 19 std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
21}; 20};
22 21
23} // namespace InputCommon 22} // namespace InputCommon
diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/helpers/udp_protocol.cpp
index 5e50bd612..cdeab7e11 100644
--- a/src/input_common/udp/protocol.cpp
+++ b/src/input_common/helpers/udp_protocol.cpp
@@ -5,7 +5,7 @@
5#include <cstddef> 5#include <cstddef>
6#include <cstring> 6#include <cstring>
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "input_common/udp/protocol.h" 8#include "input_common/helpers/udp_protocol.h"
9 9
10namespace InputCommon::CemuhookUDP { 10namespace InputCommon::CemuhookUDP {
11 11
diff --git a/src/input_common/udp/protocol.h b/src/input_common/helpers/udp_protocol.h
index 1bdc9209e..bcba12c58 100644
--- a/src/input_common/udp/protocol.h
+++ b/src/input_common/helpers/udp_protocol.h
@@ -56,6 +56,12 @@ constexpr Type GetMessageType();
56 56
57namespace Request { 57namespace Request {
58 58
59enum RegisterFlags : u8 {
60 AllPads,
61 PadID,
62 PadMACAdddress,
63};
64
59struct Version {}; 65struct Version {};
60/** 66/**
61 * Requests the server to send information about what controllers are plugged into the ports 67 * Requests the server to send information about what controllers are plugged into the ports
@@ -77,13 +83,8 @@ static_assert(std::is_trivially_copyable_v<PortInfo>,
77 * timeout seems to be 5 seconds. 83 * timeout seems to be 5 seconds.
78 */ 84 */
79struct PadData { 85struct PadData {
80 enum class Flags : u8 {
81 AllPorts,
82 Id,
83 Mac,
84 };
85 /// Determines which method will be used as a look up for the controller 86 /// Determines which method will be used as a look up for the controller
86 Flags flags{}; 87 RegisterFlags flags{};
87 /// Index of the port of the controller to retrieve data about 88 /// Index of the port of the controller to retrieve data about
88 u8 port_id{}; 89 u8 port_id{};
89 /// Mac address of the controller to retrieve data about 90 /// Mac address of the controller to retrieve data about
@@ -113,6 +114,36 @@ Message<T> Create(const T data, const u32 client_id = 0) {
113 114
114namespace Response { 115namespace Response {
115 116
117enum class ConnectionType : u8 {
118 None,
119 Usb,
120 Bluetooth,
121};
122
123enum class State : u8 {
124 Disconnected,
125 Reserved,
126 Connected,
127};
128
129enum class Model : u8 {
130 None,
131 PartialGyro,
132 FullGyro,
133 Generic,
134};
135
136enum class Battery : u8 {
137 None = 0x00,
138 Dying = 0x01,
139 Low = 0x02,
140 Medium = 0x03,
141 High = 0x04,
142 Full = 0x05,
143 Charging = 0xEE,
144 Charged = 0xEF,
145};
146
116struct Version { 147struct Version {
117 u16_le version{}; 148 u16_le version{};
118}; 149};
@@ -122,11 +153,11 @@ static_assert(std::is_trivially_copyable_v<Version>,
122 153
123struct PortInfo { 154struct PortInfo {
124 u8 id{}; 155 u8 id{};
125 u8 state{}; 156 State state{};
126 u8 model{}; 157 Model model{};
127 u8 connection_type{}; 158 ConnectionType connection_type{};
128 MacAddress mac; 159 MacAddress mac;
129 u8 battery{}; 160 Battery battery{};
130 u8 is_pad_active{}; 161 u8 is_pad_active{};
131}; 162};
132static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); 163static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
@@ -177,18 +208,18 @@ struct PadData {
177 u8 right_stick_y{}; 208 u8 right_stick_y{};
178 209
179 struct AnalogButton { 210 struct AnalogButton {
180 u8 button_8{}; 211 u8 button_dpad_left_analog{};
181 u8 button_7{}; 212 u8 button_dpad_down_analog{};
182 u8 button_6{}; 213 u8 button_dpad_right_analog{};
183 u8 button_5{}; 214 u8 button_dpad_up_analog{};
184 u8 button_12{}; 215 u8 button_square_analog{};
185 u8 button_11{}; 216 u8 button_cross_analog{};
186 u8 button_10{}; 217 u8 button_circle_analog{};
187 u8 button_9{}; 218 u8 button_triangle_analog{};
188 u8 button_16{}; 219 u8 button_r1_analog{};
189 u8 button_15{}; 220 u8 button_l1_analog{};
190 u8 button_14{}; 221 u8 trigger_r2{};
191 u8 button_13{}; 222 u8 trigger_l2{};
192 } analog_button; 223 } analog_button;
193 224
194 std::array<TouchPad, 2> touch; 225 std::array<TouchPad, 2> touch;
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
new file mode 100644
index 000000000..9c17ca4f7
--- /dev/null
+++ b/src/input_common/input_engine.cpp
@@ -0,0 +1,362 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/logging/log.h"
6#include "common/param_package.h"
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11void InputEngine::PreSetController(const PadIdentifier& identifier) {
12 std::lock_guard lock{mutex};
13 controller_list.try_emplace(identifier);
14}
15
16void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) {
17 std::lock_guard lock{mutex};
18 ControllerData& controller = controller_list.at(identifier);
19 controller.buttons.try_emplace(button, false);
20}
21
22void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) {
23 std::lock_guard lock{mutex};
24 ControllerData& controller = controller_list.at(identifier);
25 controller.hat_buttons.try_emplace(button, u8{0});
26}
27
28void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) {
29 std::lock_guard lock{mutex};
30 ControllerData& controller = controller_list.at(identifier);
31 controller.axes.try_emplace(axis, 0.0f);
32}
33
34void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) {
35 std::lock_guard lock{mutex};
36 ControllerData& controller = controller_list.at(identifier);
37 controller.motions.try_emplace(motion);
38}
39
40void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) {
41 {
42 std::lock_guard lock{mutex};
43 ControllerData& controller = controller_list.at(identifier);
44 if (!configuring) {
45 controller.buttons.insert_or_assign(button, value);
46 }
47 }
48 TriggerOnButtonChange(identifier, button, value);
49}
50
51void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) {
52 {
53 std::lock_guard lock{mutex};
54 ControllerData& controller = controller_list.at(identifier);
55 if (!configuring) {
56 controller.hat_buttons.insert_or_assign(button, value);
57 }
58 }
59 TriggerOnHatButtonChange(identifier, button, value);
60}
61
62void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) {
63 {
64 std::lock_guard lock{mutex};
65 ControllerData& controller = controller_list.at(identifier);
66 if (!configuring) {
67 controller.axes.insert_or_assign(axis, value);
68 }
69 }
70 TriggerOnAxisChange(identifier, axis, value);
71}
72
73void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) {
74 {
75 std::lock_guard lock{mutex};
76 ControllerData& controller = controller_list.at(identifier);
77 if (!configuring) {
78 controller.battery = value;
79 }
80 }
81 TriggerOnBatteryChange(identifier, value);
82}
83
84void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) {
85 {
86 std::lock_guard lock{mutex};
87 ControllerData& controller = controller_list.at(identifier);
88 if (!configuring) {
89 controller.motions.insert_or_assign(motion, value);
90 }
91 }
92 TriggerOnMotionChange(identifier, motion, value);
93}
94
95bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
96 std::lock_guard lock{mutex};
97 const auto controller_iter = controller_list.find(identifier);
98 if (controller_iter == controller_list.cend()) {
99 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
100 identifier.pad, identifier.port);
101 return false;
102 }
103 const ControllerData& controller = controller_iter->second;
104 const auto button_iter = controller.buttons.find(button);
105 if (button_iter == controller.buttons.cend()) {
106 LOG_ERROR(Input, "Invalid button {}", button);
107 return false;
108 }
109 return button_iter->second;
110}
111
112bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const {
113 std::lock_guard lock{mutex};
114 const auto controller_iter = controller_list.find(identifier);
115 if (controller_iter == controller_list.cend()) {
116 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
117 identifier.pad, identifier.port);
118 return false;
119 }
120 const ControllerData& controller = controller_iter->second;
121 const auto hat_iter = controller.hat_buttons.find(button);
122 if (hat_iter == controller.hat_buttons.cend()) {
123 LOG_ERROR(Input, "Invalid hat button {}", button);
124 return false;
125 }
126 return (hat_iter->second & direction) != 0;
127}
128
129f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const {
130 std::lock_guard lock{mutex};
131 const auto controller_iter = controller_list.find(identifier);
132 if (controller_iter == controller_list.cend()) {
133 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
134 identifier.pad, identifier.port);
135 return 0.0f;
136 }
137 const ControllerData& controller = controller_iter->second;
138 const auto axis_iter = controller.axes.find(axis);
139 if (axis_iter == controller.axes.cend()) {
140 LOG_ERROR(Input, "Invalid axis {}", axis);
141 return 0.0f;
142 }
143 return axis_iter->second;
144}
145
146BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const {
147 std::lock_guard lock{mutex};
148 const auto controller_iter = controller_list.find(identifier);
149 if (controller_iter == controller_list.cend()) {
150 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
151 identifier.pad, identifier.port);
152 return BatteryLevel::Charging;
153 }
154 const ControllerData& controller = controller_iter->second;
155 return controller.battery;
156}
157
158BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const {
159 std::lock_guard lock{mutex};
160 const auto controller_iter = controller_list.find(identifier);
161 if (controller_iter == controller_list.cend()) {
162 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(),
163 identifier.pad, identifier.port);
164 return {};
165 }
166 const ControllerData& controller = controller_iter->second;
167 return controller.motions.at(motion);
168}
169
170void InputEngine::ResetButtonState() {
171 for (const auto& controller : controller_list) {
172 for (const auto& button : controller.second.buttons) {
173 SetButton(controller.first, button.first, false);
174 }
175 for (const auto& button : controller.second.hat_buttons) {
176 SetHatButton(controller.first, button.first, false);
177 }
178 }
179}
180
181void InputEngine::ResetAnalogState() {
182 for (const auto& controller : controller_list) {
183 for (const auto& axis : controller.second.axes) {
184 SetAxis(controller.first, axis.first, 0.0);
185 }
186 }
187}
188
189void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) {
190 std::lock_guard lock{mutex_callback};
191 for (const auto& poller_pair : callback_list) {
192 const InputIdentifier& poller = poller_pair.second;
193 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) {
194 continue;
195 }
196 if (poller.callback.on_change) {
197 poller.callback.on_change();
198 }
199 }
200 if (!configuring || !mapping_callback.on_data) {
201 return;
202 }
203
204 PreSetButton(identifier, button);
205 if (value == GetButton(identifier, button)) {
206 return;
207 }
208 mapping_callback.on_data(MappingData{
209 .engine = GetEngineName(),
210 .pad = identifier,
211 .type = EngineInputType::Button,
212 .index = button,
213 .button_value = value,
214 });
215}
216
217void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) {
218 std::lock_guard lock{mutex_callback};
219 for (const auto& poller_pair : callback_list) {
220 const InputIdentifier& poller = poller_pair.second;
221 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) {
222 continue;
223 }
224 if (poller.callback.on_change) {
225 poller.callback.on_change();
226 }
227 }
228 if (!configuring || !mapping_callback.on_data) {
229 return;
230 }
231 for (std::size_t index = 1; index < 0xff; index <<= 1) {
232 bool button_value = (value & index) != 0;
233 if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) {
234 continue;
235 }
236 mapping_callback.on_data(MappingData{
237 .engine = GetEngineName(),
238 .pad = identifier,
239 .type = EngineInputType::HatButton,
240 .index = button,
241 .hat_name = GetHatButtonName(static_cast<u8>(index)),
242 });
243 }
244}
245
246void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) {
247 std::lock_guard lock{mutex_callback};
248 for (const auto& poller_pair : callback_list) {
249 const InputIdentifier& poller = poller_pair.second;
250 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) {
251 continue;
252 }
253 if (poller.callback.on_change) {
254 poller.callback.on_change();
255 }
256 }
257 if (!configuring || !mapping_callback.on_data) {
258 return;
259 }
260 if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) {
261 return;
262 }
263 mapping_callback.on_data(MappingData{
264 .engine = GetEngineName(),
265 .pad = identifier,
266 .type = EngineInputType::Analog,
267 .index = axis,
268 .axis_value = value,
269 });
270}
271
272void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier,
273 [[maybe_unused]] BatteryLevel value) {
274 std::lock_guard lock{mutex_callback};
275 for (const auto& poller_pair : callback_list) {
276 const InputIdentifier& poller = poller_pair.second;
277 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) {
278 continue;
279 }
280 if (poller.callback.on_change) {
281 poller.callback.on_change();
282 }
283 }
284}
285
286void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
287 const BasicMotion& value) {
288 std::lock_guard lock{mutex_callback};
289 for (const auto& poller_pair : callback_list) {
290 const InputIdentifier& poller = poller_pair.second;
291 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) {
292 continue;
293 }
294 if (poller.callback.on_change) {
295 poller.callback.on_change();
296 }
297 }
298 if (!configuring || !mapping_callback.on_data) {
299 return;
300 }
301 if (std::abs(value.gyro_x) < 0.6f && std::abs(value.gyro_y) < 0.6f &&
302 std::abs(value.gyro_z) < 0.6f) {
303 return;
304 }
305 mapping_callback.on_data(MappingData{
306 .engine = GetEngineName(),
307 .pad = identifier,
308 .type = EngineInputType::Motion,
309 .index = motion,
310 .motion_value = value,
311 });
312}
313
314bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
315 const PadIdentifier& identifier, EngineInputType type,
316 int index) const {
317 if (input_identifier.type != type) {
318 return false;
319 }
320 if (input_identifier.index != index) {
321 return false;
322 }
323 if (input_identifier.identifier != identifier) {
324 return false;
325 }
326 return true;
327}
328
329void InputEngine::BeginConfiguration() {
330 configuring = true;
331}
332
333void InputEngine::EndConfiguration() {
334 configuring = false;
335}
336
337const std::string& InputEngine::GetEngineName() const {
338 return input_engine;
339}
340
341int InputEngine::SetCallback(InputIdentifier input_identifier) {
342 std::lock_guard lock{mutex_callback};
343 callback_list.insert_or_assign(last_callback_key, std::move(input_identifier));
344 return last_callback_key++;
345}
346
347void InputEngine::SetMappingCallback(MappingCallback callback) {
348 std::lock_guard lock{mutex_callback};
349 mapping_callback = std::move(callback);
350}
351
352void InputEngine::DeleteCallback(int key) {
353 std::lock_guard lock{mutex_callback};
354 const auto& iterator = callback_list.find(key);
355 if (iterator == callback_list.end()) {
356 LOG_ERROR(Input, "Tried to delete non-existent callback {}", key);
357 return;
358 }
359 callback_list.erase(iterator);
360}
361
362} // namespace InputCommon
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
new file mode 100644
index 000000000..390581c94
--- /dev/null
+++ b/src/input_common/input_engine.h
@@ -0,0 +1,229 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include <functional>
8#include <mutex>
9#include <unordered_map>
10
11#include "common/common_types.h"
12#include "common/input.h"
13#include "common/param_package.h"
14#include "common/uuid.h"
15#include "input_common/main.h"
16
17// Pad Identifier of data source
18struct PadIdentifier {
19 Common::UUID guid{};
20 std::size_t port{};
21 std::size_t pad{};
22
23 friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default;
24};
25
26// Basic motion data containing data from the sensors and a timestamp in microseconds
27struct BasicMotion {
28 float gyro_x{};
29 float gyro_y{};
30 float gyro_z{};
31 float accel_x{};
32 float accel_y{};
33 float accel_z{};
34 u64 delta_timestamp{};
35};
36
37// Stages of a battery charge
38enum class BatteryLevel {
39 Empty,
40 Critical,
41 Low,
42 Medium,
43 Full,
44 Charging,
45};
46
47// Types of input that are stored in the engine
48enum class EngineInputType {
49 None,
50 Button,
51 HatButton,
52 Analog,
53 Motion,
54 Battery,
55};
56
57namespace std {
58// Hash used to create lists from PadIdentifier data
59template <>
60struct hash<PadIdentifier> {
61 size_t operator()(const PadIdentifier& pad_id) const noexcept {
62 u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0];
63 hash_value ^= (static_cast<u64>(pad_id.port) << 32);
64 hash_value ^= static_cast<u64>(pad_id.pad);
65 return static_cast<size_t>(hash_value);
66 }
67};
68
69} // namespace std
70
71namespace InputCommon {
72
73// Data from the engine and device needed for creating a ParamPackage
74struct MappingData {
75 std::string engine{};
76 PadIdentifier pad{};
77 EngineInputType type{};
78 int index{};
79 bool button_value{};
80 std::string hat_name{};
81 f32 axis_value{};
82 BasicMotion motion_value{};
83};
84
85// Triggered if data changed on the controller
86struct UpdateCallback {
87 std::function<void()> on_change;
88};
89
90// Triggered if data changed on the controller and the engine is on configuring mode
91struct MappingCallback {
92 std::function<void(MappingData)> on_data;
93};
94
95// Input Identifier of data source
96struct InputIdentifier {
97 PadIdentifier identifier;
98 EngineInputType type;
99 int index;
100 UpdateCallback callback;
101};
102
103class InputEngine {
104public:
105 explicit InputEngine(std::string input_engine_) : input_engine{std::move(input_engine_)} {}
106
107 virtual ~InputEngine() = default;
108
109 // Enable configuring mode for mapping
110 void BeginConfiguration();
111
112 // Disable configuring mode for mapping
113 void EndConfiguration();
114
115 // Sets a led pattern for a controller
116 virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier,
117 [[maybe_unused]] const Common::Input::LedStatus& led_status) {}
118
119 // Sets rumble to a controller
120 virtual Common::Input::VibrationError SetRumble(
121 [[maybe_unused]] const PadIdentifier& identifier,
122 [[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
123 return Common::Input::VibrationError::NotSupported;
124 }
125
126 // Sets polling mode to a controller
127 virtual Common::Input::PollingError SetPollingMode(
128 [[maybe_unused]] const PadIdentifier& identifier,
129 [[maybe_unused]] const Common::Input::PollingMode vibration) {
130 return Common::Input::PollingError::NotSupported;
131 }
132
133 // Returns the engine name
134 [[nodiscard]] const std::string& GetEngineName() const;
135
136 /// Used for automapping features
137 virtual std::vector<Common::ParamPackage> GetInputDevices() const {
138 return {};
139 }
140
141 /// Retrieves the button mappings for the given device
142 virtual ButtonMapping GetButtonMappingForDevice(
143 [[maybe_unused]] const Common::ParamPackage& params) {
144 return {};
145 }
146
147 /// Retrieves the analog mappings for the given device
148 virtual AnalogMapping GetAnalogMappingForDevice(
149 [[maybe_unused]] const Common::ParamPackage& params) {
150 return {};
151 }
152
153 /// Retrieves the motion mappings for the given device
154 virtual MotionMapping GetMotionMappingForDevice(
155 [[maybe_unused]] const Common::ParamPackage& params) {
156 return {};
157 }
158
159 /// Retrieves the name of the given input.
160 virtual Common::Input::ButtonNames GetUIName(
161 [[maybe_unused]] const Common::ParamPackage& params) const {
162 return Common::Input::ButtonNames::Engine;
163 }
164
165 /// Retrieves the index number of the given hat button direction
166 virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const {
167 return 0;
168 }
169
170 void PreSetController(const PadIdentifier& identifier);
171 void PreSetButton(const PadIdentifier& identifier, int button);
172 void PreSetHatButton(const PadIdentifier& identifier, int button);
173 void PreSetAxis(const PadIdentifier& identifier, int axis);
174 void PreSetMotion(const PadIdentifier& identifier, int motion);
175 void ResetButtonState();
176 void ResetAnalogState();
177
178 bool GetButton(const PadIdentifier& identifier, int button) const;
179 bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const;
180 f32 GetAxis(const PadIdentifier& identifier, int axis) const;
181 BatteryLevel GetBattery(const PadIdentifier& identifier) const;
182 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
183
184 int SetCallback(InputIdentifier input_identifier);
185 void SetMappingCallback(MappingCallback callback);
186 void DeleteCallback(int key);
187
188protected:
189 void SetButton(const PadIdentifier& identifier, int button, bool value);
190 void SetHatButton(const PadIdentifier& identifier, int button, u8 value);
191 void SetAxis(const PadIdentifier& identifier, int axis, f32 value);
192 void SetBattery(const PadIdentifier& identifier, BatteryLevel value);
193 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
194
195 virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
196 return "Unknown";
197 }
198
199private:
200 struct ControllerData {
201 std::unordered_map<int, bool> buttons;
202 std::unordered_map<int, u8> hat_buttons;
203 std::unordered_map<int, float> axes;
204 std::unordered_map<int, BasicMotion> motions;
205 BatteryLevel battery{};
206 };
207
208 void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
209 void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value);
210 void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value);
211 void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value);
212 void TriggerOnMotionChange(const PadIdentifier& identifier, int motion,
213 const BasicMotion& value);
214
215 bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
216 const PadIdentifier& identifier, EngineInputType type,
217 int index) const;
218
219 mutable std::mutex mutex;
220 mutable std::mutex mutex_callback;
221 bool configuring{false};
222 const std::string input_engine;
223 int last_callback_key = 0;
224 std::unordered_map<PadIdentifier, ControllerData> controller_list;
225 std::unordered_map<int, InputIdentifier> callback_list;
226 MappingCallback mapping_callback;
227};
228
229} // namespace InputCommon
diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp
new file mode 100644
index 000000000..6e0024b2d
--- /dev/null
+++ b/src/input_common/input_mapping.cpp
@@ -0,0 +1,207 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/common_types.h"
6#include "common/settings.h"
7#include "input_common/input_engine.h"
8#include "input_common/input_mapping.h"
9
10namespace InputCommon {
11
12MappingFactory::MappingFactory() {}
13
14void MappingFactory::BeginMapping(Polling::InputType type) {
15 is_enabled = true;
16 input_type = type;
17 input_queue.Clear();
18 first_axis = -1;
19 second_axis = -1;
20}
21
22[[nodiscard]] const Common::ParamPackage MappingFactory::GetNextInput() {
23 Common::ParamPackage input;
24 input_queue.Pop(input);
25 return input;
26}
27
28void MappingFactory::RegisterInput(const MappingData& data) {
29 if (!is_enabled) {
30 return;
31 }
32 if (!IsDriverValid(data)) {
33 return;
34 }
35
36 switch (input_type) {
37 case Polling::InputType::Button:
38 RegisterButton(data);
39 return;
40 case Polling::InputType::Stick:
41 RegisterStick(data);
42 return;
43 case Polling::InputType::Motion:
44 RegisterMotion(data);
45 return;
46 default:
47 return;
48 }
49}
50
51void MappingFactory::StopMapping() {
52 is_enabled = false;
53 input_type = Polling::InputType::None;
54 input_queue.Clear();
55}
56
57void MappingFactory::RegisterButton(const MappingData& data) {
58 Common::ParamPackage new_input;
59 new_input.Set("engine", data.engine);
60 if (data.pad.guid != Common::UUID{}) {
61 new_input.Set("guid", data.pad.guid.Format());
62 }
63 new_input.Set("port", static_cast<int>(data.pad.port));
64 new_input.Set("pad", static_cast<int>(data.pad.pad));
65
66 switch (data.type) {
67 case EngineInputType::Button:
68 // Workaround for old compatibility
69 if (data.engine == "keyboard") {
70 new_input.Set("code", data.index);
71 break;
72 }
73 new_input.Set("button", data.index);
74 break;
75 case EngineInputType::HatButton:
76 new_input.Set("hat", data.index);
77 new_input.Set("direction", data.hat_name);
78 break;
79 case EngineInputType::Analog:
80 // Ignore mouse axis when mapping buttons
81 if (data.engine == "mouse") {
82 return;
83 }
84 new_input.Set("axis", data.index);
85 new_input.Set("threshold", 0.5f);
86 break;
87 default:
88 return;
89 }
90 input_queue.Push(new_input);
91}
92
93void MappingFactory::RegisterStick(const MappingData& data) {
94 Common::ParamPackage new_input;
95 new_input.Set("engine", data.engine);
96 if (data.pad.guid != Common::UUID{}) {
97 new_input.Set("guid", data.pad.guid.Format());
98 }
99 new_input.Set("port", static_cast<int>(data.pad.port));
100 new_input.Set("pad", static_cast<int>(data.pad.pad));
101
102 // If engine is mouse map the mouse position as a joystick
103 if (data.engine == "mouse") {
104 new_input.Set("axis_x", 0);
105 new_input.Set("axis_y", 1);
106 new_input.Set("threshold", 0.5f);
107 new_input.Set("range", 1.0f);
108 new_input.Set("deadzone", 0.0f);
109 input_queue.Push(new_input);
110 return;
111 }
112
113 switch (data.type) {
114 case EngineInputType::Button:
115 case EngineInputType::HatButton:
116 RegisterButton(data);
117 return;
118 case EngineInputType::Analog:
119 if (first_axis == data.index) {
120 return;
121 }
122 if (first_axis == -1) {
123 first_axis = data.index;
124 return;
125 }
126 new_input.Set("axis_x", first_axis);
127 new_input.Set("axis_y", data.index);
128 new_input.Set("threshold", 0.5f);
129 new_input.Set("range", 0.95f);
130 new_input.Set("deadzone", 0.15f);
131 break;
132 default:
133 return;
134 }
135 input_queue.Push(new_input);
136}
137
138void MappingFactory::RegisterMotion(const MappingData& data) {
139 Common::ParamPackage new_input;
140 new_input.Set("engine", data.engine);
141 if (data.pad.guid != Common::UUID{}) {
142 new_input.Set("guid", data.pad.guid.Format());
143 }
144 new_input.Set("port", static_cast<int>(data.pad.port));
145 new_input.Set("pad", static_cast<int>(data.pad.pad));
146 switch (data.type) {
147 case EngineInputType::Button:
148 case EngineInputType::HatButton:
149 RegisterButton(data);
150 return;
151 case EngineInputType::Analog:
152 if (first_axis == data.index) {
153 return;
154 }
155 if (second_axis == data.index) {
156 return;
157 }
158 if (first_axis == -1) {
159 first_axis = data.index;
160 return;
161 }
162 if (second_axis == -1) {
163 second_axis = data.index;
164 return;
165 }
166 new_input.Set("axis_x", first_axis);
167 new_input.Set("axis_y", second_axis);
168 new_input.Set("axis_z", data.index);
169 new_input.Set("range", 1.0f);
170 new_input.Set("deadzone", 0.20f);
171 break;
172 case EngineInputType::Motion:
173 new_input.Set("motion", data.index);
174 break;
175 default:
176 return;
177 }
178 input_queue.Push(new_input);
179}
180
181bool MappingFactory::IsDriverValid(const MappingData& data) const {
182 // Only port 0 can be mapped on the keyboard
183 if (data.engine == "keyboard" && data.pad.port != 0) {
184 return false;
185 }
186 // To prevent mapping with two devices we disable any UDP except motion
187 if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" &&
188 data.type != EngineInputType::Motion) {
189 return false;
190 }
191 // The following drivers don't need to be mapped
192 if (data.engine == "tas") {
193 return false;
194 }
195 if (data.engine == "touch") {
196 return false;
197 }
198 if (data.engine == "touch_from_button") {
199 return false;
200 }
201 if (data.engine == "analog_from_button") {
202 return false;
203 }
204 return true;
205}
206
207} // namespace InputCommon
diff --git a/src/input_common/input_mapping.h b/src/input_common/input_mapping.h
new file mode 100644
index 000000000..93564b5f8
--- /dev/null
+++ b/src/input_common/input_mapping.h
@@ -0,0 +1,83 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6#include "common/threadsafe_queue.h"
7
8namespace InputCommon {
9class InputEngine;
10struct MappingData;
11
12class MappingFactory {
13public:
14 MappingFactory();
15
16 /**
17 * Resets all variables to begin the mapping process
18 * @param type type of input desired to be returned
19 */
20 void BeginMapping(Polling::InputType type);
21
22 /// Returns an input event with mapping information from the input_queue
23 [[nodiscard]] const Common::ParamPackage GetNextInput();
24
25 /**
26 * Registers mapping input data from the driver
27 * @param data A struct containing all the information needed to create a proper
28 * ParamPackage
29 */
30 void RegisterInput(const MappingData& data);
31
32 /// Stop polling from all backends
33 void StopMapping();
34
35private:
36 /**
37 * If provided data satisfies the requirements it will push an element to the input_queue
38 * Supported input:
39 * - Button: Creates a basic button ParamPackage
40 * - HatButton: Creates a basic hat button ParamPackage
41 * - Analog: Creates a basic analog ParamPackage
42 * @param data A struct containing all the information needed to create a proper
43 * ParamPackage
44 */
45 void RegisterButton(const MappingData& data);
46
47 /**
48 * If provided data satisfies the requirements it will push an element to the input_queue
49 * Supported input:
50 * - Button, HatButton: Pass the data to RegisterButton
51 * - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage
52 * @param data A struct containing all the information needed to create a proper
53 * ParamPackage
54 */
55 void RegisterStick(const MappingData& data);
56
57 /**
58 * If provided data satisfies the requirements it will push an element to the input_queue
59 * Supported input:
60 * - Button, HatButton: Pass the data to RegisterButton
61 * - Analog: Stores the first two axis and on the third axis creates a basic Motion
62 * ParamPackage
63 * - Motion: Creates a basic Motion ParamPackage
64 * @param data A struct containing all the information needed to create a proper
65 * ParamPackage
66 */
67 void RegisterMotion(const MappingData& data);
68
69 /**
70 * Returns true if driver can be mapped
71 * @param data A struct containing all the information needed to create a proper
72 * ParamPackage
73 */
74 bool IsDriverValid(const MappingData& data) const;
75
76 Common::SPSCQueue<Common::ParamPackage> input_queue;
77 Polling::InputType input_type{Polling::InputType::None};
78 bool is_enabled{};
79 int first_axis = -1;
80 int second_axis = -1;
81};
82
83} // namespace InputCommon
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
new file mode 100644
index 000000000..7b370335f
--- /dev/null
+++ b/src/input_common/input_poller.cpp
@@ -0,0 +1,970 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/common_types.h"
6#include "common/input.h"
7
8#include "input_common/input_engine.h"
9#include "input_common/input_poller.h"
10
11namespace InputCommon {
12
13class DummyInput final : public Common::Input::InputDevice {
14public:
15 explicit DummyInput() = default;
16};
17
18class InputFromButton final : public Common::Input::InputDevice {
19public:
20 explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
21 InputEngine* input_engine_)
22 : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
23 input_engine(input_engine_) {
24 UpdateCallback engine_callback{[this]() { OnChange(); }};
25 const InputIdentifier input_identifier{
26 .identifier = identifier,
27 .type = EngineInputType::Button,
28 .index = button,
29 .callback = engine_callback,
30 };
31 last_button_value = false;
32 callback_key = input_engine->SetCallback(input_identifier);
33 }
34
35 ~InputFromButton() override {
36 input_engine->DeleteCallback(callback_key);
37 }
38
39 Common::Input::ButtonStatus GetStatus() const {
40 return {
41 .value = input_engine->GetButton(identifier, button),
42 .inverted = inverted,
43 .toggle = toggle,
44 };
45 }
46
47 void ForceUpdate() override {
48 const Common::Input::CallbackStatus status{
49 .type = Common::Input::InputType::Button,
50 .button_status = GetStatus(),
51 };
52
53 last_button_value = status.button_status.value;
54 TriggerOnChange(status);
55 }
56
57 void OnChange() {
58 const Common::Input::CallbackStatus status{
59 .type = Common::Input::InputType::Button,
60 .button_status = GetStatus(),
61 };
62
63 if (status.button_status.value != last_button_value) {
64 last_button_value = status.button_status.value;
65 TriggerOnChange(status);
66 }
67 }
68
69private:
70 const PadIdentifier identifier;
71 const int button;
72 const bool toggle;
73 const bool inverted;
74 int callback_key;
75 bool last_button_value;
76 InputEngine* input_engine;
77};
78
79class InputFromHatButton final : public Common::Input::InputDevice {
80public:
81 explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_,
82 bool inverted_, InputEngine* input_engine_)
83 : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_),
84 inverted(inverted_), input_engine(input_engine_) {
85 UpdateCallback engine_callback{[this]() { OnChange(); }};
86 const InputIdentifier input_identifier{
87 .identifier = identifier,
88 .type = EngineInputType::HatButton,
89 .index = button,
90 .callback = engine_callback,
91 };
92 last_button_value = false;
93 callback_key = input_engine->SetCallback(input_identifier);
94 }
95
96 ~InputFromHatButton() override {
97 input_engine->DeleteCallback(callback_key);
98 }
99
100 Common::Input::ButtonStatus GetStatus() const {
101 return {
102 .value = input_engine->GetHatButton(identifier, button, direction),
103 .inverted = inverted,
104 .toggle = toggle,
105 };
106 }
107
108 void ForceUpdate() override {
109 const Common::Input::CallbackStatus status{
110 .type = Common::Input::InputType::Button,
111 .button_status = GetStatus(),
112 };
113
114 last_button_value = status.button_status.value;
115 TriggerOnChange(status);
116 }
117
118 void OnChange() {
119 const Common::Input::CallbackStatus status{
120 .type = Common::Input::InputType::Button,
121 .button_status = GetStatus(),
122 };
123
124 if (status.button_status.value != last_button_value) {
125 last_button_value = status.button_status.value;
126 TriggerOnChange(status);
127 }
128 }
129
130private:
131 const PadIdentifier identifier;
132 const int button;
133 const u8 direction;
134 const bool toggle;
135 const bool inverted;
136 int callback_key;
137 bool last_button_value;
138 InputEngine* input_engine;
139};
140
141class InputFromStick final : public Common::Input::InputDevice {
142public:
143 explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_,
144 Common::Input::AnalogProperties properties_x_,
145 Common::Input::AnalogProperties properties_y_,
146 InputEngine* input_engine_)
147 : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
148 properties_y(properties_y_),
149 input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} {
150 UpdateCallback engine_callback{[this]() { OnChange(); }};
151 const InputIdentifier x_input_identifier{
152 .identifier = identifier,
153 .type = EngineInputType::Analog,
154 .index = axis_x,
155 .callback = engine_callback,
156 };
157 const InputIdentifier y_input_identifier{
158 .identifier = identifier,
159 .type = EngineInputType::Analog,
160 .index = axis_y,
161 .callback = engine_callback,
162 };
163 last_axis_x_value = 0.0f;
164 last_axis_y_value = 0.0f;
165 callback_key_x = input_engine->SetCallback(x_input_identifier);
166 callback_key_y = input_engine->SetCallback(y_input_identifier);
167 }
168
169 ~InputFromStick() override {
170 input_engine->DeleteCallback(callback_key_x);
171 input_engine->DeleteCallback(callback_key_y);
172 }
173
174 Common::Input::StickStatus GetStatus() const {
175 Common::Input::StickStatus status;
176 status.x = {
177 .raw_value = input_engine->GetAxis(identifier, axis_x),
178 .properties = properties_x,
179 };
180 status.y = {
181 .raw_value = input_engine->GetAxis(identifier, axis_y),
182 .properties = properties_y,
183 };
184 // This is a workaround too keep compatibility with old yuzu versions. Vertical axis is
185 // inverted on SDL compared to Nintendo
186 if (invert_axis_y) {
187 status.y.raw_value = -status.y.raw_value;
188 }
189 return status;
190 }
191
192 void ForceUpdate() override {
193 const Common::Input::CallbackStatus status{
194 .type = Common::Input::InputType::Stick,
195 .stick_status = GetStatus(),
196 };
197
198 last_axis_x_value = status.stick_status.x.raw_value;
199 last_axis_y_value = status.stick_status.y.raw_value;
200 TriggerOnChange(status);
201 }
202
203 void OnChange() {
204 const Common::Input::CallbackStatus status{
205 .type = Common::Input::InputType::Stick,
206 .stick_status = GetStatus(),
207 };
208
209 if (status.stick_status.x.raw_value != last_axis_x_value ||
210 status.stick_status.y.raw_value != last_axis_y_value) {
211 last_axis_x_value = status.stick_status.x.raw_value;
212 last_axis_y_value = status.stick_status.y.raw_value;
213 TriggerOnChange(status);
214 }
215 }
216
217private:
218 const PadIdentifier identifier;
219 const int axis_x;
220 const int axis_y;
221 const Common::Input::AnalogProperties properties_x;
222 const Common::Input::AnalogProperties properties_y;
223 int callback_key_x;
224 int callback_key_y;
225 float last_axis_x_value;
226 float last_axis_y_value;
227 InputEngine* input_engine;
228 const bool invert_axis_y;
229};
230
231class InputFromTouch final : public Common::Input::InputDevice {
232public:
233 explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_,
234 bool inverted_, int axis_x_, int axis_y_,
235 Common::Input::AnalogProperties properties_x_,
236 Common::Input::AnalogProperties properties_y_,
237 InputEngine* input_engine_)
238 : identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_),
239 inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
240 properties_y(properties_y_), input_engine(input_engine_) {
241 UpdateCallback engine_callback{[this]() { OnChange(); }};
242 const InputIdentifier button_input_identifier{
243 .identifier = identifier,
244 .type = EngineInputType::Button,
245 .index = button,
246 .callback = engine_callback,
247 };
248 const InputIdentifier x_input_identifier{
249 .identifier = identifier,
250 .type = EngineInputType::Analog,
251 .index = axis_x,
252 .callback = engine_callback,
253 };
254 const InputIdentifier y_input_identifier{
255 .identifier = identifier,
256 .type = EngineInputType::Analog,
257 .index = axis_y,
258 .callback = engine_callback,
259 };
260 last_axis_x_value = 0.0f;
261 last_axis_y_value = 0.0f;
262 last_button_value = false;
263 callback_key_button = input_engine->SetCallback(button_input_identifier);
264 callback_key_x = input_engine->SetCallback(x_input_identifier);
265 callback_key_y = input_engine->SetCallback(y_input_identifier);
266 }
267
268 ~InputFromTouch() override {
269 input_engine->DeleteCallback(callback_key_button);
270 input_engine->DeleteCallback(callback_key_x);
271 input_engine->DeleteCallback(callback_key_y);
272 }
273
274 Common::Input::TouchStatus GetStatus() const {
275 Common::Input::TouchStatus status;
276 status.id = touch_id;
277 status.pressed = {
278 .value = input_engine->GetButton(identifier, button),
279 .inverted = inverted,
280 .toggle = toggle,
281 };
282 status.x = {
283 .raw_value = input_engine->GetAxis(identifier, axis_x),
284 .properties = properties_x,
285 };
286 status.y = {
287 .raw_value = input_engine->GetAxis(identifier, axis_y),
288 .properties = properties_y,
289 };
290 return status;
291 }
292
293 void OnChange() {
294 const Common::Input::CallbackStatus status{
295 .type = Common::Input::InputType::Touch,
296 .touch_status = GetStatus(),
297 };
298
299 if (status.touch_status.x.raw_value != last_axis_x_value ||
300 status.touch_status.y.raw_value != last_axis_y_value ||
301 status.touch_status.pressed.value != last_button_value) {
302 last_axis_x_value = status.touch_status.x.raw_value;
303 last_axis_y_value = status.touch_status.y.raw_value;
304 last_button_value = status.touch_status.pressed.value;
305 TriggerOnChange(status);
306 }
307 }
308
309private:
310 const PadIdentifier identifier;
311 const int touch_id;
312 const int button;
313 const bool toggle;
314 const bool inverted;
315 const int axis_x;
316 const int axis_y;
317 const Common::Input::AnalogProperties properties_x;
318 const Common::Input::AnalogProperties properties_y;
319 int callback_key_button;
320 int callback_key_x;
321 int callback_key_y;
322 bool last_button_value;
323 float last_axis_x_value;
324 float last_axis_y_value;
325 InputEngine* input_engine;
326};
327
328class InputFromTrigger final : public Common::Input::InputDevice {
329public:
330 explicit InputFromTrigger(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
331 int axis_, Common::Input::AnalogProperties properties_,
332 InputEngine* input_engine_)
333 : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
334 axis(axis_), properties(properties_), input_engine(input_engine_) {
335 UpdateCallback engine_callback{[this]() { OnChange(); }};
336 const InputIdentifier button_input_identifier{
337 .identifier = identifier,
338 .type = EngineInputType::Button,
339 .index = button,
340 .callback = engine_callback,
341 };
342 const InputIdentifier axis_input_identifier{
343 .identifier = identifier,
344 .type = EngineInputType::Analog,
345 .index = axis,
346 .callback = engine_callback,
347 };
348 last_axis_value = 0.0f;
349 last_button_value = false;
350 callback_key_button = input_engine->SetCallback(button_input_identifier);
351 axis_callback_key = input_engine->SetCallback(axis_input_identifier);
352 }
353
354 ~InputFromTrigger() override {
355 input_engine->DeleteCallback(callback_key_button);
356 input_engine->DeleteCallback(axis_callback_key);
357 }
358
359 Common::Input::TriggerStatus GetStatus() const {
360 const Common::Input::AnalogStatus analog_status{
361 .raw_value = input_engine->GetAxis(identifier, axis),
362 .properties = properties,
363 };
364 const Common::Input::ButtonStatus button_status{
365 .value = input_engine->GetButton(identifier, button),
366 .inverted = inverted,
367 .toggle = toggle,
368 };
369 return {
370 .analog = analog_status,
371 .pressed = button_status,
372 };
373 }
374
375 void OnChange() {
376 const Common::Input::CallbackStatus status{
377 .type = Common::Input::InputType::Trigger,
378 .trigger_status = GetStatus(),
379 };
380
381 if (status.trigger_status.analog.raw_value != last_axis_value ||
382 status.trigger_status.pressed.value != last_button_value) {
383 last_axis_value = status.trigger_status.analog.raw_value;
384 last_button_value = status.trigger_status.pressed.value;
385 TriggerOnChange(status);
386 }
387 }
388
389private:
390 const PadIdentifier identifier;
391 const int button;
392 const bool toggle;
393 const bool inverted;
394 const int axis;
395 const Common::Input::AnalogProperties properties;
396 int callback_key_button;
397 int axis_callback_key;
398 bool last_button_value;
399 float last_axis_value;
400 InputEngine* input_engine;
401};
402
403class InputFromAnalog final : public Common::Input::InputDevice {
404public:
405 explicit InputFromAnalog(PadIdentifier identifier_, int axis_,
406 Common::Input::AnalogProperties properties_,
407 InputEngine* input_engine_)
408 : identifier(identifier_), axis(axis_), properties(properties_),
409 input_engine(input_engine_) {
410 UpdateCallback engine_callback{[this]() { OnChange(); }};
411 const InputIdentifier input_identifier{
412 .identifier = identifier,
413 .type = EngineInputType::Analog,
414 .index = axis,
415 .callback = engine_callback,
416 };
417 last_axis_value = 0.0f;
418 callback_key = input_engine->SetCallback(input_identifier);
419 }
420
421 ~InputFromAnalog() override {
422 input_engine->DeleteCallback(callback_key);
423 }
424
425 Common::Input::AnalogStatus GetStatus() const {
426 return {
427 .raw_value = input_engine->GetAxis(identifier, axis),
428 .properties = properties,
429 };
430 }
431
432 void OnChange() {
433 const Common::Input::CallbackStatus status{
434 .type = Common::Input::InputType::Analog,
435 .analog_status = GetStatus(),
436 };
437
438 if (status.analog_status.raw_value != last_axis_value) {
439 last_axis_value = status.analog_status.raw_value;
440 TriggerOnChange(status);
441 }
442 }
443
444private:
445 const PadIdentifier identifier;
446 const int axis;
447 const Common::Input::AnalogProperties properties;
448 int callback_key;
449 float last_axis_value;
450 InputEngine* input_engine;
451};
452
453class InputFromBattery final : public Common::Input::InputDevice {
454public:
455 explicit InputFromBattery(PadIdentifier identifier_, InputEngine* input_engine_)
456 : identifier(identifier_), input_engine(input_engine_) {
457 UpdateCallback engine_callback{[this]() { OnChange(); }};
458 const InputIdentifier input_identifier{
459 .identifier = identifier,
460 .type = EngineInputType::Battery,
461 .index = 0,
462 .callback = engine_callback,
463 };
464 last_battery_value = Common::Input::BatteryStatus::Charging;
465 callback_key = input_engine->SetCallback(input_identifier);
466 }
467
468 ~InputFromBattery() override {
469 input_engine->DeleteCallback(callback_key);
470 }
471
472 Common::Input::BatteryStatus GetStatus() const {
473 return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier));
474 }
475
476 void ForceUpdate() override {
477 const Common::Input::CallbackStatus status{
478 .type = Common::Input::InputType::Battery,
479 .battery_status = GetStatus(),
480 };
481
482 last_battery_value = status.battery_status;
483 TriggerOnChange(status);
484 }
485
486 void OnChange() {
487 const Common::Input::CallbackStatus status{
488 .type = Common::Input::InputType::Battery,
489 .battery_status = GetStatus(),
490 };
491
492 if (status.battery_status != last_battery_value) {
493 last_battery_value = status.battery_status;
494 TriggerOnChange(status);
495 }
496 }
497
498private:
499 const PadIdentifier identifier;
500 int callback_key;
501 Common::Input::BatteryStatus last_battery_value;
502 InputEngine* input_engine;
503};
504
505class InputFromMotion final : public Common::Input::InputDevice {
506public:
507 explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_,
508 InputEngine* input_engine_)
509 : identifier(identifier_), motion_sensor(motion_sensor_), input_engine(input_engine_) {
510 UpdateCallback engine_callback{[this]() { OnChange(); }};
511 const InputIdentifier input_identifier{
512 .identifier = identifier,
513 .type = EngineInputType::Motion,
514 .index = motion_sensor,
515 .callback = engine_callback,
516 };
517 callback_key = input_engine->SetCallback(input_identifier);
518 }
519
520 ~InputFromMotion() override {
521 input_engine->DeleteCallback(callback_key);
522 }
523
524 Common::Input::MotionStatus GetStatus() const {
525 const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor);
526 Common::Input::MotionStatus status{};
527 const Common::Input::AnalogProperties properties = {
528 .deadzone = 0.001f,
529 .range = 1.0f,
530 .offset = 0.0f,
531 };
532 status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties};
533 status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties};
534 status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties};
535 status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties};
536 status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties};
537 status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties};
538 status.delta_timestamp = basic_motion.delta_timestamp;
539 return status;
540 }
541
542 void OnChange() {
543 const Common::Input::CallbackStatus status{
544 .type = Common::Input::InputType::Motion,
545 .motion_status = GetStatus(),
546 };
547
548 TriggerOnChange(status);
549 }
550
551private:
552 const PadIdentifier identifier;
553 const int motion_sensor;
554 int callback_key;
555 InputEngine* input_engine;
556};
557
558class InputFromAxisMotion final : public Common::Input::InputDevice {
559public:
560 explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_,
561 Common::Input::AnalogProperties properties_x_,
562 Common::Input::AnalogProperties properties_y_,
563 Common::Input::AnalogProperties properties_z_,
564 InputEngine* input_engine_)
565 : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_),
566 properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_),
567 input_engine(input_engine_) {
568 UpdateCallback engine_callback{[this]() { OnChange(); }};
569 const InputIdentifier x_input_identifier{
570 .identifier = identifier,
571 .type = EngineInputType::Analog,
572 .index = axis_x,
573 .callback = engine_callback,
574 };
575 const InputIdentifier y_input_identifier{
576 .identifier = identifier,
577 .type = EngineInputType::Analog,
578 .index = axis_y,
579 .callback = engine_callback,
580 };
581 const InputIdentifier z_input_identifier{
582 .identifier = identifier,
583 .type = EngineInputType::Analog,
584 .index = axis_z,
585 .callback = engine_callback,
586 };
587 last_axis_x_value = 0.0f;
588 last_axis_y_value = 0.0f;
589 last_axis_z_value = 0.0f;
590 callback_key_x = input_engine->SetCallback(x_input_identifier);
591 callback_key_y = input_engine->SetCallback(y_input_identifier);
592 callback_key_z = input_engine->SetCallback(z_input_identifier);
593 }
594
595 ~InputFromAxisMotion() override {
596 input_engine->DeleteCallback(callback_key_x);
597 input_engine->DeleteCallback(callback_key_y);
598 input_engine->DeleteCallback(callback_key_z);
599 }
600
601 Common::Input::MotionStatus GetStatus() const {
602 Common::Input::MotionStatus status{};
603 status.gyro.x = {
604 .raw_value = input_engine->GetAxis(identifier, axis_x),
605 .properties = properties_x,
606 };
607 status.gyro.y = {
608 .raw_value = input_engine->GetAxis(identifier, axis_y),
609 .properties = properties_y,
610 };
611 status.gyro.z = {
612 .raw_value = input_engine->GetAxis(identifier, axis_z),
613 .properties = properties_z,
614 };
615 status.delta_timestamp = 5000;
616 status.force_update = true;
617 return status;
618 }
619
620 void ForceUpdate() override {
621 const Common::Input::CallbackStatus status{
622 .type = Common::Input::InputType::Motion,
623 .motion_status = GetStatus(),
624 };
625
626 last_axis_x_value = status.motion_status.gyro.x.raw_value;
627 last_axis_y_value = status.motion_status.gyro.y.raw_value;
628 last_axis_z_value = status.motion_status.gyro.z.raw_value;
629 TriggerOnChange(status);
630 }
631
632 void OnChange() {
633 const Common::Input::CallbackStatus status{
634 .type = Common::Input::InputType::Motion,
635 .motion_status = GetStatus(),
636 };
637
638 if (status.motion_status.gyro.x.raw_value != last_axis_x_value ||
639 status.motion_status.gyro.y.raw_value != last_axis_y_value ||
640 status.motion_status.gyro.z.raw_value != last_axis_z_value) {
641 last_axis_x_value = status.motion_status.gyro.x.raw_value;
642 last_axis_y_value = status.motion_status.gyro.y.raw_value;
643 last_axis_z_value = status.motion_status.gyro.z.raw_value;
644 TriggerOnChange(status);
645 }
646 }
647
648private:
649 const PadIdentifier identifier;
650 const int axis_x;
651 const int axis_y;
652 const int axis_z;
653 const Common::Input::AnalogProperties properties_x;
654 const Common::Input::AnalogProperties properties_y;
655 const Common::Input::AnalogProperties properties_z;
656 int callback_key_x;
657 int callback_key_y;
658 int callback_key_z;
659 float last_axis_x_value;
660 float last_axis_y_value;
661 float last_axis_z_value;
662 InputEngine* input_engine;
663};
664
665class OutputFromIdentifier final : public Common::Input::OutputDevice {
666public:
667 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
668 : identifier(identifier_), input_engine(input_engine_) {}
669
670 void SetLED(const Common::Input::LedStatus& led_status) override {
671 input_engine->SetLeds(identifier, led_status);
672 }
673
674 Common::Input::VibrationError SetVibration(
675 const Common::Input::VibrationStatus& vibration_status) override {
676 return input_engine->SetRumble(identifier, vibration_status);
677 }
678
679 Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
680 return input_engine->SetPollingMode(identifier, polling_mode);
681 }
682
683private:
684 const PadIdentifier identifier;
685 InputEngine* input_engine;
686};
687
688std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice(
689 const Common::ParamPackage& params) {
690 const PadIdentifier identifier = {
691 .guid = Common::UUID{params.Get("guid", "")},
692 .port = static_cast<std::size_t>(params.Get("port", 0)),
693 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
694 };
695
696 const auto button_id = params.Get("button", 0);
697 const auto keyboard_key = params.Get("code", 0);
698 const auto toggle = params.Get("toggle", false);
699 const auto inverted = params.Get("inverted", false);
700 input_engine->PreSetController(identifier);
701 input_engine->PreSetButton(identifier, button_id);
702 input_engine->PreSetButton(identifier, keyboard_key);
703 if (keyboard_key != 0) {
704 return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted,
705 input_engine.get());
706 }
707 return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted,
708 input_engine.get());
709}
710
711std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice(
712 const Common::ParamPackage& params) {
713 const PadIdentifier identifier = {
714 .guid = Common::UUID{params.Get("guid", "")},
715 .port = static_cast<std::size_t>(params.Get("port", 0)),
716 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
717 };
718
719 const auto button_id = params.Get("hat", 0);
720 const auto direction = input_engine->GetHatButtonId(params.Get("direction", ""));
721 const auto toggle = params.Get("toggle", false);
722 const auto inverted = params.Get("inverted", false);
723
724 input_engine->PreSetController(identifier);
725 input_engine->PreSetHatButton(identifier, button_id);
726 return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted,
727 input_engine.get());
728}
729
730std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice(
731 const Common::ParamPackage& params) {
732 const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
733 const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
734 const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
735 const PadIdentifier identifier = {
736 .guid = Common::UUID{params.Get("guid", "")},
737 .port = static_cast<std::size_t>(params.Get("port", 0)),
738 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
739 };
740
741 const auto axis_x = params.Get("axis_x", 0);
742 const Common::Input::AnalogProperties properties_x = {
743 .deadzone = deadzone,
744 .range = range,
745 .threshold = threshold,
746 .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
747 .inverted = params.Get("invert_x", "+") == "-",
748 };
749
750 const auto axis_y = params.Get("axis_y", 1);
751 const Common::Input::AnalogProperties properties_y = {
752 .deadzone = deadzone,
753 .range = range,
754 .threshold = threshold,
755 .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
756 .inverted = params.Get("invert_y", "+") != "+",
757 };
758 input_engine->PreSetController(identifier);
759 input_engine->PreSetAxis(identifier, axis_x);
760 input_engine->PreSetAxis(identifier, axis_y);
761 return std::make_unique<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y,
762 input_engine.get());
763}
764
765std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
766 const Common::ParamPackage& params) {
767 const PadIdentifier identifier = {
768 .guid = Common::UUID{params.Get("guid", "")},
769 .port = static_cast<std::size_t>(params.Get("port", 0)),
770 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
771 };
772
773 const auto axis = params.Get("axis", 0);
774 const Common::Input::AnalogProperties properties = {
775 .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
776 .range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f),
777 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
778 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
779 .inverted = params.Get("invert", "+") == "-",
780 };
781 input_engine->PreSetController(identifier);
782 input_engine->PreSetAxis(identifier, axis);
783 return std::make_unique<InputFromAnalog>(identifier, axis, properties, input_engine.get());
784}
785
786std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(
787 const Common::ParamPackage& params) {
788 const PadIdentifier identifier = {
789 .guid = Common::UUID{params.Get("guid", "")},
790 .port = static_cast<std::size_t>(params.Get("port", 0)),
791 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
792 };
793
794 const auto button = params.Get("button", 0);
795 const auto toggle = params.Get("toggle", false);
796 const auto inverted = params.Get("inverted", false);
797
798 const auto axis = params.Get("axis", 0);
799 const Common::Input::AnalogProperties properties = {
800 .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f),
801 .range = std::clamp(params.Get("range", 1.0f), 0.25f, 2.50f),
802 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
803 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
804 .inverted = params.Get("invert", false) != 0,
805 };
806 input_engine->PreSetController(identifier);
807 input_engine->PreSetAxis(identifier, axis);
808 input_engine->PreSetButton(identifier, button);
809 return std::make_unique<InputFromTrigger>(identifier, button, toggle, inverted, axis,
810 properties, input_engine.get());
811}
812
813std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
814 const Common::ParamPackage& params) {
815 const auto touch_id = params.Get("touch_id", 0);
816 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
817 const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
818 const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
819 const PadIdentifier identifier = {
820 .guid = Common::UUID{params.Get("guid", "")},
821 .port = static_cast<std::size_t>(params.Get("port", 0)),
822 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
823 };
824
825 const auto button = params.Get("button", 0);
826 const auto toggle = params.Get("toggle", false);
827 const auto inverted = params.Get("inverted", false);
828
829 const auto axis_x = params.Get("axis_x", 0);
830 const Common::Input::AnalogProperties properties_x = {
831 .deadzone = deadzone,
832 .range = range,
833 .threshold = threshold,
834 .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
835 .inverted = params.Get("invert_x", "+") == "-",
836 };
837
838 const auto axis_y = params.Get("axis_y", 1);
839 const Common::Input::AnalogProperties properties_y = {
840 .deadzone = deadzone,
841 .range = range,
842 .threshold = threshold,
843 .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
844 .inverted = params.Get("invert_y", false) != 0,
845 };
846 input_engine->PreSetController(identifier);
847 input_engine->PreSetAxis(identifier, axis_x);
848 input_engine->PreSetAxis(identifier, axis_y);
849 input_engine->PreSetButton(identifier, button);
850 return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x,
851 axis_y, properties_x, properties_y, input_engine.get());
852}
853
854std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
855 const Common::ParamPackage& params) {
856 const PadIdentifier identifier = {
857 .guid = Common::UUID{params.Get("guid", "")},
858 .port = static_cast<std::size_t>(params.Get("port", 0)),
859 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
860 };
861
862 input_engine->PreSetController(identifier);
863 return std::make_unique<InputFromBattery>(identifier, input_engine.get());
864}
865
866std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice(
867 Common::ParamPackage params) {
868 const PadIdentifier identifier = {
869 .guid = Common::UUID{params.Get("guid", "")},
870 .port = static_cast<std::size_t>(params.Get("port", 0)),
871 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
872 };
873
874 if (params.Has("motion")) {
875 const auto motion_sensor = params.Get("motion", 0);
876 input_engine->PreSetController(identifier);
877 input_engine->PreSetMotion(identifier, motion_sensor);
878 return std::make_unique<InputFromMotion>(identifier, motion_sensor, input_engine.get());
879 }
880
881 const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f);
882 const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
883 const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
884
885 const auto axis_x = params.Get("axis_x", 0);
886 const Common::Input::AnalogProperties properties_x = {
887 .deadzone = deadzone,
888 .range = range,
889 .threshold = threshold,
890 .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f),
891 .inverted = params.Get("invert_x", "+") == "-",
892 };
893
894 const auto axis_y = params.Get("axis_y", 1);
895 const Common::Input::AnalogProperties properties_y = {
896 .deadzone = deadzone,
897 .range = range,
898 .threshold = threshold,
899 .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f),
900 .inverted = params.Get("invert_y", "+") != "+",
901 };
902
903 const auto axis_z = params.Get("axis_z", 1);
904 const Common::Input::AnalogProperties properties_z = {
905 .deadzone = deadzone,
906 .range = range,
907 .threshold = threshold,
908 .offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f),
909 .inverted = params.Get("invert_z", "+") != "+",
910 };
911 input_engine->PreSetController(identifier);
912 input_engine->PreSetAxis(identifier, axis_x);
913 input_engine->PreSetAxis(identifier, axis_y);
914 input_engine->PreSetAxis(identifier, axis_z);
915 return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x,
916 properties_y, properties_z, input_engine.get());
917}
918
919InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
920 : input_engine(std::move(input_engine_)) {}
921
922std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
923 const Common::ParamPackage& params) {
924 if (params.Has("battery")) {
925 return CreateBatteryDevice(params);
926 }
927 if (params.Has("button") && params.Has("axis")) {
928 return CreateTriggerDevice(params);
929 }
930 if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) {
931 return CreateTouchDevice(params);
932 }
933 if (params.Has("button") || params.Has("code")) {
934 return CreateButtonDevice(params);
935 }
936 if (params.Has("hat")) {
937 return CreateHatButtonDevice(params);
938 }
939 if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
940 return CreateMotionDevice(params);
941 }
942 if (params.Has("motion")) {
943 return CreateMotionDevice(params);
944 }
945 if (params.Has("axis_x") && params.Has("axis_y")) {
946 return CreateStickDevice(params);
947 }
948 if (params.Has("axis")) {
949 return CreateAnalogDevice(params);
950 }
951 LOG_ERROR(Input, "Invalid parameters given");
952 return std::make_unique<DummyInput>();
953}
954
955OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_)
956 : input_engine(std::move(input_engine_)) {}
957
958std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create(
959 const Common::ParamPackage& params) {
960 const PadIdentifier identifier = {
961 .guid = Common::UUID{params.Get("guid", "")},
962 .port = static_cast<std::size_t>(params.Get("port", 0)),
963 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
964 };
965
966 input_engine->PreSetController(identifier);
967 return std::make_unique<OutputFromIdentifier>(identifier, input_engine.get());
968}
969
970} // namespace InputCommon
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
new file mode 100644
index 000000000..8a0977d58
--- /dev/null
+++ b/src/input_common/input_poller.h
@@ -0,0 +1,217 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7namespace Input {
8class InputDevice;
9
10template <typename InputDevice>
11class Factory;
12}; // namespace Input
13
14namespace InputCommon {
15class InputEngine;
16
17class OutputFactory final : public Common::Input::Factory<Common::Input::OutputDevice> {
18public:
19 explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_);
20
21 /**
22 * Creates an output device from the parameters given.
23 * @param params contains parameters for creating the device:
24 * - "guid" text string for identifying controllers
25 * - "port": port of the connected device
26 * - "pad": slot of the connected controller
27 * @returns a unique output device with the parameters specified
28 */
29 std::unique_ptr<Common::Input::OutputDevice> Create(
30 const Common::ParamPackage& params) override;
31
32private:
33 std::shared_ptr<InputEngine> input_engine;
34};
35
36/**
37 * An Input factory. It receives input events and forward them to all input devices it created.
38 */
39class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> {
40public:
41 explicit InputFactory(std::shared_ptr<InputEngine> input_engine_);
42
43 /**
44 * Creates an input device from the parameters given. Identifies the type of input to be
45 * returned if it contains the following parameters:
46 * - button: Contains "button" or "code"
47 * - hat_button: Contains "hat"
48 * - analog: Contains "axis"
49 * - trigger: Contains "button" and "axis"
50 * - stick: Contains "axis_x" and "axis_y"
51 * - motion: Contains "axis_x", "axis_y" and "axis_z"
52 * - motion: Contains "motion"
53 * - touch: Contains "button", "axis_x" and "axis_y"
54 * - battery: Contains "battery"
55 * - output: Contains "output"
56 * @param params contains parameters for creating the device:
57 * - "code": the code of the keyboard key to bind with the input
58 * - "button": same as "code" but for controller buttons
59 * - "hat": similar as "button" but it's a group of hat buttons from SDL
60 * - "axis": the axis number of the axis to bind with the input
61 * - "motion": the motion number of the motion to bind with the input
62 * - "axis_x": same as axis but specifying horizontal direction
63 * - "axis_y": same as axis but specifying vertical direction
64 * - "axis_z": same as axis but specifying forward direction
65 * - "battery": Only used as a placeholder to set the input type
66 * @returns a unique input device with the parameters specified
67 */
68 std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override;
69
70private:
71 /**
72 * Creates a button device from the parameters given.
73 * @param params contains parameters for creating the device:
74 * - "code": the code of the keyboard key to bind with the input
75 * - "button": same as "code" but for controller buttons
76 * - "toggle": press once to enable, press again to disable
77 * - "inverted": inverts the output of the button
78 * - "guid": text string for identifying controllers
79 * - "port": port of the connected device
80 * - "pad": slot of the connected controller
81 * @returns a unique input device with the parameters specified
82 */
83 std::unique_ptr<Common::Input::InputDevice> CreateButtonDevice(
84 const Common::ParamPackage& params);
85
86 /**
87 * Creates a hat button device from the parameters given.
88 * @param params contains parameters for creating the device:
89 * - "button": the controller hat id to bind with the input
90 * - "direction": the direction id to be detected
91 * - "toggle": press once to enable, press again to disable
92 * - "inverted": inverts the output of the button
93 * - "guid": text string for identifying controllers
94 * - "port": port of the connected device
95 * - "pad": slot of the connected controller
96 * @returns a unique input device with the parameters specified
97 */
98 std::unique_ptr<Common::Input::InputDevice> CreateHatButtonDevice(
99 const Common::ParamPackage& params);
100
101 /**
102 * Creates a stick device from the parameters given.
103 * @param params contains parameters for creating the device:
104 * - "axis_x": the controller horizontal axis id to bind with the input
105 * - "axis_y": the controller vertical axis id to bind with the input
106 * - "deadzone": the minimum required value to be detected
107 * - "range": the maximum value required to reach 100%
108 * - "threshold": the minimum required value to considered pressed
109 * - "offset_x": the amount of offset in the x axis
110 * - "offset_y": the amount of offset in the y axis
111 * - "invert_x": inverts the sign of the horizontal axis
112 * - "invert_y": inverts the sign of the vertical axis
113 * - "guid": text string for identifying controllers
114 * - "port": port of the connected device
115 * - "pad": slot of the connected controller
116 * @returns a unique input device with the parameters specified
117 */
118 std::unique_ptr<Common::Input::InputDevice> CreateStickDevice(
119 const Common::ParamPackage& params);
120
121 /**
122 * Creates an analog device from the parameters given.
123 * @param params contains parameters for creating the device:
124 * - "axis": the controller axis id to bind with the input
125 * - "deadzone": the minimum required value to be detected
126 * - "range": the maximum value required to reach 100%
127 * - "threshold": the minimum required value to considered pressed
128 * - "offset": the amount of offset in the axis
129 * - "invert": inverts the sign of the axis
130 * - "guid": text string for identifying controllers
131 * - "port": port of the connected device
132 * - "pad": slot of the connected controller
133 * @returns a unique input device with the parameters specified
134 */
135 std::unique_ptr<Common::Input::InputDevice> CreateAnalogDevice(
136 const Common::ParamPackage& params);
137
138 /**
139 * Creates a trigger device from the parameters given.
140 * @param params contains parameters for creating the device:
141 * - "button": the controller hat id to bind with the input
142 * - "direction": the direction id to be detected
143 * - "toggle": press once to enable, press again to disable
144 * - "inverted": inverts the output of the button
145 * - "axis": the controller axis id to bind with the input
146 * - "deadzone": the minimum required value to be detected
147 * - "range": the maximum value required to reach 100%
148 * - "threshold": the minimum required value to considered pressed
149 * - "offset": the amount of offset in the axis
150 * - "invert": inverts the sign of the axis
151 * - "guid": text string for identifying controllers
152 * - "port": port of the connected device
153 * - "pad": slot of the connected controller
154 * @returns a unique input device with the parameters specified
155 */
156 std::unique_ptr<Common::Input::InputDevice> CreateTriggerDevice(
157 const Common::ParamPackage& params);
158
159 /**
160 * Creates a touch device from the parameters given.
161 * @param params contains parameters for creating the device:
162 * - "button": the controller hat id to bind with the input
163 * - "direction": the direction id to be detected
164 * - "toggle": press once to enable, press again to disable
165 * - "inverted": inverts the output of the button
166 * - "axis_x": the controller horizontal axis id to bind with the input
167 * - "axis_y": the controller vertical axis id to bind with the input
168 * - "deadzone": the minimum required value to be detected
169 * - "range": the maximum value required to reach 100%
170 * - "threshold": the minimum required value to considered pressed
171 * - "offset_x": the amount of offset in the x axis
172 * - "offset_y": the amount of offset in the y axis
173 * - "invert_x": inverts the sign of the horizontal axis
174 * - "invert_y": inverts the sign of the vertical axis
175 * - "guid": text string for identifying controllers
176 * - "port": port of the connected device
177 * - "pad": slot of the connected controller
178 * @returns a unique input device with the parameters specified
179 */
180 std::unique_ptr<Common::Input::InputDevice> CreateTouchDevice(
181 const Common::ParamPackage& params);
182
183 /**
184 * Creates a battery device from the parameters given.
185 * @param params contains parameters for creating the device:
186 * - "guid": text string for identifying controllers
187 * - "port": port of the connected device
188 * - "pad": slot of the connected controller
189 * @returns a unique input device with the parameters specified
190 */
191 std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice(
192 const Common::ParamPackage& params);
193
194 /**
195 * Creates a motion device from the parameters given.
196 * @param params contains parameters for creating the device:
197 * - "axis_x": the controller horizontal axis id to bind with the input
198 * - "axis_y": the controller vertical axis id to bind with the input
199 * - "axis_z": the controller forward axis id to bind with the input
200 * - "deadzone": the minimum required value to be detected
201 * - "range": the maximum value required to reach 100%
202 * - "offset_x": the amount of offset in the x axis
203 * - "offset_y": the amount of offset in the y axis
204 * - "offset_z": the amount of offset in the z axis
205 * - "invert_x": inverts the sign of the horizontal axis
206 * - "invert_y": inverts the sign of the vertical axis
207 * - "invert_z": inverts the sign of the forward axis
208 * - "guid": text string for identifying controllers
209 * - "port": port of the connected device
210 * - "pad": slot of the connected controller
211 * @returns a unique input device with the parameters specified
212 */
213 std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params);
214
215 std::shared_ptr<InputEngine> input_engine;
216};
217} // namespace InputCommon
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
deleted file mode 100644
index 8261e76fd..000000000
--- a/src/input_common/keyboard.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include <utility>
9#include "input_common/keyboard.h"
10
11namespace InputCommon {
12
13class KeyButton final : public Input::ButtonDevice {
14public:
15 explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_, bool toggle_)
16 : key_button_list(std::move(key_button_list_)), toggle(toggle_) {}
17
18 ~KeyButton() override;
19
20 bool GetStatus() const override {
21 if (toggle) {
22 return toggled_status.load(std::memory_order_relaxed);
23 }
24 return status.load();
25 }
26
27 void ToggleButton() {
28 if (lock) {
29 return;
30 }
31 lock = true;
32 const bool old_toggle_status = toggled_status.load();
33 toggled_status.store(!old_toggle_status);
34 }
35
36 void UnlockButton() {
37 lock = false;
38 }
39
40 friend class KeyButtonList;
41
42private:
43 std::shared_ptr<KeyButtonList> key_button_list;
44 std::atomic<bool> status{false};
45 std::atomic<bool> toggled_status{false};
46 bool lock{false};
47 const bool toggle;
48};
49
50struct KeyButtonPair {
51 int key_code;
52 KeyButton* key_button;
53};
54
55class KeyButtonList {
56public:
57 void AddKeyButton(int key_code, KeyButton* key_button) {
58 std::lock_guard guard{mutex};
59 list.push_back(KeyButtonPair{key_code, key_button});
60 }
61
62 void RemoveKeyButton(const KeyButton* key_button) {
63 std::lock_guard guard{mutex};
64 list.remove_if(
65 [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
66 }
67
68 void ChangeKeyStatus(int key_code, bool pressed) {
69 std::lock_guard guard{mutex};
70 for (const KeyButtonPair& pair : list) {
71 if (pair.key_code == key_code) {
72 pair.key_button->status.store(pressed);
73 if (pressed) {
74 pair.key_button->ToggleButton();
75 } else {
76 pair.key_button->UnlockButton();
77 }
78 pair.key_button->TriggerOnChange();
79 }
80 }
81 }
82
83 void ChangeAllKeyStatus(bool pressed) {
84 std::lock_guard guard{mutex};
85 for (const KeyButtonPair& pair : list) {
86 pair.key_button->status.store(pressed);
87 }
88 }
89
90private:
91 std::mutex mutex;
92 std::list<KeyButtonPair> list;
93};
94
95Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {}
96
97KeyButton::~KeyButton() {
98 key_button_list->RemoveKeyButton(this);
99}
100
101std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
102 const int key_code = params.Get("code", 0);
103 const bool toggle = params.Get("toggle", false);
104 std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list, toggle);
105 key_button_list->AddKeyButton(key_code, button.get());
106 return button;
107}
108
109void Keyboard::PressKey(int key_code) {
110 key_button_list->ChangeKeyStatus(key_code, true);
111}
112
113void Keyboard::ReleaseKey(int key_code) {
114 key_button_list->ChangeKeyStatus(key_code, false);
115}
116
117void Keyboard::ReleaseAllKeys() {
118 key_button_list->ChangeAllKeyStatus(false);
119}
120
121} // namespace InputCommon
diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h
deleted file mode 100644
index 861950472..000000000
--- a/src/input_common/keyboard.h
+++ /dev/null
@@ -1,47 +0,0 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9
10namespace InputCommon {
11
12class KeyButtonList;
13
14/**
15 * A button device factory representing a keyboard. It receives keyboard events and forward them
16 * to all button devices it created.
17 */
18class Keyboard final : public Input::Factory<Input::ButtonDevice> {
19public:
20 Keyboard();
21
22 /**
23 * Creates a button device from a keyboard key
24 * @param params contains parameters for creating the device:
25 * - "code": the code of the key to bind with the button
26 */
27 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
28
29 /**
30 * Sets the status of all buttons bound with the key to pressed
31 * @param key_code the code of the key to press
32 */
33 void PressKey(int key_code);
34
35 /**
36 * Sets the status of all buttons bound with the key to released
37 * @param key_code the code of the key to release
38 */
39 void ReleaseKey(int key_code);
40
41 void ReleaseAllKeys();
42
43private:
44 std::shared_ptr<KeyButtonList> key_button_list;
45};
46
47} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index f3907c65a..940744c5f 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -4,146 +4,173 @@
4 4
5#include <memory> 5#include <memory>
6#include <thread> 6#include <thread>
7#include "common/input.h"
7#include "common/param_package.h" 8#include "common/param_package.h"
8#include "common/settings.h" 9#include "input_common/drivers/gc_adapter.h"
9#include "input_common/analog_from_button.h" 10#include "input_common/drivers/keyboard.h"
10#include "input_common/gcadapter/gc_adapter.h" 11#include "input_common/drivers/mouse.h"
11#include "input_common/gcadapter/gc_poller.h" 12#include "input_common/drivers/tas_input.h"
12#include "input_common/keyboard.h" 13#include "input_common/drivers/touch_screen.h"
14#include "input_common/drivers/udp_client.h"
15#include "input_common/helpers/stick_from_buttons.h"
16#include "input_common/helpers/touch_from_buttons.h"
17#include "input_common/input_engine.h"
18#include "input_common/input_mapping.h"
19#include "input_common/input_poller.h"
13#include "input_common/main.h" 20#include "input_common/main.h"
14#include "input_common/motion_from_button.h"
15#include "input_common/mouse/mouse_input.h"
16#include "input_common/mouse/mouse_poller.h"
17#include "input_common/tas/tas_input.h"
18#include "input_common/tas/tas_poller.h"
19#include "input_common/touch_from_button.h"
20#include "input_common/udp/client.h"
21#include "input_common/udp/udp.h"
22#ifdef HAVE_SDL2 21#ifdef HAVE_SDL2
23#include "input_common/sdl/sdl.h" 22#include "input_common/drivers/sdl_driver.h"
24#endif 23#endif
25 24
26namespace InputCommon { 25namespace InputCommon {
27 26
28struct InputSubsystem::Impl { 27struct InputSubsystem::Impl {
29 void Initialize() { 28 void Initialize() {
30 gcadapter = std::make_shared<GCAdapter::Adapter>(); 29 mapping_factory = std::make_shared<MappingFactory>();
31 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); 30 MappingCallback mapping_callback{[this](MappingData data) { RegisterInput(data); }};
32 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); 31
33 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); 32 keyboard = std::make_shared<Keyboard>("keyboard");
34 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); 33 keyboard->SetMappingCallback(mapping_callback);
35 gcvibration = std::make_shared<GCVibrationFactory>(gcadapter); 34 keyboard_factory = std::make_shared<InputFactory>(keyboard);
36 Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration); 35 keyboard_output_factory = std::make_shared<OutputFactory>(keyboard);
37 36 Common::Input::RegisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName(),
38 keyboard = std::make_shared<Keyboard>(); 37 keyboard_factory);
39 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); 38 Common::Input::RegisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName(),
40 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", 39 keyboard_output_factory);
41 std::make_shared<AnalogFromButton>()); 40
42 Input::RegisterFactory<Input::MotionDevice>("keyboard", 41 mouse = std::make_shared<Mouse>("mouse");
43 std::make_shared<MotionFromButton>()); 42 mouse->SetMappingCallback(mapping_callback);
44 Input::RegisterFactory<Input::TouchDevice>("touch_from_button", 43 mouse_factory = std::make_shared<InputFactory>(mouse);
45 std::make_shared<TouchFromButtonFactory>()); 44 mouse_output_factory = std::make_shared<OutputFactory>(mouse);
45 Common::Input::RegisterFactory<Common::Input::InputDevice>(mouse->GetEngineName(),
46 mouse_factory);
47 Common::Input::RegisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName(),
48 mouse_output_factory);
49
50 touch_screen = std::make_shared<TouchScreen>("touch");
51 touch_screen_factory = std::make_shared<InputFactory>(touch_screen);
52 Common::Input::RegisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName(),
53 touch_screen_factory);
54
55 gcadapter = std::make_shared<GCAdapter>("gcpad");
56 gcadapter->SetMappingCallback(mapping_callback);
57 gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
58 gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
59 Common::Input::RegisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName(),
60 gcadapter_input_factory);
61 Common::Input::RegisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName(),
62 gcadapter_output_factory);
63
64 udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
65 udp_client->SetMappingCallback(mapping_callback);
66 udp_client_input_factory = std::make_shared<InputFactory>(udp_client);
67 udp_client_output_factory = std::make_shared<OutputFactory>(udp_client);
68 Common::Input::RegisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName(),
69 udp_client_input_factory);
70 Common::Input::RegisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName(),
71 udp_client_output_factory);
72
73 tas_input = std::make_shared<TasInput::Tas>("tas");
74 tas_input->SetMappingCallback(mapping_callback);
75 tas_input_factory = std::make_shared<InputFactory>(tas_input);
76 tas_output_factory = std::make_shared<OutputFactory>(tas_input);
77 Common::Input::RegisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName(),
78 tas_input_factory);
79 Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(),
80 tas_output_factory);
46 81
47#ifdef HAVE_SDL2 82#ifdef HAVE_SDL2
48 sdl = SDL::Init(); 83 sdl = std::make_shared<SDLDriver>("sdl");
84 sdl->SetMappingCallback(mapping_callback);
85 sdl_input_factory = std::make_shared<InputFactory>(sdl);
86 sdl_output_factory = std::make_shared<OutputFactory>(sdl);
87 Common::Input::RegisterFactory<Common::Input::InputDevice>(sdl->GetEngineName(),
88 sdl_input_factory);
89 Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(),
90 sdl_output_factory);
49#endif 91#endif
50 92
51 udp = std::make_shared<InputCommon::CemuhookUDP::Client>(); 93 Common::Input::RegisterFactory<Common::Input::InputDevice>(
52 udpmotion = std::make_shared<UDPMotionFactory>(udp); 94 "touch_from_button", std::make_shared<TouchFromButton>());
53 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion); 95 Common::Input::RegisterFactory<Common::Input::InputDevice>(
54 udptouch = std::make_shared<UDPTouchFactory>(udp); 96 "analog_from_button", std::make_shared<StickFromButton>());
55 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
56
57 mouse = std::make_shared<MouseInput::Mouse>();
58 mousebuttons = std::make_shared<MouseButtonFactory>(mouse);
59 Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons);
60 mouseanalog = std::make_shared<MouseAnalogFactory>(mouse);
61 Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog);
62 mousemotion = std::make_shared<MouseMotionFactory>(mouse);
63 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
64 mousetouch = std::make_shared<MouseTouchFactory>(mouse);
65 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
66
67 tas = std::make_shared<TasInput::Tas>();
68 tasbuttons = std::make_shared<TasButtonFactory>(tas);
69 Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
70 tasanalog = std::make_shared<TasAnalogFactory>(tas);
71 Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
72 } 97 }
73 98
74 void Shutdown() { 99 void Shutdown() {
75 Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); 100 Common::Input::UnregisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName());
76 Input::UnregisterFactory<Input::MotionDevice>("keyboard"); 101 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName());
77 keyboard.reset(); 102 keyboard.reset();
78 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
79 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
80#ifdef HAVE_SDL2
81 sdl.reset();
82#endif
83 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
84 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
85 Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
86 103
87 gcbuttons.reset(); 104 Common::Input::UnregisterFactory<Common::Input::InputDevice>(mouse->GetEngineName());
88 gcanalog.reset(); 105 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName());
89 gcvibration.reset(); 106 mouse.reset();
90 107
91 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); 108 Common::Input::UnregisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName());
92 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); 109 touch_screen.reset();
93 110
94 udpmotion.reset(); 111 Common::Input::UnregisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName());
95 udptouch.reset(); 112 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName());
113 gcadapter.reset();
96 114
97 Input::UnregisterFactory<Input::ButtonDevice>("mouse"); 115 Common::Input::UnregisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName());
98 Input::UnregisterFactory<Input::AnalogDevice>("mouse"); 116 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName());
99 Input::UnregisterFactory<Input::MotionDevice>("mouse"); 117 udp_client.reset();
100 Input::UnregisterFactory<Input::TouchDevice>("mouse");
101 118
102 mousebuttons.reset(); 119 Common::Input::UnregisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName());
103 mouseanalog.reset(); 120 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName());
104 mousemotion.reset(); 121 tas_input.reset();
105 mousetouch.reset();
106 122
107 Input::UnregisterFactory<Input::ButtonDevice>("tas"); 123#ifdef HAVE_SDL2
108 Input::UnregisterFactory<Input::AnalogDevice>("tas"); 124 Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName());
125 Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName());
126 sdl.reset();
127#endif
109 128
110 tasbuttons.reset(); 129 Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button");
111 tasanalog.reset(); 130 Common::Input::UnregisterFactory<Common::Input::InputDevice>("analog_from_button");
112 } 131 }
113 132
114 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 133 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
115 std::vector<Common::ParamPackage> devices = { 134 std::vector<Common::ParamPackage> devices = {
116 Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, 135 Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
117 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
118 }; 136 };
119 if (Settings::values.tas_enable) { 137
120 devices.emplace_back( 138 auto keyboard_devices = keyboard->GetInputDevices();
121 Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); 139 devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
122 } 140 auto mouse_devices = mouse->GetInputDevices();
141 devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
142 auto gcadapter_devices = gcadapter->GetInputDevices();
143 devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
144 auto udp_devices = udp_client->GetInputDevices();
145 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
123#ifdef HAVE_SDL2 146#ifdef HAVE_SDL2
124 auto sdl_devices = sdl->GetInputDevices(); 147 auto sdl_devices = sdl->GetInputDevices();
125 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); 148 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
126#endif 149#endif
127 auto udp_devices = udp->GetInputDevices(); 150
128 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
129 auto gcpad_devices = gcadapter->GetInputDevices();
130 devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
131 return devices; 151 return devices;
132 } 152 }
133 153
134 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( 154 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
135 const Common::ParamPackage& params) const { 155 const Common::ParamPackage& params) const {
136 if (!params.Has("class") || params.Get("class", "") == "any") { 156 if (!params.Has("engine") || params.Get("engine", "") == "any") {
137 return {}; 157 return {};
138 } 158 }
139 if (params.Get("class", "") == "gcpad") { 159 const std::string engine = params.Get("engine", "");
160 if (engine == mouse->GetEngineName()) {
161 return mouse->GetAnalogMappingForDevice(params);
162 }
163 if (engine == gcadapter->GetEngineName()) {
140 return gcadapter->GetAnalogMappingForDevice(params); 164 return gcadapter->GetAnalogMappingForDevice(params);
141 } 165 }
142 if (params.Get("class", "") == "tas") { 166 if (engine == udp_client->GetEngineName()) {
143 return tas->GetAnalogMappingForDevice(params); 167 return udp_client->GetAnalogMappingForDevice(params);
168 }
169 if (engine == tas_input->GetEngineName()) {
170 return tas_input->GetAnalogMappingForDevice(params);
144 } 171 }
145#ifdef HAVE_SDL2 172#ifdef HAVE_SDL2
146 if (params.Get("class", "") == "sdl") { 173 if (engine == sdl->GetEngineName()) {
147 return sdl->GetAnalogMappingForDevice(params); 174 return sdl->GetAnalogMappingForDevice(params);
148 } 175 }
149#endif 176#endif
@@ -152,17 +179,21 @@ struct InputSubsystem::Impl {
152 179
153 [[nodiscard]] ButtonMapping GetButtonMappingForDevice( 180 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
154 const Common::ParamPackage& params) const { 181 const Common::ParamPackage& params) const {
155 if (!params.Has("class") || params.Get("class", "") == "any") { 182 if (!params.Has("engine") || params.Get("engine", "") == "any") {
156 return {}; 183 return {};
157 } 184 }
158 if (params.Get("class", "") == "gcpad") { 185 const std::string engine = params.Get("engine", "");
186 if (engine == gcadapter->GetEngineName()) {
159 return gcadapter->GetButtonMappingForDevice(params); 187 return gcadapter->GetButtonMappingForDevice(params);
160 } 188 }
161 if (params.Get("class", "") == "tas") { 189 if (engine == udp_client->GetEngineName()) {
162 return tas->GetButtonMappingForDevice(params); 190 return udp_client->GetButtonMappingForDevice(params);
191 }
192 if (engine == tas_input->GetEngineName()) {
193 return tas_input->GetButtonMappingForDevice(params);
163 } 194 }
164#ifdef HAVE_SDL2 195#ifdef HAVE_SDL2
165 if (params.Get("class", "") == "sdl") { 196 if (engine == sdl->GetEngineName()) {
166 return sdl->GetButtonMappingForDevice(params); 197 return sdl->GetButtonMappingForDevice(params);
167 } 198 }
168#endif 199#endif
@@ -171,40 +202,119 @@ struct InputSubsystem::Impl {
171 202
172 [[nodiscard]] MotionMapping GetMotionMappingForDevice( 203 [[nodiscard]] MotionMapping GetMotionMappingForDevice(
173 const Common::ParamPackage& params) const { 204 const Common::ParamPackage& params) const {
174 if (!params.Has("class") || params.Get("class", "") == "any") { 205 if (!params.Has("engine") || params.Get("engine", "") == "any") {
175 return {}; 206 return {};
176 } 207 }
177 if (params.Get("class", "") == "cemuhookudp") { 208 const std::string engine = params.Get("engine", "");
178 // TODO return the correct motion device 209 if (engine == udp_client->GetEngineName()) {
179 return {}; 210 return udp_client->GetMotionMappingForDevice(params);
180 } 211 }
181#ifdef HAVE_SDL2 212#ifdef HAVE_SDL2
182 if (params.Get("class", "") == "sdl") { 213 if (engine == sdl->GetEngineName()) {
183 return sdl->GetMotionMappingForDevice(params); 214 return sdl->GetMotionMappingForDevice(params);
184 } 215 }
185#endif 216#endif
186 return {}; 217 return {};
187 } 218 }
188 219
220 Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const {
221 if (!params.Has("engine") || params.Get("engine", "") == "any") {
222 return Common::Input::ButtonNames::Undefined;
223 }
224 const std::string engine = params.Get("engine", "");
225 if (engine == mouse->GetEngineName()) {
226 return mouse->GetUIName(params);
227 }
228 if (engine == gcadapter->GetEngineName()) {
229 return gcadapter->GetUIName(params);
230 }
231 if (engine == udp_client->GetEngineName()) {
232 return udp_client->GetUIName(params);
233 }
234 if (engine == tas_input->GetEngineName()) {
235 return tas_input->GetUIName(params);
236 }
237#ifdef HAVE_SDL2
238 if (engine == sdl->GetEngineName()) {
239 return sdl->GetUIName(params);
240 }
241#endif
242 return Common::Input::ButtonNames::Invalid;
243 }
244
245 bool IsController(const Common::ParamPackage& params) {
246 const std::string engine = params.Get("engine", "");
247 if (engine == mouse->GetEngineName()) {
248 return true;
249 }
250 if (engine == gcadapter->GetEngineName()) {
251 return true;
252 }
253 if (engine == udp_client->GetEngineName()) {
254 return true;
255 }
256 if (engine == tas_input->GetEngineName()) {
257 return true;
258 }
259#ifdef HAVE_SDL2
260 if (engine == sdl->GetEngineName()) {
261 return true;
262 }
263#endif
264 return false;
265 }
266
267 void BeginConfiguration() {
268 keyboard->BeginConfiguration();
269 mouse->BeginConfiguration();
270 gcadapter->BeginConfiguration();
271 udp_client->BeginConfiguration();
272#ifdef HAVE_SDL2
273 sdl->BeginConfiguration();
274#endif
275 }
276
277 void EndConfiguration() {
278 keyboard->EndConfiguration();
279 mouse->EndConfiguration();
280 gcadapter->EndConfiguration();
281 udp_client->EndConfiguration();
282#ifdef HAVE_SDL2
283 sdl->EndConfiguration();
284#endif
285 }
286
287 void RegisterInput(MappingData data) {
288 mapping_factory->RegisterInput(data);
289 }
290
291 std::shared_ptr<MappingFactory> mapping_factory;
292
189 std::shared_ptr<Keyboard> keyboard; 293 std::shared_ptr<Keyboard> keyboard;
294 std::shared_ptr<Mouse> mouse;
295 std::shared_ptr<GCAdapter> gcadapter;
296 std::shared_ptr<TouchScreen> touch_screen;
297 std::shared_ptr<TasInput::Tas> tas_input;
298 std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
299
300 std::shared_ptr<InputFactory> keyboard_factory;
301 std::shared_ptr<InputFactory> mouse_factory;
302 std::shared_ptr<InputFactory> gcadapter_input_factory;
303 std::shared_ptr<InputFactory> touch_screen_factory;
304 std::shared_ptr<InputFactory> udp_client_input_factory;
305 std::shared_ptr<InputFactory> tas_input_factory;
306
307 std::shared_ptr<OutputFactory> keyboard_output_factory;
308 std::shared_ptr<OutputFactory> mouse_output_factory;
309 std::shared_ptr<OutputFactory> gcadapter_output_factory;
310 std::shared_ptr<OutputFactory> udp_client_output_factory;
311 std::shared_ptr<OutputFactory> tas_output_factory;
312
190#ifdef HAVE_SDL2 313#ifdef HAVE_SDL2
191 std::unique_ptr<SDL::State> sdl; 314 std::shared_ptr<SDLDriver> sdl;
315 std::shared_ptr<InputFactory> sdl_input_factory;
316 std::shared_ptr<OutputFactory> sdl_output_factory;
192#endif 317#endif
193 std::shared_ptr<GCButtonFactory> gcbuttons;
194 std::shared_ptr<GCAnalogFactory> gcanalog;
195 std::shared_ptr<GCVibrationFactory> gcvibration;
196 std::shared_ptr<UDPMotionFactory> udpmotion;
197 std::shared_ptr<UDPTouchFactory> udptouch;
198 std::shared_ptr<MouseButtonFactory> mousebuttons;
199 std::shared_ptr<MouseAnalogFactory> mouseanalog;
200 std::shared_ptr<MouseMotionFactory> mousemotion;
201 std::shared_ptr<MouseTouchFactory> mousetouch;
202 std::shared_ptr<TasButtonFactory> tasbuttons;
203 std::shared_ptr<TasAnalogFactory> tasanalog;
204 std::shared_ptr<CemuhookUDP::Client> udp;
205 std::shared_ptr<GCAdapter::Adapter> gcadapter;
206 std::shared_ptr<MouseInput::Mouse> mouse;
207 std::shared_ptr<TasInput::Tas> tas;
208}; 318};
209 319
210InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 320InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -227,20 +337,28 @@ const Keyboard* InputSubsystem::GetKeyboard() const {
227 return impl->keyboard.get(); 337 return impl->keyboard.get();
228} 338}
229 339
230MouseInput::Mouse* InputSubsystem::GetMouse() { 340Mouse* InputSubsystem::GetMouse() {
231 return impl->mouse.get(); 341 return impl->mouse.get();
232} 342}
233 343
234const MouseInput::Mouse* InputSubsystem::GetMouse() const { 344const Mouse* InputSubsystem::GetMouse() const {
235 return impl->mouse.get(); 345 return impl->mouse.get();
236} 346}
237 347
348TouchScreen* InputSubsystem::GetTouchScreen() {
349 return impl->touch_screen.get();
350}
351
352const TouchScreen* InputSubsystem::GetTouchScreen() const {
353 return impl->touch_screen.get();
354}
355
238TasInput::Tas* InputSubsystem::GetTas() { 356TasInput::Tas* InputSubsystem::GetTas() {
239 return impl->tas.get(); 357 return impl->tas_input.get();
240} 358}
241 359
242const TasInput::Tas* InputSubsystem::GetTas() const { 360const TasInput::Tas* InputSubsystem::GetTas() const {
243 return impl->tas.get(); 361 return impl->tas_input.get();
244} 362}
245 363
246std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 364std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
@@ -259,100 +377,30 @@ MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPacka
259 return impl->GetMotionMappingForDevice(device); 377 return impl->GetMotionMappingForDevice(device);
260} 378}
261 379
262GCAnalogFactory* InputSubsystem::GetGCAnalogs() { 380Common::Input::ButtonNames InputSubsystem::GetButtonName(const Common::ParamPackage& params) const {
263 return impl->gcanalog.get(); 381 return impl->GetButtonName(params);
264}
265
266const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
267 return impl->gcanalog.get();
268}
269
270GCButtonFactory* InputSubsystem::GetGCButtons() {
271 return impl->gcbuttons.get();
272}
273
274const GCButtonFactory* InputSubsystem::GetGCButtons() const {
275 return impl->gcbuttons.get();
276}
277
278UDPMotionFactory* InputSubsystem::GetUDPMotions() {
279 return impl->udpmotion.get();
280}
281
282const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
283 return impl->udpmotion.get();
284}
285
286UDPTouchFactory* InputSubsystem::GetUDPTouch() {
287 return impl->udptouch.get();
288}
289
290const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
291 return impl->udptouch.get();
292}
293
294MouseButtonFactory* InputSubsystem::GetMouseButtons() {
295 return impl->mousebuttons.get();
296} 382}
297 383
298const MouseButtonFactory* InputSubsystem::GetMouseButtons() const { 384bool InputSubsystem::IsController(const Common::ParamPackage& params) const {
299 return impl->mousebuttons.get(); 385 return impl->IsController(params);
300} 386}
301 387
302MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() { 388void InputSubsystem::ReloadInputDevices() {
303 return impl->mouseanalog.get(); 389 impl->udp_client.get()->ReloadSockets();
304}
305
306const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const {
307 return impl->mouseanalog.get();
308}
309
310MouseMotionFactory* InputSubsystem::GetMouseMotions() {
311 return impl->mousemotion.get();
312}
313
314const MouseMotionFactory* InputSubsystem::GetMouseMotions() const {
315 return impl->mousemotion.get();
316}
317
318MouseTouchFactory* InputSubsystem::GetMouseTouch() {
319 return impl->mousetouch.get();
320}
321
322const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
323 return impl->mousetouch.get();
324}
325
326TasButtonFactory* InputSubsystem::GetTasButtons() {
327 return impl->tasbuttons.get();
328}
329
330const TasButtonFactory* InputSubsystem::GetTasButtons() const {
331 return impl->tasbuttons.get();
332}
333
334TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
335 return impl->tasanalog.get();
336} 390}
337 391
338const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const { 392void InputSubsystem::BeginMapping(Polling::InputType type) {
339 return impl->tasanalog.get(); 393 impl->BeginConfiguration();
394 impl->mapping_factory->BeginMapping(type);
340} 395}
341 396
342void InputSubsystem::ReloadInputDevices() { 397const Common::ParamPackage InputSubsystem::GetNextInput() const {
343 if (!impl->udp) { 398 return impl->mapping_factory->GetNextInput();
344 return;
345 }
346 impl->udp->ReloadSockets();
347} 399}
348 400
349std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( 401void InputSubsystem::StopMapping() const {
350 [[maybe_unused]] Polling::DeviceType type) const { 402 impl->EndConfiguration();
351#ifdef HAVE_SDL2 403 impl->mapping_factory->StopMapping();
352 return impl->sdl->GetPollers(type);
353#else
354 return {};
355#endif
356} 404}
357 405
358std::string GenerateKeyboardParam(int key_code) { 406std::string GenerateKeyboardParam(int key_code) {
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 6390d3f09..c6f97f691 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -13,6 +13,10 @@ namespace Common {
13class ParamPackage; 13class ParamPackage;
14} 14}
15 15
16namespace Common::Input {
17enum class ButtonNames;
18}
19
16namespace Settings::NativeAnalog { 20namespace Settings::NativeAnalog {
17enum Values : int; 21enum Values : int;
18} 22}
@@ -25,56 +29,26 @@ namespace Settings::NativeMotion {
25enum Values : int; 29enum Values : int;
26} 30}
27 31
28namespace MouseInput { 32namespace InputCommon {
33class Keyboard;
29class Mouse; 34class Mouse;
30} 35class TouchScreen;
36struct MappingData;
37} // namespace InputCommon
31 38
32namespace TasInput { 39namespace InputCommon::TasInput {
33class Tas; 40class Tas;
34} 41} // namespace InputCommon::TasInput
35 42
36namespace InputCommon { 43namespace InputCommon {
37namespace Polling { 44namespace Polling {
38 45/// Type of input desired for mapping purposes
39enum class DeviceType { Button, AnalogPreferred, Motion }; 46enum class InputType { None, Button, Stick, Motion, Touch };
40
41/**
42 * A class that can be used to get inputs from an input device like controllers without having to
43 * poll the device's status yourself
44 */
45class DevicePoller {
46public:
47 virtual ~DevicePoller() = default;
48 /// Setup and start polling for inputs, should be called before GetNextInput
49 /// If a device_id is provided, events should be filtered to only include events from this
50 /// device id
51 virtual void Start(const std::string& device_id = "") = 0;
52 /// Stop polling
53 virtual void Stop() = 0;
54 /**
55 * Every call to this function returns the next input recorded since calling Start
56 * @return A ParamPackage of the recorded input, which can be used to create an InputDevice.
57 * If there has been no input, the package is empty
58 */
59 virtual Common::ParamPackage GetNextInput() = 0;
60};
61} // namespace Polling 47} // namespace Polling
62 48
63class GCAnalogFactory;
64class GCButtonFactory;
65class UDPMotionFactory;
66class UDPTouchFactory;
67class MouseButtonFactory;
68class MouseAnalogFactory;
69class MouseMotionFactory;
70class MouseTouchFactory;
71class TasButtonFactory;
72class TasAnalogFactory;
73class Keyboard;
74
75/** 49/**
76 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default 50 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
77 * mapping for the device. This is currently only implemented for the SDL backend devices. 51 * mapping for the device.
78 */ 52 */
79using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; 53using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
80using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; 54using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
@@ -104,20 +78,27 @@ public:
104 [[nodiscard]] const Keyboard* GetKeyboard() const; 78 [[nodiscard]] const Keyboard* GetKeyboard() const;
105 79
106 /// Retrieves the underlying mouse device. 80 /// Retrieves the underlying mouse device.
107 [[nodiscard]] MouseInput::Mouse* GetMouse(); 81 [[nodiscard]] Mouse* GetMouse();
108 82
109 /// Retrieves the underlying mouse device. 83 /// Retrieves the underlying mouse device.
110 [[nodiscard]] const MouseInput::Mouse* GetMouse() const; 84 [[nodiscard]] const Mouse* GetMouse() const;
85
86 /// Retrieves the underlying touch screen device.
87 [[nodiscard]] TouchScreen* GetTouchScreen();
111 88
112 /// Retrieves the underlying tas device. 89 /// Retrieves the underlying touch screen device.
90 [[nodiscard]] const TouchScreen* GetTouchScreen() const;
91
92 /// Retrieves the underlying tas input device.
113 [[nodiscard]] TasInput::Tas* GetTas(); 93 [[nodiscard]] TasInput::Tas* GetTas();
114 94
115 /// Retrieves the underlying tas device. 95 /// Retrieves the underlying tas input device.
116 [[nodiscard]] const TasInput::Tas* GetTas() const; 96 [[nodiscard]] const TasInput::Tas* GetTas() const;
97
117 /** 98 /**
118 * Returns all available input devices that this Factory can create a new device with. 99 * Returns all available input devices that this Factory can create a new device with.
119 * Each returned ParamPackage should have a `display` field used for display, a class field for 100 * Each returned ParamPackage should have a `display` field used for display, a `engine` field
120 * backends to determine if this backend is meant to service the request and any other 101 * for backends to determine if this backend is meant to service the request and any other
121 * information needed to identify this in the backend later. 102 * information needed to identify this in the backend later.
122 */ 103 */
123 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const; 104 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
@@ -131,83 +112,34 @@ public:
131 /// Retrieves the motion mappings for the given device. 112 /// Retrieves the motion mappings for the given device.
132 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; 113 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
133 114
134 /// Retrieves the underlying GameCube analog handler. 115 /// Returns an enum contaning the name to be displayed from the input engine.
135 [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); 116 [[nodiscard]] Common::Input::ButtonNames GetButtonName(
117 const Common::ParamPackage& params) const;
136 118
137 /// Retrieves the underlying GameCube analog handler. 119 /// Returns true if device is a controller.
138 [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; 120 [[nodiscard]] bool IsController(const Common::ParamPackage& params) const;
139 121
140 /// Retrieves the underlying GameCube button handler. 122 /// Reloads the input devices.
141 [[nodiscard]] GCButtonFactory* GetGCButtons(); 123 void ReloadInputDevices();
142
143 /// Retrieves the underlying GameCube button handler.
144 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
145
146 /// Retrieves the underlying udp motion handler.
147 [[nodiscard]] UDPMotionFactory* GetUDPMotions();
148
149 /// Retrieves the underlying udp motion handler.
150 [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
151
152 /// Retrieves the underlying udp touch handler.
153 [[nodiscard]] UDPTouchFactory* GetUDPTouch();
154
155 /// Retrieves the underlying udp touch handler.
156 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
157
158 /// Retrieves the underlying mouse button handler.
159 [[nodiscard]] MouseButtonFactory* GetMouseButtons();
160
161 /// Retrieves the underlying mouse button handler.
162 [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
163
164 /// Retrieves the underlying mouse analog handler.
165 [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
166
167 /// Retrieves the underlying mouse analog handler.
168 [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
169
170 /// Retrieves the underlying mouse motion handler.
171 [[nodiscard]] MouseMotionFactory* GetMouseMotions();
172
173 /// Retrieves the underlying mouse motion handler.
174 [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
175
176 /// Retrieves the underlying mouse touch handler.
177 [[nodiscard]] MouseTouchFactory* GetMouseTouch();
178
179 /// Retrieves the underlying mouse touch handler.
180 [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
181
182 /// Retrieves the underlying tas button handler.
183 [[nodiscard]] TasButtonFactory* GetTasButtons();
184
185 /// Retrieves the underlying tas button handler.
186 [[nodiscard]] const TasButtonFactory* GetTasButtons() const;
187
188 /// Retrieves the underlying tas analogs handler.
189 [[nodiscard]] TasAnalogFactory* GetTasAnalogs();
190 124
191 /// Retrieves the underlying tas analogs handler. 125 /// Start polling from all backends for a desired input type.
192 [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const; 126 void BeginMapping(Polling::InputType type);
193 127
194 /// Reloads the input devices 128 /// Returns an input event with mapping information.
195 void ReloadInputDevices(); 129 [[nodiscard]] const Common::ParamPackage GetNextInput() const;
196 130
197 /// Get all DevicePoller from all backends for a specific device type 131 /// Stop polling from all backends.
198 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( 132 void StopMapping() const;
199 Polling::DeviceType type) const;
200 133
201private: 134private:
202 struct Impl; 135 struct Impl;
203 std::unique_ptr<Impl> impl; 136 std::unique_ptr<Impl> impl;
204}; 137};
205 138
206/// Generates a serialized param package for creating a keyboard button device 139/// Generates a serialized param package for creating a keyboard button device.
207std::string GenerateKeyboardParam(int key_code); 140std::string GenerateKeyboardParam(int key_code);
208 141
209/// Generates a serialized param package for creating an analog device taking input from keyboard 142/// Generates a serialized param package for creating an analog device taking input from keyboard.
210std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, 143std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
211 int key_modifier, float modifier_scale); 144 int key_modifier, float modifier_scale);
212
213} // namespace InputCommon 145} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp
deleted file mode 100644
index 29045a673..000000000
--- a/src/input_common/motion_from_button.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "input_common/motion_from_button.h"
6#include "input_common/motion_input.h"
7
8namespace InputCommon {
9
10class MotionKey final : public Input::MotionDevice {
11public:
12 using Button = std::unique_ptr<Input::ButtonDevice>;
13
14 explicit MotionKey(Button key_) : key(std::move(key_)) {}
15
16 Input::MotionStatus GetStatus() const override {
17
18 if (key->GetStatus()) {
19 return motion.GetRandomMotion(2, 6);
20 }
21 return motion.GetRandomMotion(0, 0);
22 }
23
24private:
25 Button key;
26 InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
27};
28
29std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
30 auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
31 return std::make_unique<MotionKey>(std::move(key));
32}
33
34} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h
deleted file mode 100644
index a959046fb..000000000
--- a/src/input_common/motion_from_button.h
+++ /dev/null
@@ -1,25 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/frontend/input.h"
8
9namespace InputCommon {
10
11/**
12 * An motion device factory that takes a keyboard button and uses it as a random
13 * motion device.
14 */
15class MotionFromButton final : public Input::Factory<Input::MotionDevice> {
16public:
17 /**
18 * Creates an motion device from button devices
19 * @param params contains parameters for creating the device:
20 * - "key": a serialized ParamPackage for creating a button device
21 */
22 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
23};
24
25} // namespace InputCommon
diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp
deleted file mode 100644
index 3b052ffb2..000000000
--- a/src/input_common/mouse/mouse_input.cpp
+++ /dev/null
@@ -1,223 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <stop_token>
6#include <thread>
7
8#include "common/settings.h"
9#include "input_common/mouse/mouse_input.h"
10
11namespace MouseInput {
12
13Mouse::Mouse() {
14 update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
15}
16
17Mouse::~Mouse() = default;
18
19void Mouse::UpdateThread(std::stop_token stop_token) {
20 constexpr int update_time = 10;
21 while (!stop_token.stop_requested()) {
22 for (MouseInfo& info : mouse_info) {
23 const Common::Vec3f angular_direction{
24 -info.tilt_direction.y,
25 0.0f,
26 -info.tilt_direction.x,
27 };
28
29 info.motion.SetGyroscope(angular_direction * info.tilt_speed);
30 info.motion.UpdateRotation(update_time * 1000);
31 info.motion.UpdateOrientation(update_time * 1000);
32 info.tilt_speed = 0;
33 info.data.motion = info.motion.GetMotion();
34 if (Settings::values.mouse_panning) {
35 info.last_mouse_change *= 0.96f;
36 info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x),
37 static_cast<int>(16 * -info.last_mouse_change.y)};
38 }
39 }
40 if (configuring) {
41 UpdateYuzuSettings();
42 }
43 if (mouse_panning_timout++ > 20) {
44 StopPanning();
45 }
46 std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
47 }
48}
49
50void Mouse::UpdateYuzuSettings() {
51 if (buttons == 0) {
52 return;
53 }
54
55 mouse_queue.Push(MouseStatus{
56 .button = last_button,
57 });
58}
59
60void Mouse::PressButton(int x, int y, MouseButton button_) {
61 const auto button_index = static_cast<std::size_t>(button_);
62 if (button_index >= mouse_info.size()) {
63 return;
64 }
65
66 const auto button = 1U << button_index;
67 buttons |= static_cast<u16>(button);
68 last_button = button_;
69
70 mouse_info[button_index].mouse_origin = Common::MakeVec(x, y);
71 mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y);
72 mouse_info[button_index].data.pressed = true;
73}
74
75void Mouse::StopPanning() {
76 for (MouseInfo& info : mouse_info) {
77 if (Settings::values.mouse_panning) {
78 info.data.axis = {};
79 info.tilt_speed = 0;
80 info.last_mouse_change = {};
81 }
82 }
83}
84
85void Mouse::MouseMove(int x, int y, int center_x, int center_y) {
86 for (MouseInfo& info : mouse_info) {
87 if (Settings::values.mouse_panning) {
88 auto mouse_change =
89 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
90 mouse_panning_timout = 0;
91
92 if (mouse_change.y == 0 && mouse_change.x == 0) {
93 continue;
94 }
95 const auto mouse_change_length = mouse_change.Length();
96 if (mouse_change_length < 3.0f) {
97 mouse_change /= mouse_change_length / 3.0f;
98 }
99
100 info.last_mouse_change = (info.last_mouse_change * 0.91f) + (mouse_change * 0.09f);
101
102 const auto last_mouse_change_length = info.last_mouse_change.Length();
103 if (last_mouse_change_length > 8.0f) {
104 info.last_mouse_change /= last_mouse_change_length / 8.0f;
105 } else if (last_mouse_change_length < 1.0f) {
106 info.last_mouse_change = mouse_change / mouse_change.Length();
107 }
108
109 info.tilt_direction = info.last_mouse_change;
110 info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
111 continue;
112 }
113
114 if (info.data.pressed) {
115 const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin;
116 const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position;
117 info.last_mouse_position = Common::MakeVec(x, y);
118 info.data.axis = {mouse_move.x, -mouse_move.y};
119
120 if (mouse_change.x == 0 && mouse_change.y == 0) {
121 info.tilt_speed = 0;
122 } else {
123 info.tilt_direction = mouse_change.Cast<float>();
124 info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
125 }
126 }
127 }
128}
129
130void Mouse::ReleaseButton(MouseButton button_) {
131 const auto button_index = static_cast<std::size_t>(button_);
132 if (button_index >= mouse_info.size()) {
133 return;
134 }
135
136 const auto button = 1U << button_index;
137 buttons &= static_cast<u16>(0xFF - button);
138
139 mouse_info[button_index].tilt_speed = 0;
140 mouse_info[button_index].data.pressed = false;
141 mouse_info[button_index].data.axis = {0, 0};
142}
143
144void Mouse::ReleaseAllButtons() {
145 buttons = 0;
146 for (auto& info : mouse_info) {
147 info.tilt_speed = 0;
148 info.data.pressed = false;
149 info.data.axis = {0, 0};
150 }
151}
152
153void Mouse::BeginConfiguration() {
154 buttons = 0;
155 last_button = MouseButton::Undefined;
156 mouse_queue.Clear();
157 configuring = true;
158}
159
160void Mouse::EndConfiguration() {
161 buttons = 0;
162 for (MouseInfo& info : mouse_info) {
163 info.tilt_speed = 0;
164 info.data.pressed = false;
165 info.data.axis = {0, 0};
166 }
167 last_button = MouseButton::Undefined;
168 mouse_queue.Clear();
169 configuring = false;
170}
171
172bool Mouse::ToggleButton(std::size_t button_) {
173 if (button_ >= mouse_info.size()) {
174 return false;
175 }
176 const auto button = 1U << button_;
177 const bool button_state = (toggle_buttons & button) != 0;
178 const bool button_lock = (lock_buttons & button) != 0;
179
180 if (button_lock) {
181 return button_state;
182 }
183
184 lock_buttons |= static_cast<u16>(button);
185
186 if (button_state) {
187 toggle_buttons &= static_cast<u16>(0xFF - button);
188 } else {
189 toggle_buttons |= static_cast<u16>(button);
190 }
191
192 return !button_state;
193}
194
195bool Mouse::UnlockButton(std::size_t button_) {
196 if (button_ >= mouse_info.size()) {
197 return false;
198 }
199
200 const auto button = 1U << button_;
201 const bool button_state = (toggle_buttons & button) != 0;
202
203 lock_buttons &= static_cast<u16>(0xFF - button);
204
205 return button_state;
206}
207
208Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() {
209 return mouse_queue;
210}
211
212const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const {
213 return mouse_queue;
214}
215
216MouseData& Mouse::GetMouseState(std::size_t button) {
217 return mouse_info[button].data;
218}
219
220const MouseData& Mouse::GetMouseState(std::size_t button) const {
221 return mouse_info[button].data;
222}
223} // namespace MouseInput
diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h
deleted file mode 100644
index c8bae99c1..000000000
--- a/src/input_common/mouse/mouse_input.h
+++ /dev/null
@@ -1,116 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <mutex>
9#include <stop_token>
10#include <thread>
11
12#include "common/common_types.h"
13#include "common/threadsafe_queue.h"
14#include "common/vector_math.h"
15#include "core/frontend/input.h"
16#include "input_common/motion_input.h"
17
18namespace MouseInput {
19
20enum class MouseButton {
21 Left,
22 Right,
23 Wheel,
24 Backward,
25 Forward,
26 Task,
27 Extra,
28 Undefined,
29};
30
31struct MouseStatus {
32 MouseButton button{MouseButton::Undefined};
33};
34
35struct MouseData {
36 bool pressed{};
37 std::array<int, 2> axis{};
38 Input::MotionStatus motion{};
39 Input::TouchStatus touch{};
40};
41
42class Mouse {
43public:
44 Mouse();
45 ~Mouse();
46
47 /// Used for polling
48 void BeginConfiguration();
49 void EndConfiguration();
50
51 /**
52 * Signals that a button is pressed.
53 * @param x the x-coordinate of the cursor
54 * @param y the y-coordinate of the cursor
55 * @param button_ the button pressed
56 */
57 void PressButton(int x, int y, MouseButton button_);
58
59 /**
60 * Signals that mouse has moved.
61 * @param x the x-coordinate of the cursor
62 * @param y the y-coordinate of the cursor
63 * @param center_x the x-coordinate of the middle of the screen
64 * @param center_y the y-coordinate of the middle of the screen
65 */
66 void MouseMove(int x, int y, int center_x, int center_y);
67
68 /**
69 * Signals that a button is released.
70 * @param button_ the button pressed
71 */
72 void ReleaseButton(MouseButton button_);
73
74 /**
75 * Signals that all buttons are released
76 */
77 void ReleaseAllButtons();
78
79 [[nodiscard]] bool ToggleButton(std::size_t button_);
80 [[nodiscard]] bool UnlockButton(std::size_t button_);
81
82 [[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue();
83 [[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const;
84
85 [[nodiscard]] MouseData& GetMouseState(std::size_t button);
86 [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const;
87
88private:
89 void UpdateThread(std::stop_token stop_token);
90 void UpdateYuzuSettings();
91 void StopPanning();
92
93 struct MouseInfo {
94 InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
95 Common::Vec2<int> mouse_origin;
96 Common::Vec2<int> last_mouse_position;
97 Common::Vec2<float> last_mouse_change;
98 bool is_tilting = false;
99 float sensitivity{0.120f};
100
101 float tilt_speed = 0;
102 Common::Vec2<float> tilt_direction;
103 MouseData data;
104 };
105
106 u16 buttons{};
107 u16 toggle_buttons{};
108 u16 lock_buttons{};
109 std::jthread update_thread;
110 MouseButton last_button{MouseButton::Undefined};
111 std::array<MouseInfo, 7> mouse_info;
112 Common::SPSCQueue<MouseStatus> mouse_queue;
113 bool configuring{false};
114 int mouse_panning_timout{};
115};
116} // namespace MouseInput
diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp
deleted file mode 100644
index 090b26972..000000000
--- a/src/input_common/mouse/mouse_poller.cpp
+++ /dev/null
@@ -1,299 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <memory>
7#include <mutex>
8#include <utility>
9
10#include "common/settings.h"
11#include "common/threadsafe_queue.h"
12#include "input_common/mouse/mouse_input.h"
13#include "input_common/mouse/mouse_poller.h"
14
15namespace InputCommon {
16
17class MouseButton final : public Input::ButtonDevice {
18public:
19 explicit MouseButton(u32 button_, bool toggle_, MouseInput::Mouse* mouse_input_)
20 : button(button_), toggle(toggle_), mouse_input(mouse_input_) {}
21
22 bool GetStatus() const override {
23 const bool button_state = mouse_input->GetMouseState(button).pressed;
24 if (!toggle) {
25 return button_state;
26 }
27
28 if (button_state) {
29 return mouse_input->ToggleButton(button);
30 }
31 return mouse_input->UnlockButton(button);
32 }
33
34private:
35 const u32 button;
36 const bool toggle;
37 MouseInput::Mouse* mouse_input;
38};
39
40MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
41 : mouse_input(std::move(mouse_input_)) {}
42
43std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create(
44 const Common::ParamPackage& params) {
45 const auto button_id = params.Get("button", 0);
46 const auto toggle = params.Get("toggle", false);
47
48 return std::make_unique<MouseButton>(button_id, toggle, mouse_input.get());
49}
50
51Common::ParamPackage MouseButtonFactory::GetNextInput() const {
52 MouseInput::MouseStatus pad;
53 Common::ParamPackage params;
54 auto& queue = mouse_input->GetMouseQueue();
55 while (queue.Pop(pad)) {
56 // This while loop will break on the earliest detected button
57 if (pad.button != MouseInput::MouseButton::Undefined) {
58 params.Set("engine", "mouse");
59 params.Set("button", static_cast<u16>(pad.button));
60 params.Set("toggle", false);
61 return params;
62 }
63 }
64 return params;
65}
66
67void MouseButtonFactory::BeginConfiguration() {
68 polling = true;
69 mouse_input->BeginConfiguration();
70}
71
72void MouseButtonFactory::EndConfiguration() {
73 polling = false;
74 mouse_input->EndConfiguration();
75}
76
77class MouseAnalog final : public Input::AnalogDevice {
78public:
79 explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_,
80 float deadzone_, float range_, const MouseInput::Mouse* mouse_input_)
81 : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_),
82 deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {}
83
84 float GetAxis(u32 axis) const {
85 std::lock_guard lock{mutex};
86 const auto axis_value =
87 static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis));
88 const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.10f;
89 return axis_value * sensitivity / (100.0f * range);
90 }
91
92 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
93 float x = GetAxis(analog_axis_x);
94 float y = GetAxis(analog_axis_y);
95 if (invert_x) {
96 x = -x;
97 }
98 if (invert_y) {
99 y = -y;
100 }
101
102 // Make sure the coordinates are in the unit circle,
103 // otherwise normalize it.
104 float r = x * x + y * y;
105 if (r > 1.0f) {
106 r = std::sqrt(r);
107 x /= r;
108 y /= r;
109 }
110
111 return {x, y};
112 }
113
114 std::tuple<float, float> GetStatus() const override {
115 const auto [x, y] = GetAnalog(axis_x, axis_y);
116 const float r = std::sqrt((x * x) + (y * y));
117 if (r > deadzone) {
118 return {x / r * (r - deadzone) / (1 - deadzone),
119 y / r * (r - deadzone) / (1 - deadzone)};
120 }
121 return {0.0f, 0.0f};
122 }
123
124 std::tuple<float, float> GetRawStatus() const override {
125 const float x = GetAxis(axis_x);
126 const float y = GetAxis(axis_y);
127 return {x, y};
128 }
129
130 Input::AnalogProperties GetAnalogProperties() const override {
131 return {deadzone, range, 0.5f};
132 }
133
134private:
135 const u32 button;
136 const u32 axis_x;
137 const u32 axis_y;
138 const bool invert_x;
139 const bool invert_y;
140 const float deadzone;
141 const float range;
142 const MouseInput::Mouse* mouse_input;
143 mutable std::mutex mutex;
144};
145
146/// An analog device factory that creates analog devices from GC Adapter
147MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
148 : mouse_input(std::move(mouse_input_)) {}
149
150/**
151 * Creates analog device from joystick axes
152 * @param params contains parameters for creating the device:
153 * - "port": the nth gcpad on the adapter
154 * - "axis_x": the index of the axis to be bind as x-axis
155 * - "axis_y": the index of the axis to be bind as y-axis
156 */
157std::unique_ptr<Input::AnalogDevice> MouseAnalogFactory::Create(
158 const Common::ParamPackage& params) {
159 const auto port = static_cast<u32>(params.Get("port", 0));
160 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
161 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
162 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
163 const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
164 const std::string invert_x_value = params.Get("invert_x", "+");
165 const std::string invert_y_value = params.Get("invert_y", "+");
166 const bool invert_x = invert_x_value == "-";
167 const bool invert_y = invert_y_value == "-";
168
169 return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range,
170 mouse_input.get());
171}
172
173void MouseAnalogFactory::BeginConfiguration() {
174 polling = true;
175 mouse_input->BeginConfiguration();
176}
177
178void MouseAnalogFactory::EndConfiguration() {
179 polling = false;
180 mouse_input->EndConfiguration();
181}
182
183Common::ParamPackage MouseAnalogFactory::GetNextInput() const {
184 MouseInput::MouseStatus pad;
185 Common::ParamPackage params;
186 auto& queue = mouse_input->GetMouseQueue();
187 while (queue.Pop(pad)) {
188 // This while loop will break on the earliest detected button
189 if (pad.button != MouseInput::MouseButton::Undefined) {
190 params.Set("engine", "mouse");
191 params.Set("port", static_cast<u16>(pad.button));
192 params.Set("axis_x", 0);
193 params.Set("axis_y", 1);
194 params.Set("invert_x", "+");
195 params.Set("invert_y", "+");
196 return params;
197 }
198 }
199 return params;
200}
201
202class MouseMotion final : public Input::MotionDevice {
203public:
204 explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_)
205 : button(button_), mouse_input(mouse_input_) {}
206
207 Input::MotionStatus GetStatus() const override {
208 return mouse_input->GetMouseState(button).motion;
209 }
210
211private:
212 const u32 button;
213 const MouseInput::Mouse* mouse_input;
214};
215
216MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
217 : mouse_input(std::move(mouse_input_)) {}
218
219std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create(
220 const Common::ParamPackage& params) {
221 const auto button_id = params.Get("button", 0);
222
223 return std::make_unique<MouseMotion>(button_id, mouse_input.get());
224}
225
226Common::ParamPackage MouseMotionFactory::GetNextInput() const {
227 MouseInput::MouseStatus pad;
228 Common::ParamPackage params;
229 auto& queue = mouse_input->GetMouseQueue();
230 while (queue.Pop(pad)) {
231 // This while loop will break on the earliest detected button
232 if (pad.button != MouseInput::MouseButton::Undefined) {
233 params.Set("engine", "mouse");
234 params.Set("button", static_cast<u16>(pad.button));
235 return params;
236 }
237 }
238 return params;
239}
240
241void MouseMotionFactory::BeginConfiguration() {
242 polling = true;
243 mouse_input->BeginConfiguration();
244}
245
246void MouseMotionFactory::EndConfiguration() {
247 polling = false;
248 mouse_input->EndConfiguration();
249}
250
251class MouseTouch final : public Input::TouchDevice {
252public:
253 explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_)
254 : button(button_), mouse_input(mouse_input_) {}
255
256 Input::TouchStatus GetStatus() const override {
257 return mouse_input->GetMouseState(button).touch;
258 }
259
260private:
261 const u32 button;
262 const MouseInput::Mouse* mouse_input;
263};
264
265MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_)
266 : mouse_input(std::move(mouse_input_)) {}
267
268std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) {
269 const auto button_id = params.Get("button", 0);
270
271 return std::make_unique<MouseTouch>(button_id, mouse_input.get());
272}
273
274Common::ParamPackage MouseTouchFactory::GetNextInput() const {
275 MouseInput::MouseStatus pad;
276 Common::ParamPackage params;
277 auto& queue = mouse_input->GetMouseQueue();
278 while (queue.Pop(pad)) {
279 // This while loop will break on the earliest detected button
280 if (pad.button != MouseInput::MouseButton::Undefined) {
281 params.Set("engine", "mouse");
282 params.Set("button", static_cast<u16>(pad.button));
283 return params;
284 }
285 }
286 return params;
287}
288
289void MouseTouchFactory::BeginConfiguration() {
290 polling = true;
291 mouse_input->BeginConfiguration();
292}
293
294void MouseTouchFactory::EndConfiguration() {
295 polling = false;
296 mouse_input->EndConfiguration();
297}
298
299} // namespace InputCommon
diff --git a/src/input_common/mouse/mouse_poller.h b/src/input_common/mouse/mouse_poller.h
deleted file mode 100644
index cf331293b..000000000
--- a/src/input_common/mouse/mouse_poller.h
+++ /dev/null
@@ -1,109 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/mouse/mouse_input.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a mouse. It receives mouse events and forward them
15 * to all button devices it created.
16 */
17class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28 Common::ParamPackage GetNextInput() const;
29
30 /// For device input configuration/polling
31 void BeginConfiguration();
32 void EndConfiguration();
33
34 bool IsPolling() const {
35 return polling;
36 }
37
38private:
39 std::shared_ptr<MouseInput::Mouse> mouse_input;
40 bool polling = false;
41};
42
43/// An analog device factory that creates analog devices from mouse
44class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
45public:
46 explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
47
48 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
49
50 Common::ParamPackage GetNextInput() const;
51
52 /// For device input configuration/polling
53 void BeginConfiguration();
54 void EndConfiguration();
55
56 bool IsPolling() const {
57 return polling;
58 }
59
60private:
61 std::shared_ptr<MouseInput::Mouse> mouse_input;
62 bool polling = false;
63};
64
65/// A motion device factory that creates motion devices from mouse
66class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> {
67public:
68 explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
69
70 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
71
72 Common::ParamPackage GetNextInput() const;
73
74 /// For device input configuration/polling
75 void BeginConfiguration();
76 void EndConfiguration();
77
78 bool IsPolling() const {
79 return polling;
80 }
81
82private:
83 std::shared_ptr<MouseInput::Mouse> mouse_input;
84 bool polling = false;
85};
86
87/// An touch device factory that creates touch devices from mouse
88class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> {
89public:
90 explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_);
91
92 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
93
94 Common::ParamPackage GetNextInput() const;
95
96 /// For device input configuration/polling
97 void BeginConfiguration();
98 void EndConfiguration();
99
100 bool IsPolling() const {
101 return polling;
102 }
103
104private:
105 std::shared_ptr<MouseInput::Mouse> mouse_input;
106 bool polling = false;
107};
108
109} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
deleted file mode 100644
index 644db3448..000000000
--- a/src/input_common/sdl/sdl.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "input_common/sdl/sdl.h"
6#ifdef HAVE_SDL2
7#include "input_common/sdl/sdl_impl.h"
8#endif
9
10namespace InputCommon::SDL {
11
12std::unique_ptr<State> Init() {
13#ifdef HAVE_SDL2
14 return std::make_unique<SDLState>();
15#else
16 return std::make_unique<NullState>();
17#endif
18}
19} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
deleted file mode 100644
index b5d41bba4..000000000
--- a/src/input_common/sdl/sdl.h
+++ /dev/null
@@ -1,51 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <vector>
9#include "common/param_package.h"
10#include "input_common/main.h"
11
12namespace InputCommon::Polling {
13class DevicePoller;
14enum class DeviceType;
15} // namespace InputCommon::Polling
16
17namespace InputCommon::SDL {
18
19class State {
20public:
21 using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
22
23 /// Unregisters SDL device factories and shut them down.
24 virtual ~State() = default;
25
26 virtual Pollers GetPollers(Polling::DeviceType) {
27 return {};
28 }
29
30 virtual std::vector<Common::ParamPackage> GetInputDevices() {
31 return {};
32 }
33
34 virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) {
35 return {};
36 }
37 virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) {
38 return {};
39 }
40 virtual MotionMapping GetMotionMappingForDevice(const Common::ParamPackage&) {
41 return {};
42 }
43};
44
45class NullState : public State {
46public:
47};
48
49std::unique_ptr<State> Init();
50
51} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
deleted file mode 100644
index ecb00d428..000000000
--- a/src/input_common/sdl/sdl_impl.cpp
+++ /dev/null
@@ -1,1658 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <atomic>
8#include <chrono>
9#include <cmath>
10#include <functional>
11#include <mutex>
12#include <optional>
13#include <sstream>
14#include <string>
15#include <thread>
16#include <tuple>
17#include <unordered_map>
18#include <utility>
19#include <vector>
20
21#include "common/logging/log.h"
22#include "common/math_util.h"
23#include "common/param_package.h"
24#include "common/settings.h"
25#include "common/threadsafe_queue.h"
26#include "core/frontend/input.h"
27#include "input_common/motion_input.h"
28#include "input_common/sdl/sdl_impl.h"
29
30namespace InputCommon::SDL {
31
32namespace {
33std::string GetGUID(SDL_Joystick* joystick) {
34 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
35 char guid_str[33];
36 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
37 return guid_str;
38}
39
40/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
41Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
42} // Anonymous namespace
43
44static int SDLEventWatcher(void* user_data, SDL_Event* event) {
45 auto* const sdl_state = static_cast<SDLState*>(user_data);
46
47 // Don't handle the event if we are configuring
48 if (sdl_state->polling) {
49 sdl_state->event_queue.Push(*event);
50 } else {
51 sdl_state->HandleGameControllerEvent(*event);
52 }
53
54 return 0;
55}
56
57class SDLJoystick {
58public:
59 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
60 SDL_GameController* game_controller)
61 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
62 sdl_controller{game_controller, &SDL_GameControllerClose} {
63 EnableMotion();
64 }
65
66 void EnableMotion() {
67 if (sdl_controller) {
68 SDL_GameController* controller = sdl_controller.get();
69 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
70 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
71 has_accel = true;
72 }
73 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
74 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
75 has_gyro = true;
76 }
77 }
78 }
79
80 void SetButton(int button, bool value) {
81 std::lock_guard lock{mutex};
82 state.buttons.insert_or_assign(button, value);
83 }
84
85 void PreSetButton(int button) {
86 if (!state.buttons.contains(button)) {
87 SetButton(button, false);
88 }
89 }
90
91 void SetMotion(SDL_ControllerSensorEvent event) {
92 constexpr float gravity_constant = 9.80665f;
93 std::lock_guard lock{mutex};
94 u64 time_difference = event.timestamp - last_motion_update;
95 last_motion_update = event.timestamp;
96 switch (event.sensor) {
97 case SDL_SENSOR_ACCEL: {
98 const Common::Vec3f acceleration = {-event.data[0], event.data[2], -event.data[1]};
99 motion.SetAcceleration(acceleration / gravity_constant);
100 break;
101 }
102 case SDL_SENSOR_GYRO: {
103 const Common::Vec3f gyroscope = {event.data[0], -event.data[2], event.data[1]};
104 motion.SetGyroscope(gyroscope / (Common::PI * 2));
105 break;
106 }
107 }
108
109 // Ignore duplicated timestamps
110 if (time_difference == 0) {
111 return;
112 }
113
114 motion.SetGyroThreshold(0.0001f);
115 motion.UpdateRotation(time_difference * 1000);
116 motion.UpdateOrientation(time_difference * 1000);
117 }
118
119 bool GetButton(int button) const {
120 std::lock_guard lock{mutex};
121 return state.buttons.at(button);
122 }
123
124 bool ToggleButton(int button) {
125 std::lock_guard lock{mutex};
126
127 if (!state.toggle_buttons.contains(button) || !state.lock_buttons.contains(button)) {
128 state.toggle_buttons.insert_or_assign(button, false);
129 state.lock_buttons.insert_or_assign(button, false);
130 }
131
132 const bool button_state = state.toggle_buttons.at(button);
133 const bool button_lock = state.lock_buttons.at(button);
134
135 if (button_lock) {
136 return button_state;
137 }
138
139 state.lock_buttons.insert_or_assign(button, true);
140
141 if (button_state) {
142 state.toggle_buttons.insert_or_assign(button, false);
143 } else {
144 state.toggle_buttons.insert_or_assign(button, true);
145 }
146
147 return !button_state;
148 }
149
150 bool UnlockButton(int button) {
151 std::lock_guard lock{mutex};
152 if (!state.toggle_buttons.contains(button)) {
153 return false;
154 }
155 state.lock_buttons.insert_or_assign(button, false);
156 return state.toggle_buttons.at(button);
157 }
158
159 void SetAxis(int axis, Sint16 value) {
160 std::lock_guard lock{mutex};
161 state.axes.insert_or_assign(axis, value);
162 }
163
164 void PreSetAxis(int axis) {
165 if (!state.axes.contains(axis)) {
166 SetAxis(axis, 0);
167 }
168 }
169
170 float GetAxis(int axis, float range, float offset) const {
171 std::lock_guard lock{mutex};
172 const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f;
173 const float offset_scale = (value + offset) > 0.0f ? 1.0f + offset : 1.0f - offset;
174 return (value + offset) / range / offset_scale;
175 }
176
177 bool RumblePlay(u16 amp_low, u16 amp_high) {
178 constexpr u32 rumble_max_duration_ms = 1000;
179
180 if (sdl_controller) {
181 return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high,
182 rumble_max_duration_ms) != -1;
183 } else if (sdl_joystick) {
184 return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high,
185 rumble_max_duration_ms) != -1;
186 }
187
188 return false;
189 }
190
191 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x,
192 float offset_y) const {
193 float x = GetAxis(axis_x, range, offset_x);
194 float y = GetAxis(axis_y, range, offset_y);
195 y = -y; // 3DS uses an y-axis inverse from SDL
196
197 // Make sure the coordinates are in the unit circle,
198 // otherwise normalize it.
199 float r = x * x + y * y;
200 if (r > 1.0f) {
201 r = std::sqrt(r);
202 x /= r;
203 y /= r;
204 }
205
206 return std::make_tuple(x, y);
207 }
208
209 bool HasGyro() const {
210 return has_gyro;
211 }
212
213 bool HasAccel() const {
214 return has_accel;
215 }
216
217 const MotionInput& GetMotion() const {
218 return motion;
219 }
220
221 void SetHat(int hat, Uint8 direction) {
222 std::lock_guard lock{mutex};
223 state.hats.insert_or_assign(hat, direction);
224 }
225
226 bool GetHatDirection(int hat, Uint8 direction) const {
227 std::lock_guard lock{mutex};
228 return (state.hats.at(hat) & direction) != 0;
229 }
230 /**
231 * The guid of the joystick
232 */
233 const std::string& GetGUID() const {
234 return guid;
235 }
236
237 /**
238 * The number of joystick from the same type that were connected before this joystick
239 */
240 int GetPort() const {
241 return port;
242 }
243
244 SDL_Joystick* GetSDLJoystick() const {
245 return sdl_joystick.get();
246 }
247
248 SDL_GameController* GetSDLGameController() const {
249 return sdl_controller.get();
250 }
251
252 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
253 sdl_joystick.reset(joystick);
254 sdl_controller.reset(controller);
255 }
256
257 bool IsJoyconLeft() const {
258 const std::string controller_name = GetControllerName();
259 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
260 return true;
261 }
262 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
263 return true;
264 }
265 return false;
266 }
267
268 bool IsJoyconRight() const {
269 const std::string controller_name = GetControllerName();
270 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
271 return true;
272 }
273 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
274 return true;
275 }
276 return false;
277 }
278
279 std::string GetControllerName() const {
280 if (sdl_controller) {
281 switch (SDL_GameControllerGetType(sdl_controller.get())) {
282 case SDL_CONTROLLER_TYPE_XBOX360:
283 return "XBox 360 Controller";
284 case SDL_CONTROLLER_TYPE_XBOXONE:
285 return "XBox One Controller";
286 default:
287 break;
288 }
289 const auto name = SDL_GameControllerName(sdl_controller.get());
290 if (name) {
291 return name;
292 }
293 }
294
295 if (sdl_joystick) {
296 const auto name = SDL_JoystickName(sdl_joystick.get());
297 if (name) {
298 return name;
299 }
300 }
301
302 return "Unknown";
303 }
304
305private:
306 struct State {
307 std::unordered_map<int, bool> buttons;
308 std::unordered_map<int, bool> toggle_buttons{};
309 std::unordered_map<int, bool> lock_buttons{};
310 std::unordered_map<int, Sint16> axes;
311 std::unordered_map<int, Uint8> hats;
312 } state;
313 std::string guid;
314 int port;
315 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
316 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
317 mutable std::mutex mutex;
318
319 // Motion is initialized with the PID values
320 MotionInput motion{0.3f, 0.005f, 0.0f};
321 u64 last_motion_update{};
322 bool has_gyro{false};
323 bool has_accel{false};
324};
325
326std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
327 std::lock_guard lock{joystick_map_mutex};
328 const auto it = joystick_map.find(guid);
329
330 if (it != joystick_map.end()) {
331 while (it->second.size() <= static_cast<std::size_t>(port)) {
332 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
333 nullptr, nullptr);
334 it->second.emplace_back(std::move(joystick));
335 }
336
337 return it->second[static_cast<std::size_t>(port)];
338 }
339
340 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
341
342 return joystick_map[guid].emplace_back(std::move(joystick));
343}
344
345std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
346 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
347 const std::string guid = GetGUID(sdl_joystick);
348
349 std::lock_guard lock{joystick_map_mutex};
350 const auto map_it = joystick_map.find(guid);
351
352 if (map_it == joystick_map.end()) {
353 return nullptr;
354 }
355
356 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
357 [&sdl_joystick](const auto& joystick) {
358 return joystick->GetSDLJoystick() == sdl_joystick;
359 });
360
361 if (vec_it == map_it->second.end()) {
362 return nullptr;
363 }
364
365 return *vec_it;
366}
367
368void SDLState::InitJoystick(int joystick_index) {
369 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
370 SDL_GameController* sdl_gamecontroller = nullptr;
371
372 if (SDL_IsGameController(joystick_index)) {
373 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
374 }
375
376 if (!sdl_joystick) {
377 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
378 return;
379 }
380
381 const std::string guid = GetGUID(sdl_joystick);
382
383 std::lock_guard lock{joystick_map_mutex};
384 if (joystick_map.find(guid) == joystick_map.end()) {
385 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
386 joystick_map[guid].emplace_back(std::move(joystick));
387 return;
388 }
389
390 auto& joystick_guid_list = joystick_map[guid];
391 const auto joystick_it =
392 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
393 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
394
395 if (joystick_it != joystick_guid_list.end()) {
396 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
397 return;
398 }
399
400 const int port = static_cast<int>(joystick_guid_list.size());
401 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
402 joystick_guid_list.emplace_back(std::move(joystick));
403}
404
405void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
406 const std::string guid = GetGUID(sdl_joystick);
407
408 std::lock_guard lock{joystick_map_mutex};
409 // This call to guid is safe since the joystick is guaranteed to be in the map
410 const auto& joystick_guid_list = joystick_map[guid];
411 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
412 [&sdl_joystick](const auto& joystick) {
413 return joystick->GetSDLJoystick() == sdl_joystick;
414 });
415
416 if (joystick_it != joystick_guid_list.end()) {
417 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
418 }
419}
420
421void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
422 switch (event.type) {
423 case SDL_JOYBUTTONUP: {
424 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
425 joystick->SetButton(event.jbutton.button, false);
426 }
427 break;
428 }
429 case SDL_JOYBUTTONDOWN: {
430 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
431 joystick->SetButton(event.jbutton.button, true);
432 }
433 break;
434 }
435 case SDL_JOYHATMOTION: {
436 if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
437 joystick->SetHat(event.jhat.hat, event.jhat.value);
438 }
439 break;
440 }
441 case SDL_JOYAXISMOTION: {
442 if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
443 joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
444 }
445 break;
446 }
447 case SDL_CONTROLLERSENSORUPDATE: {
448 if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
449 joystick->SetMotion(event.csensor);
450 }
451 break;
452 }
453 case SDL_JOYDEVICEREMOVED:
454 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
455 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
456 break;
457 case SDL_JOYDEVICEADDED:
458 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
459 InitJoystick(event.jdevice.which);
460 break;
461 }
462}
463
464void SDLState::CloseJoysticks() {
465 std::lock_guard lock{joystick_map_mutex};
466 joystick_map.clear();
467}
468
469class SDLButton final : public Input::ButtonDevice {
470public:
471 explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_, bool toggle_)
472 : joystick(std::move(joystick_)), button(button_), toggle(toggle_) {}
473
474 bool GetStatus() const override {
475 const bool button_state = joystick->GetButton(button);
476 if (!toggle) {
477 return button_state;
478 }
479
480 if (button_state) {
481 return joystick->ToggleButton(button);
482 }
483 return joystick->UnlockButton(button);
484 }
485
486private:
487 std::shared_ptr<SDLJoystick> joystick;
488 int button;
489 bool toggle;
490};
491
492class SDLDirectionButton final : public Input::ButtonDevice {
493public:
494 explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
495 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
496
497 bool GetStatus() const override {
498 return joystick->GetHatDirection(hat, direction);
499 }
500
501private:
502 std::shared_ptr<SDLJoystick> joystick;
503 int hat;
504 Uint8 direction;
505};
506
507class SDLAxisButton final : public Input::ButtonDevice {
508public:
509 explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
510 bool trigger_if_greater_)
511 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
512 trigger_if_greater(trigger_if_greater_) {}
513
514 bool GetStatus() const override {
515 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
516 if (trigger_if_greater) {
517 return axis_value > threshold;
518 }
519 return axis_value < threshold;
520 }
521
522private:
523 std::shared_ptr<SDLJoystick> joystick;
524 int axis;
525 float threshold;
526 bool trigger_if_greater;
527};
528
529class SDLAnalog final : public Input::AnalogDevice {
530public:
531 explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
532 bool invert_x_, bool invert_y_, float deadzone_, float range_,
533 float offset_x_, float offset_y_)
534 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_),
535 invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_),
536 offset_y(offset_y_) {}
537
538 std::tuple<float, float> GetStatus() const override {
539 auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y);
540 const float r = std::sqrt((x * x) + (y * y));
541 if (invert_x) {
542 x = -x;
543 }
544 if (invert_y) {
545 y = -y;
546 }
547
548 if (r > deadzone) {
549 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
550 y / r * (r - deadzone) / (1 - deadzone));
551 }
552 return {};
553 }
554
555 std::tuple<float, float> GetRawStatus() const override {
556 const float x = joystick->GetAxis(axis_x, range, offset_x);
557 const float y = joystick->GetAxis(axis_y, range, offset_y);
558 return {x, -y};
559 }
560
561 Input::AnalogProperties GetAnalogProperties() const override {
562 return {deadzone, range, 0.5f};
563 }
564
565 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
566 const auto [x, y] = GetStatus();
567 const float directional_deadzone = 0.5f;
568 switch (direction) {
569 case Input::AnalogDirection::RIGHT:
570 return x > directional_deadzone;
571 case Input::AnalogDirection::LEFT:
572 return x < -directional_deadzone;
573 case Input::AnalogDirection::UP:
574 return y > directional_deadzone;
575 case Input::AnalogDirection::DOWN:
576 return y < -directional_deadzone;
577 }
578 return false;
579 }
580
581private:
582 std::shared_ptr<SDLJoystick> joystick;
583 const int axis_x;
584 const int axis_y;
585 const bool invert_x;
586 const bool invert_y;
587 const float deadzone;
588 const float range;
589 const float offset_x;
590 const float offset_y;
591};
592
593class SDLVibration final : public Input::VibrationDevice {
594public:
595 explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
596 : joystick(std::move(joystick_)) {}
597
598 u8 GetStatus() const override {
599 joystick->RumblePlay(1, 1);
600 return joystick->RumblePlay(0, 0);
601 }
602
603 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
604 [[maybe_unused]] f32 freq_high) const override {
605 const auto process_amplitude = [](f32 amplitude) {
606 return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF);
607 };
608
609 const auto processed_amp_low = process_amplitude(amp_low);
610 const auto processed_amp_high = process_amplitude(amp_high);
611
612 return joystick->RumblePlay(processed_amp_low, processed_amp_high);
613 }
614
615private:
616 std::shared_ptr<SDLJoystick> joystick;
617};
618
619class SDLMotion final : public Input::MotionDevice {
620public:
621 explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {}
622
623 Input::MotionStatus GetStatus() const override {
624 return joystick->GetMotion().GetMotion();
625 }
626
627private:
628 std::shared_ptr<SDLJoystick> joystick;
629};
630
631class SDLDirectionMotion final : public Input::MotionDevice {
632public:
633 explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
634 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
635
636 Input::MotionStatus GetStatus() const override {
637 if (joystick->GetHatDirection(hat, direction)) {
638 return joystick->GetMotion().GetRandomMotion(2, 6);
639 }
640 return joystick->GetMotion().GetRandomMotion(0, 0);
641 }
642
643private:
644 std::shared_ptr<SDLJoystick> joystick;
645 int hat;
646 Uint8 direction;
647};
648
649class SDLAxisMotion final : public Input::MotionDevice {
650public:
651 explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
652 bool trigger_if_greater_)
653 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
654 trigger_if_greater(trigger_if_greater_) {}
655
656 Input::MotionStatus GetStatus() const override {
657 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
658 bool trigger = axis_value < threshold;
659 if (trigger_if_greater) {
660 trigger = axis_value > threshold;
661 }
662
663 if (trigger) {
664 return joystick->GetMotion().GetRandomMotion(2, 6);
665 }
666 return joystick->GetMotion().GetRandomMotion(0, 0);
667 }
668
669private:
670 std::shared_ptr<SDLJoystick> joystick;
671 int axis;
672 float threshold;
673 bool trigger_if_greater;
674};
675
676class SDLButtonMotion final : public Input::MotionDevice {
677public:
678 explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
679 : joystick(std::move(joystick_)), button(button_) {}
680
681 Input::MotionStatus GetStatus() const override {
682 if (joystick->GetButton(button)) {
683 return joystick->GetMotion().GetRandomMotion(2, 6);
684 }
685 return joystick->GetMotion().GetRandomMotion(0, 0);
686 }
687
688private:
689 std::shared_ptr<SDLJoystick> joystick;
690 int button;
691};
692
693/// A button device factory that creates button devices from SDL joystick
694class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
695public:
696 explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
697
698 /**
699 * Creates a button device from a joystick button
700 * @param params contains parameters for creating the device:
701 * - "guid": the guid of the joystick to bind
702 * - "port": the nth joystick of the same type to bind
703 * - "button"(optional): the index of the button to bind
704 * - "hat"(optional): the index of the hat to bind as direction buttons
705 * - "axis"(optional): the index of the axis to bind
706 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
707 * "down", "left" or "right"
708 * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
709 * triggered if the axis value crosses
710 * - "direction"(only used for axis): "+" means the button is triggered when the axis
711 * value is greater than the threshold; "-" means the button is triggered when the axis
712 * value is smaller than the threshold
713 */
714 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
715 const std::string guid = params.Get("guid", "0");
716 const int port = params.Get("port", 0);
717 const auto toggle = params.Get("toggle", false);
718
719 auto joystick = state.GetSDLJoystickByGUID(guid, port);
720
721 if (params.Has("hat")) {
722 const int hat = params.Get("hat", 0);
723 const std::string direction_name = params.Get("direction", "");
724 Uint8 direction;
725 if (direction_name == "up") {
726 direction = SDL_HAT_UP;
727 } else if (direction_name == "down") {
728 direction = SDL_HAT_DOWN;
729 } else if (direction_name == "left") {
730 direction = SDL_HAT_LEFT;
731 } else if (direction_name == "right") {
732 direction = SDL_HAT_RIGHT;
733 } else {
734 direction = 0;
735 }
736 // This is necessary so accessing GetHat with hat won't crash
737 joystick->SetHat(hat, SDL_HAT_CENTERED);
738 return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
739 }
740
741 if (params.Has("axis")) {
742 const int axis = params.Get("axis", 0);
743 // Convert range from (0.0, 1.0) to (-1.0, 1.0)
744 const float threshold = (params.Get("threshold", 0.5f) - 0.5f) * 2.0f;
745 const std::string direction_name = params.Get("direction", "");
746 bool trigger_if_greater;
747 if (direction_name == "+") {
748 trigger_if_greater = true;
749 } else if (direction_name == "-") {
750 trigger_if_greater = false;
751 } else {
752 trigger_if_greater = true;
753 LOG_ERROR(Input, "Unknown direction {}", direction_name);
754 }
755 // This is necessary so accessing GetAxis with axis won't crash
756 joystick->PreSetAxis(axis);
757 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
758 }
759
760 const int button = params.Get("button", 0);
761 // This is necessary so accessing GetButton with button won't crash
762 joystick->PreSetButton(button);
763 return std::make_unique<SDLButton>(joystick, button, toggle);
764 }
765
766private:
767 SDLState& state;
768};
769
770/// An analog device factory that creates analog devices from SDL joystick
771class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
772public:
773 explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
774 /**
775 * Creates an analog device from joystick axes
776 * @param params contains parameters for creating the device:
777 * - "guid": the guid of the joystick to bind
778 * - "port": the nth joystick of the same type
779 * - "axis_x": the index of the axis to be bind as x-axis
780 * - "axis_y": the index of the axis to be bind as y-axis
781 */
782 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
783 const std::string guid = params.Get("guid", "0");
784 const int port = params.Get("port", 0);
785 const int axis_x = params.Get("axis_x", 0);
786 const int axis_y = params.Get("axis_y", 1);
787 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
788 const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
789 const std::string invert_x_value = params.Get("invert_x", "+");
790 const std::string invert_y_value = params.Get("invert_y", "+");
791 const bool invert_x = invert_x_value == "-";
792 const bool invert_y = invert_y_value == "-";
793 const float offset_x = std::clamp(params.Get("offset_x", 0.0f), -0.99f, 0.99f);
794 const float offset_y = std::clamp(params.Get("offset_y", 0.0f), -0.99f, 0.99f);
795 auto joystick = state.GetSDLJoystickByGUID(guid, port);
796
797 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
798 joystick->PreSetAxis(axis_x);
799 joystick->PreSetAxis(axis_y);
800 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone,
801 range, offset_x, offset_y);
802 }
803
804private:
805 SDLState& state;
806};
807
808/// An vibration device factory that creates vibration devices from SDL joystick
809class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
810public:
811 explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
812 /**
813 * Creates a vibration device from a joystick
814 * @param params contains parameters for creating the device:
815 * - "guid": the guid of the joystick to bind
816 * - "port": the nth joystick of the same type
817 */
818 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
819 const std::string guid = params.Get("guid", "0");
820 const int port = params.Get("port", 0);
821 return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
822 }
823
824private:
825 SDLState& state;
826};
827
828/// A motion device factory that creates motion devices from SDL joystick
829class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
830public:
831 explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
832 /**
833 * Creates motion device from joystick axes
834 * @param params contains parameters for creating the device:
835 * - "guid": the guid of the joystick to bind
836 * - "port": the nth joystick of the same type
837 */
838 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
839 const std::string guid = params.Get("guid", "0");
840 const int port = params.Get("port", 0);
841
842 auto joystick = state.GetSDLJoystickByGUID(guid, port);
843
844 if (params.Has("motion")) {
845 return std::make_unique<SDLMotion>(joystick);
846 }
847
848 if (params.Has("hat")) {
849 const int hat = params.Get("hat", 0);
850 const std::string direction_name = params.Get("direction", "");
851 Uint8 direction;
852 if (direction_name == "up") {
853 direction = SDL_HAT_UP;
854 } else if (direction_name == "down") {
855 direction = SDL_HAT_DOWN;
856 } else if (direction_name == "left") {
857 direction = SDL_HAT_LEFT;
858 } else if (direction_name == "right") {
859 direction = SDL_HAT_RIGHT;
860 } else {
861 direction = 0;
862 }
863 // This is necessary so accessing GetHat with hat won't crash
864 joystick->SetHat(hat, SDL_HAT_CENTERED);
865 return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
866 }
867
868 if (params.Has("axis")) {
869 const int axis = params.Get("axis", 0);
870 const float threshold = params.Get("threshold", 0.5f);
871 const std::string direction_name = params.Get("direction", "");
872 bool trigger_if_greater;
873 if (direction_name == "+") {
874 trigger_if_greater = true;
875 } else if (direction_name == "-") {
876 trigger_if_greater = false;
877 } else {
878 trigger_if_greater = true;
879 LOG_ERROR(Input, "Unknown direction {}", direction_name);
880 }
881 // This is necessary so accessing GetAxis with axis won't crash
882 joystick->PreSetAxis(axis);
883 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
884 }
885
886 const int button = params.Get("button", 0);
887 // This is necessary so accessing GetButton with button won't crash
888 joystick->PreSetButton(button);
889 return std::make_unique<SDLButtonMotion>(joystick, button);
890 }
891
892private:
893 SDLState& state;
894};
895
896SDLState::SDLState() {
897 using namespace Input;
898 button_factory = std::make_shared<SDLButtonFactory>(*this);
899 analog_factory = std::make_shared<SDLAnalogFactory>(*this);
900 vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
901 motion_factory = std::make_shared<SDLMotionFactory>(*this);
902 RegisterFactory<ButtonDevice>("sdl", button_factory);
903 RegisterFactory<AnalogDevice>("sdl", analog_factory);
904 RegisterFactory<VibrationDevice>("sdl", vibration_factory);
905 RegisterFactory<MotionDevice>("sdl", motion_factory);
906
907 if (!Settings::values.enable_raw_input) {
908 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
909 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
910 }
911
912 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
913 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
914 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
915
916 // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a
917 // GameController and not a generic one
918 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
919
920 // Turn off Pro controller home led
921 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
922
923 // If the frontend is going to manage the event loop, then we don't start one here
924 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
925 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
926 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
927 return;
928 }
929 has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0;
930 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
931 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
932 }
933
934 SDL_AddEventWatch(&SDLEventWatcher, this);
935
936 initialized = true;
937 if (start_thread) {
938 poll_thread = std::thread([this] {
939 using namespace std::chrono_literals;
940 while (initialized) {
941 SDL_PumpEvents();
942 std::this_thread::sleep_for(1ms);
943 }
944 });
945 }
946 // Because the events for joystick connection happens before we have our event watcher added, we
947 // can just open all the joysticks right here
948 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
949 InitJoystick(i);
950 }
951}
952
953SDLState::~SDLState() {
954 using namespace Input;
955 UnregisterFactory<ButtonDevice>("sdl");
956 UnregisterFactory<AnalogDevice>("sdl");
957 UnregisterFactory<VibrationDevice>("sdl");
958 UnregisterFactory<MotionDevice>("sdl");
959
960 CloseJoysticks();
961 SDL_DelEventWatch(&SDLEventWatcher, this);
962
963 initialized = false;
964 if (start_thread) {
965 poll_thread.join();
966 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
967 }
968}
969
970std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
971 std::scoped_lock lock(joystick_map_mutex);
972 std::vector<Common::ParamPackage> devices;
973 std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
974 for (const auto& [key, value] : joystick_map) {
975 for (const auto& joystick : value) {
976 if (!joystick->GetSDLJoystick()) {
977 continue;
978 }
979 std::string name =
980 fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
981 devices.emplace_back(Common::ParamPackage{
982 {"class", "sdl"},
983 {"display", std::move(name)},
984 {"guid", joystick->GetGUID()},
985 {"port", std::to_string(joystick->GetPort())},
986 });
987 if (joystick->IsJoyconLeft()) {
988 joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
989 }
990 }
991 }
992
993 // Add dual controllers
994 for (const auto& [key, value] : joystick_map) {
995 for (const auto& joystick : value) {
996 if (joystick->IsJoyconRight()) {
997 if (!joycon_pairs.contains(joystick->GetPort())) {
998 continue;
999 }
1000 const auto joystick2 = joycon_pairs.at(joystick->GetPort());
1001
1002 std::string name =
1003 fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
1004 devices.emplace_back(Common::ParamPackage{
1005 {"class", "sdl"},
1006 {"display", std::move(name)},
1007 {"guid", joystick->GetGUID()},
1008 {"guid2", joystick2->GetGUID()},
1009 {"port", std::to_string(joystick->GetPort())},
1010 });
1011 }
1012 }
1013 }
1014 return devices;
1015}
1016
1017namespace {
1018Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
1019 float value = 0.1f) {
1020 Common::ParamPackage params({{"engine", "sdl"}});
1021 params.Set("port", port);
1022 params.Set("guid", std::move(guid));
1023 params.Set("axis", axis);
1024 params.Set("threshold", "0.5");
1025 if (value > 0) {
1026 params.Set("direction", "+");
1027 } else {
1028 params.Set("direction", "-");
1029 }
1030 return params;
1031}
1032
1033Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) {
1034 Common::ParamPackage params({{"engine", "sdl"}});
1035 params.Set("port", port);
1036 params.Set("guid", std::move(guid));
1037 params.Set("button", button);
1038 params.Set("toggle", false);
1039 return params;
1040}
1041
1042Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) {
1043 Common::ParamPackage params({{"engine", "sdl"}});
1044
1045 params.Set("port", port);
1046 params.Set("guid", std::move(guid));
1047 params.Set("hat", hat);
1048 switch (value) {
1049 case SDL_HAT_UP:
1050 params.Set("direction", "up");
1051 break;
1052 case SDL_HAT_DOWN:
1053 params.Set("direction", "down");
1054 break;
1055 case SDL_HAT_LEFT:
1056 params.Set("direction", "left");
1057 break;
1058 case SDL_HAT_RIGHT:
1059 params.Set("direction", "right");
1060 break;
1061 default:
1062 return {};
1063 }
1064 return params;
1065}
1066
1067Common::ParamPackage BuildMotionParam(int port, std::string guid) {
1068 Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}});
1069 params.Set("port", port);
1070 params.Set("guid", std::move(guid));
1071 return params;
1072}
1073
1074Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
1075 switch (event.type) {
1076 case SDL_JOYAXISMOTION: {
1077 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1078 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1079 static_cast<s32>(event.jaxis.axis),
1080 event.jaxis.value);
1081 }
1082 break;
1083 }
1084 case SDL_JOYBUTTONUP: {
1085 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
1086 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1087 static_cast<s32>(event.jbutton.button));
1088 }
1089 break;
1090 }
1091 case SDL_JOYHATMOTION: {
1092 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
1093 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1094 static_cast<s32>(event.jhat.hat),
1095 static_cast<s32>(event.jhat.value));
1096 }
1097 break;
1098 }
1099 }
1100 return {};
1101}
1102
1103Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
1104 switch (event.type) {
1105 case SDL_JOYAXISMOTION: {
1106 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1107 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1108 static_cast<s32>(event.jaxis.axis),
1109 event.jaxis.value);
1110 }
1111 break;
1112 }
1113 case SDL_JOYBUTTONUP: {
1114 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
1115 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1116 static_cast<s32>(event.jbutton.button));
1117 }
1118 break;
1119 }
1120 case SDL_JOYHATMOTION: {
1121 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
1122 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1123 static_cast<s32>(event.jhat.hat),
1124 static_cast<s32>(event.jhat.value));
1125 }
1126 break;
1127 }
1128 case SDL_CONTROLLERSENSORUPDATE: {
1129 bool is_motion_shaking = false;
1130 constexpr float gyro_threshold = 5.0f;
1131 constexpr float accel_threshold = 11.0f;
1132 if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
1133 const Common::Vec3f acceleration = {-event.csensor.data[0], event.csensor.data[2],
1134 -event.csensor.data[1]};
1135 if (acceleration.Length() > accel_threshold) {
1136 is_motion_shaking = true;
1137 }
1138 }
1139
1140 if (event.csensor.sensor == SDL_SENSOR_GYRO) {
1141 const Common::Vec3f gyroscope = {event.csensor.data[0], -event.csensor.data[2],
1142 event.csensor.data[1]};
1143 if (gyroscope.Length() > gyro_threshold) {
1144 is_motion_shaking = true;
1145 }
1146 }
1147
1148 if (!is_motion_shaking) {
1149 break;
1150 }
1151
1152 if (const auto joystick = state.GetSDLJoystickBySDLID(event.csensor.which)) {
1153 return BuildMotionParam(joystick->GetPort(), joystick->GetGUID());
1154 }
1155 break;
1156 }
1157 }
1158 return {};
1159}
1160
1161Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
1162 const SDL_GameControllerButtonBind& binding) {
1163 switch (binding.bindType) {
1164 case SDL_CONTROLLER_BINDTYPE_NONE:
1165 break;
1166 case SDL_CONTROLLER_BINDTYPE_AXIS:
1167 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
1168 case SDL_CONTROLLER_BINDTYPE_BUTTON:
1169 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
1170 case SDL_CONTROLLER_BINDTYPE_HAT:
1171 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
1172 binding.value.hat.hat_mask);
1173 }
1174 return {};
1175}
1176
1177Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
1178 int axis_y, float offset_x, float offset_y) {
1179 Common::ParamPackage params;
1180 params.Set("engine", "sdl");
1181 params.Set("port", port);
1182 params.Set("guid", guid);
1183 params.Set("axis_x", axis_x);
1184 params.Set("axis_y", axis_y);
1185 params.Set("offset_x", offset_x);
1186 params.Set("offset_y", offset_y);
1187 params.Set("invert_x", "+");
1188 params.Set("invert_y", "+");
1189 return params;
1190}
1191} // Anonymous namespace
1192
1193ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
1194 if (!params.Has("guid") || !params.Has("port")) {
1195 return {};
1196 }
1197 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1198
1199 auto* controller = joystick->GetSDLGameController();
1200 if (controller == nullptr) {
1201 return {};
1202 }
1203
1204 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
1205 // We will add those afterwards
1206 // This list also excludes Screenshot since theres not really a mapping for that
1207 ButtonBindings switch_to_sdl_button;
1208
1209 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
1210 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
1211 } else {
1212 switch_to_sdl_button = GetDefaultButtonBinding();
1213 }
1214
1215 // Add the missing bindings for ZL/ZR
1216 static constexpr ZButtonBindings switch_to_sdl_axis{{
1217 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
1218 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
1219 }};
1220
1221 // Parameters contain two joysticks return dual
1222 if (params.Has("guid2")) {
1223 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1224
1225 if (joystick2->GetSDLGameController() != nullptr) {
1226 return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
1227 switch_to_sdl_axis);
1228 }
1229 }
1230
1231 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
1232}
1233
1234ButtonBindings SDLState::GetDefaultButtonBinding() const {
1235 return {
1236 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
1237 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
1238 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
1239 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
1240 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
1241 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
1242 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1243 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1244 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
1245 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
1246 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
1247 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
1248 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
1249 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
1250 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1251 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1252 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
1253 };
1254}
1255
1256ButtonBindings SDLState::GetNintendoButtonBinding(
1257 const std::shared_ptr<SDLJoystick>& joystick) const {
1258 // Default SL/SR mapping for pro controllers
1259 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
1260 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
1261
1262 if (joystick->IsJoyconLeft()) {
1263 sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
1264 sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
1265 }
1266 if (joystick->IsJoyconRight()) {
1267 sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
1268 sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
1269 }
1270
1271 return {
1272 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
1273 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
1274 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
1275 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
1276 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
1277 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
1278 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1279 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1280 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
1281 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
1282 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
1283 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
1284 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
1285 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
1286 {Settings::NativeButton::SL, sl_button},
1287 {Settings::NativeButton::SR, sr_button},
1288 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
1289 };
1290}
1291
1292ButtonMapping SDLState::GetSingleControllerMapping(
1293 const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
1294 const ZButtonBindings& switch_to_sdl_axis) const {
1295 ButtonMapping mapping;
1296 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
1297 auto* controller = joystick->GetSDLGameController();
1298
1299 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
1300 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
1301 mapping.insert_or_assign(
1302 switch_button,
1303 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1304 }
1305 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
1306 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
1307 mapping.insert_or_assign(
1308 switch_button,
1309 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1310 }
1311
1312 return mapping;
1313}
1314
1315ButtonMapping SDLState::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
1316 const std::shared_ptr<SDLJoystick>& joystick2,
1317 const ButtonBindings& switch_to_sdl_button,
1318 const ZButtonBindings& switch_to_sdl_axis) const {
1319 ButtonMapping mapping;
1320 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
1321 auto* controller = joystick->GetSDLGameController();
1322 auto* controller2 = joystick2->GetSDLGameController();
1323
1324 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
1325 if (IsButtonOnLeftSide(switch_button)) {
1326 const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
1327 mapping.insert_or_assign(
1328 switch_button,
1329 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
1330 continue;
1331 }
1332 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
1333 mapping.insert_or_assign(
1334 switch_button,
1335 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1336 }
1337 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
1338 if (IsButtonOnLeftSide(switch_button)) {
1339 const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
1340 mapping.insert_or_assign(
1341 switch_button,
1342 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
1343 continue;
1344 }
1345 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
1346 mapping.insert_or_assign(
1347 switch_button,
1348 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1349 }
1350
1351 return mapping;
1352}
1353
1354bool SDLState::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
1355 switch (button) {
1356 case Settings::NativeButton::DDown:
1357 case Settings::NativeButton::DLeft:
1358 case Settings::NativeButton::DRight:
1359 case Settings::NativeButton::DUp:
1360 case Settings::NativeButton::L:
1361 case Settings::NativeButton::LStick:
1362 case Settings::NativeButton::Minus:
1363 case Settings::NativeButton::Screenshot:
1364 case Settings::NativeButton::ZL:
1365 return true;
1366 default:
1367 return false;
1368 }
1369}
1370
1371AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
1372 if (!params.Has("guid") || !params.Has("port")) {
1373 return {};
1374 }
1375 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1376 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1377 auto* controller = joystick->GetSDLGameController();
1378 if (controller == nullptr) {
1379 return {};
1380 }
1381
1382 AnalogMapping mapping = {};
1383 const auto& binding_left_x =
1384 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
1385 const auto& binding_left_y =
1386 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
1387 if (params.Has("guid2")) {
1388 joystick2->PreSetAxis(binding_left_x.value.axis);
1389 joystick2->PreSetAxis(binding_left_y.value.axis);
1390 const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1391 const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1392 mapping.insert_or_assign(
1393 Settings::NativeAnalog::LStick,
1394 BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(),
1395 binding_left_x.value.axis, binding_left_y.value.axis,
1396 left_offset_x, left_offset_y));
1397 } else {
1398 joystick->PreSetAxis(binding_left_x.value.axis);
1399 joystick->PreSetAxis(binding_left_y.value.axis);
1400 const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1401 const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1402 mapping.insert_or_assign(
1403 Settings::NativeAnalog::LStick,
1404 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1405 binding_left_x.value.axis, binding_left_y.value.axis,
1406 left_offset_x, left_offset_y));
1407 }
1408 const auto& binding_right_x =
1409 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
1410 const auto& binding_right_y =
1411 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
1412 joystick->PreSetAxis(binding_right_x.value.axis);
1413 joystick->PreSetAxis(binding_right_y.value.axis);
1414 const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0);
1415 const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0);
1416 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
1417 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1418 binding_right_x.value.axis,
1419 binding_right_y.value.axis, right_offset_x,
1420 right_offset_y));
1421 return mapping;
1422}
1423
1424MotionMapping SDLState::GetMotionMappingForDevice(const Common::ParamPackage& params) {
1425 if (!params.Has("guid") || !params.Has("port")) {
1426 return {};
1427 }
1428 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1429 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1430 auto* controller = joystick->GetSDLGameController();
1431 if (controller == nullptr) {
1432 return {};
1433 }
1434
1435 MotionMapping mapping = {};
1436 joystick->EnableMotion();
1437
1438 if (joystick->HasGyro() || joystick->HasAccel()) {
1439 mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
1440 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
1441 }
1442 if (params.Has("guid2")) {
1443 joystick2->EnableMotion();
1444 if (joystick2->HasGyro() || joystick2->HasAccel()) {
1445 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
1446 BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
1447 }
1448 } else {
1449 if (joystick->HasGyro() || joystick->HasAccel()) {
1450 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
1451 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
1452 }
1453 }
1454
1455 return mapping;
1456}
1457namespace Polling {
1458class SDLPoller : public InputCommon::Polling::DevicePoller {
1459public:
1460 explicit SDLPoller(SDLState& state_) : state(state_) {}
1461
1462 void Start([[maybe_unused]] const std::string& device_id) override {
1463 state.event_queue.Clear();
1464 state.polling = true;
1465 }
1466
1467 void Stop() override {
1468 state.polling = false;
1469 }
1470
1471protected:
1472 SDLState& state;
1473};
1474
1475class SDLButtonPoller final : public SDLPoller {
1476public:
1477 explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
1478
1479 Common::ParamPackage GetNextInput() override {
1480 SDL_Event event;
1481 while (state.event_queue.Pop(event)) {
1482 const auto package = FromEvent(event);
1483 if (package) {
1484 return *package;
1485 }
1486 }
1487 return {};
1488 }
1489 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) {
1490 switch (event.type) {
1491 case SDL_JOYAXISMOTION:
1492 if (!axis_memory.count(event.jaxis.which) ||
1493 !axis_memory[event.jaxis.which].count(event.jaxis.axis)) {
1494 axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value;
1495 axis_event_count[event.jaxis.which][event.jaxis.axis] = 1;
1496 break;
1497 } else {
1498 axis_event_count[event.jaxis.which][event.jaxis.axis]++;
1499 // The joystick and axis exist in our map if we take this branch, so no checks
1500 // needed
1501 if (std::abs(
1502 (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) /
1503 32767.0) < 0.5) {
1504 break;
1505 } else {
1506 if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 &&
1507 IsAxisAtPole(event.jaxis.value) &&
1508 IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) {
1509 // If we have exactly two events and both are near a pole, this is
1510 // likely a digital input masquerading as an analog axis; Instead of
1511 // trying to look at the direction the axis travelled, assume the first
1512 // event was press and the second was release; This should handle most
1513 // digital axes while deferring to the direction of travel for analog
1514 // axes
1515 event.jaxis.value = static_cast<Sint16>(
1516 std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis]));
1517 } else {
1518 // There are more than two events, so this is likely a true analog axis,
1519 // check the direction it travelled
1520 event.jaxis.value = static_cast<Sint16>(std::copysign(
1521 32767,
1522 event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]));
1523 }
1524 axis_memory.clear();
1525 axis_event_count.clear();
1526 }
1527 }
1528 [[fallthrough]];
1529 case SDL_JOYBUTTONUP:
1530 case SDL_JOYHATMOTION:
1531 return {SDLEventToButtonParamPackage(state, event)};
1532 }
1533 return std::nullopt;
1534 }
1535
1536private:
1537 // Determine whether an axis value is close to an extreme or center
1538 // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per
1539 // axis, which is why the center must be considered a pole
1540 bool IsAxisAtPole(int16_t value) const {
1541 return std::abs(value) >= 32767 || std::abs(value) < 327;
1542 }
1543 std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
1544 std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count;
1545};
1546
1547class SDLMotionPoller final : public SDLPoller {
1548public:
1549 explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
1550
1551 Common::ParamPackage GetNextInput() override {
1552 SDL_Event event;
1553 while (state.event_queue.Pop(event)) {
1554 const auto package = FromEvent(event);
1555 if (package) {
1556 return *package;
1557 }
1558 }
1559 return {};
1560 }
1561 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
1562 switch (event.type) {
1563 case SDL_JOYAXISMOTION:
1564 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1565 break;
1566 }
1567 [[fallthrough]];
1568 case SDL_JOYBUTTONUP:
1569 case SDL_JOYHATMOTION:
1570 case SDL_CONTROLLERSENSORUPDATE:
1571 return {SDLEventToMotionParamPackage(state, event)};
1572 }
1573 return std::nullopt;
1574 }
1575};
1576
1577/**
1578 * Attempts to match the press to a controller joy axis (left/right stick) and if a match
1579 * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
1580 * instead
1581 */
1582class SDLAnalogPreferredPoller final : public SDLPoller {
1583public:
1584 explicit SDLAnalogPreferredPoller(SDLState& state_)
1585 : SDLPoller(state_), button_poller(state_) {}
1586
1587 void Start(const std::string& device_id) override {
1588 SDLPoller::Start(device_id);
1589 // Reset stored axes
1590 first_axis = -1;
1591 }
1592
1593 Common::ParamPackage GetNextInput() override {
1594 SDL_Event event;
1595 while (state.event_queue.Pop(event)) {
1596 if (event.type != SDL_JOYAXISMOTION) {
1597 // Check for a button press
1598 auto button_press = button_poller.FromEvent(event);
1599 if (button_press) {
1600 return *button_press;
1601 }
1602 continue;
1603 }
1604 const auto axis = event.jaxis.axis;
1605
1606 // Filter out axis events that are below a threshold
1607 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1608 continue;
1609 }
1610
1611 // Filter out axis events that are the same
1612 if (first_axis == axis) {
1613 continue;
1614 }
1615
1616 // In order to return a complete analog param, we need inputs for both axes.
1617 // If the first axis isn't set we set the value then wait till next event
1618 if (first_axis == -1) {
1619 first_axis = axis;
1620 continue;
1621 }
1622
1623 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1624 // Set offset to zero since the joystick is not on center
1625 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1626 first_axis, axis, 0, 0);
1627 first_axis = -1;
1628 return params;
1629 }
1630 }
1631 return {};
1632 }
1633
1634private:
1635 int first_axis = -1;
1636 SDLButtonPoller button_poller;
1637};
1638} // namespace Polling
1639
1640SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
1641 Pollers pollers;
1642
1643 switch (type) {
1644 case InputCommon::Polling::DeviceType::AnalogPreferred:
1645 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
1646 break;
1647 case InputCommon::Polling::DeviceType::Button:
1648 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
1649 break;
1650 case InputCommon::Polling::DeviceType::Motion:
1651 pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
1652 break;
1653 }
1654
1655 return pollers;
1656}
1657
1658} // namespace InputCommon::SDL
diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp
deleted file mode 100644
index 1598092b6..000000000
--- a/src/input_common/tas/tas_input.cpp
+++ /dev/null
@@ -1,455 +0,0 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <regex>
7
8#include "common/fs/file.h"
9#include "common/fs/fs_types.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/tas/tas_input.h"
14
15namespace TasInput {
16
17// Supported keywords and buttons from a TAS file
18constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
19 std::pair{"KEY_A", TasButton::BUTTON_A},
20 {"KEY_B", TasButton::BUTTON_B},
21 {"KEY_X", TasButton::BUTTON_X},
22 {"KEY_Y", TasButton::BUTTON_Y},
23 {"KEY_LSTICK", TasButton::STICK_L},
24 {"KEY_RSTICK", TasButton::STICK_R},
25 {"KEY_L", TasButton::TRIGGER_L},
26 {"KEY_R", TasButton::TRIGGER_R},
27 {"KEY_PLUS", TasButton::BUTTON_PLUS},
28 {"KEY_MINUS", TasButton::BUTTON_MINUS},
29 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
30 {"KEY_DUP", TasButton::BUTTON_UP},
31 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
32 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
33 {"KEY_SL", TasButton::BUTTON_SL},
34 {"KEY_SR", TasButton::BUTTON_SR},
35 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
36 {"KEY_HOME", TasButton::BUTTON_HOME},
37 {"KEY_ZL", TasButton::TRIGGER_ZL},
38 {"KEY_ZR", TasButton::TRIGGER_ZR},
39};
40
41Tas::Tas() {
42 if (!Settings::values.tas_enable) {
43 needs_reset = true;
44 return;
45 }
46 LoadTasFiles();
47}
48
49Tas::~Tas() {
50 Stop();
51};
52
53void Tas::LoadTasFiles() {
54 script_length = 0;
55 for (size_t i = 0; i < commands.size(); i++) {
56 LoadTasFile(i);
57 if (commands[i].size() > script_length) {
58 script_length = commands[i].size();
59 }
60 }
61}
62
63void Tas::LoadTasFile(size_t player_index) {
64 if (!commands[player_index].empty()) {
65 commands[player_index].clear();
66 }
67 std::string file =
68 Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
69 fmt::format("script0-{}.txt", player_index + 1),
70 Common::FS::FileType::BinaryFile);
71 std::stringstream command_line(file);
72 std::string line;
73 int frame_no = 0;
74 while (std::getline(command_line, line, '\n')) {
75 if (line.empty()) {
76 continue;
77 }
78 LOG_DEBUG(Input, "Loading line: {}", line);
79 std::smatch m;
80
81 std::stringstream linestream(line);
82 std::string segment;
83 std::vector<std::string> seglist;
84
85 while (std::getline(linestream, segment, ' ')) {
86 seglist.push_back(segment);
87 }
88
89 if (seglist.size() < 4) {
90 continue;
91 }
92
93 while (frame_no < std::stoi(seglist.at(0))) {
94 commands[player_index].push_back({});
95 frame_no++;
96 }
97
98 TASCommand command = {
99 .buttons = ReadCommandButtons(seglist.at(1)),
100 .l_axis = ReadCommandAxis(seglist.at(2)),
101 .r_axis = ReadCommandAxis(seglist.at(3)),
102 };
103 commands[player_index].push_back(command);
104 frame_no++;
105 }
106 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
107}
108
109void Tas::WriteTasFile(std::u8string file_name) {
110 std::string output_text;
111 for (size_t frame = 0; frame < record_commands.size(); frame++) {
112 if (!output_text.empty()) {
113 output_text += "\n";
114 }
115 const TASCommand& line = record_commands[frame];
116 output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
117 WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
118 }
119 const auto bytes_written = Common::FS::WriteStringToFile(
120 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
121 Common::FS::FileType::TextFile, output_text);
122 if (bytes_written == output_text.size()) {
123 LOG_INFO(Input, "TAS file written to file!");
124 } else {
125 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
126 output_text.size());
127 }
128}
129
130std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) {
131 auto [x, y] = old;
132 return {x, -y};
133}
134
135void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
136 last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])};
137}
138
139std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
140 TasState state;
141 if (is_recording) {
142 return {TasState::Recording, 0, record_commands.size()};
143 }
144
145 if (is_running) {
146 state = TasState::Running;
147 } else {
148 state = TasState::Stopped;
149 }
150
151 return {state, current_command, script_length};
152}
153
154std::string Tas::DebugButtons(u32 buttons) const {
155 return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons));
156}
157
158std::string Tas::DebugJoystick(float x, float y) const {
159 return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y));
160}
161
162std::string Tas::DebugInput(const TasData& data) const {
163 return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons),
164 DebugJoystick(data.axis[0], data.axis[1]),
165 DebugJoystick(data.axis[2], data.axis[3]));
166}
167
168std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const {
169 std::string returns = "[ ";
170 for (size_t i = 0; i < arr.size(); i++) {
171 returns += DebugInput(arr[i]);
172 if (i != arr.size() - 1) {
173 returns += " , ";
174 }
175 }
176 return returns + "]";
177}
178
179std::string Tas::ButtonsToString(u32 button) const {
180 std::string returns;
181 for (auto [text_button, tas_button] : text_to_tas_button) {
182 if ((button & static_cast<u32>(tas_button)) != 0)
183 returns += fmt::format(", {}", text_button.substr(4));
184 }
185 return returns.empty() ? "" : returns.substr(2);
186}
187
188void Tas::UpdateThread() {
189 if (!Settings::values.tas_enable) {
190 if (is_running) {
191 Stop();
192 }
193 return;
194 }
195
196 if (is_recording) {
197 record_commands.push_back(last_input);
198 }
199 if (needs_reset) {
200 current_command = 0;
201 needs_reset = false;
202 LoadTasFiles();
203 LOG_DEBUG(Input, "tas_reset done");
204 }
205
206 if (!is_running) {
207 tas_data.fill({});
208 return;
209 }
210 if (current_command < script_length) {
211 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
212 size_t frame = current_command++;
213 for (size_t i = 0; i < commands.size(); i++) {
214 if (frame < commands[i].size()) {
215 TASCommand command = commands[i][frame];
216 tas_data[i].buttons = command.buttons;
217 auto [l_axis_x, l_axis_y] = command.l_axis;
218 tas_data[i].axis[0] = l_axis_x;
219 tas_data[i].axis[1] = l_axis_y;
220 auto [r_axis_x, r_axis_y] = command.r_axis;
221 tas_data[i].axis[2] = r_axis_x;
222 tas_data[i].axis[3] = r_axis_y;
223 } else {
224 tas_data[i] = {};
225 }
226 }
227 } else {
228 is_running = Settings::values.tas_loop.GetValue();
229 current_command = 0;
230 tas_data.fill({});
231 if (!is_running) {
232 SwapToStoredController();
233 }
234 }
235 LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
236}
237
238TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
239 std::stringstream linestream(line);
240 std::string segment;
241 std::vector<std::string> seglist;
242
243 while (std::getline(linestream, segment, ';')) {
244 seglist.push_back(segment);
245 }
246
247 const float x = std::stof(seglist.at(0)) / 32767.0f;
248 const float y = std::stof(seglist.at(1)) / 32767.0f;
249
250 return {x, y};
251}
252
253u32 Tas::ReadCommandButtons(const std::string& data) const {
254 std::stringstream button_text(data);
255 std::string line;
256 u32 buttons = 0;
257 while (std::getline(button_text, line, ';')) {
258 for (auto [text, tas_button] : text_to_tas_button) {
259 if (text == line) {
260 buttons |= static_cast<u32>(tas_button);
261 break;
262 }
263 }
264 }
265 return buttons;
266}
267
268std::string Tas::WriteCommandAxis(TasAnalog data) const {
269 auto [x, y] = data;
270 std::string line;
271 line += std::to_string(static_cast<int>(x * 32767));
272 line += ";";
273 line += std::to_string(static_cast<int>(y * 32767));
274 return line;
275}
276
277std::string Tas::WriteCommandButtons(u32 data) const {
278 if (data == 0) {
279 return "NONE";
280 }
281
282 std::string line;
283 u32 index = 0;
284 while (data > 0) {
285 if ((data & 1) == 1) {
286 for (auto [text, tas_button] : text_to_tas_button) {
287 if (tas_button == static_cast<TasButton>(1 << index)) {
288 if (line.size() > 0) {
289 line += ";";
290 }
291 line += text;
292 break;
293 }
294 }
295 }
296 index++;
297 data >>= 1;
298 }
299 return line;
300}
301
302void Tas::StartStop() {
303 if (!Settings::values.tas_enable) {
304 return;
305 }
306 if (is_running) {
307 Stop();
308 } else {
309 is_running = true;
310 SwapToTasController();
311 }
312}
313
314void Tas::Stop() {
315 is_running = false;
316 SwapToStoredController();
317}
318
319void Tas::SwapToTasController() {
320 if (!Settings::values.tas_swap_controllers) {
321 return;
322 }
323 auto& players = Settings::values.players.GetValue();
324 for (std::size_t index = 0; index < players.size(); index++) {
325 auto& player = players[index];
326 player_mappings[index] = player;
327
328 // Only swap active controllers
329 if (!player.connected) {
330 continue;
331 }
332
333 Common::ParamPackage tas_param;
334 tas_param.Set("pad", static_cast<u8>(index));
335 auto button_mapping = GetButtonMappingForDevice(tas_param);
336 auto analog_mapping = GetAnalogMappingForDevice(tas_param);
337 auto& buttons = player.buttons;
338 auto& analogs = player.analogs;
339
340 for (std::size_t i = 0; i < buttons.size(); ++i) {
341 buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize();
342 }
343 for (std::size_t i = 0; i < analogs.size(); ++i) {
344 analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
345 }
346 }
347 is_old_input_saved = true;
348 Settings::values.is_device_reload_pending.store(true);
349}
350
351void Tas::SwapToStoredController() {
352 if (!is_old_input_saved) {
353 return;
354 }
355 auto& players = Settings::values.players.GetValue();
356 for (std::size_t index = 0; index < players.size(); index++) {
357 players[index] = player_mappings[index];
358 }
359 is_old_input_saved = false;
360 Settings::values.is_device_reload_pending.store(true);
361}
362
363void Tas::Reset() {
364 if (!Settings::values.tas_enable) {
365 return;
366 }
367 needs_reset = true;
368}
369
370bool Tas::Record() {
371 if (!Settings::values.tas_enable) {
372 return true;
373 }
374 is_recording = !is_recording;
375 return is_recording;
376}
377
378void Tas::SaveRecording(bool overwrite_file) {
379 if (is_recording) {
380 return;
381 }
382 if (record_commands.empty()) {
383 return;
384 }
385 WriteTasFile(u8"record.txt");
386 if (overwrite_file) {
387 WriteTasFile(u8"script0-1.txt");
388 }
389 needs_reset = true;
390 record_commands.clear();
391}
392
393InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
394 const Common::ParamPackage& params) const {
395 // This list is missing ZL/ZR since those are not considered buttons.
396 // We will add those afterwards
397 // This list also excludes any button that can't be really mapped
398 static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20>
399 switch_to_tas_button = {
400 std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
401 {Settings::NativeButton::B, TasButton::BUTTON_B},
402 {Settings::NativeButton::X, TasButton::BUTTON_X},
403 {Settings::NativeButton::Y, TasButton::BUTTON_Y},
404 {Settings::NativeButton::LStick, TasButton::STICK_L},
405 {Settings::NativeButton::RStick, TasButton::STICK_R},
406 {Settings::NativeButton::L, TasButton::TRIGGER_L},
407 {Settings::NativeButton::R, TasButton::TRIGGER_R},
408 {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
409 {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
410 {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
411 {Settings::NativeButton::DUp, TasButton::BUTTON_UP},
412 {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
413 {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
414 {Settings::NativeButton::SL, TasButton::BUTTON_SL},
415 {Settings::NativeButton::SR, TasButton::BUTTON_SR},
416 {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
417 {Settings::NativeButton::Home, TasButton::BUTTON_HOME},
418 {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
419 {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
420 };
421
422 InputCommon::ButtonMapping mapping{};
423 for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
424 Common::ParamPackage button_params({{"engine", "tas"}});
425 button_params.Set("pad", params.Get("pad", 0));
426 button_params.Set("button", static_cast<int>(tas_button));
427 mapping.insert_or_assign(switch_button, std::move(button_params));
428 }
429
430 return mapping;
431}
432
433InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
434 const Common::ParamPackage& params) const {
435
436 InputCommon::AnalogMapping mapping = {};
437 Common::ParamPackage left_analog_params;
438 left_analog_params.Set("engine", "tas");
439 left_analog_params.Set("pad", params.Get("pad", 0));
440 left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
441 left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
442 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
443 Common::ParamPackage right_analog_params;
444 right_analog_params.Set("engine", "tas");
445 right_analog_params.Set("pad", params.Get("pad", 0));
446 right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
447 right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
448 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
449 return mapping;
450}
451
452const TasData& Tas::GetTasState(std::size_t pad) const {
453 return tas_data[pad];
454}
455} // namespace TasInput
diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h
deleted file mode 100644
index 3e2db8f00..000000000
--- a/src/input_common/tas/tas_input.h
+++ /dev/null
@@ -1,237 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8
9#include "common/common_types.h"
10#include "common/settings_input.h"
11#include "core/frontend/input.h"
12#include "input_common/main.h"
13
14/*
15To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
16Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
17for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
18
19A script file has the same format as TAS-nx uses, so final files will look like this:
20
211 KEY_B 0;0 0;0
226 KEY_ZL 0;0 0;0
2341 KEY_ZL;KEY_Y 0;0 0;0
2443 KEY_X;KEY_A 32767;0 0;0
2544 KEY_A 32767;0 0;0
2645 KEY_A 32767;0 0;0
2746 KEY_A 32767;0 0;0
2847 KEY_A 32767;0 0;0
29
30After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
31CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
32has. Playback can be started or stopped using CTRL+F5.
33
34However, for playback to actually work, the correct input device has to be selected: In the Controls
35menu, select TAS from the device list for the controller that the script should be played on.
36
37Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
38connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
39again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
40record.txt.
41
42For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
43P1).
44*/
45
46namespace TasInput {
47
48constexpr size_t PLAYER_NUMBER = 8;
49
50using TasAnalog = std::pair<float, float>;
51
52enum class TasState {
53 Running,
54 Recording,
55 Stopped,
56};
57
58enum class TasButton : u32 {
59 BUTTON_A = 1U << 0,
60 BUTTON_B = 1U << 1,
61 BUTTON_X = 1U << 2,
62 BUTTON_Y = 1U << 3,
63 STICK_L = 1U << 4,
64 STICK_R = 1U << 5,
65 TRIGGER_L = 1U << 6,
66 TRIGGER_R = 1U << 7,
67 TRIGGER_ZL = 1U << 8,
68 TRIGGER_ZR = 1U << 9,
69 BUTTON_PLUS = 1U << 10,
70 BUTTON_MINUS = 1U << 11,
71 BUTTON_LEFT = 1U << 12,
72 BUTTON_UP = 1U << 13,
73 BUTTON_RIGHT = 1U << 14,
74 BUTTON_DOWN = 1U << 15,
75 BUTTON_SL = 1U << 16,
76 BUTTON_SR = 1U << 17,
77 BUTTON_HOME = 1U << 18,
78 BUTTON_CAPTURE = 1U << 19,
79};
80
81enum class TasAxes : u8 {
82 StickX,
83 StickY,
84 SubstickX,
85 SubstickY,
86 Undefined,
87};
88
89struct TasData {
90 u32 buttons{};
91 std::array<float, 4> axis{};
92};
93
94class Tas {
95public:
96 Tas();
97 ~Tas();
98
99 // Changes the input status that will be stored in each frame
100 void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
101
102 // Main loop that records or executes input
103 void UpdateThread();
104
105 // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
106 void StartStop();
107
108 // Stop the TAS and reverts any controller profile
109 void Stop();
110
111 // Sets the flag to reload the file and start from the begining in the next update
112 void Reset();
113
114 /**
115 * Sets the flag to enable or disable recording of inputs
116 * @return Returns true if the current recording status is enabled
117 */
118 bool Record();
119
120 // Saves contents of record_commands on a file if overwrite is enabled player 1 will be
121 // overwritten with the recorded commands
122 void SaveRecording(bool overwrite_file);
123
124 /**
125 * Returns the current status values of TAS playback/recording
126 * @return Tuple of
127 * TasState indicating the current state out of Running, Recording or Stopped ;
128 * Current playback progress or amount of frames (so far) for Recording ;
129 * Total length of script file currently loaded or amount of frames (so far) for Recording
130 */
131 std::tuple<TasState, size_t, size_t> GetStatus() const;
132
133 // Retuns an array of the default button mappings
134 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
135
136 // Retuns an array of the default analog mappings
137 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
138 [[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
139
140private:
141 struct TASCommand {
142 u32 buttons{};
143 TasAnalog l_axis{};
144 TasAnalog r_axis{};
145 };
146
147 // Loads TAS files from all players
148 void LoadTasFiles();
149
150 // Loads TAS file from the specified player
151 void LoadTasFile(size_t player_index);
152
153 // Writes a TAS file from the recorded commands
154 void WriteTasFile(std::u8string file_name);
155
156 /**
157 * Parses a string containing the axis values with the following format "x;y"
158 * X and Y have a range from -32767 to 32767
159 * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
160 */
161 TasAnalog ReadCommandAxis(const std::string& line) const;
162
163 /**
164 * Parses a string containing the button values with the following format "a;b;c;d..."
165 * Each button is represented by it's text format specified in text_to_tas_button array
166 * @return Returns a u32 with each bit representing the status of a button
167 */
168 u32 ReadCommandButtons(const std::string& line) const;
169
170 /**
171 * Converts an u32 containing the button status into the text equivalent
172 * @return Returns a string with the name of the buttons to be written to the file
173 */
174 std::string WriteCommandButtons(u32 data) const;
175
176 /**
177 * Converts an TAS analog object containing the axis status into the text equivalent
178 * @return Returns a string with the value of the axis to be written to the file
179 */
180 std::string WriteCommandAxis(TasAnalog data) const;
181
182 // Inverts the Y axis polarity
183 std::pair<float, float> FlipAxisY(std::pair<float, float> old);
184
185 /**
186 * Converts an u32 containing the button status into the text equivalent
187 * @return Returns a string with the name of the buttons to be printed on console
188 */
189 std::string DebugButtons(u32 buttons) const;
190
191 /**
192 * Converts an TAS analog object containing the axis status into the text equivalent
193 * @return Returns a string with the value of the axis to be printed on console
194 */
195 std::string DebugJoystick(float x, float y) const;
196
197 /**
198 * Converts the given TAS status into the text equivalent
199 * @return Returns a string with the value of the TAS status to be printed on console
200 */
201 std::string DebugInput(const TasData& data) const;
202
203 /**
204 * Converts the given TAS status of multiple players into the text equivalent
205 * @return Returns a string with the value of the status of all TAS players to be printed on
206 * console
207 */
208 std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
209
210 /**
211 * Converts an u32 containing the button status into the text equivalent
212 * @return Returns a string with the name of the buttons
213 */
214 std::string ButtonsToString(u32 button) const;
215
216 // Stores current controller configuration and sets a TAS controller for every active controller
217 // to the current config
218 void SwapToTasController();
219
220 // Sets the stored controller configuration to the current config
221 void SwapToStoredController();
222
223 size_t script_length{0};
224 std::array<TasData, PLAYER_NUMBER> tas_data;
225 bool is_old_input_saved{false};
226 bool is_recording{false};
227 bool is_running{false};
228 bool needs_reset{false};
229 std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
230 std::vector<TASCommand> record_commands{};
231 size_t current_command{0};
232 TASCommand last_input{}; // only used for recording
233
234 // Old settings for swapping controllers
235 std::array<Settings::PlayerInput, 10> player_mappings;
236};
237} // namespace TasInput
diff --git a/src/input_common/tas/tas_poller.cpp b/src/input_common/tas/tas_poller.cpp
deleted file mode 100644
index 15810d6b0..000000000
--- a/src/input_common/tas/tas_poller.cpp
+++ /dev/null
@@ -1,101 +0,0 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7
8#include "common/settings.h"
9#include "common/threadsafe_queue.h"
10#include "input_common/tas/tas_input.h"
11#include "input_common/tas/tas_poller.h"
12
13namespace InputCommon {
14
15class TasButton final : public Input::ButtonDevice {
16public:
17 explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_)
18 : button(button_), pad(pad_), tas_input(tas_input_) {}
19
20 bool GetStatus() const override {
21 return (tas_input->GetTasState(pad).buttons & button) != 0;
22 }
23
24private:
25 const u32 button;
26 const u32 pad;
27 const TasInput::Tas* tas_input;
28};
29
30TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_)
31 : tas_input(std::move(tas_input_)) {}
32
33std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) {
34 const auto button_id = params.Get("button", 0);
35 const auto pad = params.Get("pad", 0);
36
37 return std::make_unique<TasButton>(button_id, pad, tas_input.get());
38}
39
40class TasAnalog final : public Input::AnalogDevice {
41public:
42 explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_)
43 : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {}
44
45 float GetAxis(u32 axis) const {
46 std::lock_guard lock{mutex};
47 return tas_input->GetTasState(pad).axis.at(axis);
48 }
49
50 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
51 float x = GetAxis(analog_axis_x);
52 float y = GetAxis(analog_axis_y);
53
54 // Make sure the coordinates are in the unit circle,
55 // otherwise normalize it.
56 float r = x * x + y * y;
57 if (r > 1.0f) {
58 r = std::sqrt(r);
59 x /= r;
60 y /= r;
61 }
62
63 return {x, y};
64 }
65
66 std::tuple<float, float> GetStatus() const override {
67 return GetAnalog(axis_x, axis_y);
68 }
69
70 Input::AnalogProperties GetAnalogProperties() const override {
71 return {0.0f, 1.0f, 0.5f};
72 }
73
74private:
75 const u32 pad;
76 const u32 axis_x;
77 const u32 axis_y;
78 const TasInput::Tas* tas_input;
79 mutable std::mutex mutex;
80};
81
82/// An analog device factory that creates analog devices from GC Adapter
83TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_)
84 : tas_input(std::move(tas_input_)) {}
85
86/**
87 * Creates analog device from joystick axes
88 * @param params contains parameters for creating the device:
89 * - "port": the nth gcpad on the adapter
90 * - "axis_x": the index of the axis to be bind as x-axis
91 * - "axis_y": the index of the axis to be bind as y-axis
92 */
93std::unique_ptr<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) {
94 const auto pad = static_cast<u32>(params.Get("pad", 0));
95 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
96 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
97
98 return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get());
99}
100
101} // namespace InputCommon
diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h
deleted file mode 100644
index 09e426cef..000000000
--- a/src/input_common/tas/tas_poller.h
+++ /dev/null
@@ -1,43 +0,0 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/tas/tas_input.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a tas bot. It receives tas events and forward them
15 * to all button devices it created.
16 */
17class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28private:
29 std::shared_ptr<TasInput::Tas> tas_input;
30};
31
32/// An analog device factory that creates analog devices from tas
33class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
34public:
35 explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
36
37 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
38
39private:
40 std::shared_ptr<TasInput::Tas> tas_input;
41};
42
43} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
deleted file mode 100644
index 7878a56d7..000000000
--- a/src/input_common/touch_from_button.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include "common/settings.h"
7#include "core/frontend/framebuffer_layout.h"
8#include "input_common/touch_from_button.h"
9
10namespace InputCommon {
11
12class TouchFromButtonDevice final : public Input::TouchDevice {
13public:
14 TouchFromButtonDevice() {
15 const auto button_index =
16 static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
17 const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
18
19 for (const auto& config_entry : buttons) {
20 const Common::ParamPackage package{config_entry};
21 map.emplace_back(
22 Input::CreateDevice<Input::ButtonDevice>(config_entry),
23 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
24 std::clamp(package.Get("y", 0), 0,
25 static_cast<int>(Layout::ScreenUndocked::Height)));
26 }
27 }
28
29 Input::TouchStatus GetStatus() const override {
30 Input::TouchStatus touch_status{};
31 for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) {
32 const bool state = std::get<0>(map[id])->GetStatus();
33 if (state) {
34 const float x = static_cast<float>(std::get<1>(map[id])) /
35 static_cast<int>(Layout::ScreenUndocked::Width);
36 const float y = static_cast<float>(std::get<2>(map[id])) /
37 static_cast<int>(Layout::ScreenUndocked::Height);
38 touch_status[id] = {x, y, true};
39 }
40 }
41 return touch_status;
42 }
43
44private:
45 // A vector of the mapped button, its x and its y-coordinate
46 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
47};
48
49std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) {
50 return std::make_unique<TouchFromButtonDevice>();
51}
52
53} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
deleted file mode 100644
index b9512aa2e..000000000
--- a/src/input_common/udp/client.cpp
+++ /dev/null
@@ -1,526 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cstring>
7#include <functional>
8#include <random>
9#include <thread>
10#include <boost/asio.hpp>
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/udp/client.h"
14#include "input_common/udp/protocol.h"
15
16using boost::asio::ip::udp;
17
18namespace InputCommon::CemuhookUDP {
19
20struct SocketCallback {
21 std::function<void(Response::Version)> version;
22 std::function<void(Response::PortInfo)> port_info;
23 std::function<void(Response::PadData)> pad_data;
24};
25
26class Socket {
27public:
28 using clock = std::chrono::system_clock;
29
30 explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
31 : callback(std::move(callback_)), timer(io_service),
32 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
33 boost::system::error_code ec{};
34 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
35 if (ec.value() != boost::system::errc::success) {
36 LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
37 ipv4 = boost::asio::ip::address_v4{};
38 }
39
40 send_endpoint = {udp::endpoint(ipv4, port)};
41 }
42
43 void Stop() {
44 io_service.stop();
45 }
46
47 void Loop() {
48 io_service.run();
49 }
50
51 void StartSend(const clock::time_point& from) {
52 timer.expires_at(from + std::chrono::seconds(3));
53 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
54 }
55
56 void StartReceive() {
57 socket.async_receive_from(
58 boost::asio::buffer(receive_buffer), receive_endpoint,
59 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
60 HandleReceive(error, bytes_transferred);
61 });
62 }
63
64private:
65 u32 GenerateRandomClientId() const {
66 std::random_device device;
67 return device();
68 }
69
70 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
71 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
72 switch (*type) {
73 case Type::Version: {
74 Response::Version version;
75 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
76 callback.version(std::move(version));
77 break;
78 }
79 case Type::PortInfo: {
80 Response::PortInfo port_info;
81 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
82 sizeof(Response::PortInfo));
83 callback.port_info(std::move(port_info));
84 break;
85 }
86 case Type::PadData: {
87 Response::PadData pad_data;
88 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
89 SanitizeMotion(pad_data);
90 callback.pad_data(std::move(pad_data));
91 break;
92 }
93 }
94 }
95 StartReceive();
96 }
97
98 void HandleSend(const boost::system::error_code&) {
99 boost::system::error_code _ignored{};
100 // Send a request for getting port info for the pad
101 const Request::PortInfo port_info{4, {0, 1, 2, 3}};
102 const auto port_message = Request::Create(port_info, client_id);
103 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
104 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
105
106 // Send a request for getting pad data for the pad
107 const Request::PadData pad_data{
108 Request::PadData::Flags::AllPorts,
109 0,
110 EMPTY_MAC_ADDRESS,
111 };
112 const auto pad_message = Request::Create(pad_data, client_id);
113 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
114 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
115 StartSend(timer.expiry());
116 }
117
118 void SanitizeMotion(Response::PadData& data) {
119 // Zero out any non number value
120 if (!std::isnormal(data.gyro.pitch)) {
121 data.gyro.pitch = 0;
122 }
123 if (!std::isnormal(data.gyro.roll)) {
124 data.gyro.roll = 0;
125 }
126 if (!std::isnormal(data.gyro.yaw)) {
127 data.gyro.yaw = 0;
128 }
129 if (!std::isnormal(data.accel.x)) {
130 data.accel.x = 0;
131 }
132 if (!std::isnormal(data.accel.y)) {
133 data.accel.y = 0;
134 }
135 if (!std::isnormal(data.accel.z)) {
136 data.accel.z = 0;
137 }
138 }
139
140 SocketCallback callback;
141 boost::asio::io_service io_service;
142 boost::asio::basic_waitable_timer<clock> timer;
143 udp::socket socket;
144
145 const u32 client_id;
146
147 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
148 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
149 std::array<u8, PORT_INFO_SIZE> send_buffer1;
150 std::array<u8, PAD_DATA_SIZE> send_buffer2;
151 udp::endpoint send_endpoint;
152
153 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
154 udp::endpoint receive_endpoint;
155};
156
157static void SocketLoop(Socket* socket) {
158 socket->StartReceive();
159 socket->StartSend(Socket::clock::now());
160 socket->Loop();
161}
162
163Client::Client() {
164 LOG_INFO(Input, "Udp Initialization started");
165 finger_id.fill(MAX_TOUCH_FINGERS);
166 ReloadSockets();
167}
168
169Client::~Client() {
170 Reset();
171}
172
173Client::ClientConnection::ClientConnection() = default;
174
175Client::ClientConnection::~ClientConnection() = default;
176
177std::vector<Common::ParamPackage> Client::GetInputDevices() const {
178 std::vector<Common::ParamPackage> devices;
179 for (std::size_t pad = 0; pad < pads.size(); pad++) {
180 if (!DeviceConnected(pad)) {
181 continue;
182 }
183 std::string name = fmt::format("UDP Controller {}", pad);
184 devices.emplace_back(Common::ParamPackage{
185 {"class", "cemuhookudp"},
186 {"display", std::move(name)},
187 {"port", std::to_string(pad)},
188 });
189 }
190 return devices;
191}
192
193bool Client::DeviceConnected(std::size_t pad) const {
194 // Use last timestamp to detect if the socket has stopped sending data
195 const auto now = std::chrono::steady_clock::now();
196 const auto time_difference = static_cast<u64>(
197 std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count());
198 return time_difference < 1000 && pads[pad].connected;
199}
200
201void Client::ReloadSockets() {
202 Reset();
203
204 std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers));
205 std::string server_token;
206 std::size_t client = 0;
207 while (std::getline(servers_ss, server_token, ',')) {
208 if (client == MAX_UDP_CLIENTS) {
209 break;
210 }
211 std::stringstream server_ss(server_token);
212 std::string token;
213 std::getline(server_ss, token, ':');
214 std::string udp_input_address = token;
215 std::getline(server_ss, token, ':');
216 char* temp;
217 const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
218 if (*temp != '\0') {
219 LOG_ERROR(Input, "Port number is not valid {}", token);
220 continue;
221 }
222
223 const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
224 if (client_number != MAX_UDP_CLIENTS) {
225 LOG_ERROR(Input, "Duplicated UDP servers found");
226 continue;
227 }
228 StartCommunication(client++, udp_input_address, udp_input_port);
229 }
230}
231
232std::size_t Client::GetClientNumber(std::string_view host, u16 port) const {
233 for (std::size_t client = 0; client < clients.size(); client++) {
234 if (clients[client].active == -1) {
235 continue;
236 }
237 if (clients[client].host == host && clients[client].port == port) {
238 return client;
239 }
240 }
241 return MAX_UDP_CLIENTS;
242}
243
244void Client::OnVersion([[maybe_unused]] Response::Version data) {
245 LOG_TRACE(Input, "Version packet received: {}", data.version);
246}
247
248void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
249 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
250}
251
252void Client::OnPadData(Response::PadData data, std::size_t client) {
253 const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
254
255 if (pad_index >= pads.size()) {
256 LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
257 return;
258 }
259
260 LOG_TRACE(Input, "PadData packet received");
261 if (data.packet_counter == pads[pad_index].packet_sequence) {
262 LOG_WARNING(
263 Input,
264 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
265 pads[pad_index].packet_sequence, data.packet_counter);
266 pads[pad_index].connected = false;
267 return;
268 }
269
270 clients[client].active = 1;
271 pads[pad_index].connected = true;
272 pads[pad_index].packet_sequence = data.packet_counter;
273
274 const auto now = std::chrono::steady_clock::now();
275 const auto time_difference = static_cast<u64>(
276 std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
277 .count());
278 pads[pad_index].last_update = now;
279
280 const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
281 pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
282 // Gyroscope values are not it the correct scale from better joy.
283 // Dividing by 312 allows us to make one full turn = 1 turn
284 // This must be a configurable valued called sensitivity
285 pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f);
286 pads[pad_index].motion.UpdateRotation(time_difference);
287 pads[pad_index].motion.UpdateOrientation(time_difference);
288
289 {
290 std::lock_guard guard(pads[pad_index].status.update_mutex);
291 pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion();
292
293 for (std::size_t id = 0; id < data.touch.size(); ++id) {
294 UpdateTouchInput(data.touch[id], client, id);
295 }
296
297 if (configuring) {
298 const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope();
299 const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration();
300 UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope);
301 }
302 }
303}
304
305void Client::StartCommunication(std::size_t client, const std::string& host, u16 port) {
306 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
307 [this](Response::PortInfo info) { OnPortInfo(info); },
308 [this, client](Response::PadData data) { OnPadData(data, client); }};
309 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
310 clients[client].host = host;
311 clients[client].port = port;
312 clients[client].active = 0;
313 clients[client].socket = std::make_unique<Socket>(host, port, callback);
314 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
315
316 // Set motion parameters
317 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
318 // Real HW values are unknown, 0.0001 is an approximate to Standard
319 for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) {
320 pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f);
321 }
322}
323
324void Client::Reset() {
325 for (auto& client : clients) {
326 if (client.thread.joinable()) {
327 client.active = -1;
328 client.socket->Stop();
329 client.thread.join();
330 }
331 }
332}
333
334void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
335 const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro) {
336 if (gyro.Length() > 0.2f) {
337 LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client,
338 gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]);
339 }
340 UDPPadStatus pad{
341 .host = clients[client].host,
342 .port = clients[client].port,
343 .pad_index = pad_index,
344 };
345 for (std::size_t i = 0; i < 3; ++i) {
346 if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
347 pad.motion = static_cast<PadMotion>(i);
348 pad.motion_value = gyro[i];
349 pad_queue.Push(pad);
350 }
351 if (acc[i] > 1.75f || acc[i] < -1.75f) {
352 pad.motion = static_cast<PadMotion>(i + 3);
353 pad.motion_value = acc[i];
354 pad_queue.Push(pad);
355 }
356 }
357}
358
359std::optional<std::size_t> Client::GetUnusedFingerID() const {
360 std::size_t first_free_id = 0;
361 while (first_free_id < MAX_TOUCH_FINGERS) {
362 if (!std::get<2>(touch_status[first_free_id])) {
363 return first_free_id;
364 } else {
365 first_free_id++;
366 }
367 }
368 return std::nullopt;
369}
370
371void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) {
372 // TODO: Use custom calibration per device
373 const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
374 const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
375 const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
376 const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
377 const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
378 const std::size_t touch_id = client * 2 + id;
379 if (touch_pad.is_active) {
380 if (finger_id[touch_id] == MAX_TOUCH_FINGERS) {
381 const auto first_free_id = GetUnusedFingerID();
382 if (!first_free_id) {
383 // Invalid finger id skip to next input
384 return;
385 }
386 finger_id[touch_id] = *first_free_id;
387 }
388 auto& [x, y, pressed] = touch_status[finger_id[touch_id]];
389 x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
390 static_cast<float>(max_x - min_x);
391 y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
392 static_cast<float>(max_y - min_y);
393 pressed = true;
394 return;
395 }
396
397 if (finger_id[touch_id] != MAX_TOUCH_FINGERS) {
398 touch_status[finger_id[touch_id]] = {};
399 finger_id[touch_id] = MAX_TOUCH_FINGERS;
400 }
401}
402
403void Client::BeginConfiguration() {
404 pad_queue.Clear();
405 configuring = true;
406}
407
408void Client::EndConfiguration() {
409 pad_queue.Clear();
410 configuring = false;
411}
412
413DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) {
414 const std::size_t client_number = GetClientNumber(host, port);
415 if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
416 return pads[0].status;
417 }
418 return pads[(client_number * PADS_PER_CLIENT) + pad].status;
419}
420
421const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const {
422 const std::size_t client_number = GetClientNumber(host, port);
423 if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
424 return pads[0].status;
425 }
426 return pads[(client_number * PADS_PER_CLIENT) + pad].status;
427}
428
429Input::TouchStatus& Client::GetTouchState() {
430 return touch_status;
431}
432
433const Input::TouchStatus& Client::GetTouchState() const {
434 return touch_status;
435}
436
437Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() {
438 return pad_queue;
439}
440
441const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const {
442 return pad_queue;
443}
444
445void TestCommunication(const std::string& host, u16 port,
446 const std::function<void()>& success_callback,
447 const std::function<void()>& failure_callback) {
448 std::thread([=] {
449 Common::Event success_event;
450 SocketCallback callback{
451 .version = [](Response::Version) {},
452 .port_info = [](Response::PortInfo) {},
453 .pad_data = [&](Response::PadData) { success_event.Set(); },
454 };
455 Socket socket{host, port, std::move(callback)};
456 std::thread worker_thread{SocketLoop, &socket};
457 const bool result =
458 success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
459 socket.Stop();
460 worker_thread.join();
461 if (result) {
462 success_callback();
463 } else {
464 failure_callback();
465 }
466 }).detach();
467}
468
469CalibrationConfigurationJob::CalibrationConfigurationJob(
470 const std::string& host, u16 port, std::function<void(Status)> status_callback,
471 std::function<void(u16, u16, u16, u16)> data_callback) {
472
473 std::thread([=, this] {
474 Status current_status{Status::Initialized};
475 SocketCallback callback{
476 [](Response::Version) {}, [](Response::PortInfo) {},
477 [&](Response::PadData data) {
478 static constexpr u16 CALIBRATION_THRESHOLD = 100;
479 static constexpr u16 MAX_VALUE = UINT16_MAX;
480
481 if (current_status == Status::Initialized) {
482 // Receiving data means the communication is ready now
483 current_status = Status::Ready;
484 status_callback(current_status);
485 }
486 const auto& touchpad_0 = data.touch[0];
487 if (touchpad_0.is_active == 0) {
488 return;
489 }
490 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
491 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
492 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
493 if (current_status == Status::Ready) {
494 // First touch - min data (min_x/min_y)
495 current_status = Status::Stage1Completed;
496 status_callback(current_status);
497 }
498 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
499 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
500 // Set the current position as max value and finishes configuration
501 const u16 max_x = touchpad_0.x;
502 const u16 max_y = touchpad_0.y;
503 current_status = Status::Completed;
504 data_callback(min_x, min_y, max_x, max_y);
505 status_callback(current_status);
506
507 complete_event.Set();
508 }
509 }};
510 Socket socket{host, port, std::move(callback)};
511 std::thread worker_thread{SocketLoop, &socket};
512 complete_event.Wait();
513 socket.Stop();
514 worker_thread.join();
515 }).detach();
516}
517
518CalibrationConfigurationJob::~CalibrationConfigurationJob() {
519 Stop();
520}
521
522void CalibrationConfigurationJob::Stop() {
523 complete_event.Set();
524}
525
526} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
deleted file mode 100644
index 9829da6f0..000000000
--- a/src/input_common/udp/udp.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7#include "common/assert.h"
8#include "common/threadsafe_queue.h"
9#include "input_common/udp/client.h"
10#include "input_common/udp/udp.h"
11
12namespace InputCommon {
13
14class UDPMotion final : public Input::MotionDevice {
15public:
16 explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
17 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
18
19 Input::MotionStatus GetStatus() const override {
20 return client->GetPadState(ip, port, pad).motion_status;
21 }
22
23private:
24 const std::string ip;
25 const u16 port;
26 const u16 pad;
27 CemuhookUDP::Client* client;
28 mutable std::mutex mutex;
29};
30
31/// A motion device factory that creates motion devices from a UDP client
32UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
33 : client(std::move(client_)) {}
34
35/**
36 * Creates motion device
37 * @param params contains parameters for creating the device:
38 * - "port": the UDP port number
39 */
40std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
41 auto ip = params.Get("ip", "127.0.0.1");
42 const auto port = static_cast<u16>(params.Get("port", 26760));
43 const auto pad = static_cast<u16>(params.Get("pad_index", 0));
44
45 return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
46}
47
48void UDPMotionFactory::BeginConfiguration() {
49 polling = true;
50 client->BeginConfiguration();
51}
52
53void UDPMotionFactory::EndConfiguration() {
54 polling = false;
55 client->EndConfiguration();
56}
57
58Common::ParamPackage UDPMotionFactory::GetNextInput() {
59 Common::ParamPackage params;
60 CemuhookUDP::UDPPadStatus pad;
61 auto& queue = client->GetPadQueue();
62 while (queue.Pop(pad)) {
63 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
64 continue;
65 }
66 params.Set("engine", "cemuhookudp");
67 params.Set("ip", pad.host);
68 params.Set("port", static_cast<u16>(pad.port));
69 params.Set("pad_index", static_cast<u16>(pad.pad_index));
70 params.Set("motion", static_cast<u16>(pad.motion));
71 return params;
72 }
73 return params;
74}
75
76class UDPTouch final : public Input::TouchDevice {
77public:
78 explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
79 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
80
81 Input::TouchStatus GetStatus() const override {
82 return client->GetTouchState();
83 }
84
85private:
86 const std::string ip;
87 [[maybe_unused]] const u16 port;
88 [[maybe_unused]] const u16 pad;
89 CemuhookUDP::Client* client;
90 mutable std::mutex mutex;
91};
92
93/// A motion device factory that creates motion devices from a UDP client
94UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
95 : client(std::move(client_)) {}
96
97/**
98 * Creates motion device
99 * @param params contains parameters for creating the device:
100 * - "port": the UDP port number
101 */
102std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
103 auto ip = params.Get("ip", "127.0.0.1");
104 const auto port = static_cast<u16>(params.Get("port", 26760));
105 const auto pad = static_cast<u16>(params.Get("pad_index", 0));
106
107 return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
108}
109
110} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
deleted file mode 100644
index ea3fd4175..000000000
--- a/src/input_common/udp/udp.h
+++ /dev/null
@@ -1,57 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/udp/client.h"
10
11namespace InputCommon {
12
13/// A motion device factory that creates motion devices from udp clients
14class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
15public:
16 explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
17
18 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
19
20 Common::ParamPackage GetNextInput();
21
22 /// For device input configuration/polling
23 void BeginConfiguration();
24 void EndConfiguration();
25
26 bool IsPolling() const {
27 return polling;
28 }
29
30private:
31 std::shared_ptr<CemuhookUDP::Client> client;
32 bool polling = false;
33};
34
35/// A touch device factory that creates touch devices from udp clients
36class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
37public:
38 explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
39
40 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
41
42 Common::ParamPackage GetNextInput();
43
44 /// For device input configuration/polling
45 void BeginConfiguration();
46 void EndConfiguration();
47
48 bool IsPolling() const {
49 return polling;
50 }
51
52private:
53 std::shared_ptr<CemuhookUDP::Client> client;
54 bool polling = false;
55};
56
57} // namespace InputCommon