summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt8
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp290
-rw-r--r--src/input_common/gcadapter/gc_adapter.h51
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp40
-rw-r--r--src/input_common/main.cpp263
-rw-r--r--src/input_common/main.h152
-rw-r--r--src/input_common/motion_emu.cpp17
-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/motion_input.cpp305
-rw-r--r--src/input_common/motion_input.h73
-rw-r--r--src/input_common/sdl/sdl.h19
-rw-r--r--src/input_common/sdl/sdl_impl.cpp665
-rw-r--r--src/input_common/sdl/sdl_impl.h10
-rw-r--r--src/input_common/settings.cpp40
-rw-r--r--src/input_common/settings.h352
-rw-r--r--src/input_common/touch_from_button.cpp50
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/input_common/udp/client.cpp185
-rw-r--r--src/input_common/udp/client.h77
-rw-r--r--src/input_common/udp/udp.cpp177
-rw-r--r--src/input_common/udp/udp.h54
22 files changed, 2380 insertions, 530 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 317c25bad..c84685214 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,6 +7,14 @@ add_library(input_common STATIC
7 main.h 7 main.h
8 motion_emu.cpp 8 motion_emu.cpp
9 motion_emu.h 9 motion_emu.h
10 motion_from_button.cpp
11 motion_from_button.h
12 motion_input.cpp
13 motion_input.h
14 settings.cpp
15 settings.h
16 touch_from_button.cpp
17 touch_from_button.h
10 gcadapter/gc_adapter.cpp 18 gcadapter/gc_adapter.cpp
11 gcadapter/gc_adapter.h 19 gcadapter/gc_adapter.h
12 gcadapter/gc_poller.cpp 20 gcadapter/gc_poller.cpp
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
index 898a278a9..89c148aba 100644
--- a/src/input_common/gcadapter/gc_adapter.cpp
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -4,9 +4,20 @@
4 4
5#include <chrono> 5#include <chrono>
6#include <thread> 6#include <thread>
7
8#ifdef _MSC_VER
9#pragma warning(push)
10#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union
11#endif
7#include <libusb.h> 12#include <libusb.h>
13#ifdef _MSC_VER
14#pragma warning(pop)
15#endif
16
8#include "common/logging/log.h" 17#include "common/logging/log.h"
18#include "common/param_package.h"
9#include "input_common/gcadapter/gc_adapter.h" 19#include "input_common/gcadapter/gc_adapter.h"
20#include "input_common/settings.h"
10 21
11namespace GCAdapter { 22namespace GCAdapter {
12 23
@@ -24,12 +35,9 @@ Adapter::Adapter() {
24 } 35 }
25 LOG_INFO(Input, "GC Adapter Initialization started"); 36 LOG_INFO(Input, "GC Adapter Initialization started");
26 37
27 current_status = NO_ADAPTER_DETECTED;
28 get_origin.fill(true);
29
30 const int init_res = libusb_init(&libusb_ctx); 38 const int init_res = libusb_init(&libusb_ctx);
31 if (init_res == LIBUSB_SUCCESS) { 39 if (init_res == LIBUSB_SUCCESS) {
32 StartScanThread(); 40 Setup();
33 } else { 41 } else {
34 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); 42 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
35 } 43 }
@@ -37,9 +45,9 @@ Adapter::Adapter() {
37 45
38GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { 46GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) {
39 GCPadStatus pad = {}; 47 GCPadStatus pad = {};
48 const std::size_t offset = 1 + (9 * port);
40 49
41 ControllerTypes type = ControllerTypes(adapter_payload[1 + (9 * port)] >> 4); 50 adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
42 adapter_controllers_status[port] = type;
43 51
44 static constexpr std::array<PadButton, 8> b1_buttons{ 52 static constexpr std::array<PadButton, 8> b1_buttons{
45 PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, 53 PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X,
@@ -54,14 +62,19 @@ GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& ad
54 PadButton::PAD_TRIGGER_L, 62 PadButton::PAD_TRIGGER_L,
55 }; 63 };
56 64
65 static constexpr std::array<PadAxes, 6> axes{
66 PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
67 PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
68 };
69
57 if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) { 70 if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) {
58 // Controller may have been disconnected, recalibrate if reconnected. 71 // Controller may have been disconnected, recalibrate if reconnected.
59 get_origin[port] = true; 72 get_origin[port] = true;
60 } 73 }
61 74
62 if (adapter_controllers_status[port] != ControllerTypes::None) { 75 if (adapter_controllers_status[port] != ControllerTypes::None) {
63 const u8 b1 = adapter_payload[1 + (9 * port) + 1]; 76 const u8 b1 = adapter_payload[offset + 1];
64 const u8 b2 = adapter_payload[1 + (9 * port) + 2]; 77 const u8 b2 = adapter_payload[offset + 2];
65 78
66 for (std::size_t i = 0; i < b1_buttons.size(); ++i) { 79 for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
67 if ((b1 & (1U << i)) != 0) { 80 if ((b1 & (1U << i)) != 0) {
@@ -74,21 +87,13 @@ GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& ad
74 pad.button |= static_cast<u16>(b2_buttons[j]); 87 pad.button |= static_cast<u16>(b2_buttons[j]);
75 } 88 }
76 } 89 }
77 90 for (PadAxes axis : axes) {
78 pad.stick_x = adapter_payload[1 + (9 * port) + 3]; 91 const std::size_t index = static_cast<std::size_t>(axis);
79 pad.stick_y = adapter_payload[1 + (9 * port) + 4]; 92 pad.axis_values[index] = adapter_payload[offset + 3 + index];
80 pad.substick_x = adapter_payload[1 + (9 * port) + 5]; 93 }
81 pad.substick_y = adapter_payload[1 + (9 * port) + 6];
82 pad.trigger_left = adapter_payload[1 + (9 * port) + 7];
83 pad.trigger_right = adapter_payload[1 + (9 * port) + 8];
84 94
85 if (get_origin[port]) { 95 if (get_origin[port]) {
86 origin_status[port].stick_x = pad.stick_x; 96 origin_status[port].axis_values = pad.axis_values;
87 origin_status[port].stick_y = pad.stick_y;
88 origin_status[port].substick_x = pad.substick_x;
89 origin_status[port].substick_y = pad.substick_y;
90 origin_status[port].trigger_left = pad.trigger_left;
91 origin_status[port].trigger_right = pad.trigger_right;
92 get_origin[port] = false; 97 get_origin[port] = false;
93 } 98 }
94 } 99 }
@@ -101,82 +106,47 @@ void Adapter::PadToState(const GCPadStatus& pad, GCState& state) {
101 state.buttons.insert_or_assign(button_value, pad.button & button_value); 106 state.buttons.insert_or_assign(button_value, pad.button & button_value);
102 } 107 }
103 108
104 state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickX), pad.stick_x); 109 for (size_t i = 0; i < pad.axis_values.size(); ++i) {
105 state.axes.insert_or_assign(static_cast<u8>(PadAxes::StickY), pad.stick_y); 110 state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]);
106 state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickX), pad.substick_x); 111 }
107 state.axes.insert_or_assign(static_cast<u8>(PadAxes::SubstickY), pad.substick_y);
108 state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerLeft), pad.trigger_left);
109 state.axes.insert_or_assign(static_cast<u8>(PadAxes::TriggerRight), pad.trigger_right);
110} 112}
111 113
112void Adapter::Read() { 114void Adapter::Read() {
113 LOG_DEBUG(Input, "GC Adapter Read() thread started"); 115 LOG_DEBUG(Input, "GC Adapter Read() thread started");
114 116
115 int payload_size_in, payload_size_copy; 117 int payload_size;
116 std::array<u8, 37> adapter_payload; 118 std::array<u8, 37> adapter_payload;
117 std::array<u8, 37> adapter_payload_copy;
118 std::array<GCPadStatus, 4> pads; 119 std::array<GCPadStatus, 4> pads;
119 120
120 while (adapter_thread_running) { 121 while (adapter_thread_running) {
121 libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), 122 libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
122 sizeof(adapter_payload), &payload_size_in, 16); 123 sizeof(adapter_payload), &payload_size, 16);
123 payload_size_copy = 0;
124 // this mutex might be redundant?
125 {
126 std::lock_guard<std::mutex> lk(s_mutex);
127 std::copy(std::begin(adapter_payload), std::end(adapter_payload),
128 std::begin(adapter_payload_copy));
129 payload_size_copy = payload_size_in;
130 }
131 124
132 if (payload_size_copy != sizeof(adapter_payload_copy) || 125 if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) {
133 adapter_payload_copy[0] != LIBUSB_DT_HID) { 126 LOG_ERROR(Input,
134 LOG_ERROR(Input, "error reading payload (size: {}, type: {:02x})", payload_size_copy, 127 "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?",
135 adapter_payload_copy[0]); 128 payload_size, adapter_payload[0]);
136 adapter_thread_running = false; // error reading from adapter, stop reading. 129 adapter_thread_running = false; // error reading from adapter, stop reading.
137 break; 130 break;
138 } 131 }
139 for (std::size_t port = 0; port < pads.size(); ++port) { 132 for (std::size_t port = 0; port < pads.size(); ++port) {
140 pads[port] = GetPadStatus(port, adapter_payload_copy); 133 pads[port] = GetPadStatus(port, adapter_payload);
141 if (DeviceConnected(port) && configuring) { 134 if (DeviceConnected(port) && configuring) {
142 if (pads[port].button != 0) { 135 if (pads[port].button != 0) {
143 pad_queue[port].Push(pads[port]); 136 pad_queue[port].Push(pads[port]);
144 } 137 }
145 138
146 // Accounting for a threshold here because of some controller variance 139 // Accounting for a threshold here to ensure an intentional press
147 if (pads[port].stick_x > origin_status[port].stick_x + pads[port].THRESHOLD || 140 for (size_t i = 0; i < pads[port].axis_values.size(); ++i) {
148 pads[port].stick_x < origin_status[port].stick_x - pads[port].THRESHOLD) { 141 const u8 value = pads[port].axis_values[i];
149 pads[port].axis = GCAdapter::PadAxes::StickX; 142 const u8 origin = origin_status[port].axis_values[i];
150 pads[port].axis_value = pads[port].stick_x; 143
151 pad_queue[port].Push(pads[port]); 144 if (value > origin + pads[port].THRESHOLD ||
152 } 145 value < origin - pads[port].THRESHOLD) {
153 if (pads[port].stick_y > origin_status[port].stick_y + pads[port].THRESHOLD || 146 pads[port].axis = static_cast<PadAxes>(i);
154 pads[port].stick_y < origin_status[port].stick_y - pads[port].THRESHOLD) { 147 pads[port].axis_value = pads[port].axis_values[i];
155 pads[port].axis = GCAdapter::PadAxes::StickY; 148 pad_queue[port].Push(pads[port]);
156 pads[port].axis_value = pads[port].stick_y; 149 }
157 pad_queue[port].Push(pads[port]);
158 }
159 if (pads[port].substick_x > origin_status[port].substick_x + pads[port].THRESHOLD ||
160 pads[port].substick_x < origin_status[port].substick_x - pads[port].THRESHOLD) {
161 pads[port].axis = GCAdapter::PadAxes::SubstickX;
162 pads[port].axis_value = pads[port].substick_x;
163 pad_queue[port].Push(pads[port]);
164 }
165 if (pads[port].substick_y > origin_status[port].substick_y + pads[port].THRESHOLD ||
166 pads[port].substick_y < origin_status[port].substick_y - pads[port].THRESHOLD) {
167 pads[port].axis = GCAdapter::PadAxes::SubstickY;
168 pads[port].axis_value = pads[port].substick_y;
169 pad_queue[port].Push(pads[port]);
170 }
171 if (pads[port].trigger_left > pads[port].TRIGGER_THRESHOLD) {
172 pads[port].axis = GCAdapter::PadAxes::TriggerLeft;
173 pads[port].axis_value = pads[port].trigger_left;
174 pad_queue[port].Push(pads[port]);
175 }
176 if (pads[port].trigger_right > pads[port].TRIGGER_THRESHOLD) {
177 pads[port].axis = GCAdapter::PadAxes::TriggerRight;
178 pads[port].axis_value = pads[port].trigger_right;
179 pad_queue[port].Push(pads[port]);
180 } 150 }
181 } 151 }
182 PadToState(pads[port], state[port]); 152 PadToState(pads[port], state[port]);
@@ -185,42 +155,11 @@ void Adapter::Read() {
185 } 155 }
186} 156}
187 157
188void Adapter::ScanThreadFunc() {
189 LOG_INFO(Input, "GC Adapter scanning thread started");
190
191 while (detect_thread_running) {
192 if (usb_adapter_handle == nullptr) {
193 std::lock_guard<std::mutex> lk(initialization_mutex);
194 Setup();
195 }
196 std::this_thread::sleep_for(std::chrono::milliseconds(500));
197 }
198}
199
200void Adapter::StartScanThread() {
201 if (detect_thread_running) {
202 return;
203 }
204 if (!libusb_ctx) {
205 return;
206 }
207
208 detect_thread_running = true;
209 detect_thread = std::thread(&Adapter::ScanThreadFunc, this);
210}
211
212void Adapter::StopScanThread() {
213 detect_thread_running = false;
214 detect_thread.join();
215}
216
217void Adapter::Setup() { 158void Adapter::Setup() {
218 // Reset the error status in case the adapter gets unplugged 159 // Initialize all controllers as unplugged
219 if (current_status < 0) {
220 current_status = NO_ADAPTER_DETECTED;
221 }
222
223 adapter_controllers_status.fill(ControllerTypes::None); 160 adapter_controllers_status.fill(ControllerTypes::None);
161 // Initialize all ports to store axis origin values
162 get_origin.fill(true);
224 163
225 // pointer to list of connected usb devices 164 // pointer to list of connected usb devices
226 libusb_device** devices{}; 165 libusb_device** devices{};
@@ -229,8 +168,6 @@ void Adapter::Setup() {
229 const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices); 168 const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
230 if (device_count < 0) { 169 if (device_count < 0) {
231 LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count); 170 LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
232 detect_thread_running = false; // Stop the loop constantly checking for gc adapter
233 // TODO: For hotplug+gc adapter checkbox implementation, revert this.
234 return; 171 return;
235 } 172 }
236 173
@@ -244,9 +181,6 @@ void Adapter::Setup() {
244 } 181 }
245 libusb_free_device_list(devices, 1); 182 libusb_free_device_list(devices, 1);
246 } 183 }
247 // Break out of the ScanThreadFunc() loop that is constantly looking for the device
248 // Assumes user has GC adapter plugged in before launch to use the adapter
249 detect_thread_running = false;
250} 184}
251 185
252bool Adapter::CheckDeviceAccess(libusb_device* device) { 186bool Adapter::CheckDeviceAccess(libusb_device* device) {
@@ -331,32 +265,23 @@ void Adapter::GetGCEndpoint(libusb_device* device) {
331 sizeof(clear_payload), nullptr, 16); 265 sizeof(clear_payload), nullptr, 16);
332 266
333 adapter_thread_running = true; 267 adapter_thread_running = true;
334 current_status = ADAPTER_DETECTED; 268 adapter_input_thread = std::thread(&Adapter::Read, this);
335 adapter_input_thread = std::thread([=] { Read(); }); // Read input
336} 269}
337 270
338Adapter::~Adapter() { 271Adapter::~Adapter() {
339 StopScanThread();
340 Reset(); 272 Reset();
341} 273}
342 274
343void Adapter::Reset() { 275void Adapter::Reset() {
344 std::unique_lock<std::mutex> lock(initialization_mutex, std::defer_lock);
345 if (!lock.try_lock()) {
346 return;
347 }
348 if (current_status != ADAPTER_DETECTED) {
349 return;
350 }
351
352 if (adapter_thread_running) { 276 if (adapter_thread_running) {
353 adapter_thread_running = false; 277 adapter_thread_running = false;
354 } 278 }
355 adapter_input_thread.join(); 279 if (adapter_input_thread.joinable()) {
280 adapter_input_thread.join();
281 }
356 282
357 adapter_controllers_status.fill(ControllerTypes::None); 283 adapter_controllers_status.fill(ControllerTypes::None);
358 get_origin.fill(true); 284 get_origin.fill(true);
359 current_status = NO_ADAPTER_DETECTED;
360 285
361 if (usb_adapter_handle) { 286 if (usb_adapter_handle) {
362 libusb_release_interface(usb_adapter_handle, 1); 287 libusb_release_interface(usb_adapter_handle, 1);
@@ -369,7 +294,93 @@ void Adapter::Reset() {
369 } 294 }
370} 295}
371 296
372bool Adapter::DeviceConnected(std::size_t port) { 297std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
298 std::vector<Common::ParamPackage> devices;
299 for (std::size_t port = 0; port < state.size(); ++port) {
300 if (!DeviceConnected(port)) {
301 continue;
302 }
303 std::string name = fmt::format("Gamecube Controller {}", port);
304 devices.emplace_back(Common::ParamPackage{
305 {"class", "gcpad"},
306 {"display", std::move(name)},
307 {"port", std::to_string(port)},
308 });
309 }
310 return devices;
311}
312
313InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
314 const Common::ParamPackage& params) const {
315 // This list is missing ZL/ZR since those are not considered buttons.
316 // We will add those afterwards
317 // This list also excludes any button that can't be really mapped
318 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
319 switch_to_gcadapter_button = {
320 std::pair{Settings::NativeButton::A, PadButton::PAD_BUTTON_A},
321 {Settings::NativeButton::B, PadButton::PAD_BUTTON_B},
322 {Settings::NativeButton::X, PadButton::PAD_BUTTON_X},
323 {Settings::NativeButton::Y, PadButton::PAD_BUTTON_Y},
324 {Settings::NativeButton::Plus, PadButton::PAD_BUTTON_START},
325 {Settings::NativeButton::DLeft, PadButton::PAD_BUTTON_LEFT},
326 {Settings::NativeButton::DUp, PadButton::PAD_BUTTON_UP},
327 {Settings::NativeButton::DRight, PadButton::PAD_BUTTON_RIGHT},
328 {Settings::NativeButton::DDown, PadButton::PAD_BUTTON_DOWN},
329 {Settings::NativeButton::SL, PadButton::PAD_TRIGGER_L},
330 {Settings::NativeButton::SR, PadButton::PAD_TRIGGER_R},
331 {Settings::NativeButton::R, PadButton::PAD_TRIGGER_Z},
332 };
333 if (!params.Has("port")) {
334 return {};
335 }
336
337 InputCommon::ButtonMapping mapping{};
338 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
339 Common::ParamPackage button_params({{"engine", "gcpad"}});
340 button_params.Set("port", params.Get("port", 0));
341 button_params.Set("button", static_cast<int>(gcadapter_button));
342 mapping.insert_or_assign(switch_button, std::move(button_params));
343 }
344
345 // Add the missing bindings for ZL/ZR
346 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
347 switch_to_gcadapter_axis = {
348 std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
349 {Settings::NativeButton::ZR, PadAxes::TriggerRight},
350 };
351 for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
352 Common::ParamPackage button_params({{"engine", "gcpad"}});
353 button_params.Set("port", params.Get("port", 0));
354 button_params.Set("button", static_cast<int>(PadButton::PAD_STICK));
355 button_params.Set("axis", static_cast<int>(gcadapter_axis));
356 mapping.insert_or_assign(switch_button, std::move(button_params));
357 }
358 return mapping;
359}
360
361InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
362 const Common::ParamPackage& params) const {
363 if (!params.Has("port")) {
364 return {};
365 }
366
367 InputCommon::AnalogMapping mapping = {};
368 Common::ParamPackage left_analog_params;
369 left_analog_params.Set("engine", "gcpad");
370 left_analog_params.Set("port", params.Get("port", 0));
371 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
372 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
373 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
374 Common::ParamPackage right_analog_params;
375 right_analog_params.Set("engine", "gcpad");
376 right_analog_params.Set("port", params.Get("port", 0));
377 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
378 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
379 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
380 return mapping;
381}
382
383bool Adapter::DeviceConnected(std::size_t port) const {
373 return adapter_controllers_status[port] != ControllerTypes::None; 384 return adapter_controllers_status[port] != ControllerTypes::None;
374} 385}
375 386
@@ -409,24 +420,7 @@ const std::array<GCState, 4>& Adapter::GetPadState() const {
409} 420}
410 421
411int Adapter::GetOriginValue(int port, int axis) const { 422int Adapter::GetOriginValue(int port, int axis) const {
412 const auto& status = origin_status[port]; 423 return origin_status[port].axis_values[axis];
413
414 switch (static_cast<PadAxes>(axis)) {
415 case PadAxes::StickX:
416 return status.stick_x;
417 case PadAxes::StickY:
418 return status.stick_y;
419 case PadAxes::SubstickX:
420 return status.substick_x;
421 case PadAxes::SubstickY:
422 return status.substick_y;
423 case PadAxes::TriggerLeft:
424 return status.trigger_left;
425 case PadAxes::TriggerRight:
426 return status.trigger_right;
427 default:
428 return 0;
429 }
430} 424}
431 425
432} // namespace GCAdapter 426} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
index 3586c8bda..75bf9fe74 100644
--- a/src/input_common/gcadapter/gc_adapter.h
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -10,6 +10,7 @@
10#include <unordered_map> 10#include <unordered_map>
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/threadsafe_queue.h" 12#include "common/threadsafe_queue.h"
13#include "input_common/main.h"
13 14
14struct libusb_context; 15struct libusb_context;
15struct libusb_device; 16struct libusb_device;
@@ -47,24 +48,10 @@ enum class PadAxes : u8 {
47}; 48};
48 49
49struct GCPadStatus { 50struct GCPadStatus {
50 u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits 51 u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
51 u8 stick_x{}; // 0 <= stick_x <= 255 52
52 u8 stick_y{}; // 0 <= stick_y <= 255 53 std::array<u8, 6> axis_values{}; // Triggers and sticks, following indices defined in PadAxes
53 u8 substick_x{}; // 0 <= substick_x <= 255 54 static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling
54 u8 substick_y{}; // 0 <= substick_y <= 255
55 u8 trigger_left{}; // 0 <= trigger_left <= 255
56 u8 trigger_right{}; // 0 <= trigger_right <= 255
57
58 static constexpr u8 MAIN_STICK_CENTER_X = 0x80;
59 static constexpr u8 MAIN_STICK_CENTER_Y = 0x80;
60 static constexpr u8 MAIN_STICK_RADIUS = 0x7f;
61 static constexpr u8 C_STICK_CENTER_X = 0x80;
62 static constexpr u8 C_STICK_CENTER_Y = 0x80;
63 static constexpr u8 C_STICK_RADIUS = 0x7f;
64 static constexpr u8 THRESHOLD = 10;
65
66 // 256/4, at least a quarter press to count as a press. For polling mostly
67 static constexpr u8 TRIGGER_THRESHOLD = 64;
68 55
69 u8 port{}; 56 u8 port{};
70 PadAxes axis{PadAxes::Undefined}; 57 PadAxes axis{PadAxes::Undefined};
@@ -78,11 +65,6 @@ struct GCState {
78 65
79enum class ControllerTypes { None, Wired, Wireless }; 66enum class ControllerTypes { None, Wired, Wireless };
80 67
81enum {
82 NO_ADAPTER_DETECTED = 0,
83 ADAPTER_DETECTED = 1,
84};
85
86class Adapter { 68class Adapter {
87public: 69public:
88 /// Initialize the GC Adapter capture and read sequence 70 /// Initialize the GC Adapter capture and read sequence
@@ -94,8 +76,12 @@ public:
94 void BeginConfiguration(); 76 void BeginConfiguration();
95 void EndConfiguration(); 77 void EndConfiguration();
96 78
79 std::vector<Common::ParamPackage> GetInputDevices() const;
80 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
81 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
82
97 /// Returns true if there is a device connected to port 83 /// Returns true if there is a device connected to port
98 bool DeviceConnected(std::size_t port); 84 bool DeviceConnected(std::size_t port) const;
99 85
100 std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); 86 std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue();
101 const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; 87 const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const;
@@ -111,12 +97,6 @@ private:
111 void PadToState(const GCPadStatus& pad, GCState& state); 97 void PadToState(const GCPadStatus& pad, GCState& state);
112 98
113 void Read(); 99 void Read();
114 void ScanThreadFunc();
115 /// Begin scanning for the GC Adapter.
116 void StartScanThread();
117
118 /// Stop scanning for the adapter
119 void StopScanThread();
120 100
121 /// Resets status of device connected to port 101 /// Resets status of device connected to port
122 void ResetDeviceType(std::size_t port); 102 void ResetDeviceType(std::size_t port);
@@ -133,19 +113,11 @@ private:
133 /// For use in initialization, querying devices to find the adapter 113 /// For use in initialization, querying devices to find the adapter
134 void Setup(); 114 void Setup();
135 115
136 int current_status = NO_ADAPTER_DETECTED;
137 libusb_device_handle* usb_adapter_handle = nullptr; 116 libusb_device_handle* usb_adapter_handle = nullptr;
138 std::array<ControllerTypes, 4> adapter_controllers_status{};
139
140 std::mutex s_mutex;
141 117
142 std::thread adapter_input_thread; 118 std::thread adapter_input_thread;
143 bool adapter_thread_running; 119 bool adapter_thread_running;
144 120
145 std::mutex initialization_mutex;
146 std::thread detect_thread;
147 bool detect_thread_running = false;
148
149 libusb_context* libusb_ctx; 121 libusb_context* libusb_ctx;
150 122
151 u8 input_endpoint = 0; 123 u8 input_endpoint = 0;
@@ -153,10 +125,11 @@ private:
153 125
154 bool configuring = false; 126 bool configuring = false;
155 127
156 std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
157 std::array<GCState, 4> state; 128 std::array<GCState, 4> state;
158 std::array<bool, 4> get_origin; 129 std::array<bool, 4> get_origin;
159 std::array<GCPadStatus, 4> origin_status; 130 std::array<GCPadStatus, 4> origin_status;
131 std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue;
132 std::array<ControllerTypes, 4> adapter_controllers_status{};
160}; 133};
161 134
162} // namespace GCAdapter 135} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
index 96e22d3ad..92e9e8e89 100644
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -15,7 +15,7 @@ namespace InputCommon {
15 15
16class GCButton final : public Input::ButtonDevice { 16class GCButton final : public Input::ButtonDevice {
17public: 17public:
18 explicit GCButton(int port_, int button_, GCAdapter::Adapter* adapter) 18 explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter)
19 : port(port_), button(button_), gcadapter(adapter) {} 19 : port(port_), button(button_), gcadapter(adapter) {}
20 20
21 ~GCButton() override; 21 ~GCButton() override;
@@ -30,15 +30,16 @@ public:
30private: 30private:
31 const int port; 31 const int port;
32 const int button; 32 const int button;
33 GCAdapter::Adapter* gcadapter; 33 const GCAdapter::Adapter* gcadapter;
34}; 34};
35 35
36class GCAxisButton final : public Input::ButtonDevice { 36class GCAxisButton final : public Input::ButtonDevice {
37public: 37public:
38 explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, 38 explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
39 GCAdapter::Adapter* adapter) 39 const GCAdapter::Adapter* adapter)
40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), 40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
41 gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {} 41 gcadapter(adapter),
42 origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}
42 43
43 bool GetStatus() const override { 44 bool GetStatus() const override {
44 if (gcadapter->DeviceConnected(port)) { 45 if (gcadapter->DeviceConnected(port)) {
@@ -59,7 +60,7 @@ private:
59 const int axis; 60 const int axis;
60 float threshold; 61 float threshold;
61 bool trigger_if_greater; 62 bool trigger_if_greater;
62 GCAdapter::Adapter* gcadapter; 63 const GCAdapter::Adapter* gcadapter;
63 const float origin_value; 64 const float origin_value;
64}; 65};
65 66
@@ -76,8 +77,7 @@ std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::Param
76 77
77 // button is not an axis/stick button 78 // button is not an axis/stick button
78 if (button_id != PAD_STICK_ID) { 79 if (button_id != PAD_STICK_ID) {
79 auto button = std::make_unique<GCButton>(port, button_id, adapter.get()); 80 return std::make_unique<GCButton>(port, button_id, adapter.get());
80 return std::move(button);
81 } 81 }
82 82
83 // For Axis buttons, used by the binary sticks. 83 // For Axis buttons, used by the binary sticks.
@@ -149,19 +149,18 @@ void GCButtonFactory::EndConfiguration() {
149 149
150class GCAnalog final : public Input::AnalogDevice { 150class GCAnalog final : public Input::AnalogDevice {
151public: 151public:
152 GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter) 152 GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_,
153 const GCAdapter::Adapter* adapter, float range_)
153 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), 154 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
154 origin_value_x(adapter->GetOriginValue(port_, axis_x_)), 155 origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))),
155 origin_value_y(adapter->GetOriginValue(port_, axis_y_)) {} 156 origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))),
157 range(range_) {}
156 158
157 float GetAxis(int axis) const { 159 float GetAxis(int axis) const {
158 if (gcadapter->DeviceConnected(port)) { 160 if (gcadapter->DeviceConnected(port)) {
159 std::lock_guard lock{mutex}; 161 std::lock_guard lock{mutex};
160 const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; 162 const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y;
161 // division is not by a perfect 128 to account for some variance in center location 163 return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range);
162 // e.g. my device idled at 131 in X, 120 in Y, and full range of motion was in range
163 // [20-230]
164 return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / 95.0f;
165 } 164 }
166 return 0.0f; 165 return 0.0f;
167 } 166 }
@@ -194,7 +193,7 @@ public:
194 193
195 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { 194 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
196 const auto [x, y] = GetStatus(); 195 const auto [x, y] = GetStatus();
197 const float directional_deadzone = 0.4f; 196 const float directional_deadzone = 0.5f;
198 switch (direction) { 197 switch (direction) {
199 case Input::AnalogDirection::RIGHT: 198 case Input::AnalogDirection::RIGHT:
200 return x > directional_deadzone; 199 return x > directional_deadzone;
@@ -213,9 +212,10 @@ private:
213 const int axis_x; 212 const int axis_x;
214 const int axis_y; 213 const int axis_y;
215 const float deadzone; 214 const float deadzone;
216 GCAdapter::Adapter* gcadapter; 215 const GCAdapter::Adapter* gcadapter;
217 const float origin_value_x; 216 const float origin_value_x;
218 const float origin_value_y; 217 const float origin_value_y;
218 const float range;
219 mutable std::mutex mutex; 219 mutable std::mutex mutex;
220}; 220};
221 221
@@ -234,9 +234,10 @@ std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::Param
234 const int port = params.Get("port", 0); 234 const int port = params.Get("port", 0);
235 const int axis_x = params.Get("axis_x", 0); 235 const int axis_x = params.Get("axis_x", 0);
236 const int axis_y = params.Get("axis_y", 1); 236 const int axis_y = params.Get("axis_y", 1);
237 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); 237 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
238 const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
238 239
239 return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get()); 240 return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
240} 241}
241 242
242void GCAnalogFactory::BeginConfiguration() { 243void GCAnalogFactory::BeginConfiguration() {
@@ -264,7 +265,8 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
264 if (analog_x_axis == -1) { 265 if (analog_x_axis == -1) {
265 analog_x_axis = axis; 266 analog_x_axis = axis;
266 controller_number = static_cast<int>(port); 267 controller_number = static_cast<int>(port);
267 } else if (analog_y_axis == -1 && analog_x_axis != axis && controller_number == port) { 268 } else if (analog_y_axis == -1 && analog_x_axis != axis &&
269 controller_number == static_cast<int>(port)) {
268 analog_y_axis = axis; 270 analog_y_axis = axis;
269 } 271 }
270 } 272 }
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index b9d5d0ec3..3d97d95f7 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,9 @@
11#include "input_common/keyboard.h" 11#include "input_common/keyboard.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/motion_emu.h" 13#include "input_common/motion_emu.h"
14#include "input_common/motion_from_button.h"
15#include "input_common/touch_from_button.h"
16#include "input_common/udp/client.h"
14#include "input_common/udp/udp.h" 17#include "input_common/udp/udp.h"
15#ifdef HAVE_SDL2 18#ifdef HAVE_SDL2
16#include "input_common/sdl/sdl.h" 19#include "input_common/sdl/sdl.h"
@@ -18,67 +21,227 @@
18 21
19namespace InputCommon { 22namespace InputCommon {
20 23
21static std::shared_ptr<Keyboard> keyboard; 24struct InputSubsystem::Impl {
22static std::shared_ptr<MotionEmu> motion_emu; 25 void Initialize() {
26 gcadapter = std::make_shared<GCAdapter::Adapter>();
27 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
28 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
29 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
30 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
31
32 keyboard = std::make_shared<Keyboard>();
33 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
34 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
35 std::make_shared<AnalogFromButton>());
36 Input::RegisterFactory<Input::MotionDevice>("keyboard",
37 std::make_shared<MotionFromButton>());
38 motion_emu = std::make_shared<MotionEmu>();
39 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
40 Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
41 std::make_shared<TouchFromButtonFactory>());
42
23#ifdef HAVE_SDL2 43#ifdef HAVE_SDL2
24static std::unique_ptr<SDL::State> sdl; 44 sdl = SDL::Init();
25#endif 45#endif
26static std::unique_ptr<CemuhookUDP::State> udp;
27static std::shared_ptr<GCButtonFactory> gcbuttons;
28static std::shared_ptr<GCAnalogFactory> gcanalog;
29
30void Init() {
31 auto gcadapter = std::make_shared<GCAdapter::Adapter>();
32 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
33 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
34 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
35 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
36
37 keyboard = std::make_shared<Keyboard>();
38 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
39 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
40 std::make_shared<AnalogFromButton>());
41 motion_emu = std::make_shared<MotionEmu>();
42 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
43 46
47 udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
48 udpmotion = std::make_shared<UDPMotionFactory>(udp);
49 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
50 udptouch = std::make_shared<UDPTouchFactory>(udp);
51 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
52 }
53
54 void Shutdown() {
55 Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
56 Input::UnregisterFactory<Input::MotionDevice>("keyboard");
57 keyboard.reset();
58 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
59 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
60 motion_emu.reset();
61 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
44#ifdef HAVE_SDL2 62#ifdef HAVE_SDL2
45 sdl = SDL::Init(); 63 sdl.reset();
46#endif 64#endif
65 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
66 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
47 67
48 udp = CemuhookUDP::Init(); 68 gcbuttons.reset();
49} 69 gcanalog.reset();
70
71 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
72 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
73
74 udpmotion.reset();
75 udptouch.reset();
76 }
77
78 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
79 std::vector<Common::ParamPackage> devices = {
80 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
81 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
82 };
83#ifdef HAVE_SDL2
84 auto sdl_devices = sdl->GetInputDevices();
85 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
86#endif
87 auto udp_devices = udp->GetInputDevices();
88 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
89 auto gcpad_devices = gcadapter->GetInputDevices();
90 devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
91 return devices;
92 }
93
94 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
95 const Common::ParamPackage& params) const {
96 if (!params.Has("class") || params.Get("class", "") == "any") {
97 return {};
98 }
99 if (params.Get("class", "") == "key") {
100 // TODO consider returning the SDL key codes for the default keybindings
101 return {};
102 }
103 if (params.Get("class", "") == "gcpad") {
104 return gcadapter->GetAnalogMappingForDevice(params);
105 }
106#ifdef HAVE_SDL2
107 if (params.Get("class", "") == "sdl") {
108 return sdl->GetAnalogMappingForDevice(params);
109 }
110#endif
111 return {};
112 }
113
114 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
115 const Common::ParamPackage& params) const {
116 if (!params.Has("class") || params.Get("class", "") == "any") {
117 return {};
118 }
119 if (params.Get("class", "") == "key") {
120 // TODO consider returning the SDL key codes for the default keybindings
121 return {};
122 }
123 if (params.Get("class", "") == "gcpad") {
124 return gcadapter->GetButtonMappingForDevice(params);
125 }
126#ifdef HAVE_SDL2
127 if (params.Get("class", "") == "sdl") {
128 return sdl->GetButtonMappingForDevice(params);
129 }
130#endif
131 return {};
132 }
133
134 [[nodiscard]] MotionMapping GetMotionMappingForDevice(
135 const Common::ParamPackage& params) const {
136 if (!params.Has("class") || params.Get("class", "") == "any") {
137 return {};
138 }
139 if (params.Get("class", "") == "cemuhookudp") {
140 // TODO return the correct motion device
141 return {};
142 }
143 return {};
144 }
50 145
51void Shutdown() { 146 std::shared_ptr<Keyboard> keyboard;
52 Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); 147 std::shared_ptr<MotionEmu> motion_emu;
53 keyboard.reset();
54 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
55 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
56 motion_emu.reset();
57#ifdef HAVE_SDL2 148#ifdef HAVE_SDL2
58 sdl.reset(); 149 std::unique_ptr<SDL::State> sdl;
59#endif 150#endif
60 udp.reset(); 151 std::shared_ptr<GCButtonFactory> gcbuttons;
61 Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); 152 std::shared_ptr<GCAnalogFactory> gcanalog;
62 Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); 153 std::shared_ptr<UDPMotionFactory> udpmotion;
154 std::shared_ptr<UDPTouchFactory> udptouch;
155 std::shared_ptr<CemuhookUDP::Client> udp;
156 std::shared_ptr<GCAdapter::Adapter> gcadapter;
157};
158
159InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
160
161InputSubsystem::~InputSubsystem() = default;
162
163void InputSubsystem::Initialize() {
164 impl->Initialize();
165}
166
167void InputSubsystem::Shutdown() {
168 impl->Shutdown();
169}
170
171Keyboard* InputSubsystem::GetKeyboard() {
172 return impl->keyboard.get();
173}
174
175const Keyboard* InputSubsystem::GetKeyboard() const {
176 return impl->keyboard.get();
177}
178
179MotionEmu* InputSubsystem::GetMotionEmu() {
180 return impl->motion_emu.get();
181}
63 182
64 gcbuttons.reset(); 183const MotionEmu* InputSubsystem::GetMotionEmu() const {
65 gcanalog.reset(); 184 return impl->motion_emu.get();
66} 185}
67 186
68Keyboard* GetKeyboard() { 187std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
69 return keyboard.get(); 188 return impl->GetInputDevices();
70} 189}
71 190
72MotionEmu* GetMotionEmu() { 191AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
73 return motion_emu.get(); 192 return impl->GetAnalogMappingForDevice(device);
74} 193}
75 194
76GCButtonFactory* GetGCButtons() { 195ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
77 return gcbuttons.get(); 196 return impl->GetButtonMappingForDevice(device);
78} 197}
79 198
80GCAnalogFactory* GetGCAnalogs() { 199GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
81 return gcanalog.get(); 200 return impl->gcanalog.get();
201}
202
203const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
204 return impl->gcanalog.get();
205}
206
207GCButtonFactory* InputSubsystem::GetGCButtons() {
208 return impl->gcbuttons.get();
209}
210
211const GCButtonFactory* InputSubsystem::GetGCButtons() const {
212 return impl->gcbuttons.get();
213}
214
215UDPMotionFactory* InputSubsystem::GetUDPMotions() {
216 return impl->udpmotion.get();
217}
218
219const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
220 return impl->udpmotion.get();
221}
222
223UDPTouchFactory* InputSubsystem::GetUDPTouch() {
224 return impl->udptouch.get();
225}
226
227const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
228 return impl->udptouch.get();
229}
230
231void InputSubsystem::ReloadInputDevices() {
232 if (!impl->udp) {
233 return;
234 }
235 impl->udp->ReloadUDPClient();
236}
237
238std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
239 Polling::DeviceType type) const {
240#ifdef HAVE_SDL2
241 return impl->sdl->GetPollers(type);
242#else
243 return {};
244#endif
82} 245}
83 246
84std::string GenerateKeyboardParam(int key_code) { 247std::string GenerateKeyboardParam(int key_code) {
@@ -102,18 +265,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
102 }; 265 };
103 return circle_pad_param.Serialize(); 266 return circle_pad_param.Serialize();
104} 267}
105
106namespace Polling {
107
108std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
109 std::vector<std::unique_ptr<DevicePoller>> pollers;
110
111#ifdef HAVE_SDL2
112 pollers = sdl->GetPollers(type);
113#endif
114
115 return pollers;
116}
117
118} // namespace Polling
119} // namespace InputCommon 268} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 0e32856f6..dded3f1ef 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -6,45 +6,29 @@
6 6
7#include <memory> 7#include <memory>
8#include <string> 8#include <string>
9#include <unordered_map>
9#include <vector> 10#include <vector>
10#include "input_common/gcadapter/gc_poller.h"
11 11
12namespace Common { 12namespace Common {
13class ParamPackage; 13class ParamPackage;
14} 14}
15 15
16namespace InputCommon { 16namespace Settings::NativeAnalog {
17 17enum Values : int;
18/// Initializes and registers all built-in input device factories. 18}
19void Init();
20
21/// Deregisters all built-in input device factories and shuts them down.
22void Shutdown();
23
24class Keyboard;
25
26/// Gets the keyboard button device factory.
27Keyboard* GetKeyboard();
28
29class MotionEmu;
30
31/// Gets the motion emulation factory.
32MotionEmu* GetMotionEmu();
33
34GCButtonFactory* GetGCButtons();
35
36GCAnalogFactory* GetGCAnalogs();
37 19
38/// Generates a serialized param package for creating a keyboard button device 20namespace Settings::NativeButton {
39std::string GenerateKeyboardParam(int key_code); 21enum Values : int;
22}
40 23
41/// Generates a serialized param package for creating an analog device taking input from keyboard 24namespace Settings::NativeMotion {
42std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, 25enum Values : int;
43 int key_modifier, float modifier_scale); 26}
44 27
28namespace InputCommon {
45namespace Polling { 29namespace Polling {
46 30
47enum class DeviceType { Button, Analog }; 31enum class DeviceType { Button, AnalogPreferred, Motion };
48 32
49/** 33/**
50 * A class that can be used to get inputs from an input device like controllers without having to 34 * A class that can be used to get inputs from an input device like controllers without having to
@@ -54,7 +38,9 @@ class DevicePoller {
54public: 38public:
55 virtual ~DevicePoller() = default; 39 virtual ~DevicePoller() = default;
56 /// Setup and start polling for inputs, should be called before GetNextInput 40 /// Setup and start polling for inputs, should be called before GetNextInput
57 virtual void Start() = 0; 41 /// If a device_id is provided, events should be filtered to only include events from this
42 /// device id
43 virtual void Start(const std::string& device_id = "") = 0;
58 /// Stop polling 44 /// Stop polling
59 virtual void Stop() = 0; 45 virtual void Stop() = 0;
60 /** 46 /**
@@ -64,8 +50,110 @@ public:
64 */ 50 */
65 virtual Common::ParamPackage GetNextInput() = 0; 51 virtual Common::ParamPackage GetNextInput() = 0;
66}; 52};
67
68// Get all DevicePoller from all backends for a specific device type
69std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
70} // namespace Polling 53} // namespace Polling
54
55class GCAnalogFactory;
56class GCButtonFactory;
57class UDPMotionFactory;
58class UDPTouchFactory;
59class Keyboard;
60class MotionEmu;
61
62/**
63 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
64 * mapping for the device. This is currently only implemented for the SDL backend devices.
65 */
66using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
67using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
68using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>;
69
70class InputSubsystem {
71public:
72 explicit InputSubsystem();
73 ~InputSubsystem();
74
75 InputSubsystem(const InputSubsystem&) = delete;
76 InputSubsystem& operator=(const InputSubsystem&) = delete;
77
78 InputSubsystem(InputSubsystem&&) = delete;
79 InputSubsystem& operator=(InputSubsystem&&) = delete;
80
81 /// Initializes and registers all built-in input device factories.
82 void Initialize();
83
84 /// Unregisters all built-in input device factories and shuts them down.
85 void Shutdown();
86
87 /// Retrieves the underlying keyboard device.
88 [[nodiscard]] Keyboard* GetKeyboard();
89
90 /// Retrieves the underlying keyboard device.
91 [[nodiscard]] const Keyboard* GetKeyboard() const;
92
93 /// Retrieves the underlying motion emulation factory.
94 [[nodiscard]] MotionEmu* GetMotionEmu();
95
96 /// Retrieves the underlying motion emulation factory.
97 [[nodiscard]] const MotionEmu* GetMotionEmu() const;
98
99 /**
100 * Returns all available input devices that this Factory can create a new device with.
101 * Each returned ParamPackage should have a `display` field used for display, a class field for
102 * backends to determine if this backend is meant to service the request and any other
103 * information needed to identify this in the backend later.
104 */
105 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
106
107 /// Retrieves the analog mappings for the given device.
108 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const;
109
110 /// Retrieves the button mappings for the given device.
111 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
112
113 /// Retrieves the motion mappings for the given device.
114 [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const;
115
116 /// Retrieves the underlying GameCube analog handler.
117 [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
118
119 /// Retrieves the underlying GameCube analog handler.
120 [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const;
121
122 /// Retrieves the underlying GameCube button handler.
123 [[nodiscard]] GCButtonFactory* GetGCButtons();
124
125 /// Retrieves the underlying GameCube button handler.
126 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
127
128 /// Retrieves the underlying udp motion handler.
129 [[nodiscard]] UDPMotionFactory* GetUDPMotions();
130
131 /// Retrieves the underlying udp motion handler.
132 [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const;
133
134 /// Retrieves the underlying udp touch handler.
135 [[nodiscard]] UDPTouchFactory* GetUDPTouch();
136
137 /// Retrieves the underlying udp touch handler.
138 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
139
140 /// Reloads the input devices
141 void ReloadInputDevices();
142
143 /// Get all DevicePoller from all backends for a specific device type
144 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
145 Polling::DeviceType type) const;
146
147private:
148 struct Impl;
149 std::unique_ptr<Impl> impl;
150};
151
152/// Generates a serialized param package for creating a keyboard button device
153std::string GenerateKeyboardParam(int key_code);
154
155/// Generates a serialized param package for creating an analog device taking input from keyboard
156std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
157 int key_modifier, float modifier_scale);
158
71} // namespace InputCommon 159} // namespace InputCommon
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index d4cdf76a3..69fd3c1d2 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -56,7 +56,7 @@ public:
56 is_tilting = false; 56 is_tilting = false;
57 } 57 }
58 58
59 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { 59 Input::MotionStatus GetStatus() {
60 std::lock_guard guard{status_mutex}; 60 std::lock_guard guard{status_mutex};
61 return status; 61 return status;
62 } 62 }
@@ -76,7 +76,7 @@ private:
76 76
77 Common::Event shutdown_event; 77 Common::Event shutdown_event;
78 78
79 std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; 79 Input::MotionStatus status;
80 std::mutex status_mutex; 80 std::mutex status_mutex;
81 81
82 // Note: always keep the thread declaration at the end so that other objects are initialized 82 // Note: always keep the thread declaration at the end so that other objects are initialized
@@ -113,10 +113,19 @@ private:
113 gravity = QuaternionRotate(inv_q, gravity); 113 gravity = QuaternionRotate(inv_q, gravity);
114 angular_rate = QuaternionRotate(inv_q, angular_rate); 114 angular_rate = QuaternionRotate(inv_q, angular_rate);
115 115
116 // TODO: Calculate the correct rotation vector and orientation matrix
117 const auto matrix4x4 = q.ToMatrix();
118 const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
119 const std::array orientation{
120 Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
121 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
122 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]),
123 };
124
116 // Update the sensor state 125 // Update the sensor state
117 { 126 {
118 std::lock_guard guard{status_mutex}; 127 std::lock_guard guard{status_mutex};
119 status = std::make_tuple(gravity, angular_rate); 128 status = std::make_tuple(gravity, angular_rate, rotation, orientation);
120 } 129 }
121 } 130 }
122 } 131 }
@@ -131,7 +140,7 @@ public:
131 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); 140 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
132 } 141 }
133 142
134 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 143 Input::MotionStatus GetStatus() const override {
135 return device->GetStatus(); 144 return device->GetStatus();
136 } 145 }
137 146
diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp
new file mode 100644
index 000000000..9d459f963
--- /dev/null
+++ b/src/input_common/motion_from_button.cpp
@@ -0,0 +1,34 @@
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 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
new file mode 100644
index 000000000..a959046fb
--- /dev/null
+++ b/src/input_common/motion_from_button.h
@@ -0,0 +1,25 @@
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/motion_input.cpp b/src/input_common/motion_input.cpp
new file mode 100644
index 000000000..e89019723
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,305 @@
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 <random>
6#include "common/math_util.h"
7#include "input_common/motion_input.h"
8
9namespace InputCommon {
10
11MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd)
12 : kp(new_kp), ki(new_ki), kd(new_kd), quat{{0, 0, -1}, 0} {}
13
14void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
15 accel = acceleration;
16}
17
18void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
19 gyro = gyroscope - gyro_drift;
20
21 // Auto adjust drift to minimize drift
22 if (!IsMoving(0.1f)) {
23 gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
24 }
25
26 if (gyro.Length2() < gyro_threshold) {
27 gyro = {};
28 } else {
29 only_accelerometer = false;
30 }
31}
32
33void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
34 quat = quaternion;
35}
36
37void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
38 gyro_drift = drift;
39}
40
41void MotionInput::SetGyroThreshold(f32 threshold) {
42 gyro_threshold = threshold;
43}
44
45void MotionInput::EnableReset(bool reset) {
46 reset_enabled = reset;
47}
48
49void MotionInput::ResetRotations() {
50 rotations = {};
51}
52
53bool MotionInput::IsMoving(f32 sensitivity) const {
54 return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
55}
56
57bool MotionInput::IsCalibrated(f32 sensitivity) const {
58 return real_error.Length() < sensitivity;
59}
60
61void MotionInput::UpdateRotation(u64 elapsed_time) {
62 const f32 sample_period = elapsed_time / 1000000.0f;
63 if (sample_period > 0.1f) {
64 return;
65 }
66 rotations += gyro * sample_period;
67}
68
69void MotionInput::UpdateOrientation(u64 elapsed_time) {
70 if (!IsCalibrated(0.1f)) {
71 ResetOrientation();
72 }
73 // Short name local variable for readability
74 f32 q1 = quat.w;
75 f32 q2 = quat.xyz[0];
76 f32 q3 = quat.xyz[1];
77 f32 q4 = quat.xyz[2];
78 const f32 sample_period = elapsed_time / 1000000.0f;
79
80 // Ignore invalid elapsed time
81 if (sample_period > 0.1f) {
82 return;
83 }
84
85 const auto normal_accel = accel.Normalized();
86 auto rad_gyro = gyro * Common::PI * 2;
87 const f32 swap = rad_gyro.x;
88 rad_gyro.x = rad_gyro.y;
89 rad_gyro.y = -swap;
90 rad_gyro.z = -rad_gyro.z;
91
92 // Clear gyro values if there is no gyro present
93 if (only_accelerometer) {
94 rad_gyro.x = 0;
95 rad_gyro.y = 0;
96 rad_gyro.z = 0;
97 }
98
99 // Ignore drift correction if acceleration is not reliable
100 if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
101 const f32 ax = -normal_accel.x;
102 const f32 ay = normal_accel.y;
103 const f32 az = -normal_accel.z;
104
105 // Estimated direction of gravity
106 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
107 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
108 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
109
110 // Error is cross product between estimated direction and measured direction of gravity
111 const Common::Vec3f new_real_error = {
112 az * vx - ax * vz,
113 ay * vz - az * vy,
114 ax * vy - ay * vx,
115 };
116
117 derivative_error = new_real_error - real_error;
118 real_error = new_real_error;
119
120 // Prevent integral windup
121 if (ki != 0.0f && !IsCalibrated(0.05f)) {
122 integral_error += real_error;
123 } else {
124 integral_error = {};
125 }
126
127 // Apply feedback terms
128 if (!only_accelerometer) {
129 rad_gyro += kp * real_error;
130 rad_gyro += ki * integral_error;
131 rad_gyro += kd * derivative_error;
132 } else {
133 // Give more weight to acelerometer values to compensate for the lack of gyro
134 rad_gyro += 35.0f * kp * real_error;
135 rad_gyro += 10.0f * ki * integral_error;
136 rad_gyro += 10.0f * kd * derivative_error;
137
138 // Emulate gyro values for games that need them
139 gyro.x = -rad_gyro.y;
140 gyro.y = rad_gyro.x;
141 gyro.z = -rad_gyro.z;
142 UpdateRotation(elapsed_time);
143 }
144 }
145
146 const f32 gx = rad_gyro.y;
147 const f32 gy = rad_gyro.x;
148 const f32 gz = rad_gyro.z;
149
150 // Integrate rate of change of quaternion
151 const f32 pa = q2;
152 const f32 pb = q3;
153 const f32 pc = q4;
154 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
155 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
156 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
157 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
158
159 quat.w = q1;
160 quat.xyz[0] = q2;
161 quat.xyz[1] = q3;
162 quat.xyz[2] = q4;
163 quat = quat.Normalized();
164}
165
166std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
167 const Common::Quaternion<float> quad{
168 .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
169 .w = -quat.xyz[2],
170 };
171 const std::array<float, 16> matrix4x4 = quad.ToMatrix();
172
173 return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
174 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
175 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
176}
177
178Common::Vec3f MotionInput::GetAcceleration() const {
179 return accel;
180}
181
182Common::Vec3f MotionInput::GetGyroscope() const {
183 return gyro;
184}
185
186Common::Quaternion<f32> MotionInput::GetQuaternion() const {
187 return quat;
188}
189
190Common::Vec3f MotionInput::GetRotations() const {
191 return rotations;
192}
193
194Input::MotionStatus MotionInput::GetMotion() const {
195 const Common::Vec3f gyroscope = GetGyroscope();
196 const Common::Vec3f accelerometer = GetAcceleration();
197 const Common::Vec3f rotation = GetRotations();
198 const std::array<Common::Vec3f, 3> orientation = GetOrientation();
199 return {accelerometer, gyroscope, rotation, orientation};
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 distribution(gen) * 0.001f,
208 distribution(gen) * 0.001f,
209 distribution(gen) * 0.001f,
210 };
211 const Common::Vec3f accelerometer = {
212 distribution(gen) * 0.001f,
213 distribution(gen) * 0.001f,
214 distribution(gen) * 0.001f,
215 };
216 const Common::Vec3f rotation = {};
217 const std::array<Common::Vec3f, 3> orientation = {
218 Common::Vec3f{1.0f, 0, 0},
219 Common::Vec3f{0, 1.0f, 0},
220 Common::Vec3f{0, 0, 1.0f},
221 };
222 return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation};
223}
224
225void MotionInput::ResetOrientation() {
226 if (!reset_enabled || only_accelerometer) {
227 return;
228 }
229 if (!IsMoving(0.5f) && accel.z <= -0.9f) {
230 ++reset_counter;
231 if (reset_counter > 900) {
232 quat.w = 0;
233 quat.xyz[0] = 0;
234 quat.xyz[1] = 0;
235 quat.xyz[2] = -1;
236 SetOrientationFromAccelerometer();
237 integral_error = {};
238 reset_counter = 0;
239 }
240 } else {
241 reset_counter = 0;
242 }
243}
244
245void MotionInput::SetOrientationFromAccelerometer() {
246 int iterations = 0;
247 const f32 sample_period = 0.015f;
248
249 const auto normal_accel = accel.Normalized();
250 const f32 ax = -normal_accel.x;
251 const f32 ay = normal_accel.y;
252 const f32 az = -normal_accel.z;
253
254 while (!IsCalibrated(0.01f) && ++iterations < 100) {
255 // Short name local variable for readability
256 f32 q1 = quat.w;
257 f32 q2 = quat.xyz[0];
258 f32 q3 = quat.xyz[1];
259 f32 q4 = quat.xyz[2];
260
261 Common::Vec3f rad_gyro = {};
262 const f32 ax = -normal_accel.x;
263 const f32 ay = normal_accel.y;
264 const f32 az = -normal_accel.z;
265
266 // Estimated direction of gravity
267 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
268 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
269 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
270
271 // Error is cross product between estimated direction and measured direction of gravity
272 const Common::Vec3f new_real_error = {
273 az * vx - ax * vz,
274 ay * vz - az * vy,
275 ax * vy - ay * vx,
276 };
277
278 derivative_error = new_real_error - real_error;
279 real_error = new_real_error;
280
281 rad_gyro += 10.0f * kp * real_error;
282 rad_gyro += 5.0f * ki * integral_error;
283 rad_gyro += 10.0f * kd * derivative_error;
284
285 const f32 gx = rad_gyro.y;
286 const f32 gy = rad_gyro.x;
287 const f32 gz = rad_gyro.z;
288
289 // Integrate rate of change of quaternion
290 const f32 pa = q2;
291 const f32 pb = q3;
292 const f32 pc = q4;
293 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
294 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
295 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
296 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
297
298 quat.w = q1;
299 quat.xyz[0] = q2;
300 quat.xyz[1] = q3;
301 quat.xyz[2] = q4;
302 quat = quat.Normalized();
303 }
304}
305} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..6342d0318
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,73 @@
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 "common/common_types.h"
8#include "common/quaternion.h"
9#include "common/vector_math.h"
10#include "core/frontend/input.h"
11
12namespace InputCommon {
13
14class MotionInput {
15public:
16 MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
17
18 MotionInput(const MotionInput&) = default;
19 MotionInput& operator=(const MotionInput&) = default;
20
21 MotionInput(MotionInput&&) = default;
22 MotionInput& operator=(MotionInput&&) = default;
23
24 void SetAcceleration(const Common::Vec3f& acceleration);
25 void SetGyroscope(const Common::Vec3f& acceleration);
26 void SetQuaternion(const Common::Quaternion<f32>& quaternion);
27 void SetGyroDrift(const Common::Vec3f& drift);
28 void SetGyroThreshold(f32 threshold);
29
30 void EnableReset(bool reset);
31 void ResetRotations();
32
33 void UpdateRotation(u64 elapsed_time);
34 void UpdateOrientation(u64 elapsed_time);
35
36 std::array<Common::Vec3f, 3> GetOrientation() const;
37 Common::Vec3f GetAcceleration() const;
38 Common::Vec3f GetGyroscope() const;
39 Common::Vec3f GetRotations() const;
40 Common::Quaternion<f32> GetQuaternion() const;
41 Input::MotionStatus GetMotion() const;
42 Input::MotionStatus GetRandomMotion(int accel_magnitude, int gyro_magnitude) const;
43
44 bool IsMoving(f32 sensitivity) const;
45 bool IsCalibrated(f32 sensitivity) const;
46
47private:
48 void ResetOrientation();
49 void SetOrientationFromAccelerometer();
50
51 // PID constants
52 const f32 kp;
53 const f32 ki;
54 const f32 kd;
55
56 // PID errors
57 Common::Vec3f real_error;
58 Common::Vec3f integral_error;
59 Common::Vec3f derivative_error;
60
61 Common::Quaternion<f32> quat;
62 Common::Vec3f rotations;
63 Common::Vec3f accel;
64 Common::Vec3f gyro;
65 Common::Vec3f gyro_drift;
66
67 f32 gyro_threshold = 0.0f;
68 u32 reset_counter = 0;
69 bool reset_enabled = true;
70 bool only_accelerometer = true;
71};
72
73} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 5306daa70..f3554be9a 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -6,6 +6,7 @@
6 6
7#include <memory> 7#include <memory>
8#include <vector> 8#include <vector>
9#include "common/param_package.h"
9#include "input_common/main.h" 10#include "input_common/main.h"
10 11
11namespace InputCommon::Polling { 12namespace InputCommon::Polling {
@@ -22,14 +23,24 @@ public:
22 /// Unregisters SDL device factories and shut them down. 23 /// Unregisters SDL device factories and shut them down.
23 virtual ~State() = default; 24 virtual ~State() = default;
24 25
25 virtual Pollers GetPollers(Polling::DeviceType type) = 0; 26 virtual Pollers GetPollers(Polling::DeviceType type) {
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 }
26}; 40};
27 41
28class NullState : public State { 42class NullState : public State {
29public: 43public:
30 Pollers GetPollers(Polling::DeviceType type) override {
31 return {};
32 }
33}; 44};
34 45
35std::unique_ptr<State> Init(); 46std::unique_ptr<State> Init();
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index 675b477fa..bd480570a 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -3,10 +3,14 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <array>
6#include <atomic> 7#include <atomic>
8#include <chrono>
7#include <cmath> 9#include <cmath>
8#include <functional> 10#include <functional>
9#include <mutex> 11#include <mutex>
12#include <optional>
13#include <sstream>
10#include <string> 14#include <string>
11#include <thread> 15#include <thread>
12#include <tuple> 16#include <tuple>
@@ -15,15 +19,17 @@
15#include <vector> 19#include <vector>
16#include <SDL.h> 20#include <SDL.h>
17#include "common/logging/log.h" 21#include "common/logging/log.h"
18#include "common/math_util.h"
19#include "common/param_package.h" 22#include "common/param_package.h"
20#include "common/threadsafe_queue.h" 23#include "common/threadsafe_queue.h"
21#include "core/frontend/input.h" 24#include "core/frontend/input.h"
25#include "input_common/motion_input.h"
22#include "input_common/sdl/sdl_impl.h" 26#include "input_common/sdl/sdl_impl.h"
27#include "input_common/settings.h"
23 28
24namespace InputCommon::SDL { 29namespace InputCommon::SDL {
25 30
26static std::string GetGUID(SDL_Joystick* joystick) { 31namespace {
32std::string GetGUID(SDL_Joystick* joystick) {
27 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); 33 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
28 char guid_str[33]; 34 char guid_str[33];
29 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); 35 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
@@ -31,7 +37,8 @@ static std::string GetGUID(SDL_Joystick* joystick) {
31} 37}
32 38
33/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice 39/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
34static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); 40Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
41} // Anonymous namespace
35 42
36static int SDLEventWatcher(void* user_data, SDL_Event* event) { 43static int SDLEventWatcher(void* user_data, SDL_Event* event) {
37 auto* const sdl_state = static_cast<SDLState*>(user_data); 44 auto* const sdl_state = static_cast<SDLState*>(user_data);
@@ -48,8 +55,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) {
48 55
49class SDLJoystick { 56class SDLJoystick {
50public: 57public:
51 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick) 58 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
52 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {} 59 SDL_GameController* gamecontroller)
60 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
61 sdl_controller{gamecontroller, &SDL_GameControllerClose} {}
53 62
54 void SetButton(int button, bool value) { 63 void SetButton(int button, bool value) {
55 std::lock_guard lock{mutex}; 64 std::lock_guard lock{mutex};
@@ -66,14 +75,41 @@ public:
66 state.axes.insert_or_assign(axis, value); 75 state.axes.insert_or_assign(axis, value);
67 } 76 }
68 77
69 float GetAxis(int axis) const { 78 float GetAxis(int axis, float range) const {
70 std::lock_guard lock{mutex}; 79 std::lock_guard lock{mutex};
71 return state.axes.at(axis) / 32767.0f; 80 return state.axes.at(axis) / (32767.0f * range);
72 } 81 }
73 82
74 std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { 83 bool RumblePlay(f32 amp_low, f32 amp_high, int time) {
75 float x = GetAxis(axis_x); 84 const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF);
76 float y = GetAxis(axis_y); 85 const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF);
86 // Lower drastically the number of state changes
87 if (raw_amp_low >> 11 == last_state_rumble_low >> 11 &&
88 raw_amp_high >> 11 == last_state_rumble_high >> 11) {
89 if (raw_amp_low + raw_amp_high != 0 ||
90 last_state_rumble_low + last_state_rumble_high == 0) {
91 return false;
92 }
93 }
94 // Don't change state if last vibration was < 20ms
95 const auto now = std::chrono::system_clock::now();
96 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) <
97 std::chrono::milliseconds(20)) {
98 return raw_amp_low + raw_amp_high == 0;
99 }
100
101 last_vibration = now;
102 last_state_rumble_low = raw_amp_low;
103 last_state_rumble_high = raw_amp_high;
104 if (sdl_joystick) {
105 SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time);
106 }
107 return false;
108 }
109
110 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
111 float x = GetAxis(axis_x, range);
112 float y = GetAxis(axis_y, range);
77 y = -y; // 3DS uses an y-axis inverse from SDL 113 y = -y; // 3DS uses an y-axis inverse from SDL
78 114
79 // Make sure the coordinates are in the unit circle, 115 // Make sure the coordinates are in the unit circle,
@@ -88,6 +124,10 @@ public:
88 return std::make_tuple(x, y); 124 return std::make_tuple(x, y);
89 } 125 }
90 126
127 const InputCommon::MotionInput& GetMotion() const {
128 return motion;
129 }
130
91 void SetHat(int hat, Uint8 direction) { 131 void SetHat(int hat, Uint8 direction) {
92 std::lock_guard lock{mutex}; 132 std::lock_guard lock{mutex};
93 state.hats.insert_or_assign(hat, direction); 133 state.hats.insert_or_assign(hat, direction);
@@ -115,10 +155,15 @@ public:
115 return sdl_joystick.get(); 155 return sdl_joystick.get();
116 } 156 }
117 157
118 void SetSDLJoystick(SDL_Joystick* joystick) { 158 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
159 sdl_controller.reset(controller);
119 sdl_joystick.reset(joystick); 160 sdl_joystick.reset(joystick);
120 } 161 }
121 162
163 SDL_GameController* GetSDLGameController() const {
164 return sdl_controller.get();
165 }
166
122private: 167private:
123 struct State { 168 struct State {
124 std::unordered_map<int, bool> buttons; 169 std::unordered_map<int, bool> buttons;
@@ -127,8 +172,15 @@ private:
127 } state; 172 } state;
128 std::string guid; 173 std::string guid;
129 int port; 174 int port;
175 u16 last_state_rumble_high;
176 u16 last_state_rumble_low;
177 std::chrono::time_point<std::chrono::system_clock> last_vibration;
130 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; 178 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
179 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
131 mutable std::mutex mutex; 180 mutable std::mutex mutex;
181
182 // motion is initalized without PID values as motion input is not aviable for SDL2
183 InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
132}; 184};
133 185
134std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { 186std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
@@ -136,18 +188,19 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g
136 const auto it = joystick_map.find(guid); 188 const auto it = joystick_map.find(guid);
137 if (it != joystick_map.end()) { 189 if (it != joystick_map.end()) {
138 while (it->second.size() <= static_cast<std::size_t>(port)) { 190 while (it->second.size() <= static_cast<std::size_t>(port)) {
139 auto joystick = 191 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
140 std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr); 192 nullptr, nullptr);
141 it->second.emplace_back(std::move(joystick)); 193 it->second.emplace_back(std::move(joystick));
142 } 194 }
143 return it->second[port]; 195 return it->second[port];
144 } 196 }
145 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr); 197 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
146 return joystick_map[guid].emplace_back(std::move(joystick)); 198 return joystick_map[guid].emplace_back(std::move(joystick));
147} 199}
148 200
149std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { 201std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
150 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); 202 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
203 auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id);
151 const std::string guid = GetGUID(sdl_joystick); 204 const std::string guid = GetGUID(sdl_joystick);
152 205
153 std::lock_guard lock{joystick_map_mutex}; 206 std::lock_guard lock{joystick_map_mutex};
@@ -171,32 +224,36 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_
171 }); 224 });
172 if (nullptr_it != map_it->second.end()) { 225 if (nullptr_it != map_it->second.end()) {
173 // ... and map it 226 // ... and map it
174 (*nullptr_it)->SetSDLJoystick(sdl_joystick); 227 (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller);
175 return *nullptr_it; 228 return *nullptr_it;
176 } 229 }
177 230
178 // There is no SDLJoystick without a mapped SDL_Joystick 231 // There is no SDLJoystick without a mapped SDL_Joystick
179 // Create a new SDLJoystick 232 // Create a new SDLJoystick
180 const int port = static_cast<int>(map_it->second.size()); 233 const int port = static_cast<int>(map_it->second.size());
181 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); 234 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller);
182 return map_it->second.emplace_back(std::move(joystick)); 235 return map_it->second.emplace_back(std::move(joystick));
183 } 236 }
184 237
185 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); 238 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller);
186 return joystick_map[guid].emplace_back(std::move(joystick)); 239 return joystick_map[guid].emplace_back(std::move(joystick));
187} 240}
188 241
189void SDLState::InitJoystick(int joystick_index) { 242void SDLState::InitJoystick(int joystick_index) {
190 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); 243 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
244 SDL_GameController* sdl_gamecontroller = nullptr;
245 if (SDL_IsGameController(joystick_index)) {
246 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
247 }
191 if (!sdl_joystick) { 248 if (!sdl_joystick) {
192 LOG_ERROR(Input, "failed to open joystick {}", joystick_index); 249 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
193 return; 250 return;
194 } 251 }
195 const std::string guid = GetGUID(sdl_joystick); 252 const std::string guid = GetGUID(sdl_joystick);
196 253
197 std::lock_guard lock{joystick_map_mutex}; 254 std::lock_guard lock{joystick_map_mutex};
198 if (joystick_map.find(guid) == joystick_map.end()) { 255 if (joystick_map.find(guid) == joystick_map.end()) {
199 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); 256 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
200 joystick_map[guid].emplace_back(std::move(joystick)); 257 joystick_map[guid].emplace_back(std::move(joystick));
201 return; 258 return;
202 } 259 }
@@ -205,11 +262,11 @@ void SDLState::InitJoystick(int joystick_index) {
205 joystick_guid_list.begin(), joystick_guid_list.end(), 262 joystick_guid_list.begin(), joystick_guid_list.end(),
206 [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); 263 [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
207 if (it != joystick_guid_list.end()) { 264 if (it != joystick_guid_list.end()) {
208 (*it)->SetSDLJoystick(sdl_joystick); 265 (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
209 return; 266 return;
210 } 267 }
211 const int port = static_cast<int>(joystick_guid_list.size()); 268 const int port = static_cast<int>(joystick_guid_list.size());
212 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); 269 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
213 joystick_guid_list.emplace_back(std::move(joystick)); 270 joystick_guid_list.emplace_back(std::move(joystick));
214} 271}
215 272
@@ -231,7 +288,7 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
231 288
232 // Destruct SDL_Joystick outside the lock guard because SDL can internally call the 289 // Destruct SDL_Joystick outside the lock guard because SDL can internally call the
233 // event callback which locks the mutex again. 290 // event callback which locks the mutex again.
234 joystick->SetSDLJoystick(nullptr); 291 joystick->SetSDLJoystick(nullptr, nullptr);
235} 292}
236 293
237void SDLState::HandleGameControllerEvent(const SDL_Event& event) { 294void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@@ -285,6 +342,12 @@ public:
285 return joystick->GetButton(button); 342 return joystick->GetButton(button);
286 } 343 }
287 344
345 bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
346 const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f));
347 const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f));
348 return joystick->RumblePlay(new_amp_low, new_amp_high, 250);
349 }
350
288private: 351private:
289 std::shared_ptr<SDLJoystick> joystick; 352 std::shared_ptr<SDLJoystick> joystick;
290 int button; 353 int button;
@@ -313,7 +376,7 @@ public:
313 trigger_if_greater(trigger_if_greater_) {} 376 trigger_if_greater(trigger_if_greater_) {}
314 377
315 bool GetStatus() const override { 378 bool GetStatus() const override {
316 const float axis_value = joystick->GetAxis(axis); 379 const float axis_value = joystick->GetAxis(axis, 1.0f);
317 if (trigger_if_greater) { 380 if (trigger_if_greater) {
318 return axis_value > threshold; 381 return axis_value > threshold;
319 } 382 }
@@ -329,22 +392,24 @@ private:
329 392
330class SDLAnalog final : public Input::AnalogDevice { 393class SDLAnalog final : public Input::AnalogDevice {
331public: 394public:
332 SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_) 395 SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_,
333 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} 396 float range_)
397 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_),
398 range(range_) {}
334 399
335 std::tuple<float, float> GetStatus() const override { 400 std::tuple<float, float> GetStatus() const override {
336 const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); 401 const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
337 const float r = std::sqrt((x * x) + (y * y)); 402 const float r = std::sqrt((x * x) + (y * y));
338 if (r > deadzone) { 403 if (r > deadzone) {
339 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), 404 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
340 y / r * (r - deadzone) / (1 - deadzone)); 405 y / r * (r - deadzone) / (1 - deadzone));
341 } 406 }
342 return std::make_tuple<float, float>(0.0f, 0.0f); 407 return {};
343 } 408 }
344 409
345 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { 410 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
346 const auto [x, y] = GetStatus(); 411 const auto [x, y] = GetStatus();
347 const float directional_deadzone = 0.4f; 412 const float directional_deadzone = 0.5f;
348 switch (direction) { 413 switch (direction) {
349 case Input::AnalogDirection::RIGHT: 414 case Input::AnalogDirection::RIGHT:
350 return x > directional_deadzone; 415 return x > directional_deadzone;
@@ -363,6 +428,69 @@ private:
363 const int axis_x; 428 const int axis_x;
364 const int axis_y; 429 const int axis_y;
365 const float deadzone; 430 const float deadzone;
431 const float range;
432};
433
434class SDLDirectionMotion final : public Input::MotionDevice {
435public:
436 explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
437 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
438
439 Input::MotionStatus GetStatus() const override {
440 if (joystick->GetHatDirection(hat, direction)) {
441 return joystick->GetMotion().GetRandomMotion(2, 6);
442 }
443 return joystick->GetMotion().GetRandomMotion(0, 0);
444 }
445
446private:
447 std::shared_ptr<SDLJoystick> joystick;
448 int hat;
449 Uint8 direction;
450};
451
452class SDLAxisMotion final : public Input::MotionDevice {
453public:
454 explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
455 bool trigger_if_greater_)
456 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
457 trigger_if_greater(trigger_if_greater_) {}
458
459 Input::MotionStatus GetStatus() const override {
460 const float axis_value = joystick->GetAxis(axis, 1.0f);
461 bool trigger = axis_value < threshold;
462 if (trigger_if_greater) {
463 trigger = axis_value > threshold;
464 }
465
466 if (trigger) {
467 return joystick->GetMotion().GetRandomMotion(2, 6);
468 }
469 return joystick->GetMotion().GetRandomMotion(0, 0);
470 }
471
472private:
473 std::shared_ptr<SDLJoystick> joystick;
474 int axis;
475 float threshold;
476 bool trigger_if_greater;
477};
478
479class SDLButtonMotion final : public Input::MotionDevice {
480public:
481 explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
482 : joystick(std::move(joystick_)), button(button_) {}
483
484 Input::MotionStatus GetStatus() const override {
485 if (joystick->GetButton(button)) {
486 return joystick->GetMotion().GetRandomMotion(2, 6);
487 }
488 return joystick->GetMotion().GetRandomMotion(0, 0);
489 }
490
491private:
492 std::shared_ptr<SDLJoystick> joystick;
493 int button;
366}; 494};
367 495
368/// A button device factory that creates button devices from SDL joystick 496/// A button device factory that creates button devices from SDL joystick
@@ -457,14 +585,78 @@ public:
457 const int port = params.Get("port", 0); 585 const int port = params.Get("port", 0);
458 const int axis_x = params.Get("axis_x", 0); 586 const int axis_x = params.Get("axis_x", 0);
459 const int axis_y = params.Get("axis_y", 1); 587 const int axis_y = params.Get("axis_y", 1);
460 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); 588 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
461 589 const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
462 auto joystick = state.GetSDLJoystickByGUID(guid, port); 590 auto joystick = state.GetSDLJoystickByGUID(guid, port);
463 591
464 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash 592 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
465 joystick->SetAxis(axis_x, 0); 593 joystick->SetAxis(axis_x, 0);
466 joystick->SetAxis(axis_y, 0); 594 joystick->SetAxis(axis_y, 0);
467 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone); 595 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);
596 }
597
598private:
599 SDLState& state;
600};
601
602/// A motion device factory that creates motion devices from SDL joystick
603class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
604public:
605 explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
606 /**
607 * Creates motion device from joystick axes
608 * @param params contains parameters for creating the device:
609 * - "guid": the guid of the joystick to bind
610 * - "port": the nth joystick of the same type
611 */
612 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
613 const std::string guid = params.Get("guid", "0");
614 const int port = params.Get("port", 0);
615
616 auto joystick = state.GetSDLJoystickByGUID(guid, port);
617
618 if (params.Has("hat")) {
619 const int hat = params.Get("hat", 0);
620 const std::string direction_name = params.Get("direction", "");
621 Uint8 direction;
622 if (direction_name == "up") {
623 direction = SDL_HAT_UP;
624 } else if (direction_name == "down") {
625 direction = SDL_HAT_DOWN;
626 } else if (direction_name == "left") {
627 direction = SDL_HAT_LEFT;
628 } else if (direction_name == "right") {
629 direction = SDL_HAT_RIGHT;
630 } else {
631 direction = 0;
632 }
633 // This is necessary so accessing GetHat with hat won't crash
634 joystick->SetHat(hat, SDL_HAT_CENTERED);
635 return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
636 }
637
638 if (params.Has("axis")) {
639 const int axis = params.Get("axis", 0);
640 const float threshold = params.Get("threshold", 0.5f);
641 const std::string direction_name = params.Get("direction", "");
642 bool trigger_if_greater;
643 if (direction_name == "+") {
644 trigger_if_greater = true;
645 } else if (direction_name == "-") {
646 trigger_if_greater = false;
647 } else {
648 trigger_if_greater = true;
649 LOG_ERROR(Input, "Unknown direction {}", direction_name);
650 }
651 // This is necessary so accessing GetAxis with axis won't crash
652 joystick->SetAxis(axis, 0);
653 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
654 }
655
656 const int button = params.Get("button", 0);
657 // This is necessary so accessing GetButton with button won't crash
658 joystick->SetButton(button, false);
659 return std::make_unique<SDLButtonMotion>(joystick, button);
468 } 660 }
469 661
470private: 662private:
@@ -473,8 +665,12 @@ private:
473 665
474SDLState::SDLState() { 666SDLState::SDLState() {
475 using namespace Input; 667 using namespace Input;
476 RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); 668 analog_factory = std::make_shared<SDLAnalogFactory>(*this);
477 RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); 669 button_factory = std::make_shared<SDLButtonFactory>(*this);
670 motion_factory = std::make_shared<SDLMotionFactory>(*this);
671 RegisterFactory<AnalogDevice>("sdl", analog_factory);
672 RegisterFactory<ButtonDevice>("sdl", button_factory);
673 RegisterFactory<MotionDevice>("sdl", motion_factory);
478 674
479 // If the frontend is going to manage the event loop, then we dont start one here 675 // If the frontend is going to manage the event loop, then we dont start one here
480 start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); 676 start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
@@ -482,6 +678,7 @@ SDLState::SDLState() {
482 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); 678 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
483 return; 679 return;
484 } 680 }
681 has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
485 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { 682 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
486 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); 683 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
487 } 684 }
@@ -494,7 +691,7 @@ SDLState::SDLState() {
494 using namespace std::chrono_literals; 691 using namespace std::chrono_literals;
495 while (initialized) { 692 while (initialized) {
496 SDL_PumpEvents(); 693 SDL_PumpEvents();
497 std::this_thread::sleep_for(10ms); 694 std::this_thread::sleep_for(5ms);
498 } 695 }
499 }); 696 });
500 } 697 }
@@ -509,6 +706,7 @@ SDLState::~SDLState() {
509 using namespace Input; 706 using namespace Input;
510 UnregisterFactory<ButtonDevice>("sdl"); 707 UnregisterFactory<ButtonDevice>("sdl");
511 UnregisterFactory<AnalogDevice>("sdl"); 708 UnregisterFactory<AnalogDevice>("sdl");
709 UnregisterFactory<MotionDevice>("sdl");
512 710
513 CloseJoysticks(); 711 CloseJoysticks();
514 SDL_DelEventWatch(&SDLEventWatcher, this); 712 SDL_DelEventWatch(&SDLEventWatcher, this);
@@ -520,65 +718,251 @@ SDLState::~SDLState() {
520 } 718 }
521} 719}
522 720
523static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { 721std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
722 std::scoped_lock lock(joystick_map_mutex);
723 std::vector<Common::ParamPackage> devices;
724 for (const auto& [key, value] : joystick_map) {
725 for (const auto& joystick : value) {
726 auto joy = joystick->GetSDLJoystick();
727 if (auto controller = joystick->GetSDLGameController()) {
728 std::string name =
729 fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort());
730 devices.emplace_back(Common::ParamPackage{
731 {"class", "sdl"},
732 {"display", std::move(name)},
733 {"guid", joystick->GetGUID()},
734 {"port", std::to_string(joystick->GetPort())},
735 });
736 } else if (joy) {
737 std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort());
738 devices.emplace_back(Common::ParamPackage{
739 {"class", "sdl"},
740 {"display", std::move(name)},
741 {"guid", joystick->GetGUID()},
742 {"port", std::to_string(joystick->GetPort())},
743 });
744 }
745 }
746 }
747 return devices;
748}
749
750namespace {
751Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis,
752 float value = 0.1f) {
524 Common::ParamPackage params({{"engine", "sdl"}}); 753 Common::ParamPackage params({{"engine", "sdl"}});
754 params.Set("port", port);
755 params.Set("guid", std::move(guid));
756 params.Set("axis", axis);
757 if (value > 0) {
758 params.Set("direction", "+");
759 params.Set("threshold", "0.5");
760 } else {
761 params.Set("direction", "-");
762 params.Set("threshold", "-0.5");
763 }
764 return params;
765}
766
767Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) {
768 Common::ParamPackage params({{"engine", "sdl"}});
769 params.Set("port", port);
770 params.Set("guid", std::move(guid));
771 params.Set("button", button);
772 return params;
773}
525 774
775Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) {
776 Common::ParamPackage params({{"engine", "sdl"}});
777
778 params.Set("port", port);
779 params.Set("guid", std::move(guid));
780 params.Set("hat", hat);
781 switch (value) {
782 case SDL_HAT_UP:
783 params.Set("direction", "up");
784 break;
785 case SDL_HAT_DOWN:
786 params.Set("direction", "down");
787 break;
788 case SDL_HAT_LEFT:
789 params.Set("direction", "left");
790 break;
791 case SDL_HAT_RIGHT:
792 params.Set("direction", "right");
793 break;
794 default:
795 return {};
796 }
797 return params;
798}
799
800Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
526 switch (event.type) { 801 switch (event.type) {
527 case SDL_JOYAXISMOTION: { 802 case SDL_JOYAXISMOTION: {
528 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); 803 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
529 params.Set("port", joystick->GetPort()); 804 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
530 params.Set("guid", joystick->GetGUID()); 805 event.jaxis.axis, event.jaxis.value);
531 params.Set("axis", event.jaxis.axis);
532 if (event.jaxis.value > 0) {
533 params.Set("direction", "+");
534 params.Set("threshold", "0.5");
535 } else {
536 params.Set("direction", "-");
537 params.Set("threshold", "-0.5");
538 }
539 break;
540 } 806 }
541 case SDL_JOYBUTTONUP: { 807 case SDL_JOYBUTTONUP: {
542 const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); 808 const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
543 params.Set("port", joystick->GetPort()); 809 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
544 params.Set("guid", joystick->GetGUID()); 810 event.jbutton.button);
545 params.Set("button", event.jbutton.button);
546 break;
547 } 811 }
548 case SDL_JOYHATMOTION: { 812 case SDL_JOYHATMOTION: {
549 const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); 813 const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
550 params.Set("port", joystick->GetPort()); 814 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
551 params.Set("guid", joystick->GetGUID()); 815 event.jhat.hat, event.jhat.value);
552 params.Set("hat", event.jhat.hat); 816 }
553 switch (event.jhat.value) { 817 }
554 case SDL_HAT_UP: 818 return {};
555 params.Set("direction", "up"); 819}
556 break; 820
557 case SDL_HAT_DOWN: 821Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
558 params.Set("direction", "down"); 822 switch (event.type) {
559 break; 823 case SDL_JOYAXISMOTION: {
560 case SDL_HAT_LEFT: 824 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
561 params.Set("direction", "left"); 825 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
562 break; 826 event.jaxis.axis, event.jaxis.value);
563 case SDL_HAT_RIGHT:
564 params.Set("direction", "right");
565 break;
566 default:
567 return {};
568 }
569 break;
570 } 827 }
828 case SDL_JOYBUTTONUP: {
829 const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
830 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
831 event.jbutton.button);
571 } 832 }
833 case SDL_JOYHATMOTION: {
834 const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
835 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
836 event.jhat.hat, event.jhat.value);
837 }
838 }
839 return {};
840}
841
842Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
843 const SDL_GameControllerButtonBind& binding) {
844 switch (binding.bindType) {
845 case SDL_CONTROLLER_BINDTYPE_AXIS:
846 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
847 case SDL_CONTROLLER_BINDTYPE_BUTTON:
848 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
849 case SDL_CONTROLLER_BINDTYPE_HAT:
850 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
851 binding.value.hat.hat_mask);
852 }
853 return {};
854}
855
856Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
857 int axis_y) {
858 Common::ParamPackage params;
859 params.Set("engine", "sdl");
860 params.Set("port", port);
861 params.Set("guid", guid);
862 params.Set("axis_x", axis_x);
863 params.Set("axis_y", axis_y);
572 return params; 864 return params;
573} 865}
866} // Anonymous namespace
574 867
575namespace Polling { 868ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
869 if (!params.Has("guid") || !params.Has("port")) {
870 return {};
871 }
872 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
873 auto* controller = joystick->GetSDLGameController();
874 if (controller == nullptr) {
875 return {};
876 }
877
878 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
879 // We will add those afterwards
880 // This list also excludes Screenshot since theres not really a mapping for that
881 using ButtonBindings =
882 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
883 static constexpr ButtonBindings switch_to_sdl_button{{
884 {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
885 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
886 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
887 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
888 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
889 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
890 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
891 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
892 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
893 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
894 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
895 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
896 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
897 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
898 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
899 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
900 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
901 }};
902
903 // Add the missing bindings for ZL/ZR
904 using ZBindings =
905 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
906 static constexpr ZBindings switch_to_sdl_axis{{
907 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
908 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
909 }};
910
911 ButtonMapping mapping;
912 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
913
914 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
915 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
916 mapping.insert_or_assign(
917 switch_button,
918 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
919 }
920 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
921 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
922 mapping.insert_or_assign(
923 switch_button,
924 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
925 }
926
927 return mapping;
928}
929
930AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
931 if (!params.Has("guid") || !params.Has("port")) {
932 return {};
933 }
934 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
935 auto* controller = joystick->GetSDLGameController();
936 if (controller == nullptr) {
937 return {};
938 }
939
940 AnalogMapping mapping = {};
941 const auto& binding_left_x =
942 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
943 const auto& binding_left_y =
944 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
945 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
946 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
947 binding_left_x.value.axis,
948 binding_left_y.value.axis));
949 const auto& binding_right_x =
950 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
951 const auto& binding_right_y =
952 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
953 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
954 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
955 binding_right_x.value.axis,
956 binding_right_y.value.axis));
957 return mapping;
958}
576 959
960namespace Polling {
577class SDLPoller : public InputCommon::Polling::DevicePoller { 961class SDLPoller : public InputCommon::Polling::DevicePoller {
578public: 962public:
579 explicit SDLPoller(SDLState& state_) : state(state_) {} 963 explicit SDLPoller(SDLState& state_) : state(state_) {}
580 964
581 void Start() override { 965 void Start(const std::string& device_id) override {
582 state.event_queue.Clear(); 966 state.event_queue.Clear();
583 state.polling = true; 967 state.polling = true;
584 } 968 }
@@ -598,71 +982,135 @@ public:
598 Common::ParamPackage GetNextInput() override { 982 Common::ParamPackage GetNextInput() override {
599 SDL_Event event; 983 SDL_Event event;
600 while (state.event_queue.Pop(event)) { 984 while (state.event_queue.Pop(event)) {
601 switch (event.type) { 985 const auto package = FromEvent(event);
602 case SDL_JOYAXISMOTION: 986 if (package) {
603 if (std::abs(event.jaxis.value / 32767.0) < 0.5) { 987 return *package;
604 break;
605 }
606 [[fallthrough]];
607 case SDL_JOYBUTTONUP:
608 case SDL_JOYHATMOTION:
609 return SDLEventToButtonParamPackage(state, event);
610 } 988 }
611 } 989 }
612 return {}; 990 return {};
613 } 991 }
992 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
993 switch (event.type) {
994 case SDL_JOYAXISMOTION:
995 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
996 break;
997 }
998 [[fallthrough]];
999 case SDL_JOYBUTTONUP:
1000 case SDL_JOYHATMOTION:
1001 return {SDLEventToButtonParamPackage(state, event)};
1002 }
1003 return std::nullopt;
1004 }
614}; 1005};
615 1006
616class SDLAnalogPoller final : public SDLPoller { 1007class SDLMotionPoller final : public SDLPoller {
617public: 1008public:
618 explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} 1009 explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
619 1010
620 void Start() override { 1011 Common::ParamPackage GetNextInput() override {
621 SDLPoller::Start(); 1012 SDL_Event event;
1013 while (state.event_queue.Pop(event)) {
1014 const auto package = FromEvent(event);
1015 if (package) {
1016 return *package;
1017 }
1018 }
1019 return {};
1020 }
1021 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
1022 switch (event.type) {
1023 case SDL_JOYAXISMOTION:
1024 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1025 break;
1026 }
1027 [[fallthrough]];
1028 case SDL_JOYBUTTONUP:
1029 case SDL_JOYHATMOTION:
1030 return {SDLEventToMotionParamPackage(state, event)};
1031 }
1032 return std::nullopt;
1033 }
1034};
1035
1036/**
1037 * Attempts to match the press to a controller joy axis (left/right stick) and if a match
1038 * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
1039 * instead
1040 */
1041class SDLAnalogPreferredPoller final : public SDLPoller {
1042public:
1043 explicit SDLAnalogPreferredPoller(SDLState& state_)
1044 : SDLPoller(state_), button_poller(state_) {}
622 1045
1046 void Start(const std::string& device_id) override {
1047 SDLPoller::Start(device_id);
1048 // Load the game controller
623 // Reset stored axes 1049 // Reset stored axes
624 analog_x_axis = -1; 1050 analog_x_axis = -1;
625 analog_y_axis = -1; 1051 analog_y_axis = -1;
626 analog_axes_joystick = -1;
627 } 1052 }
628 1053
629 Common::ParamPackage GetNextInput() override { 1054 Common::ParamPackage GetNextInput() override {
630 SDL_Event event; 1055 SDL_Event event;
631 while (state.event_queue.Pop(event)) { 1056 while (state.event_queue.Pop(event)) {
632 if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { 1057 // Filter out axis events that are below a threshold
1058 if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
633 continue; 1059 continue;
634 } 1060 }
635 // An analog device needs two axes, so we need to store the axis for later and wait for 1061 // Simplify controller config by testing if game controller support is enabled.
636 // a second SDL event. The axes also must be from the same joystick. 1062 if (event.type == SDL_JOYAXISMOTION) {
637 const int axis = event.jaxis.axis; 1063 const auto axis = event.jaxis.axis;
638 if (analog_x_axis == -1) { 1064 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
639 analog_x_axis = axis; 1065 const auto controller = joystick->GetSDLGameController();
640 analog_axes_joystick = event.jaxis.which; 1066 if (controller) {
641 } else if (analog_y_axis == -1 && analog_x_axis != axis && 1067 const auto axis_left_x =
642 analog_axes_joystick == event.jaxis.which) { 1068 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX)
643 analog_y_axis = axis; 1069 .value.axis;
1070 const auto axis_left_y =
1071 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY)
1072 .value.axis;
1073 const auto axis_right_x =
1074 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX)
1075 .value.axis;
1076 const auto axis_right_y =
1077 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY)
1078 .value.axis;
1079
1080 if (axis == axis_left_x || axis == axis_left_y) {
1081 analog_x_axis = axis_left_x;
1082 analog_y_axis = axis_left_y;
1083 break;
1084 } else if (axis == axis_right_x || axis == axis_right_y) {
1085 analog_x_axis = axis_right_x;
1086 analog_y_axis = axis_right_y;
1087 break;
1088 }
1089 }
1090 }
1091
1092 // If the press wasn't accepted as a joy axis, check for a button press
1093 auto button_press = button_poller.FromEvent(event);
1094 if (button_press) {
1095 return *button_press;
644 } 1096 }
645 } 1097 }
646 Common::ParamPackage params; 1098
647 if (analog_x_axis != -1 && analog_y_axis != -1) { 1099 if (analog_x_axis != -1 && analog_y_axis != -1) {
648 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); 1100 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
649 params.Set("engine", "sdl"); 1101 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
650 params.Set("port", joystick->GetPort()); 1102 analog_x_axis, analog_y_axis);
651 params.Set("guid", joystick->GetGUID());
652 params.Set("axis_x", analog_x_axis);
653 params.Set("axis_y", analog_y_axis);
654 analog_x_axis = -1; 1103 analog_x_axis = -1;
655 analog_y_axis = -1; 1104 analog_y_axis = -1;
656 analog_axes_joystick = -1;
657 return params; 1105 return params;
658 } 1106 }
659 return params; 1107 return {};
660 } 1108 }
661 1109
662private: 1110private:
663 int analog_x_axis = -1; 1111 int analog_x_axis = -1;
664 int analog_y_axis = -1; 1112 int analog_y_axis = -1;
665 SDL_JoystickID analog_axes_joystick = -1; 1113 SDLButtonPoller button_poller;
666}; 1114};
667} // namespace Polling 1115} // namespace Polling
668 1116
@@ -670,12 +1118,15 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
670 Pollers pollers; 1118 Pollers pollers;
671 1119
672 switch (type) { 1120 switch (type) {
673 case InputCommon::Polling::DeviceType::Analog: 1121 case InputCommon::Polling::DeviceType::AnalogPreferred:
674 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); 1122 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
675 break; 1123 break;
676 case InputCommon::Polling::DeviceType::Button: 1124 case InputCommon::Polling::DeviceType::Button:
677 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); 1125 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
678 break; 1126 break;
1127 case InputCommon::Polling::DeviceType::Motion:
1128 pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
1129 break;
679 } 1130 }
680 1131
681 return pollers; 1132 return pollers;
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index 606a32c5b..b9bb4dc56 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -21,6 +21,7 @@ namespace InputCommon::SDL {
21 21
22class SDLAnalogFactory; 22class SDLAnalogFactory;
23class SDLButtonFactory; 23class SDLButtonFactory;
24class SDLMotionFactory;
24class SDLJoystick; 25class SDLJoystick;
25 26
26class SDLState : public State { 27class SDLState : public State {
@@ -50,6 +51,11 @@ public:
50 std::atomic<bool> polling = false; 51 std::atomic<bool> polling = false;
51 Common::SPSCQueue<SDL_Event> event_queue; 52 Common::SPSCQueue<SDL_Event> event_queue;
52 53
54 std::vector<Common::ParamPackage> GetInputDevices() override;
55
56 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
57 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
58
53private: 59private:
54 void InitJoystick(int joystick_index); 60 void InitJoystick(int joystick_index);
55 void CloseJoystick(SDL_Joystick* sdl_joystick); 61 void CloseJoystick(SDL_Joystick* sdl_joystick);
@@ -57,12 +63,16 @@ private:
57 /// Needs to be called before SDL_QuitSubSystem. 63 /// Needs to be called before SDL_QuitSubSystem.
58 void CloseJoysticks(); 64 void CloseJoysticks();
59 65
66 // Set to true if SDL supports game controller subsystem
67 bool has_gamecontroller = false;
68
60 /// Map of GUID of a list of corresponding virtual Joysticks 69 /// Map of GUID of a list of corresponding virtual Joysticks
61 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; 70 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
62 std::mutex joystick_map_mutex; 71 std::mutex joystick_map_mutex;
63 72
64 std::shared_ptr<SDLButtonFactory> button_factory; 73 std::shared_ptr<SDLButtonFactory> button_factory;
65 std::shared_ptr<SDLAnalogFactory> analog_factory; 74 std::shared_ptr<SDLAnalogFactory> analog_factory;
75 std::shared_ptr<SDLMotionFactory> motion_factory;
66 76
67 bool start_thread = false; 77 bool start_thread = false;
68 std::atomic<bool> initialized = false; 78 std::atomic<bool> initialized = false;
diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp
new file mode 100644
index 000000000..b66c05856
--- /dev/null
+++ b/src/input_common/settings.cpp
@@ -0,0 +1,40 @@
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/settings.h"
6
7namespace Settings {
8namespace NativeButton {
9const std::array<const char*, NumButtons> mapping = {{
10 "button_a", "button_b", "button_x", "button_y", "button_lstick",
11 "button_rstick", "button_l", "button_r", "button_zl", "button_zr",
12 "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright",
13 "button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot",
14}};
15}
16
17namespace NativeMotion {
18const std::array<const char*, NumMotions> mapping = {{
19 "motionleft",
20 "motionright",
21}};
22}
23
24namespace NativeAnalog {
25const std::array<const char*, NumAnalogs> mapping = {{
26 "lstick",
27 "rstick",
28}};
29}
30
31namespace NativeMouseButton {
32const std::array<const char*, NumMouseButtons> mapping = {{
33 "left",
34 "right",
35 "middle",
36 "forward",
37 "back",
38}};
39}
40} // namespace Settings
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
new file mode 100644
index 000000000..ab0b95cf1
--- /dev/null
+++ b/src/input_common/settings.h
@@ -0,0 +1,352 @@
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 "common/common_types.h"
10
11namespace Settings {
12namespace NativeButton {
13enum Values : int {
14 A,
15 B,
16 X,
17 Y,
18 LStick,
19 RStick,
20 L,
21 R,
22 ZL,
23 ZR,
24 Plus,
25 Minus,
26
27 DLeft,
28 DUp,
29 DRight,
30 DDown,
31
32 SL,
33 SR,
34
35 Home,
36 Screenshot,
37
38 NumButtons,
39};
40
41constexpr int BUTTON_HID_BEGIN = A;
42constexpr int BUTTON_NS_BEGIN = Home;
43
44constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN;
45constexpr int BUTTON_NS_END = NumButtons;
46
47constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN;
48constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN;
49
50extern const std::array<const char*, NumButtons> mapping;
51
52} // namespace NativeButton
53
54namespace NativeAnalog {
55enum Values : int {
56 LStick,
57 RStick,
58
59 NumAnalogs,
60};
61
62constexpr int STICK_HID_BEGIN = LStick;
63constexpr int STICK_HID_END = NumAnalogs;
64constexpr int NUM_STICKS_HID = NumAnalogs;
65
66extern const std::array<const char*, NumAnalogs> mapping;
67} // namespace NativeAnalog
68
69namespace NativeMotion {
70enum Values : int {
71 MOTIONLEFT,
72 MOTIONRIGHT,
73
74 NumMotions,
75};
76
77constexpr int MOTION_HID_BEGIN = MOTIONLEFT;
78constexpr int MOTION_HID_END = NumMotions;
79constexpr int NUM_MOTION_HID = NumMotions;
80
81extern const std::array<const char*, NumMotions> mapping;
82} // namespace NativeMotion
83
84namespace NativeMouseButton {
85enum Values {
86 Left,
87 Right,
88 Middle,
89 Forward,
90 Back,
91
92 NumMouseButtons,
93};
94
95constexpr int MOUSE_HID_BEGIN = Left;
96constexpr int MOUSE_HID_END = NumMouseButtons;
97constexpr int NUM_MOUSE_HID = NumMouseButtons;
98
99extern const std::array<const char*, NumMouseButtons> mapping;
100} // namespace NativeMouseButton
101
102namespace NativeKeyboard {
103enum Keys {
104 None,
105 Error,
106
107 A = 4,
108 B,
109 C,
110 D,
111 E,
112 F,
113 G,
114 H,
115 I,
116 J,
117 K,
118 L,
119 M,
120 N,
121 O,
122 P,
123 Q,
124 R,
125 S,
126 T,
127 U,
128 V,
129 W,
130 X,
131 Y,
132 Z,
133 N1,
134 N2,
135 N3,
136 N4,
137 N5,
138 N6,
139 N7,
140 N8,
141 N9,
142 N0,
143 Enter,
144 Escape,
145 Backspace,
146 Tab,
147 Space,
148 Minus,
149 Equal,
150 LeftBrace,
151 RightBrace,
152 Backslash,
153 Tilde,
154 Semicolon,
155 Apostrophe,
156 Grave,
157 Comma,
158 Dot,
159 Slash,
160 CapsLockKey,
161
162 F1,
163 F2,
164 F3,
165 F4,
166 F5,
167 F6,
168 F7,
169 F8,
170 F9,
171 F10,
172 F11,
173 F12,
174
175 SystemRequest,
176 ScrollLockKey,
177 Pause,
178 Insert,
179 Home,
180 PageUp,
181 Delete,
182 End,
183 PageDown,
184 Right,
185 Left,
186 Down,
187 Up,
188
189 NumLockKey,
190 KPSlash,
191 KPAsterisk,
192 KPMinus,
193 KPPlus,
194 KPEnter,
195 KP1,
196 KP2,
197 KP3,
198 KP4,
199 KP5,
200 KP6,
201 KP7,
202 KP8,
203 KP9,
204 KP0,
205 KPDot,
206
207 Key102,
208 Compose,
209 Power,
210 KPEqual,
211
212 F13,
213 F14,
214 F15,
215 F16,
216 F17,
217 F18,
218 F19,
219 F20,
220 F21,
221 F22,
222 F23,
223 F24,
224
225 Open,
226 Help,
227 Properties,
228 Front,
229 Stop,
230 Repeat,
231 Undo,
232 Cut,
233 Copy,
234 Paste,
235 Find,
236 Mute,
237 VolumeUp,
238 VolumeDown,
239 CapsLockActive,
240 NumLockActive,
241 ScrollLockActive,
242 KPComma,
243
244 KPLeftParenthesis,
245 KPRightParenthesis,
246
247 LeftControlKey = 0xE0,
248 LeftShiftKey,
249 LeftAltKey,
250 LeftMetaKey,
251 RightControlKey,
252 RightShiftKey,
253 RightAltKey,
254 RightMetaKey,
255
256 MediaPlayPause,
257 MediaStopCD,
258 MediaPrevious,
259 MediaNext,
260 MediaEject,
261 MediaVolumeUp,
262 MediaVolumeDown,
263 MediaMute,
264 MediaWebsite,
265 MediaBack,
266 MediaForward,
267 MediaStop,
268 MediaFind,
269 MediaScrollUp,
270 MediaScrollDown,
271 MediaEdit,
272 MediaSleep,
273 MediaCoffee,
274 MediaRefresh,
275 MediaCalculator,
276
277 NumKeyboardKeys,
278};
279
280static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
281
282enum Modifiers {
283 LeftControl,
284 LeftShift,
285 LeftAlt,
286 LeftMeta,
287 RightControl,
288 RightShift,
289 RightAlt,
290 RightMeta,
291 CapsLock,
292 ScrollLock,
293 NumLock,
294
295 NumKeyboardMods,
296};
297
298constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
299constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
300constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
301
302constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
303constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
304constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
305
306} // namespace NativeKeyboard
307
308using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
309using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
310using MotionRaw = std::array<std::string, NativeMotion::NumMotions>;
311using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
312using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
313using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
314
315constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
316constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
317constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
318constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
319
320enum class ControllerType {
321 ProController,
322 DualJoyconDetached,
323 LeftJoycon,
324 RightJoycon,
325 Handheld,
326};
327
328struct PlayerInput {
329 bool connected;
330 ControllerType controller_type;
331 ButtonsRaw buttons;
332 AnalogsRaw analogs;
333 MotionRaw motions;
334 std::string lstick_mod;
335 std::string rstick_mod;
336
337 u32 body_color_left;
338 u32 body_color_right;
339 u32 button_color_left;
340 u32 button_color_right;
341};
342
343struct TouchscreenInput {
344 bool enabled;
345 std::string device;
346
347 u32 finger;
348 u32 diameter_x;
349 u32 diameter_y;
350 u32 rotation_angle;
351};
352} // namespace Settings
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..98da0ef1a
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,50 @@
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 "core/frontend/framebuffer_layout.h"
6#include "core/settings.h"
7#include "input_common/touch_from_button.h"
8
9namespace InputCommon {
10
11class TouchFromButtonDevice final : public Input::TouchDevice {
12public:
13 TouchFromButtonDevice() {
14 for (const auto& config_entry :
15 Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index]
16 .buttons) {
17 const Common::ParamPackage package{config_entry};
18 map.emplace_back(
19 Input::CreateDevice<Input::ButtonDevice>(config_entry),
20 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
21 std::clamp(package.Get("y", 0), 0,
22 static_cast<int>(Layout::ScreenUndocked::Height)));
23 }
24 }
25
26 std::tuple<float, float, bool> GetStatus() const override {
27 for (const auto& m : map) {
28 const bool state = std::get<0>(m)->GetStatus();
29 if (state) {
30 const float x = static_cast<float>(std::get<1>(m)) /
31 static_cast<int>(Layout::ScreenUndocked::Width);
32 const float y = static_cast<float>(std::get<2>(m)) /
33 static_cast<int>(Layout::ScreenUndocked::Height);
34 return {x, y, true};
35 }
36 }
37 return {};
38 }
39
40private:
41 // A vector of the mapped button, its x and its y-coordinate
42 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
43};
44
45std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
46 const Common::ParamPackage& params) {
47 return std::make_unique<TouchFromButtonDevice>();
48}
49
50} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
1// Copyright 2020 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
12/**
13 * A touch device factory that takes a list of button devices and combines them into a touch device.
14 */
15class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
16public:
17 /**
18 * Creates a touch device from a list of button devices
19 */
20 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
21};
22
23} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index e63c73c4f..9d0b9f31d 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -2,15 +2,13 @@
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 <algorithm>
6#include <array>
7#include <chrono> 5#include <chrono>
8#include <cstring> 6#include <cstring>
9#include <functional> 7#include <functional>
10#include <thread> 8#include <thread>
11#include <boost/asio.hpp> 9#include <boost/asio.hpp>
12#include <boost/bind.hpp>
13#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/settings.h"
14#include "input_common/udp/client.h" 12#include "input_common/udp/client.h"
15#include "input_common/udp/protocol.h" 13#include "input_common/udp/protocol.h"
16 14
@@ -132,21 +130,59 @@ static void SocketLoop(Socket* socket) {
132 socket->Loop(); 130 socket->Loop();
133} 131}
134 132
135Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, 133Client::Client() {
136 u8 pad_index, u32 client_id) 134 LOG_INFO(Input, "Udp Initialization started");
137 : status(std::move(status)) { 135 for (std::size_t client = 0; client < clients.size(); client++) {
138 StartCommunication(host, port, pad_index, client_id); 136 u8 pad = client % 4;
137 StartCommunication(client, Settings::values.udp_input_address,
138 Settings::values.udp_input_port, pad, 24872);
139 // Set motion parameters
140 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
141 // Real HW values are unknown, 0.0001 is an approximate to Standard
142 clients[client].motion.SetGyroThreshold(0.0001f);
143 }
139} 144}
140 145
141Client::~Client() { 146Client::~Client() {
142 socket->Stop(); 147 Reset();
143 thread.join(); 148}
149
150std::vector<Common::ParamPackage> Client::GetInputDevices() const {
151 std::vector<Common::ParamPackage> devices;
152 for (std::size_t client = 0; client < clients.size(); client++) {
153 if (!DeviceConnected(client)) {
154 continue;
155 }
156 std::string name = fmt::format("UDP Controller {}", client);
157 devices.emplace_back(Common::ParamPackage{
158 {"class", "cemuhookudp"},
159 {"display", std::move(name)},
160 {"port", std::to_string(client)},
161 });
162 }
163 return devices;
144} 164}
145 165
166bool Client::DeviceConnected(std::size_t pad) const {
167 // Use last timestamp to detect if the socket has stopped sending data
168 const auto now = std::chrono::system_clock::now();
169 u64 time_difference =
170 std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
171 .count();
172 return time_difference < 1000 && clients[pad].active == 1;
173}
174
175void Client::ReloadUDPClient() {
176 for (std::size_t client = 0; client < clients.size(); client++) {
177 ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
178 }
179}
146void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 180void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
147 socket->Stop(); 181 // client number must be determined from host / port and pad index
148 thread.join(); 182 std::size_t client = pad_index;
149 StartCommunication(host, port, pad_index, client_id); 183 clients[client].socket->Stop();
184 clients[client].thread.join();
185 StartCommunication(client, host, port, pad_index, client_id);
150} 186}
151 187
152void Client::OnVersion(Response::Version data) { 188void Client::OnVersion(Response::Version data) {
@@ -158,23 +194,35 @@ void Client::OnPortInfo(Response::PortInfo data) {
158} 194}
159 195
160void Client::OnPadData(Response::PadData data) { 196void Client::OnPadData(Response::PadData data) {
197 // client number must be determined from host / port and pad index
198 std::size_t client = data.info.id;
161 LOG_TRACE(Input, "PadData packet received"); 199 LOG_TRACE(Input, "PadData packet received");
162 if (data.packet_counter <= packet_sequence) { 200 if (data.packet_counter == clients[client].packet_sequence) {
163 LOG_WARNING( 201 LOG_WARNING(
164 Input, 202 Input,
165 "PadData packet dropped because its stale info. Current count: {} Packet count: {}", 203 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
166 packet_sequence, data.packet_counter); 204 clients[client].packet_sequence, data.packet_counter);
167 return; 205 return;
168 } 206 }
169 packet_sequence = data.packet_counter; 207 clients[client].active = data.info.is_pad_active;
170 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion 208 clients[client].packet_sequence = data.packet_counter;
171 // directions correspond to the ones of the Switch 209 const auto now = std::chrono::system_clock::now();
172 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); 210 u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>(
173 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); 211 now - clients[client].last_motion_update)
174 { 212 .count();
175 std::lock_guard guard(status->update_mutex); 213 clients[client].last_motion_update = now;
214 Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
215 clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
216 // Gyroscope values are not it the correct scale from better joy.
217 // Dividing by 312 allows us to make one full turn = 1 turn
218 // This must be a configurable valued called sensitivity
219 clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
220 clients[client].motion.UpdateRotation(time_difference);
221 clients[client].motion.UpdateOrientation(time_difference);
176 222
177 status->motion_status = {accel, gyro}; 223 {
224 std::lock_guard guard(clients[client].status.update_mutex);
225 clients[client].status.motion_status = clients[client].motion.GetMotion();
178 226
179 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates 227 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
180 // between a simple "tap" and a hard press that causes the touch screen to click. 228 // between a simple "tap" and a hard press that causes the touch screen to click.
@@ -183,11 +231,11 @@ void Client::OnPadData(Response::PadData data) {
183 float x = 0; 231 float x = 0;
184 float y = 0; 232 float y = 0;
185 233
186 if (is_active && status->touch_calibration) { 234 if (is_active && clients[client].status.touch_calibration) {
187 const u16 min_x = status->touch_calibration->min_x; 235 const u16 min_x = clients[client].status.touch_calibration->min_x;
188 const u16 max_x = status->touch_calibration->max_x; 236 const u16 max_x = clients[client].status.touch_calibration->max_x;
189 const u16 min_y = status->touch_calibration->min_y; 237 const u16 min_y = clients[client].status.touch_calibration->min_y;
190 const u16 max_y = status->touch_calibration->max_y; 238 const u16 max_y = clients[client].status.touch_calibration->max_y;
191 239
192 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / 240 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
193 static_cast<float>(max_x - min_x); 241 static_cast<float>(max_x - min_x);
@@ -195,17 +243,86 @@ void Client::OnPadData(Response::PadData data) {
195 static_cast<float>(max_y - min_y); 243 static_cast<float>(max_y - min_y);
196 } 244 }
197 245
198 status->touch_status = {x, y, is_active}; 246 clients[client].status.touch_status = {x, y, is_active};
247
248 if (configuring) {
249 const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
250 const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
251 UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
252 }
199 } 253 }
200} 254}
201 255
202void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 256void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
257 u32 client_id) {
203 SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, 258 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
204 [this](Response::PortInfo info) { OnPortInfo(info); }, 259 [this](Response::PortInfo info) { OnPortInfo(info); },
205 [this](Response::PadData data) { OnPadData(data); }}; 260 [this](Response::PadData data) { OnPadData(data); }};
206 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); 261 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
207 socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); 262 clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
208 thread = std::thread{SocketLoop, this->socket.get()}; 263 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
264}
265
266void Client::Reset() {
267 for (std::size_t client = 0; client < clients.size(); client++) {
268 clients[client].socket->Stop();
269 clients[client].thread.join();
270 }
271}
272
273void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
274 const Common::Vec3<float>& gyro, bool touch) {
275 if (gyro.Length() > 0.2f) {
276 LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {}), touch={}",
277 client, gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2], touch);
278 }
279 UDPPadStatus pad;
280 if (touch) {
281 pad.touch = PadTouch::Click;
282 pad_queue[client].Push(pad);
283 }
284 for (size_t i = 0; i < 3; ++i) {
285 if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
286 pad.motion = static_cast<PadMotion>(i);
287 pad.motion_value = gyro[i];
288 pad_queue[client].Push(pad);
289 }
290 if (acc[i] > 1.75f || acc[i] < -1.75f) {
291 pad.motion = static_cast<PadMotion>(i + 3);
292 pad.motion_value = acc[i];
293 pad_queue[client].Push(pad);
294 }
295 }
296}
297
298void Client::BeginConfiguration() {
299 for (auto& pq : pad_queue) {
300 pq.Clear();
301 }
302 configuring = true;
303}
304
305void Client::EndConfiguration() {
306 for (auto& pq : pad_queue) {
307 pq.Clear();
308 }
309 configuring = false;
310}
311
312DeviceStatus& Client::GetPadState(std::size_t pad) {
313 return clients[pad].status;
314}
315
316const DeviceStatus& Client::GetPadState(std::size_t pad) const {
317 return clients[pad].status;
318}
319
320std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
321 return pad_queue;
322}
323
324const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
325 return pad_queue;
209} 326}
210 327
211void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, 328void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
@@ -225,8 +342,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
225 } else { 342 } else {
226 failure_callback(); 343 failure_callback();
227 } 344 }
228 }) 345 }).detach();
229 .detach();
230} 346}
231 347
232CalibrationConfigurationJob::CalibrationConfigurationJob( 348CalibrationConfigurationJob::CalibrationConfigurationJob(
@@ -280,8 +396,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
280 complete_event.Wait(); 396 complete_event.Wait();
281 socket.Stop(); 397 socket.Stop();
282 worker_thread.join(); 398 worker_thread.join();
283 }) 399 }).detach();
284 .detach();
285} 400}
286 401
287CalibrationConfigurationJob::~CalibrationConfigurationJob() { 402CalibrationConfigurationJob::~CalibrationConfigurationJob() {
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..523dc6a7a 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -12,8 +12,12 @@
12#include <thread> 12#include <thread>
13#include <tuple> 13#include <tuple>
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "common/param_package.h"
15#include "common/thread.h" 16#include "common/thread.h"
17#include "common/threadsafe_queue.h"
16#include "common/vector_math.h" 18#include "common/vector_math.h"
19#include "core/frontend/input.h"
20#include "input_common/motion_input.h"
17 21
18namespace InputCommon::CemuhookUDP { 22namespace InputCommon::CemuhookUDP {
19 23
@@ -28,9 +32,30 @@ struct PortInfo;
28struct Version; 32struct Version;
29} // namespace Response 33} // namespace Response
30 34
35enum class PadMotion {
36 GyroX,
37 GyroY,
38 GyroZ,
39 AccX,
40 AccY,
41 AccZ,
42 Undefined,
43};
44
45enum class PadTouch {
46 Click,
47 Undefined,
48};
49
50struct UDPPadStatus {
51 PadTouch touch{PadTouch::Undefined};
52 PadMotion motion{PadMotion::Undefined};
53 f32 motion_value{0.0f};
54};
55
31struct DeviceStatus { 56struct DeviceStatus {
32 std::mutex update_mutex; 57 std::mutex update_mutex;
33 std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; 58 Input::MotionStatus motion_status;
34 std::tuple<float, float, bool> touch_status; 59 std::tuple<float, float, bool> touch_status;
35 60
36 // calibration data for scaling the device's touch area to 3ds 61 // calibration data for scaling the device's touch area to 3ds
@@ -45,22 +70,58 @@ struct DeviceStatus {
45 70
46class Client { 71class Client {
47public: 72public:
48 explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, 73 // Initialize the UDP client capture and read sequence
49 u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); 74 Client();
75
76 // Close and release the client
50 ~Client(); 77 ~Client();
78
79 // Used for polling
80 void BeginConfiguration();
81 void EndConfiguration();
82
83 std::vector<Common::ParamPackage> GetInputDevices() const;
84
85 bool DeviceConnected(std::size_t pad) const;
86 void ReloadUDPClient();
51 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, 87 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
52 u32 client_id = 24872); 88 u32 client_id = 24872);
53 89
90 std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue();
91 const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const;
92
93 DeviceStatus& GetPadState(std::size_t pad);
94 const DeviceStatus& GetPadState(std::size_t pad) const;
95
54private: 96private:
97 struct ClientData {
98 std::unique_ptr<Socket> socket;
99 DeviceStatus status;
100 std::thread thread;
101 u64 packet_sequence = 0;
102 u8 active;
103
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::system_clock> last_motion_update;
108 };
109
110 // For shutting down, clear all data, join all threads, release usb
111 void Reset();
112
55 void OnVersion(Response::Version); 113 void OnVersion(Response::Version);
56 void OnPortInfo(Response::PortInfo); 114 void OnPortInfo(Response::PortInfo);
57 void OnPadData(Response::PadData); 115 void OnPadData(Response::PadData);
58 void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); 116 void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index,
117 u32 client_id);
118 void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
119 const Common::Vec3<float>& gyro, bool touch);
120
121 bool configuring = false;
59 122
60 std::unique_ptr<Socket> socket; 123 std::array<ClientData, 4> clients;
61 std::shared_ptr<DeviceStatus> status; 124 std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue;
62 std::thread thread;
63 u64 packet_sequence = 0;
64}; 125};
65 126
66/// An async job allowing configuration of the touchpad calibration. 127/// An async job allowing configuration of the touchpad calibration.
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 8c6ef1394..eba077a36 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,99 +1,144 @@
1// Copyright 2018 Citra Emulator Project 1// Copyright 2020 yuzu Emulator Project
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 <atomic>
6#include <list>
5#include <mutex> 7#include <mutex>
6#include <optional> 8#include <utility>
7#include <tuple> 9#include "common/assert.h"
8 10#include "common/threadsafe_queue.h"
9#include "common/param_package.h"
10#include "core/frontend/input.h"
11#include "core/settings.h"
12#include "input_common/udp/client.h" 11#include "input_common/udp/client.h"
13#include "input_common/udp/udp.h" 12#include "input_common/udp/udp.h"
14 13
15namespace InputCommon::CemuhookUDP { 14namespace InputCommon {
16 15
17class UDPTouchDevice final : public Input::TouchDevice { 16class UDPMotion final : public Input::MotionDevice {
18public: 17public:
19 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 18 UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
20 std::tuple<float, float, bool> GetStatus() const override { 19 : ip(ip_), port(port_), pad(pad_), client(client_) {}
21 std::lock_guard guard(status->update_mutex); 20
22 return status->touch_status; 21 Input::MotionStatus GetStatus() const override {
22 return client->GetPadState(pad).motion_status;
23 } 23 }
24 24
25private: 25private:
26 std::shared_ptr<DeviceStatus> status; 26 const std::string ip;
27 const int port;
28 const int pad;
29 CemuhookUDP::Client* client;
30 mutable std::mutex mutex;
27}; 31};
28 32
29class UDPMotionDevice final : public Input::MotionDevice { 33/// A motion device factory that creates motion devices from JC Adapter
30public: 34UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
31 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 35 : client(std::move(client_)) {}
32 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 36
33 std::lock_guard guard(status->update_mutex); 37/**
34 return status->motion_status; 38 * Creates motion device
35 } 39 * @param params contains parameters for creating the device:
40 * - "port": the nth jcpad on the adapter
41 */
42std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
43 const std::string ip = params.Get("ip", "127.0.0.1");
44 const int port = params.Get("port", 26760);
45 const int pad = params.Get("pad_index", 0);
46
47 return std::make_unique<UDPMotion>(ip, port, pad, client.get());
48}
36 49
37private: 50void UDPMotionFactory::BeginConfiguration() {
38 std::shared_ptr<DeviceStatus> status; 51 polling = true;
39}; 52 client->BeginConfiguration();
53}
40 54
41class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { 55void UDPMotionFactory::EndConfiguration() {
42public: 56 polling = false;
43 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 57 client->EndConfiguration();
44 58}
45 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { 59
46 { 60Common::ParamPackage UDPMotionFactory::GetNextInput() {
47 std::lock_guard guard(status->update_mutex); 61 Common::ParamPackage params;
48 status->touch_calibration = DeviceStatus::CalibrationData{}; 62 CemuhookUDP::UDPPadStatus pad;
49 // These default values work well for DS4 but probably not other touch inputs 63 auto& queue = client->GetPadQueue();
50 status->touch_calibration->min_x = params.Get("min_x", 100); 64 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
51 status->touch_calibration->min_y = params.Get("min_y", 50); 65 while (queue[pad_number].Pop(pad)) {
52 status->touch_calibration->max_x = params.Get("max_x", 1800); 66 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
53 status->touch_calibration->max_y = params.Get("max_y", 850); 67 continue;
68 }
69 params.Set("engine", "cemuhookudp");
70 params.Set("ip", "127.0.0.1");
71 params.Set("port", 26760);
72 params.Set("pad_index", static_cast<int>(pad_number));
73 params.Set("motion", static_cast<u16>(pad.motion));
74 return params;
54 } 75 }
55 return std::make_unique<UDPTouchDevice>(status);
56 } 76 }
77 return params;
78}
57 79
58private: 80class UDPTouch final : public Input::TouchDevice {
59 std::shared_ptr<DeviceStatus> status;
60};
61
62class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
63public: 81public:
64 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 82 UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_)
83 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
65 84
66 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { 85 std::tuple<float, float, bool> GetStatus() const override {
67 return std::make_unique<UDPMotionDevice>(status); 86 return client->GetPadState(pad).touch_status;
68 } 87 }
69 88
70private: 89private:
71 std::shared_ptr<DeviceStatus> status; 90 const std::string ip;
91 const int port;
92 const int pad;
93 CemuhookUDP::Client* client;
94 mutable std::mutex mutex;
72}; 95};
73 96
74State::State() { 97/// A motion device factory that creates motion devices from JC Adapter
75 auto status = std::make_shared<DeviceStatus>(); 98UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
76 client = 99 : client(std::move(client_)) {}
77 std::make_unique<Client>(status, Settings::values.udp_input_address, 100
78 Settings::values.udp_input_port, Settings::values.udp_pad_index); 101/**
79 102 * Creates motion device
80 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", 103 * @param params contains parameters for creating the device:
81 std::make_shared<UDPTouchFactory>(status)); 104 * - "port": the nth jcpad on the adapter
82 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", 105 */
83 std::make_shared<UDPMotionFactory>(status)); 106std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
107 const std::string ip = params.Get("ip", "127.0.0.1");
108 const int port = params.Get("port", 26760);
109 const int pad = params.Get("pad_index", 0);
110
111 return std::make_unique<UDPTouch>(ip, port, pad, client.get());
84} 112}
85 113
86State::~State() { 114void UDPTouchFactory::BeginConfiguration() {
87 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); 115 polling = true;
88 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); 116 client->BeginConfiguration();
89} 117}
90 118
91void State::ReloadUDPClient() { 119void UDPTouchFactory::EndConfiguration() {
92 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, 120 polling = false;
93 Settings::values.udp_pad_index); 121 client->EndConfiguration();
94} 122}
95 123
96std::unique_ptr<State> Init() { 124Common::ParamPackage UDPTouchFactory::GetNextInput() {
97 return std::make_unique<State>(); 125 Common::ParamPackage params;
126 CemuhookUDP::UDPPadStatus pad;
127 auto& queue = client->GetPadQueue();
128 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
129 while (queue[pad_number].Pop(pad)) {
130 if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
131 continue;
132 }
133 params.Set("engine", "cemuhookudp");
134 params.Set("ip", "127.0.0.1");
135 params.Set("port", 26760);
136 params.Set("pad_index", static_cast<int>(pad_number));
137 params.Set("touch", static_cast<u16>(pad.touch));
138 return params;
139 }
140 }
141 return params;
98} 142}
99} // namespace InputCommon::CemuhookUDP 143
144} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
index 4f83f0441..ea3fd4175 100644
--- a/src/input_common/udp/udp.h
+++ b/src/input_common/udp/udp.h
@@ -1,25 +1,57 @@
1// Copyright 2018 Citra Emulator Project 1// Copyright 2020 yuzu Emulator Project
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#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/udp/client.h"
8 10
9namespace InputCommon::CemuhookUDP { 11namespace InputCommon {
10 12
11class Client; 13/// A motion device factory that creates motion devices from udp clients
12 14class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
13class State {
14public: 15public:
15 State(); 16 explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
16 ~State(); 17
17 void ReloadUDPClient(); 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 }
18 29
19private: 30private:
20 std::unique_ptr<Client> client; 31 std::shared_ptr<CemuhookUDP::Client> client;
32 bool polling = false;
21}; 33};
22 34
23std::unique_ptr<State> Init(); 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};
24 56
25} // namespace InputCommon::CemuhookUDP 57} // namespace InputCommon