summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt50
-rwxr-xr-xsrc/input_common/analog_from_button.cpp18
-rw-r--r--src/input_common/gcadapter/gc_adapter.cpp509
-rw-r--r--src/input_common/gcadapter/gc_adapter.h167
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp332
-rw-r--r--src/input_common/gcadapter/gc_poller.h78
-rw-r--r--src/input_common/keyboard.cpp7
-rw-r--r--src/input_common/main.cpp257
-rw-r--r--src/input_common/main.h147
-rw-r--r--src/input_common/motion_emu.cpp60
-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.cpp301
-rw-r--r--src/input_common/motion_input.h74
-rw-r--r--src/input_common/sdl/sdl.h19
-rw-r--r--src/input_common/sdl/sdl_impl.cpp772
-rw-r--r--src/input_common/sdl/sdl_impl.h12
-rw-r--r--src/input_common/settings.cpp47
-rw-r--r--src/input_common/settings.h371
-rw-r--r--src/input_common/touch_from_button.cpp51
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/input_common/udp/client.cpp245
-rw-r--r--src/input_common/udp/client.h89
-rw-r--r--src/input_common/udp/protocol.h11
-rw-r--r--src/input_common/udp/udp.cpp175
-rw-r--r--src/input_common/udp/udp.h54
26 files changed, 3513 insertions, 415 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 2520ba321..1d1b2e08a 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,6 +7,18 @@ 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
18 gcadapter/gc_adapter.cpp
19 gcadapter/gc_adapter.h
20 gcadapter/gc_poller.cpp
21 gcadapter/gc_poller.h
10 sdl/sdl.cpp 22 sdl/sdl.cpp
11 sdl/sdl.h 23 sdl/sdl.h
12 udp/client.cpp 24 udp/client.cpp
@@ -17,6 +29,39 @@ add_library(input_common STATIC
17 udp/udp.h 29 udp/udp.h
18) 30)
19 31
32if (MSVC)
33 target_compile_options(input_common PRIVATE
34 /W4
35 /WX
36
37 # 'expression' : signed/unsigned mismatch
38 /we4018
39 # 'argument' : conversion from 'type1' to 'type2', possible loss of data (floating-point)
40 /we4244
41 # 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch
42 /we4245
43 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
44 /we4254
45 # 'var' : conversion from 'size_t' to 'type', possible loss of data
46 /we4267
47 # 'context' : truncation from 'type1' to 'type2'
48 /we4305
49 )
50else()
51 target_compile_options(input_common PRIVATE
52 -Werror
53 -Werror=conversion
54 -Werror=ignored-qualifiers
55 -Werror=implicit-fallthrough
56 -Werror=reorder
57 -Werror=shadow
58 -Werror=sign-compare
59 -Werror=unused-but-set-parameter
60 -Werror=unused-but-set-variable
61 -Werror=unused-variable
62 )
63endif()
64
20if(SDL2_FOUND) 65if(SDL2_FOUND)
21 target_sources(input_common PRIVATE 66 target_sources(input_common PRIVATE
22 sdl/sdl_impl.cpp 67 sdl/sdl_impl.cpp
@@ -26,5 +71,8 @@ if(SDL2_FOUND)
26 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 71 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
27endif() 72endif()
28 73
74target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR})
75target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES})
76
29create_target_directory_groups(input_common) 77create_target_directory_groups(input_common)
30target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) 78target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost)
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp
index 6cabdaa3c..74744d7f3 100755
--- a/src/input_common/analog_from_button.cpp
+++ b/src/input_common/analog_from_button.cpp
@@ -20,18 +20,22 @@ public:
20 constexpr float SQRT_HALF = 0.707106781f; 20 constexpr float SQRT_HALF = 0.707106781f;
21 int x = 0, y = 0; 21 int x = 0, y = 0;
22 22
23 if (right->GetStatus()) 23 if (right->GetStatus()) {
24 ++x; 24 ++x;
25 if (left->GetStatus()) 25 }
26 if (left->GetStatus()) {
26 --x; 27 --x;
27 if (up->GetStatus()) 28 }
29 if (up->GetStatus()) {
28 ++y; 30 ++y;
29 if (down->GetStatus()) 31 }
32 if (down->GetStatus()) {
30 --y; 33 --y;
34 }
31 35
32 float coef = modifier->GetStatus() ? modifier_scale : 1.0f; 36 const float coef = modifier->GetStatus() ? modifier_scale : 1.0f;
33 return std::make_tuple(x * coef * (y == 0 ? 1.0f : SQRT_HALF), 37 return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF),
34 y * coef * (x == 0 ? 1.0f : SQRT_HALF)); 38 static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF));
35 } 39 }
36 40
37 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { 41 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp
new file mode 100644
index 000000000..d80195c82
--- /dev/null
+++ b/src/input_common/gcadapter/gc_adapter.cpp
@@ -0,0 +1,509 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <chrono>
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
12#include <libusb.h>
13#ifdef _MSC_VER
14#pragma warning(pop)
15#endif
16
17#include "common/logging/log.h"
18#include "common/param_package.h"
19#include "input_common/gcadapter/gc_adapter.h"
20#include "input_common/settings.h"
21
22namespace GCAdapter {
23
24Adapter::Adapter() {
25 if (usb_adapter_handle != nullptr) {
26 return;
27 }
28 LOG_INFO(Input, "GC Adapter Initialization started");
29
30 const int init_res = libusb_init(&libusb_ctx);
31 if (init_res == LIBUSB_SUCCESS) {
32 adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
33 } else {
34 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
35 }
36}
37
38Adapter::~Adapter() {
39 Reset();
40}
41
42void Adapter::AdapterInputThread() {
43 LOG_DEBUG(Input, "GC Adapter input thread started");
44 s32 payload_size{};
45 AdapterPayload adapter_payload{};
46
47 if (adapter_scan_thread.joinable()) {
48 adapter_scan_thread.join();
49 }
50
51 while (adapter_input_thread_running) {
52 libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(),
53 static_cast<s32>(adapter_payload.size()), &payload_size, 16);
54 if (IsPayloadCorrect(adapter_payload, payload_size)) {
55 UpdateControllers(adapter_payload);
56 UpdateVibrations();
57 }
58 std::this_thread::yield();
59 }
60
61 if (restart_scan_thread) {
62 adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this);
63 restart_scan_thread = false;
64 }
65}
66
67bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
68 if (payload_size != static_cast<s32>(adapter_payload.size()) ||
69 adapter_payload[0] != LIBUSB_DT_HID) {
70 LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
71 adapter_payload[0]);
72 if (input_error_counter++ > 20) {
73 LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?");
74 adapter_input_thread_running = false;
75 restart_scan_thread = true;
76 }
77 return false;
78 }
79
80 input_error_counter = 0;
81 return true;
82}
83
84void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) {
85 for (std::size_t port = 0; port < pads.size(); ++port) {
86 const std::size_t offset = 1 + (9 * port);
87 const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
88 UpdatePadType(port, type);
89 if (DeviceConnected(port)) {
90 const u8 b1 = adapter_payload[offset + 1];
91 const u8 b2 = adapter_payload[offset + 2];
92 UpdateStateButtons(port, b1, b2);
93 UpdateStateAxes(port, adapter_payload);
94 if (configuring) {
95 UpdateYuzuSettings(port);
96 }
97 }
98 }
99}
100
101void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
102 if (pads[port].type == pad_type) {
103 return;
104 }
105 // Device changed reset device and set new type
106 ResetDevice(port);
107 pads[port].type = pad_type;
108}
109
110void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) {
111 if (port >= pads.size()) {
112 return;
113 }
114
115 static constexpr std::array<PadButton, 8> b1_buttons{
116 PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY,
117 PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp,
118 };
119
120 static constexpr std::array<PadButton, 4> b2_buttons{
121 PadButton::ButtonStart,
122 PadButton::TriggerZ,
123 PadButton::TriggerR,
124 PadButton::TriggerL,
125 };
126 pads[port].buttons = 0;
127 for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
128 if ((b1 & (1U << i)) != 0) {
129 pads[port].buttons =
130 static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i]));
131 pads[port].last_button = b1_buttons[i];
132 }
133 }
134
135 for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
136 if ((b2 & (1U << j)) != 0) {
137 pads[port].buttons =
138 static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j]));
139 pads[port].last_button = b2_buttons[j];
140 }
141 }
142}
143
144void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
145 if (port >= pads.size()) {
146 return;
147 }
148
149 const std::size_t offset = 1 + (9 * port);
150 static constexpr std::array<PadAxes, 6> axes{
151 PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
152 PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
153 };
154
155 for (const PadAxes axis : axes) {
156 const auto index = static_cast<std::size_t>(axis);
157 const u8 axis_value = adapter_payload[offset + 3 + index];
158 if (pads[port].axis_origin[index] == 255) {
159 pads[port].axis_origin[index] = axis_value;
160 }
161 pads[port].axis_values[index] =
162 static_cast<s16>(axis_value - pads[port].axis_origin[index]);
163 }
164}
165
166void Adapter::UpdateYuzuSettings(std::size_t port) {
167 if (port >= pads.size()) {
168 return;
169 }
170
171 constexpr u8 axis_threshold = 50;
172 GCPadStatus pad_status = {.port = port};
173
174 if (pads[port].buttons != 0) {
175 pad_status.button = pads[port].last_button;
176 pad_queue.Push(pad_status);
177 }
178
179 // Accounting for a threshold here to ensure an intentional press
180 for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) {
181 const s16 value = pads[port].axis_values[i];
182
183 if (value > axis_threshold || value < -axis_threshold) {
184 pad_status.axis = static_cast<PadAxes>(i);
185 pad_status.axis_value = value;
186 pad_status.axis_threshold = axis_threshold;
187 pad_queue.Push(pad_status);
188 }
189 }
190}
191
192void Adapter::UpdateVibrations() {
193 // Use 8 states to keep the switching between on/off fast enough for
194 // a human to not notice the difference between switching from on/off
195 // More states = more rumble strengths = slower update time
196 constexpr u8 vibration_states = 8;
197
198 vibration_counter = (vibration_counter + 1) % vibration_states;
199
200 for (GCController& pad : pads) {
201 const bool vibrate = pad.rumble_amplitude > vibration_counter;
202 vibration_changed |= vibrate != pad.enable_vibration;
203 pad.enable_vibration = vibrate;
204 }
205 SendVibrations();
206}
207
208void Adapter::SendVibrations() {
209 if (!rumble_enabled || !vibration_changed) {
210 return;
211 }
212 s32 size{};
213 constexpr u8 rumble_command = 0x11;
214 const u8 p1 = pads[0].enable_vibration;
215 const u8 p2 = pads[1].enable_vibration;
216 const u8 p3 = pads[2].enable_vibration;
217 const u8 p4 = pads[3].enable_vibration;
218 std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
219 const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(),
220 static_cast<s32>(payload.size()), &size, 16);
221 if (err) {
222 LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err));
223 if (output_error_counter++ > 5) {
224 LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled");
225 rumble_enabled = false;
226 }
227 return;
228 }
229 output_error_counter = 0;
230 vibration_changed = false;
231}
232
233bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
234 pads[port].rumble_amplitude = amplitude;
235
236 return rumble_enabled;
237}
238
239void Adapter::AdapterScanThread() {
240 adapter_scan_thread_running = true;
241 adapter_input_thread_running = false;
242 if (adapter_input_thread.joinable()) {
243 adapter_input_thread.join();
244 }
245 ClearLibusbHandle();
246 ResetDevices();
247 while (adapter_scan_thread_running && !adapter_input_thread_running) {
248 Setup();
249 std::this_thread::sleep_for(std::chrono::seconds(1));
250 }
251}
252
253void Adapter::Setup() {
254 usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337);
255
256 if (usb_adapter_handle == NULL) {
257 return;
258 }
259 if (!CheckDeviceAccess()) {
260 ClearLibusbHandle();
261 return;
262 }
263
264 libusb_device* device = libusb_get_device(usb_adapter_handle);
265
266 LOG_INFO(Input, "GC adapter is now connected");
267 // GC Adapter found and accessible, registering it
268 if (GetGCEndpoint(device)) {
269 adapter_scan_thread_running = false;
270 adapter_input_thread_running = true;
271 rumble_enabled = true;
272 input_error_counter = 0;
273 output_error_counter = 0;
274 adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this);
275 }
276}
277
278bool Adapter::CheckDeviceAccess() {
279 // This fixes payload problems from offbrand GCAdapters
280 const s32 control_transfer_error =
281 libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
282 if (control_transfer_error < 0) {
283 LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
284 }
285
286 s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0);
287 if (kernel_driver_error == 1) {
288 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0);
289 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
290 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
291 kernel_driver_error);
292 }
293 }
294
295 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
296 libusb_close(usb_adapter_handle);
297 usb_adapter_handle = nullptr;
298 return false;
299 }
300
301 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0);
302 if (interface_claim_error) {
303 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
304 libusb_close(usb_adapter_handle);
305 usb_adapter_handle = nullptr;
306 return false;
307 }
308
309 return true;
310}
311
312bool Adapter::GetGCEndpoint(libusb_device* device) {
313 libusb_config_descriptor* config = nullptr;
314 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
315 if (config_descriptor_return != LIBUSB_SUCCESS) {
316 LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
317 config_descriptor_return);
318 return false;
319 }
320
321 for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
322 const libusb_interface* interfaceContainer = &config->interface[ic];
323 for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
324 const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
325 for (u8 e = 0; e < interface->bNumEndpoints; e++) {
326 const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
327 if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) {
328 input_endpoint = endpoint->bEndpointAddress;
329 } else {
330 output_endpoint = endpoint->bEndpointAddress;
331 }
332 }
333 }
334 }
335 // This transfer seems to be responsible for clearing the state of the adapter
336 // Used to clear the "busy" state of when the device is unexpectedly unplugged
337 unsigned char clear_payload = 0x13;
338 libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload,
339 sizeof(clear_payload), nullptr, 16);
340 return true;
341}
342
343void Adapter::JoinThreads() {
344 restart_scan_thread = false;
345 adapter_input_thread_running = false;
346 adapter_scan_thread_running = false;
347
348 if (adapter_scan_thread.joinable()) {
349 adapter_scan_thread.join();
350 }
351
352 if (adapter_input_thread.joinable()) {
353 adapter_input_thread.join();
354 }
355}
356
357void Adapter::ClearLibusbHandle() {
358 if (usb_adapter_handle) {
359 libusb_release_interface(usb_adapter_handle, 1);
360 libusb_close(usb_adapter_handle);
361 usb_adapter_handle = nullptr;
362 }
363}
364
365void Adapter::ResetDevices() {
366 for (std::size_t i = 0; i < pads.size(); ++i) {
367 ResetDevice(i);
368 }
369}
370
371void Adapter::ResetDevice(std::size_t port) {
372 pads[port].type = ControllerTypes::None;
373 pads[port].enable_vibration = false;
374 pads[port].rumble_amplitude = 0;
375 pads[port].buttons = 0;
376 pads[port].last_button = PadButton::Undefined;
377 pads[port].axis_values.fill(0);
378 pads[port].axis_origin.fill(255);
379}
380
381void Adapter::Reset() {
382 JoinThreads();
383 ClearLibusbHandle();
384 ResetDevices();
385
386 if (libusb_ctx) {
387 libusb_exit(libusb_ctx);
388 }
389}
390
391std::vector<Common::ParamPackage> Adapter::GetInputDevices() const {
392 std::vector<Common::ParamPackage> devices;
393 for (std::size_t port = 0; port < pads.size(); ++port) {
394 if (!DeviceConnected(port)) {
395 continue;
396 }
397 std::string name = fmt::format("Gamecube Controller {}", port + 1);
398 devices.emplace_back(Common::ParamPackage{
399 {"class", "gcpad"},
400 {"display", std::move(name)},
401 {"port", std::to_string(port)},
402 });
403 }
404 return devices;
405}
406
407InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice(
408 const Common::ParamPackage& params) const {
409 // This list is missing ZL/ZR since those are not considered buttons.
410 // We will add those afterwards
411 // This list also excludes any button that can't be really mapped
412 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
413 switch_to_gcadapter_button = {
414 std::pair{Settings::NativeButton::A, PadButton::ButtonA},
415 {Settings::NativeButton::B, PadButton::ButtonB},
416 {Settings::NativeButton::X, PadButton::ButtonX},
417 {Settings::NativeButton::Y, PadButton::ButtonY},
418 {Settings::NativeButton::Plus, PadButton::ButtonStart},
419 {Settings::NativeButton::DLeft, PadButton::ButtonLeft},
420 {Settings::NativeButton::DUp, PadButton::ButtonUp},
421 {Settings::NativeButton::DRight, PadButton::ButtonRight},
422 {Settings::NativeButton::DDown, PadButton::ButtonDown},
423 {Settings::NativeButton::SL, PadButton::TriggerL},
424 {Settings::NativeButton::SR, PadButton::TriggerR},
425 {Settings::NativeButton::R, PadButton::TriggerZ},
426 };
427 if (!params.Has("port")) {
428 return {};
429 }
430
431 InputCommon::ButtonMapping mapping{};
432 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
433 Common::ParamPackage button_params({{"engine", "gcpad"}});
434 button_params.Set("port", params.Get("port", 0));
435 button_params.Set("button", static_cast<int>(gcadapter_button));
436 mapping.insert_or_assign(switch_button, std::move(button_params));
437 }
438
439 // Add the missing bindings for ZL/ZR
440 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2>
441 switch_to_gcadapter_axis = {
442 std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft},
443 {Settings::NativeButton::ZR, PadAxes::TriggerRight},
444 };
445 for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) {
446 Common::ParamPackage button_params({{"engine", "gcpad"}});
447 button_params.Set("port", params.Get("port", 0));
448 button_params.Set("button", static_cast<s32>(PadButton::Stick));
449 button_params.Set("axis", static_cast<s32>(gcadapter_axis));
450 button_params.Set("threshold", 0.5f);
451 button_params.Set("direction", "+");
452 mapping.insert_or_assign(switch_button, std::move(button_params));
453 }
454 return mapping;
455}
456
457InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice(
458 const Common::ParamPackage& params) const {
459 if (!params.Has("port")) {
460 return {};
461 }
462
463 InputCommon::AnalogMapping mapping = {};
464 Common::ParamPackage left_analog_params;
465 left_analog_params.Set("engine", "gcpad");
466 left_analog_params.Set("port", params.Get("port", 0));
467 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
468 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
469 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
470 Common::ParamPackage right_analog_params;
471 right_analog_params.Set("engine", "gcpad");
472 right_analog_params.Set("port", params.Get("port", 0));
473 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
474 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
475 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
476 return mapping;
477}
478
479bool Adapter::DeviceConnected(std::size_t port) const {
480 return pads[port].type != ControllerTypes::None;
481}
482
483void Adapter::BeginConfiguration() {
484 pad_queue.Clear();
485 configuring = true;
486}
487
488void Adapter::EndConfiguration() {
489 pad_queue.Clear();
490 configuring = false;
491}
492
493Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() {
494 return pad_queue;
495}
496
497const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const {
498 return pad_queue;
499}
500
501GCController& Adapter::GetPadState(std::size_t port) {
502 return pads.at(port);
503}
504
505const GCController& Adapter::GetPadState(std::size_t port) const {
506 return pads.at(port);
507}
508
509} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h
new file mode 100644
index 000000000..f1256c9da
--- /dev/null
+++ b/src/input_common/gcadapter/gc_adapter.h
@@ -0,0 +1,167 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6#include <algorithm>
7#include <functional>
8#include <mutex>
9#include <thread>
10#include <unordered_map>
11#include "common/common_types.h"
12#include "common/threadsafe_queue.h"
13#include "input_common/main.h"
14
15struct libusb_context;
16struct libusb_device;
17struct libusb_device_handle;
18
19namespace GCAdapter {
20
21enum class PadButton {
22 Undefined = 0x0000,
23 ButtonLeft = 0x0001,
24 ButtonRight = 0x0002,
25 ButtonDown = 0x0004,
26 ButtonUp = 0x0008,
27 TriggerZ = 0x0010,
28 TriggerR = 0x0020,
29 TriggerL = 0x0040,
30 ButtonA = 0x0100,
31 ButtonB = 0x0200,
32 ButtonX = 0x0400,
33 ButtonY = 0x0800,
34 ButtonStart = 0x1000,
35 // Below is for compatibility with "AxisButton" type
36 Stick = 0x2000,
37};
38
39enum class PadAxes : u8 {
40 StickX,
41 StickY,
42 SubstickX,
43 SubstickY,
44 TriggerLeft,
45 TriggerRight,
46 Undefined,
47};
48
49enum class ControllerTypes {
50 None,
51 Wired,
52 Wireless,
53};
54
55struct GCPadStatus {
56 std::size_t port{};
57
58 PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits
59
60 PadAxes axis{PadAxes::Undefined};
61 s16 axis_value{};
62 u8 axis_threshold{50};
63};
64
65struct GCController {
66 ControllerTypes type{};
67 bool enable_vibration{};
68 u8 rumble_amplitude{};
69 u16 buttons{};
70 PadButton last_button{};
71 std::array<s16, 6> axis_values{};
72 std::array<u8, 6> axis_origin{};
73};
74
75class Adapter {
76public:
77 Adapter();
78 ~Adapter();
79
80 /// Request a vibration for a controller
81 bool RumblePlay(std::size_t port, u8 amplitude);
82
83 /// Used for polling
84 void BeginConfiguration();
85 void EndConfiguration();
86
87 Common::SPSCQueue<GCPadStatus>& GetPadQueue();
88 const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const;
89
90 GCController& GetPadState(std::size_t port);
91 const GCController& GetPadState(std::size_t port) const;
92
93 /// Returns true if there is a device connected to port
94 bool DeviceConnected(std::size_t port) const;
95
96 /// Used for automapping features
97 std::vector<Common::ParamPackage> GetInputDevices() const;
98 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
99 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
100
101private:
102 using AdapterPayload = std::array<u8, 37>;
103
104 void UpdatePadType(std::size_t port, ControllerTypes pad_type);
105 void UpdateControllers(const AdapterPayload& adapter_payload);
106 void UpdateYuzuSettings(std::size_t port);
107 void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
108 void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
109 void UpdateVibrations();
110
111 void AdapterInputThread();
112
113 void AdapterScanThread();
114
115 bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
116
117 // Updates vibration state of all controllers
118 void SendVibrations();
119
120 /// For use in initialization, querying devices to find the adapter
121 void Setup();
122
123 /// Resets status of all GC controller devices to a disconected state
124 void ResetDevices();
125
126 /// Resets status of device connected to a disconected state
127 void ResetDevice(std::size_t port);
128
129 /// Returns true if we successfully gain access to GC Adapter
130 bool CheckDeviceAccess();
131
132 /// Captures GC Adapter endpoint address
133 /// Returns true if the endpoind was set correctly
134 bool GetGCEndpoint(libusb_device* device);
135
136 /// For shutting down, clear all data, join all threads, release usb
137 void Reset();
138
139 // Join all threads
140 void JoinThreads();
141
142 // Release usb handles
143 void ClearLibusbHandle();
144
145 libusb_device_handle* usb_adapter_handle = nullptr;
146 std::array<GCController, 4> pads;
147 Common::SPSCQueue<GCPadStatus> pad_queue;
148
149 std::thread adapter_input_thread;
150 std::thread adapter_scan_thread;
151 bool adapter_input_thread_running;
152 bool adapter_scan_thread_running;
153 bool restart_scan_thread;
154
155 libusb_context* libusb_ctx;
156
157 u8 input_endpoint{0};
158 u8 output_endpoint{0};
159 u8 input_error_counter{0};
160 u8 output_error_counter{0};
161 int vibration_counter{0};
162
163 bool configuring{false};
164 bool rumble_enabled{true};
165 bool vibration_changed{true};
166};
167} // namespace GCAdapter
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
new file mode 100644
index 000000000..4d1052414
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -0,0 +1,332 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <atomic>
6#include <list>
7#include <mutex>
8#include <utility>
9#include "common/assert.h"
10#include "common/threadsafe_queue.h"
11#include "input_common/gcadapter/gc_adapter.h"
12#include "input_common/gcadapter/gc_poller.h"
13
14namespace InputCommon {
15
16class GCButton final : public Input::ButtonDevice {
17public:
18 explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
19 : port(port_), button(button_), gcadapter(adapter) {}
20
21 ~GCButton() override;
22
23 bool GetStatus() const override {
24 if (gcadapter->DeviceConnected(port)) {
25 return (gcadapter->GetPadState(port).buttons & button) != 0;
26 }
27 return false;
28 }
29
30private:
31 const u32 port;
32 const s32 button;
33 const GCAdapter::Adapter* gcadapter;
34};
35
36class GCAxisButton final : public Input::ButtonDevice {
37public:
38 explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_,
39 const GCAdapter::Adapter* adapter)
40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
41 gcadapter(adapter) {}
42
43 bool GetStatus() const override {
44 if (gcadapter->DeviceConnected(port)) {
45 const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis);
46 const float axis_value = current_axis_value / 128.0f;
47 if (trigger_if_greater) {
48 // TODO: Might be worthwile to set a slider for the trigger threshold. It is
49 // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick
50 return axis_value > threshold;
51 }
52 return axis_value < -threshold;
53 }
54 return false;
55 }
56
57private:
58 const u32 port;
59 const u32 axis;
60 float threshold;
61 bool trigger_if_greater;
62 const GCAdapter::Adapter* gcadapter;
63};
64
65GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
66 : adapter(std::move(adapter_)) {}
67
68GCButton::~GCButton() = default;
69
70std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) {
71 const auto button_id = params.Get("button", 0);
72 const auto port = static_cast<u32>(params.Get("port", 0));
73
74 constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick);
75
76 // button is not an axis/stick button
77 if (button_id != PAD_STICK_ID) {
78 return std::make_unique<GCButton>(port, button_id, adapter.get());
79 }
80
81 // For Axis buttons, used by the binary sticks.
82 if (button_id == PAD_STICK_ID) {
83 const int axis = params.Get("axis", 0);
84 const float threshold = params.Get("threshold", 0.25f);
85 const std::string direction_name = params.Get("direction", "");
86 bool trigger_if_greater;
87 if (direction_name == "+") {
88 trigger_if_greater = true;
89 } else if (direction_name == "-") {
90 trigger_if_greater = false;
91 } else {
92 trigger_if_greater = true;
93 LOG_ERROR(Input, "Unknown direction {}", direction_name);
94 }
95 return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater,
96 adapter.get());
97 }
98
99 return nullptr;
100}
101
102Common::ParamPackage GCButtonFactory::GetNextInput() const {
103 Common::ParamPackage params;
104 GCAdapter::GCPadStatus pad;
105 auto& queue = adapter->GetPadQueue();
106 while (queue.Pop(pad)) {
107 // This while loop will break on the earliest detected button
108 params.Set("engine", "gcpad");
109 params.Set("port", static_cast<s32>(pad.port));
110 if (pad.button != GCAdapter::PadButton::Undefined) {
111 params.Set("button", static_cast<u16>(pad.button));
112 }
113
114 // For Axis button implementation
115 if (pad.axis != GCAdapter::PadAxes::Undefined) {
116 params.Set("axis", static_cast<u8>(pad.axis));
117 params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick));
118 params.Set("threshold", "0.25");
119 if (pad.axis_value > 0) {
120 params.Set("direction", "+");
121 } else {
122 params.Set("direction", "-");
123 }
124 break;
125 }
126 }
127 return params;
128}
129
130void GCButtonFactory::BeginConfiguration() {
131 polling = true;
132 adapter->BeginConfiguration();
133}
134
135void GCButtonFactory::EndConfiguration() {
136 polling = false;
137 adapter->EndConfiguration();
138}
139
140class GCAnalog final : public Input::AnalogDevice {
141public:
142 explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, float deadzone_,
143 const GCAdapter::Adapter* adapter, float range_)
144 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
145 range(range_) {}
146
147 float GetAxis(u32 axis) const {
148 if (gcadapter->DeviceConnected(port)) {
149 std::lock_guard lock{mutex};
150 const auto axis_value =
151 static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis));
152 return (axis_value) / (100.0f * range);
153 }
154 return 0.0f;
155 }
156
157 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
158 float x = GetAxis(analog_axis_x);
159 float y = GetAxis(analog_axis_y);
160
161 // Make sure the coordinates are in the unit circle,
162 // otherwise normalize it.
163 float r = x * x + y * y;
164 if (r > 1.0f) {
165 r = std::sqrt(r);
166 x /= r;
167 y /= r;
168 }
169
170 return {x, y};
171 }
172
173 std::tuple<float, float> GetStatus() const override {
174 const auto [x, y] = GetAnalog(axis_x, axis_y);
175 const float r = std::sqrt((x * x) + (y * y));
176 if (r > deadzone) {
177 return {x / r * (r - deadzone) / (1 - deadzone),
178 y / r * (r - deadzone) / (1 - deadzone)};
179 }
180 return {0.0f, 0.0f};
181 }
182
183 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
184 const auto [x, y] = GetStatus();
185 const float directional_deadzone = 0.5f;
186 switch (direction) {
187 case Input::AnalogDirection::RIGHT:
188 return x > directional_deadzone;
189 case Input::AnalogDirection::LEFT:
190 return x < -directional_deadzone;
191 case Input::AnalogDirection::UP:
192 return y > directional_deadzone;
193 case Input::AnalogDirection::DOWN:
194 return y < -directional_deadzone;
195 }
196 return false;
197 }
198
199private:
200 const u32 port;
201 const u32 axis_x;
202 const u32 axis_y;
203 const float deadzone;
204 const GCAdapter::Adapter* gcadapter;
205 const float range;
206 mutable std::mutex mutex;
207};
208
209/// An analog device factory that creates analog devices from GC Adapter
210GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
211 : adapter(std::move(adapter_)) {}
212
213/**
214 * Creates analog device from joystick axes
215 * @param params contains parameters for creating the device:
216 * - "port": the nth gcpad on the adapter
217 * - "axis_x": the index of the axis to be bind as x-axis
218 * - "axis_y": the index of the axis to be bind as y-axis
219 */
220std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) {
221 const auto port = static_cast<u32>(params.Get("port", 0));
222 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
223 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
224 const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
225 const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
226
227 return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range);
228}
229
230void GCAnalogFactory::BeginConfiguration() {
231 polling = true;
232 adapter->BeginConfiguration();
233}
234
235void GCAnalogFactory::EndConfiguration() {
236 polling = false;
237 adapter->EndConfiguration();
238}
239
240Common::ParamPackage GCAnalogFactory::GetNextInput() {
241 GCAdapter::GCPadStatus pad;
242 Common::ParamPackage params;
243 auto& queue = adapter->GetPadQueue();
244 while (queue.Pop(pad)) {
245 if (pad.button != GCAdapter::PadButton::Undefined) {
246 params.Set("engine", "gcpad");
247 params.Set("port", static_cast<s32>(pad.port));
248 params.Set("button", static_cast<u16>(pad.button));
249 return params;
250 }
251 if (pad.axis == GCAdapter::PadAxes::Undefined ||
252 std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) {
253 continue;
254 }
255 // An analog device needs two axes, so we need to store the axis for later and wait for
256 // a second input event. The axes also must be from the same joystick.
257 const u8 axis = static_cast<u8>(pad.axis);
258 if (axis == 0 || axis == 1) {
259 analog_x_axis = 0;
260 analog_y_axis = 1;
261 controller_number = static_cast<s32>(pad.port);
262 break;
263 }
264 if (axis == 2 || axis == 3) {
265 analog_x_axis = 2;
266 analog_y_axis = 3;
267 controller_number = static_cast<s32>(pad.port);
268 break;
269 }
270
271 if (analog_x_axis == -1) {
272 analog_x_axis = axis;
273 controller_number = static_cast<s32>(pad.port);
274 } else if (analog_y_axis == -1 && analog_x_axis != axis &&
275 controller_number == static_cast<s32>(pad.port)) {
276 analog_y_axis = axis;
277 break;
278 }
279 }
280 if (analog_x_axis != -1 && analog_y_axis != -1) {
281 params.Set("engine", "gcpad");
282 params.Set("port", controller_number);
283 params.Set("axis_x", analog_x_axis);
284 params.Set("axis_y", analog_y_axis);
285 analog_x_axis = -1;
286 analog_y_axis = -1;
287 controller_number = -1;
288 return params;
289 }
290 return params;
291}
292
293class GCVibration final : public Input::VibrationDevice {
294public:
295 explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
296 : port(port_), gcadapter(adapter) {}
297
298 u8 GetStatus() const override {
299 return gcadapter->RumblePlay(port, 0);
300 }
301
302 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
303 [[maybe_unused]] f32 freq_high) const override {
304 const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
305 const auto processed_amplitude =
306 static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
307
308 return gcadapter->RumblePlay(port, processed_amplitude);
309 }
310
311private:
312 const u32 port;
313 GCAdapter::Adapter* gcadapter;
314};
315
316/// An vibration device factory that creates vibration devices from GC Adapter
317GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
318 : adapter(std::move(adapter_)) {}
319
320/**
321 * Creates a vibration device from a joystick
322 * @param params contains parameters for creating the device:
323 * - "port": the nth gcpad on the adapter
324 */
325std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
326 const Common::ParamPackage& params) {
327 const auto port = static_cast<u32>(params.Get("port", 0));
328
329 return std::make_unique<GCVibration>(port, adapter.get());
330}
331
332} // namespace InputCommon
diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h
new file mode 100644
index 000000000..d1271e3ea
--- /dev/null
+++ b/src/input_common/gcadapter/gc_poller.h
@@ -0,0 +1,78 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/gcadapter/gc_adapter.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a gcpad. It receives gcpad events and forward them
15 * to all button devices it created.
16 */
17class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28 Common::ParamPackage GetNextInput() const;
29
30 /// For device input configuration/polling
31 void BeginConfiguration();
32 void EndConfiguration();
33
34 bool IsPolling() const {
35 return polling;
36 }
37
38private:
39 std::shared_ptr<GCAdapter::Adapter> adapter;
40 bool polling = false;
41};
42
43/// An analog device factory that creates analog devices from GC Adapter
44class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
45public:
46 explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
47
48 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
49 Common::ParamPackage GetNextInput();
50
51 /// For device input configuration/polling
52 void BeginConfiguration();
53 void EndConfiguration();
54
55 bool IsPolling() const {
56 return polling;
57 }
58
59private:
60 std::shared_ptr<GCAdapter::Adapter> adapter;
61 int analog_x_axis = -1;
62 int analog_y_axis = -1;
63 int controller_number = -1;
64 bool polling = false;
65};
66
67/// A vibration device factory creates vibration devices from GC Adapter
68class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
69public:
70 explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
71
72 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
73
74private:
75 std::shared_ptr<GCAdapter::Adapter> adapter;
76};
77
78} // namespace InputCommon
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
index 078374be5..24a6f7a33 100644
--- a/src/input_common/keyboard.cpp
+++ b/src/input_common/keyboard.cpp
@@ -49,8 +49,9 @@ public:
49 void ChangeKeyStatus(int key_code, bool pressed) { 49 void ChangeKeyStatus(int key_code, bool pressed) {
50 std::lock_guard guard{mutex}; 50 std::lock_guard guard{mutex};
51 for (const KeyButtonPair& pair : list) { 51 for (const KeyButtonPair& pair : list) {
52 if (pair.key_code == key_code) 52 if (pair.key_code == key_code) {
53 pair.key_button->status.store(pressed); 53 pair.key_button->status.store(pressed);
54 }
54 } 55 }
55 } 56 }
56 57
@@ -73,10 +74,10 @@ KeyButton::~KeyButton() {
73} 74}
74 75
75std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) { 76std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) {
76 int key_code = params.Get("code", 0); 77 const int key_code = params.Get("code", 0);
77 std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list); 78 std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list);
78 key_button_list->AddKeyButton(key_code, button.get()); 79 key_button_list->AddKeyButton(key_code, button.get());
79 return std::move(button); 80 return button;
80} 81}
81 82
82void Keyboard::PressKey(int key_code) { 83void Keyboard::PressKey(int key_code) {
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index c98c848cf..e59ad4ff5 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -6,9 +6,14 @@
6#include <thread> 6#include <thread>
7#include "common/param_package.h" 7#include "common/param_package.h"
8#include "input_common/analog_from_button.h" 8#include "input_common/analog_from_button.h"
9#include "input_common/gcadapter/gc_adapter.h"
10#include "input_common/gcadapter/gc_poller.h"
9#include "input_common/keyboard.h" 11#include "input_common/keyboard.h"
10#include "input_common/main.h" 12#include "input_common/main.h"
11#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"
12#include "input_common/udp/udp.h" 17#include "input_common/udp/udp.h"
13#ifdef HAVE_SDL2 18#ifdef HAVE_SDL2
14#include "input_common/sdl/sdl.h" 19#include "input_common/sdl/sdl.h"
@@ -16,40 +21,228 @@
16 21
17namespace InputCommon { 22namespace InputCommon {
18 23
19static std::shared_ptr<Keyboard> keyboard; 24struct InputSubsystem::Impl {
20static std::shared_ptr<MotionEmu> motion_emu; 25 void Initialize() {
21static std::unique_ptr<SDL::State> sdl; 26 gcadapter = std::make_shared<GCAdapter::Adapter>();
22static std::unique_ptr<CemuhookUDP::State> udp; 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 gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
32 Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
23 33
24void Init() { 34 keyboard = std::make_shared<Keyboard>();
25 keyboard = std::make_shared<Keyboard>(); 35 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
26 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); 36 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
27 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", 37 std::make_shared<AnalogFromButton>());
28 std::make_shared<AnalogFromButton>()); 38 Input::RegisterFactory<Input::MotionDevice>("keyboard",
29 motion_emu = std::make_shared<MotionEmu>(); 39 std::make_shared<MotionFromButton>());
30 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); 40 motion_emu = std::make_shared<MotionEmu>();
41 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
42 Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
43 std::make_shared<TouchFromButtonFactory>());
31 44
32 sdl = SDL::Init(); 45#ifdef HAVE_SDL2
46 sdl = SDL::Init();
47#endif
48
49 udp = std::make_shared<InputCommon::CemuhookUDP::Client>();
50 udpmotion = std::make_shared<UDPMotionFactory>(udp);
51 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion);
52 udptouch = std::make_shared<UDPTouchFactory>(udp);
53 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch);
54 }
55
56 void Shutdown() {
57 Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
58 Input::UnregisterFactory<Input::MotionDevice>("keyboard");
59 keyboard.reset();
60 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
61 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
62 motion_emu.reset();
63 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
64#ifdef HAVE_SDL2
65 sdl.reset();
66#endif
67 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
68 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
69 Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
70
71 gcbuttons.reset();
72 gcanalog.reset();
73 gcvibration.reset();
74
75 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
76 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
77
78 udpmotion.reset();
79 udptouch.reset();
80 }
81
82 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
83 std::vector<Common::ParamPackage> devices = {
84 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
85 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
86 };
87#ifdef HAVE_SDL2
88 auto sdl_devices = sdl->GetInputDevices();
89 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
90#endif
91 auto udp_devices = udp->GetInputDevices();
92 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
93 auto gcpad_devices = gcadapter->GetInputDevices();
94 devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end());
95 return devices;
96 }
97
98 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
99 const Common::ParamPackage& params) const {
100 if (!params.Has("class") || params.Get("class", "") == "any") {
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", "") == "gcpad") {
120 return gcadapter->GetButtonMappingForDevice(params);
121 }
122#ifdef HAVE_SDL2
123 if (params.Get("class", "") == "sdl") {
124 return sdl->GetButtonMappingForDevice(params);
125 }
126#endif
127 return {};
128 }
129
130 [[nodiscard]] MotionMapping GetMotionMappingForDevice(
131 const Common::ParamPackage& params) const {
132 if (!params.Has("class") || params.Get("class", "") == "any") {
133 return {};
134 }
135 if (params.Get("class", "") == "cemuhookudp") {
136 // TODO return the correct motion device
137 return {};
138 }
139 return {};
140 }
141
142 std::shared_ptr<Keyboard> keyboard;
143 std::shared_ptr<MotionEmu> motion_emu;
144#ifdef HAVE_SDL2
145 std::unique_ptr<SDL::State> sdl;
146#endif
147 std::shared_ptr<GCButtonFactory> gcbuttons;
148 std::shared_ptr<GCAnalogFactory> gcanalog;
149 std::shared_ptr<GCVibrationFactory> gcvibration;
150 std::shared_ptr<UDPMotionFactory> udpmotion;
151 std::shared_ptr<UDPTouchFactory> udptouch;
152 std::shared_ptr<CemuhookUDP::Client> udp;
153 std::shared_ptr<GCAdapter::Adapter> gcadapter;
154};
155
156InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
157
158InputSubsystem::~InputSubsystem() = default;
159
160void InputSubsystem::Initialize() {
161 impl->Initialize();
162}
163
164void InputSubsystem::Shutdown() {
165 impl->Shutdown();
166}
167
168Keyboard* InputSubsystem::GetKeyboard() {
169 return impl->keyboard.get();
170}
171
172const Keyboard* InputSubsystem::GetKeyboard() const {
173 return impl->keyboard.get();
174}
175
176MotionEmu* InputSubsystem::GetMotionEmu() {
177 return impl->motion_emu.get();
178}
179
180const MotionEmu* InputSubsystem::GetMotionEmu() const {
181 return impl->motion_emu.get();
182}
183
184std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
185 return impl->GetInputDevices();
186}
187
188AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
189 return impl->GetAnalogMappingForDevice(device);
190}
191
192ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
193 return impl->GetButtonMappingForDevice(device);
194}
195
196MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPackage& device) const {
197 return impl->GetMotionMappingForDevice(device);
198}
199
200GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
201 return impl->gcanalog.get();
202}
203
204const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
205 return impl->gcanalog.get();
206}
207
208GCButtonFactory* InputSubsystem::GetGCButtons() {
209 return impl->gcbuttons.get();
210}
33 211
34 udp = CemuhookUDP::Init(); 212const GCButtonFactory* InputSubsystem::GetGCButtons() const {
213 return impl->gcbuttons.get();
35} 214}
36 215
37void Shutdown() { 216UDPMotionFactory* InputSubsystem::GetUDPMotions() {
38 Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); 217 return impl->udpmotion.get();
39 keyboard.reset();
40 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
41 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
42 motion_emu.reset();
43 sdl.reset();
44 udp.reset();
45} 218}
46 219
47Keyboard* GetKeyboard() { 220const UDPMotionFactory* InputSubsystem::GetUDPMotions() const {
48 return keyboard.get(); 221 return impl->udpmotion.get();
49} 222}
50 223
51MotionEmu* GetMotionEmu() { 224UDPTouchFactory* InputSubsystem::GetUDPTouch() {
52 return motion_emu.get(); 225 return impl->udptouch.get();
226}
227
228const UDPTouchFactory* InputSubsystem::GetUDPTouch() const {
229 return impl->udptouch.get();
230}
231
232void InputSubsystem::ReloadInputDevices() {
233 if (!impl->udp) {
234 return;
235 }
236 impl->udp->ReloadUDPClient();
237}
238
239std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
240 Polling::DeviceType type) const {
241#ifdef HAVE_SDL2
242 return impl->sdl->GetPollers(type);
243#else
244 return {};
245#endif
53} 246}
54 247
55std::string GenerateKeyboardParam(int key_code) { 248std::string GenerateKeyboardParam(int key_code) {
@@ -73,18 +266,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
73 }; 266 };
74 return circle_pad_param.Serialize(); 267 return circle_pad_param.Serialize();
75} 268}
76
77namespace Polling {
78
79std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
80 std::vector<std::unique_ptr<DevicePoller>> pollers;
81
82#ifdef HAVE_SDL2
83 pollers = sdl->GetPollers(type);
84#endif
85
86 return pollers;
87}
88
89} // namespace Polling
90} // namespace InputCommon 269} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 77a0ce90b..dded3f1ef 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -6,40 +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 11
11namespace Common { 12namespace Common {
12class ParamPackage; 13class ParamPackage;
13} 14}
14 15
15namespace InputCommon { 16namespace Settings::NativeAnalog {
16 17enum Values : int;
17/// Initializes and registers all built-in input device factories. 18}
18void Init();
19
20/// Deregisters all built-in input device factories and shuts them down.
21void Shutdown();
22
23class Keyboard;
24
25/// Gets the keyboard button device factory.
26Keyboard* GetKeyboard();
27
28class MotionEmu;
29
30/// Gets the motion emulation factory.
31MotionEmu* GetMotionEmu();
32 19
33/// Generates a serialized param package for creating a keyboard button device 20namespace Settings::NativeButton {
34std::string GenerateKeyboardParam(int key_code); 21enum Values : int;
22}
35 23
36/// Generates a serialized param package for creating an analog device taking input from keyboard 24namespace Settings::NativeMotion {
37std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, 25enum Values : int;
38 int key_modifier, float modifier_scale); 26}
39 27
28namespace InputCommon {
40namespace Polling { 29namespace Polling {
41 30
42enum class DeviceType { Button, Analog }; 31enum class DeviceType { Button, AnalogPreferred, Motion };
43 32
44/** 33/**
45 * 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
@@ -49,7 +38,9 @@ class DevicePoller {
49public: 38public:
50 virtual ~DevicePoller() = default; 39 virtual ~DevicePoller() = default;
51 /// Setup and start polling for inputs, should be called before GetNextInput 40 /// Setup and start polling for inputs, should be called before GetNextInput
52 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;
53 /// Stop polling 44 /// Stop polling
54 virtual void Stop() = 0; 45 virtual void Stop() = 0;
55 /** 46 /**
@@ -59,8 +50,110 @@ public:
59 */ 50 */
60 virtual Common::ParamPackage GetNextInput() = 0; 51 virtual Common::ParamPackage GetNextInput() = 0;
61}; 52};
62
63// Get all DevicePoller from all backends for a specific device type
64std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
65} // 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
66} // namespace InputCommon 159} // namespace InputCommon
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index 868251628..d4da5596b 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -18,11 +18,11 @@ namespace InputCommon {
18// Implementation class of the motion emulation device 18// Implementation class of the motion emulation device
19class MotionEmuDevice { 19class MotionEmuDevice {
20public: 20public:
21 MotionEmuDevice(int update_millisecond, float sensitivity) 21 explicit MotionEmuDevice(int update_millisecond_, float sensitivity_)
22 : update_millisecond(update_millisecond), 22 : update_millisecond(update_millisecond_),
23 update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>( 23 update_duration(std::chrono::duration_cast<std::chrono::steady_clock::duration>(
24 std::chrono::milliseconds(update_millisecond))), 24 std::chrono::milliseconds(update_millisecond))),
25 sensitivity(sensitivity), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} 25 sensitivity(sensitivity_), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {}
26 26
27 ~MotionEmuDevice() { 27 ~MotionEmuDevice() {
28 if (motion_emu_thread.joinable()) { 28 if (motion_emu_thread.joinable()) {
@@ -37,16 +37,18 @@ public:
37 } 37 }
38 38
39 void Tilt(int x, int y) { 39 void Tilt(int x, int y) {
40 auto mouse_move = Common::MakeVec(x, y) - mouse_origin; 40 if (!is_tilting) {
41 if (is_tilting) { 41 return;
42 std::lock_guard guard{tilt_mutex}; 42 }
43 if (mouse_move.x == 0 && mouse_move.y == 0) { 43
44 tilt_angle = 0; 44 std::lock_guard guard{tilt_mutex};
45 } else { 45 const auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
46 tilt_direction = mouse_move.Cast<float>(); 46 if (mouse_move.x == 0 && mouse_move.y == 0) {
47 tilt_angle = 47 tilt_angle = 0;
48 std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f); 48 } else {
49 } 49 tilt_direction = mouse_move.Cast<float>();
50 tilt_angle =
51 std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f);
50 } 52 }
51 } 53 }
52 54
@@ -56,7 +58,7 @@ public:
56 is_tilting = false; 58 is_tilting = false;
57 } 59 }
58 60
59 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { 61 Input::MotionStatus GetStatus() {
60 std::lock_guard guard{status_mutex}; 62 std::lock_guard guard{status_mutex};
61 return status; 63 return status;
62 } 64 }
@@ -76,7 +78,7 @@ private:
76 78
77 Common::Event shutdown_event; 79 Common::Event shutdown_event;
78 80
79 std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; 81 Input::MotionStatus status;
80 std::mutex status_mutex; 82 std::mutex status_mutex;
81 83
82 // Note: always keep the thread declaration at the end so that other objects are initialized 84 // Note: always keep the thread declaration at the end so that other objects are initialized
@@ -86,11 +88,10 @@ private:
86 void MotionEmuThread() { 88 void MotionEmuThread() {
87 auto update_time = std::chrono::steady_clock::now(); 89 auto update_time = std::chrono::steady_clock::now();
88 Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0); 90 Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0);
89 Common::Quaternion<float> old_q;
90 91
91 while (!shutdown_event.WaitUntil(update_time)) { 92 while (!shutdown_event.WaitUntil(update_time)) {
92 update_time += update_duration; 93 update_time += update_duration;
93 old_q = q; 94 const Common::Quaternion<float> old_q = q;
94 95
95 { 96 {
96 std::lock_guard guard{tilt_mutex}; 97 std::lock_guard guard{tilt_mutex};
@@ -100,23 +101,32 @@ private:
100 Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle); 101 Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle);
101 } 102 }
102 103
103 auto inv_q = q.Inverse(); 104 const auto inv_q = q.Inverse();
104 105
105 // Set the gravity vector in world space 106 // Set the gravity vector in world space
106 auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f); 107 auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f);
107 108
108 // Find the angular rate vector in world space 109 // Find the angular rate vector in world space
109 auto angular_rate = ((q - old_q) * inv_q).xyz * 2; 110 auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
110 angular_rate *= 1000 / update_millisecond / Common::PI * 180; 111 angular_rate *= static_cast<float>(1000 / update_millisecond) / Common::PI * 180.0f;
111 112
112 // Transform the two vectors from world space to 3DS space 113 // Transform the two vectors from world space to 3DS space
113 gravity = QuaternionRotate(inv_q, gravity); 114 gravity = QuaternionRotate(inv_q, gravity);
114 angular_rate = QuaternionRotate(inv_q, angular_rate); 115 angular_rate = QuaternionRotate(inv_q, angular_rate);
115 116
117 // TODO: Calculate the correct rotation vector and orientation matrix
118 const auto matrix4x4 = q.ToMatrix();
119 const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f);
120 const std::array orientation{
121 Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
122 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
123 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]),
124 };
125
116 // Update the sensor state 126 // Update the sensor state
117 { 127 {
118 std::lock_guard guard{status_mutex}; 128 std::lock_guard guard{status_mutex};
119 status = std::make_tuple(gravity, angular_rate); 129 status = std::make_tuple(gravity, angular_rate, rotation, orientation);
120 } 130 }
121 } 131 }
122 } 132 }
@@ -127,11 +137,11 @@ private:
127// can forward all the inputs to the implementation only when it is valid. 137// can forward all the inputs to the implementation only when it is valid.
128class MotionEmuDeviceWrapper : public Input::MotionDevice { 138class MotionEmuDeviceWrapper : public Input::MotionDevice {
129public: 139public:
130 MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) { 140 explicit MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) {
131 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); 141 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
132 } 142 }
133 143
134 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 144 Input::MotionStatus GetStatus() const override {
135 return device->GetStatus(); 145 return device->GetStatus();
136 } 146 }
137 147
@@ -139,13 +149,13 @@ public:
139}; 149};
140 150
141std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) { 151std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackage& params) {
142 int update_period = params.Get("update_period", 100); 152 const int update_period = params.Get("update_period", 100);
143 float sensitivity = params.Get("sensitivity", 0.01f); 153 const float sensitivity = params.Get("sensitivity", 0.01f);
144 auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity); 154 auto device_wrapper = std::make_unique<MotionEmuDeviceWrapper>(update_period, sensitivity);
145 // Previously created device is disconnected here. Having two motion devices for 3DS is not 155 // Previously created device is disconnected here. Having two motion devices for 3DS is not
146 // expected. 156 // expected.
147 current_device = device_wrapper->device; 157 current_device = device_wrapper->device;
148 return std::move(device_wrapper); 158 return device_wrapper;
149} 159}
150 160
151void MotionEmu::BeginTilt(int x, int y) { 161void MotionEmu::BeginTilt(int x, int y) {
diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp
new file mode 100644
index 000000000..29045a673
--- /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 explicit MotionKey(Button key_) : key(std::move(key_)) {}
15
16 Input::MotionStatus GetStatus() const override {
17
18 if (key->GetStatus()) {
19 return motion.GetRandomMotion(2, 6);
20 }
21 return motion.GetRandomMotion(0, 0);
22 }
23
24private:
25 Button key;
26 InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
27};
28
29std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) {
30 auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize());
31 return std::make_unique<MotionKey>(std::move(key));
32}
33
34} // namespace InputCommon
diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h
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..f77ba535d
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,301 @@
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) : kp(new_kp), ki(new_ki), kd(new_kd) {}
12
13void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
14 accel = acceleration;
15}
16
17void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
18 gyro = gyroscope - gyro_drift;
19
20 // Auto adjust drift to minimize drift
21 if (!IsMoving(0.1f)) {
22 gyro_drift = (gyro_drift * 0.9999f) + (gyroscope * 0.0001f);
23 }
24
25 if (gyro.Length2() < gyro_threshold) {
26 gyro = {};
27 } else {
28 only_accelerometer = false;
29 }
30}
31
32void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
33 quat = quaternion;
34}
35
36void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
37 gyro_drift = drift;
38}
39
40void MotionInput::SetGyroThreshold(f32 threshold) {
41 gyro_threshold = threshold;
42}
43
44void MotionInput::EnableReset(bool reset) {
45 reset_enabled = reset;
46}
47
48void MotionInput::ResetRotations() {
49 rotations = {};
50}
51
52bool MotionInput::IsMoving(f32 sensitivity) const {
53 return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
54}
55
56bool MotionInput::IsCalibrated(f32 sensitivity) const {
57 return real_error.Length() < sensitivity;
58}
59
60void MotionInput::UpdateRotation(u64 elapsed_time) {
61 const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
62 if (sample_period > 0.1f) {
63 return;
64 }
65 rotations += gyro * sample_period;
66}
67
68void MotionInput::UpdateOrientation(u64 elapsed_time) {
69 if (!IsCalibrated(0.1f)) {
70 ResetOrientation();
71 }
72 // Short name local variable for readability
73 f32 q1 = quat.w;
74 f32 q2 = quat.xyz[0];
75 f32 q3 = quat.xyz[1];
76 f32 q4 = quat.xyz[2];
77 const auto sample_period = static_cast<f32>(elapsed_time) / 1000000.0f;
78
79 // Ignore invalid elapsed time
80 if (sample_period > 0.1f) {
81 return;
82 }
83
84 const auto normal_accel = accel.Normalized();
85 auto rad_gyro = gyro * Common::PI * 2;
86 const f32 swap = rad_gyro.x;
87 rad_gyro.x = rad_gyro.y;
88 rad_gyro.y = -swap;
89 rad_gyro.z = -rad_gyro.z;
90
91 // Clear gyro values if there is no gyro present
92 if (only_accelerometer) {
93 rad_gyro.x = 0;
94 rad_gyro.y = 0;
95 rad_gyro.z = 0;
96 }
97
98 // Ignore drift correction if acceleration is not reliable
99 if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
100 const f32 ax = -normal_accel.x;
101 const f32 ay = normal_accel.y;
102 const f32 az = -normal_accel.z;
103
104 // Estimated direction of gravity
105 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
106 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
107 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
108
109 // Error is cross product between estimated direction and measured direction of gravity
110 const Common::Vec3f new_real_error = {
111 az * vx - ax * vz,
112 ay * vz - az * vy,
113 ax * vy - ay * vx,
114 };
115
116 derivative_error = new_real_error - real_error;
117 real_error = new_real_error;
118
119 // Prevent integral windup
120 if (ki != 0.0f && !IsCalibrated(0.05f)) {
121 integral_error += real_error;
122 } else {
123 integral_error = {};
124 }
125
126 // Apply feedback terms
127 if (!only_accelerometer) {
128 rad_gyro += kp * real_error;
129 rad_gyro += ki * integral_error;
130 rad_gyro += kd * derivative_error;
131 } else {
132 // Give more weight to acelerometer values to compensate for the lack of gyro
133 rad_gyro += 35.0f * kp * real_error;
134 rad_gyro += 10.0f * ki * integral_error;
135 rad_gyro += 10.0f * kd * derivative_error;
136
137 // Emulate gyro values for games that need them
138 gyro.x = -rad_gyro.y;
139 gyro.y = rad_gyro.x;
140 gyro.z = -rad_gyro.z;
141 UpdateRotation(elapsed_time);
142 }
143 }
144
145 const f32 gx = rad_gyro.y;
146 const f32 gy = rad_gyro.x;
147 const f32 gz = rad_gyro.z;
148
149 // Integrate rate of change of quaternion
150 const f32 pa = q2;
151 const f32 pb = q3;
152 const f32 pc = q4;
153 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
154 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
155 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
156 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
157
158 quat.w = q1;
159 quat.xyz[0] = q2;
160 quat.xyz[1] = q3;
161 quat.xyz[2] = q4;
162 quat = quat.Normalized();
163}
164
165std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
166 const Common::Quaternion<float> quad{
167 .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
168 .w = -quat.xyz[2],
169 };
170 const std::array<float, 16> matrix4x4 = quad.ToMatrix();
171
172 return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
173 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
174 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
175}
176
177Common::Vec3f MotionInput::GetAcceleration() const {
178 return accel;
179}
180
181Common::Vec3f MotionInput::GetGyroscope() const {
182 return gyro;
183}
184
185Common::Quaternion<f32> MotionInput::GetQuaternion() const {
186 return quat;
187}
188
189Common::Vec3f MotionInput::GetRotations() const {
190 return rotations;
191}
192
193Input::MotionStatus MotionInput::GetMotion() const {
194 const Common::Vec3f gyroscope = GetGyroscope();
195 const Common::Vec3f accelerometer = GetAcceleration();
196 const Common::Vec3f rotation = GetRotations();
197 const std::array<Common::Vec3f, 3> orientation = GetOrientation();
198 return {accelerometer, gyroscope, rotation, orientation};
199}
200
201Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const {
202 std::random_device device;
203 std::mt19937 gen(device());
204 std::uniform_int_distribution<s16> distribution(-1000, 1000);
205 const Common::Vec3f gyroscope{
206 static_cast<f32>(distribution(gen)) * 0.001f,
207 static_cast<f32>(distribution(gen)) * 0.001f,
208 static_cast<f32>(distribution(gen)) * 0.001f,
209 };
210 const Common::Vec3f accelerometer{
211 static_cast<f32>(distribution(gen)) * 0.001f,
212 static_cast<f32>(distribution(gen)) * 0.001f,
213 static_cast<f32>(distribution(gen)) * 0.001f,
214 };
215 constexpr Common::Vec3f rotation;
216 constexpr std::array orientation{
217 Common::Vec3f{1.0f, 0.0f, 0.0f},
218 Common::Vec3f{0.0f, 1.0f, 0.0f},
219 Common::Vec3f{0.0f, 0.0f, 1.0f},
220 };
221 return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation};
222}
223
224void MotionInput::ResetOrientation() {
225 if (!reset_enabled || only_accelerometer) {
226 return;
227 }
228 if (!IsMoving(0.5f) && accel.z <= -0.9f) {
229 ++reset_counter;
230 if (reset_counter > 900) {
231 quat.w = 0;
232 quat.xyz[0] = 0;
233 quat.xyz[1] = 0;
234 quat.xyz[2] = -1;
235 SetOrientationFromAccelerometer();
236 integral_error = {};
237 reset_counter = 0;
238 }
239 } else {
240 reset_counter = 0;
241 }
242}
243
244void MotionInput::SetOrientationFromAccelerometer() {
245 int iterations = 0;
246 const f32 sample_period = 0.015f;
247
248 const auto normal_accel = accel.Normalized();
249
250 while (!IsCalibrated(0.01f) && ++iterations < 100) {
251 // Short name local variable for readability
252 f32 q1 = quat.w;
253 f32 q2 = quat.xyz[0];
254 f32 q3 = quat.xyz[1];
255 f32 q4 = quat.xyz[2];
256
257 Common::Vec3f rad_gyro;
258 const f32 ax = -normal_accel.x;
259 const f32 ay = normal_accel.y;
260 const f32 az = -normal_accel.z;
261
262 // Estimated direction of gravity
263 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
264 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
265 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
266
267 // Error is cross product between estimated direction and measured direction of gravity
268 const Common::Vec3f new_real_error = {
269 az * vx - ax * vz,
270 ay * vz - az * vy,
271 ax * vy - ay * vx,
272 };
273
274 derivative_error = new_real_error - real_error;
275 real_error = new_real_error;
276
277 rad_gyro += 10.0f * kp * real_error;
278 rad_gyro += 5.0f * ki * integral_error;
279 rad_gyro += 10.0f * kd * derivative_error;
280
281 const f32 gx = rad_gyro.y;
282 const f32 gy = rad_gyro.x;
283 const f32 gz = rad_gyro.z;
284
285 // Integrate rate of change of quaternion
286 const f32 pa = q2;
287 const f32 pb = q3;
288 const f32 pc = q4;
289 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
290 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
291 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
292 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
293
294 quat.w = q1;
295 quat.xyz[0] = q2;
296 quat.xyz[1] = q3;
297 quat.xyz[2] = q4;
298 quat = quat.Normalized();
299 }
300}
301} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..efe74cf19
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,74 @@
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 explicit 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& gyroscope);
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 [[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
37 [[nodiscard]] Common::Vec3f GetAcceleration() const;
38 [[nodiscard]] Common::Vec3f GetGyroscope() const;
39 [[nodiscard]] Common::Vec3f GetRotations() const;
40 [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
41 [[nodiscard]] Input::MotionStatus GetMotion() const;
42 [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude,
43 int gyro_magnitude) const;
44
45 [[nodiscard]] bool IsMoving(f32 sensitivity) const;
46 [[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
47
48private:
49 void ResetOrientation();
50 void SetOrientationFromAccelerometer();
51
52 // PID constants
53 f32 kp;
54 f32 ki;
55 f32 kd;
56
57 // PID errors
58 Common::Vec3f real_error;
59 Common::Vec3f integral_error;
60 Common::Vec3f derivative_error;
61
62 Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f};
63 Common::Vec3f rotations;
64 Common::Vec3f accel;
65 Common::Vec3f gyro;
66 Common::Vec3f gyro_drift;
67
68 f32 gyro_threshold = 0.0f;
69 u32 reset_counter = 0;
70 bool reset_enabled = true;
71 bool only_accelerometer = true;
72};
73
74} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 5306daa70..42bbf14d4 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) {
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 a2e0c0bd2..7827e324c 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* game_controller)
60 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
61 sdl_controller{game_controller, &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,24 @@ 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 static_cast<float>(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(u16 amp_low, u16 amp_high) {
75 float x = GetAxis(axis_x); 84 if (sdl_controller) {
76 float y = GetAxis(axis_y); 85 return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0;
86 } else if (sdl_joystick) {
87 return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0;
88 }
89
90 return false;
91 }
92
93 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const {
94 float x = GetAxis(axis_x, range);
95 float y = GetAxis(axis_y, range);
77 y = -y; // 3DS uses an y-axis inverse from SDL 96 y = -y; // 3DS uses an y-axis inverse from SDL
78 97
79 // Make sure the coordinates are in the unit circle, 98 // Make sure the coordinates are in the unit circle,
@@ -88,6 +107,10 @@ public:
88 return std::make_tuple(x, y); 107 return std::make_tuple(x, y);
89 } 108 }
90 109
110 const MotionInput& GetMotion() const {
111 return motion;
112 }
113
91 void SetHat(int hat, Uint8 direction) { 114 void SetHat(int hat, Uint8 direction) {
92 std::lock_guard lock{mutex}; 115 std::lock_guard lock{mutex};
93 state.hats.insert_or_assign(hat, direction); 116 state.hats.insert_or_assign(hat, direction);
@@ -115,8 +138,13 @@ public:
115 return sdl_joystick.get(); 138 return sdl_joystick.get();
116 } 139 }
117 140
118 void SetSDLJoystick(SDL_Joystick* joystick) { 141 SDL_GameController* GetSDLGameController() const {
142 return sdl_controller.get();
143 }
144
145 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
119 sdl_joystick.reset(joystick); 146 sdl_joystick.reset(joystick);
147 sdl_controller.reset(controller);
120 } 148 }
121 149
122private: 150private:
@@ -128,21 +156,29 @@ private:
128 std::string guid; 156 std::string guid;
129 int port; 157 int port;
130 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; 158 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
159 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
131 mutable std::mutex mutex; 160 mutable std::mutex mutex;
161
162 // Motion is initialized without PID values as motion input is not aviable for SDL2
163 MotionInput motion{0.0f, 0.0f, 0.0f};
132}; 164};
133 165
134std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { 166std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
135 std::lock_guard lock{joystick_map_mutex}; 167 std::lock_guard lock{joystick_map_mutex};
136 const auto it = joystick_map.find(guid); 168 const auto it = joystick_map.find(guid);
169
137 if (it != joystick_map.end()) { 170 if (it != joystick_map.end()) {
138 while (it->second.size() <= static_cast<std::size_t>(port)) { 171 while (it->second.size() <= static_cast<std::size_t>(port)) {
139 auto joystick = 172 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); 173 nullptr, nullptr);
141 it->second.emplace_back(std::move(joystick)); 174 it->second.emplace_back(std::move(joystick));
142 } 175 }
143 return it->second[port]; 176
177 return it->second[static_cast<std::size_t>(port)];
144 } 178 }
145 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr); 179
180 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
181
146 return joystick_map[guid].emplace_back(std::move(joystick)); 182 return joystick_map[guid].emplace_back(std::move(joystick));
147} 183}
148 184
@@ -152,86 +188,72 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_
152 188
153 std::lock_guard lock{joystick_map_mutex}; 189 std::lock_guard lock{joystick_map_mutex};
154 const auto map_it = joystick_map.find(guid); 190 const auto map_it = joystick_map.find(guid);
155 if (map_it != joystick_map.end()) {
156 const auto vec_it =
157 std::find_if(map_it->second.begin(), map_it->second.end(),
158 [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
159 return sdl_joystick == joystick->GetSDLJoystick();
160 });
161 if (vec_it != map_it->second.end()) {
162 // This is the common case: There is already an existing SDL_Joystick maped to a
163 // SDLJoystick. return the SDLJoystick
164 return *vec_it;
165 }
166 191
167 // Search for a SDLJoystick without a mapped SDL_Joystick... 192 if (map_it == joystick_map.end()) {
168 const auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), 193 return nullptr;
169 [](const std::shared_ptr<SDLJoystick>& joystick) { 194 }
170 return !joystick->GetSDLJoystick(); 195
171 }); 196 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
172 if (nullptr_it != map_it->second.end()) { 197 [&sdl_joystick](const auto& joystick) {
173 // ... and map it 198 return joystick->GetSDLJoystick() == sdl_joystick;
174 (*nullptr_it)->SetSDLJoystick(sdl_joystick); 199 });
175 return *nullptr_it;
176 }
177 200
178 // There is no SDLJoystick without a mapped SDL_Joystick 201 if (vec_it == map_it->second.end()) {
179 // Create a new SDLJoystick 202 return nullptr;
180 const int port = static_cast<int>(map_it->second.size());
181 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick);
182 return map_it->second.emplace_back(std::move(joystick));
183 } 203 }
184 204
185 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); 205 return *vec_it;
186 return joystick_map[guid].emplace_back(std::move(joystick));
187} 206}
188 207
189void SDLState::InitJoystick(int joystick_index) { 208void SDLState::InitJoystick(int joystick_index) {
190 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); 209 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
210 SDL_GameController* sdl_gamecontroller = nullptr;
211
212 if (SDL_IsGameController(joystick_index)) {
213 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
214 }
215
191 if (!sdl_joystick) { 216 if (!sdl_joystick) {
192 LOG_ERROR(Input, "failed to open joystick {}", joystick_index); 217 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
193 return; 218 return;
194 } 219 }
220
195 const std::string guid = GetGUID(sdl_joystick); 221 const std::string guid = GetGUID(sdl_joystick);
196 222
197 std::lock_guard lock{joystick_map_mutex}; 223 std::lock_guard lock{joystick_map_mutex};
198 if (joystick_map.find(guid) == joystick_map.end()) { 224 if (joystick_map.find(guid) == joystick_map.end()) {
199 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); 225 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
200 joystick_map[guid].emplace_back(std::move(joystick)); 226 joystick_map[guid].emplace_back(std::move(joystick));
201 return; 227 return;
202 } 228 }
229
203 auto& joystick_guid_list = joystick_map[guid]; 230 auto& joystick_guid_list = joystick_map[guid];
204 const auto it = std::find_if( 231 const auto joystick_it =
205 joystick_guid_list.begin(), joystick_guid_list.end(), 232 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
206 [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); 233 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
207 if (it != joystick_guid_list.end()) { 234
208 (*it)->SetSDLJoystick(sdl_joystick); 235 if (joystick_it != joystick_guid_list.end()) {
236 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
209 return; 237 return;
210 } 238 }
239
211 const int port = static_cast<int>(joystick_guid_list.size()); 240 const int port = static_cast<int>(joystick_guid_list.size());
212 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); 241 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
213 joystick_guid_list.emplace_back(std::move(joystick)); 242 joystick_guid_list.emplace_back(std::move(joystick));
214} 243}
215 244
216void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { 245void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
217 const std::string guid = GetGUID(sdl_joystick); 246 const std::string guid = GetGUID(sdl_joystick);
218 247
219 std::shared_ptr<SDLJoystick> joystick; 248 std::lock_guard lock{joystick_map_mutex};
220 { 249 // This call to guid is safe since the joystick is guaranteed to be in the map
221 std::lock_guard lock{joystick_map_mutex}; 250 const auto& joystick_guid_list = joystick_map[guid];
222 // This call to guid is safe since the joystick is guaranteed to be in the map 251 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
223 const auto& joystick_guid_list = joystick_map[guid]; 252 [&sdl_joystick](const auto& joystick) {
224 const auto joystick_it = 253 return joystick->GetSDLJoystick() == sdl_joystick;
225 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), 254 });
226 [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { 255
227 return joystick->GetSDLJoystick() == sdl_joystick; 256 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
228 });
229 joystick = *joystick_it;
230 }
231
232 // Destruct SDL_Joystick outside the lock guard because SDL can internally call the
233 // event callback which locks the mutex again.
234 joystick->SetSDLJoystick(nullptr);
235} 257}
236 258
237void SDLState::HandleGameControllerEvent(const SDL_Event& event) { 259void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@@ -313,7 +335,7 @@ public:
313 trigger_if_greater(trigger_if_greater_) {} 335 trigger_if_greater(trigger_if_greater_) {}
314 336
315 bool GetStatus() const override { 337 bool GetStatus() const override {
316 const float axis_value = joystick->GetAxis(axis); 338 const float axis_value = joystick->GetAxis(axis, 1.0f);
317 if (trigger_if_greater) { 339 if (trigger_if_greater) {
318 return axis_value > threshold; 340 return axis_value > threshold;
319 } 341 }
@@ -329,22 +351,24 @@ private:
329 351
330class SDLAnalog final : public Input::AnalogDevice { 352class SDLAnalog final : public Input::AnalogDevice {
331public: 353public:
332 SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_) 354 explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
333 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} 355 float deadzone_, float range_)
356 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_),
357 range(range_) {}
334 358
335 std::tuple<float, float> GetStatus() const override { 359 std::tuple<float, float> GetStatus() const override {
336 const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); 360 const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range);
337 const float r = std::sqrt((x * x) + (y * y)); 361 const float r = std::sqrt((x * x) + (y * y));
338 if (r > deadzone) { 362 if (r > deadzone) {
339 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), 363 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
340 y / r * (r - deadzone) / (1 - deadzone)); 364 y / r * (r - deadzone) / (1 - deadzone));
341 } 365 }
342 return std::make_tuple<float, float>(0.0f, 0.0f); 366 return {};
343 } 367 }
344 368
345 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { 369 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
346 const auto [x, y] = GetStatus(); 370 const auto [x, y] = GetStatus();
347 const float directional_deadzone = 0.4f; 371 const float directional_deadzone = 0.5f;
348 switch (direction) { 372 switch (direction) {
349 case Input::AnalogDirection::RIGHT: 373 case Input::AnalogDirection::RIGHT:
350 return x > directional_deadzone; 374 return x > directional_deadzone;
@@ -363,6 +387,95 @@ private:
363 const int axis_x; 387 const int axis_x;
364 const int axis_y; 388 const int axis_y;
365 const float deadzone; 389 const float deadzone;
390 const float range;
391};
392
393class SDLVibration final : public Input::VibrationDevice {
394public:
395 explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
396 : joystick(std::move(joystick_)) {}
397
398 u8 GetStatus() const override {
399 joystick->RumblePlay(1, 1);
400 return joystick->RumblePlay(0, 0);
401 }
402
403 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
404 [[maybe_unused]] f32 freq_high) const override {
405 const auto process_amplitude = [](f32 amplitude) {
406 return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF);
407 };
408
409 const auto processed_amp_low = process_amplitude(amp_low);
410 const auto processed_amp_high = process_amplitude(amp_high);
411
412 return joystick->RumblePlay(processed_amp_low, processed_amp_high);
413 }
414
415private:
416 std::shared_ptr<SDLJoystick> joystick;
417};
418
419class SDLDirectionMotion final : public Input::MotionDevice {
420public:
421 explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
422 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
423
424 Input::MotionStatus GetStatus() const override {
425 if (joystick->GetHatDirection(hat, direction)) {
426 return joystick->GetMotion().GetRandomMotion(2, 6);
427 }
428 return joystick->GetMotion().GetRandomMotion(0, 0);
429 }
430
431private:
432 std::shared_ptr<SDLJoystick> joystick;
433 int hat;
434 Uint8 direction;
435};
436
437class SDLAxisMotion final : public Input::MotionDevice {
438public:
439 explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
440 bool trigger_if_greater_)
441 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
442 trigger_if_greater(trigger_if_greater_) {}
443
444 Input::MotionStatus GetStatus() const override {
445 const float axis_value = joystick->GetAxis(axis, 1.0f);
446 bool trigger = axis_value < threshold;
447 if (trigger_if_greater) {
448 trigger = axis_value > threshold;
449 }
450
451 if (trigger) {
452 return joystick->GetMotion().GetRandomMotion(2, 6);
453 }
454 return joystick->GetMotion().GetRandomMotion(0, 0);
455 }
456
457private:
458 std::shared_ptr<SDLJoystick> joystick;
459 int axis;
460 float threshold;
461 bool trigger_if_greater;
462};
463
464class SDLButtonMotion final : public Input::MotionDevice {
465public:
466 explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
467 : joystick(std::move(joystick_)), button(button_) {}
468
469 Input::MotionStatus GetStatus() const override {
470 if (joystick->GetButton(button)) {
471 return joystick->GetMotion().GetRandomMotion(2, 6);
472 }
473 return joystick->GetMotion().GetRandomMotion(0, 0);
474 }
475
476private:
477 std::shared_ptr<SDLJoystick> joystick;
478 int button;
366}; 479};
367 480
368/// A button device factory that creates button devices from SDL joystick 481/// A button device factory that creates button devices from SDL joystick
@@ -445,7 +558,7 @@ class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
445public: 558public:
446 explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} 559 explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
447 /** 560 /**
448 * Creates analog device from joystick axes 561 * Creates an analog device from joystick axes
449 * @param params contains parameters for creating the device: 562 * @param params contains parameters for creating the device:
450 * - "guid": the guid of the joystick to bind 563 * - "guid": the guid of the joystick to bind
451 * - "port": the nth joystick of the same type 564 * - "port": the nth joystick of the same type
@@ -457,14 +570,98 @@ public:
457 const int port = params.Get("port", 0); 570 const int port = params.Get("port", 0);
458 const int axis_x = params.Get("axis_x", 0); 571 const int axis_x = params.Get("axis_x", 0);
459 const int axis_y = params.Get("axis_y", 1); 572 const int axis_y = params.Get("axis_y", 1);
460 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); 573 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
461 574 const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
462 auto joystick = state.GetSDLJoystickByGUID(guid, port); 575 auto joystick = state.GetSDLJoystickByGUID(guid, port);
463 576
464 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash 577 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
465 joystick->SetAxis(axis_x, 0); 578 joystick->SetAxis(axis_x, 0);
466 joystick->SetAxis(axis_y, 0); 579 joystick->SetAxis(axis_y, 0);
467 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone); 580 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range);
581 }
582
583private:
584 SDLState& state;
585};
586
587/// An vibration device factory that creates vibration devices from SDL joystick
588class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
589public:
590 explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
591 /**
592 * Creates a vibration device from a joystick
593 * @param params contains parameters for creating the device:
594 * - "guid": the guid of the joystick to bind
595 * - "port": the nth joystick of the same type
596 */
597 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
598 const std::string guid = params.Get("guid", "0");
599 const int port = params.Get("port", 0);
600 return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
601 }
602
603private:
604 SDLState& state;
605};
606
607/// A motion device factory that creates motion devices from SDL joystick
608class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
609public:
610 explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
611 /**
612 * Creates motion device from joystick axes
613 * @param params contains parameters for creating the device:
614 * - "guid": the guid of the joystick to bind
615 * - "port": the nth joystick of the same type
616 */
617 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
618 const std::string guid = params.Get("guid", "0");
619 const int port = params.Get("port", 0);
620
621 auto joystick = state.GetSDLJoystickByGUID(guid, port);
622
623 if (params.Has("hat")) {
624 const int hat = params.Get("hat", 0);
625 const std::string direction_name = params.Get("direction", "");
626 Uint8 direction;
627 if (direction_name == "up") {
628 direction = SDL_HAT_UP;
629 } else if (direction_name == "down") {
630 direction = SDL_HAT_DOWN;
631 } else if (direction_name == "left") {
632 direction = SDL_HAT_LEFT;
633 } else if (direction_name == "right") {
634 direction = SDL_HAT_RIGHT;
635 } else {
636 direction = 0;
637 }
638 // This is necessary so accessing GetHat with hat won't crash
639 joystick->SetHat(hat, SDL_HAT_CENTERED);
640 return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
641 }
642
643 if (params.Has("axis")) {
644 const int axis = params.Get("axis", 0);
645 const float threshold = params.Get("threshold", 0.5f);
646 const std::string direction_name = params.Get("direction", "");
647 bool trigger_if_greater;
648 if (direction_name == "+") {
649 trigger_if_greater = true;
650 } else if (direction_name == "-") {
651 trigger_if_greater = false;
652 } else {
653 trigger_if_greater = true;
654 LOG_ERROR(Input, "Unknown direction {}", direction_name);
655 }
656 // This is necessary so accessing GetAxis with axis won't crash
657 joystick->SetAxis(axis, 0);
658 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
659 }
660
661 const int button = params.Get("button", 0);
662 // This is necessary so accessing GetButton with button won't crash
663 joystick->SetButton(button, false);
664 return std::make_unique<SDLButtonMotion>(joystick, button);
468 } 665 }
469 666
470private: 667private:
@@ -473,15 +670,22 @@ private:
473 670
474SDLState::SDLState() { 671SDLState::SDLState() {
475 using namespace Input; 672 using namespace Input;
476 RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); 673 button_factory = std::make_shared<SDLButtonFactory>(*this);
477 RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); 674 analog_factory = std::make_shared<SDLAnalogFactory>(*this);
478 675 vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
479 // If the frontend is going to manage the event loop, then we dont start one here 676 motion_factory = std::make_shared<SDLMotionFactory>(*this);
480 start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); 677 RegisterFactory<ButtonDevice>("sdl", button_factory);
678 RegisterFactory<AnalogDevice>("sdl", analog_factory);
679 RegisterFactory<VibrationDevice>("sdl", vibration_factory);
680 RegisterFactory<MotionDevice>("sdl", motion_factory);
681
682 // If the frontend is going to manage the event loop, then we don't start one here
683 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
481 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { 684 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
482 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); 685 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
483 return; 686 return;
484 } 687 }
688 has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0;
485 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { 689 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()); 690 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
487 } 691 }
@@ -494,7 +698,7 @@ SDLState::SDLState() {
494 using namespace std::chrono_literals; 698 using namespace std::chrono_literals;
495 while (initialized) { 699 while (initialized) {
496 SDL_PumpEvents(); 700 SDL_PumpEvents();
497 std::this_thread::sleep_for(10ms); 701 std::this_thread::sleep_for(1ms);
498 } 702 }
499 }); 703 });
500 } 704 }
@@ -509,6 +713,8 @@ SDLState::~SDLState() {
509 using namespace Input; 713 using namespace Input;
510 UnregisterFactory<ButtonDevice>("sdl"); 714 UnregisterFactory<ButtonDevice>("sdl");
511 UnregisterFactory<AnalogDevice>("sdl"); 715 UnregisterFactory<AnalogDevice>("sdl");
716 UnregisterFactory<VibrationDevice>("sdl");
717 UnregisterFactory<MotionDevice>("sdl");
512 718
513 CloseJoysticks(); 719 CloseJoysticks();
514 SDL_DelEventWatch(&SDLEventWatcher, this); 720 SDL_DelEventWatch(&SDLEventWatcher, this);
@@ -520,65 +726,268 @@ SDLState::~SDLState() {
520 } 726 }
521} 727}
522 728
523static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { 729std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
730 std::scoped_lock lock(joystick_map_mutex);
731 std::vector<Common::ParamPackage> devices;
732 for (const auto& [key, value] : joystick_map) {
733 for (const auto& joystick : value) {
734 if (auto* const controller = joystick->GetSDLGameController()) {
735 std::string name =
736 fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort());
737 devices.emplace_back(Common::ParamPackage{
738 {"class", "sdl"},
739 {"display", std::move(name)},
740 {"guid", joystick->GetGUID()},
741 {"port", std::to_string(joystick->GetPort())},
742 });
743 } else if (auto* const joy = joystick->GetSDLJoystick()) {
744 std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort());
745 devices.emplace_back(Common::ParamPackage{
746 {"class", "sdl"},
747 {"display", std::move(name)},
748 {"guid", joystick->GetGUID()},
749 {"port", std::to_string(joystick->GetPort())},
750 });
751 }
752 }
753 }
754 return devices;
755}
756
757namespace {
758Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
759 float value = 0.1f) {
760 Common::ParamPackage params({{"engine", "sdl"}});
761 params.Set("port", port);
762 params.Set("guid", std::move(guid));
763 params.Set("axis", axis);
764 if (value > 0) {
765 params.Set("direction", "+");
766 params.Set("threshold", "0.5");
767 } else {
768 params.Set("direction", "-");
769 params.Set("threshold", "-0.5");
770 }
771 return params;
772}
773
774Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) {
775 Common::ParamPackage params({{"engine", "sdl"}});
776 params.Set("port", port);
777 params.Set("guid", std::move(guid));
778 params.Set("button", button);
779 return params;
780}
781
782Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) {
524 Common::ParamPackage params({{"engine", "sdl"}}); 783 Common::ParamPackage params({{"engine", "sdl"}});
525 784
785 params.Set("port", port);
786 params.Set("guid", std::move(guid));
787 params.Set("hat", hat);
788 switch (value) {
789 case SDL_HAT_UP:
790 params.Set("direction", "up");
791 break;
792 case SDL_HAT_DOWN:
793 params.Set("direction", "down");
794 break;
795 case SDL_HAT_LEFT:
796 params.Set("direction", "left");
797 break;
798 case SDL_HAT_RIGHT:
799 params.Set("direction", "right");
800 break;
801 default:
802 return {};
803 }
804 return params;
805}
806
807Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
808 switch (event.type) {
809 case SDL_JOYAXISMOTION: {
810 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
811 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
812 static_cast<s32>(event.jaxis.axis),
813 event.jaxis.value);
814 }
815 break;
816 }
817 case SDL_JOYBUTTONUP: {
818 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
819 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
820 static_cast<s32>(event.jbutton.button));
821 }
822 break;
823 }
824 case SDL_JOYHATMOTION: {
825 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
826 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
827 static_cast<s32>(event.jhat.hat),
828 static_cast<s32>(event.jhat.value));
829 }
830 break;
831 }
832 }
833 return {};
834}
835
836Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
526 switch (event.type) { 837 switch (event.type) {
527 case SDL_JOYAXISMOTION: { 838 case SDL_JOYAXISMOTION: {
528 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); 839 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
529 params.Set("port", joystick->GetPort()); 840 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
530 params.Set("guid", joystick->GetGUID()); 841 static_cast<s32>(event.jaxis.axis),
531 params.Set("axis", event.jaxis.axis); 842 event.jaxis.value);
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 } 843 }
539 break; 844 break;
540 } 845 }
541 case SDL_JOYBUTTONUP: { 846 case SDL_JOYBUTTONUP: {
542 const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); 847 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
543 params.Set("port", joystick->GetPort()); 848 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
544 params.Set("guid", joystick->GetGUID()); 849 static_cast<s32>(event.jbutton.button));
545 params.Set("button", event.jbutton.button); 850 }
546 break; 851 break;
547 } 852 }
548 case SDL_JOYHATMOTION: { 853 case SDL_JOYHATMOTION: {
549 const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); 854 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
550 params.Set("port", joystick->GetPort()); 855 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
551 params.Set("guid", joystick->GetGUID()); 856 static_cast<s32>(event.jhat.hat),
552 params.Set("hat", event.jhat.hat); 857 static_cast<s32>(event.jhat.value));
553 switch (event.jhat.value) {
554 case SDL_HAT_UP:
555 params.Set("direction", "up");
556 break;
557 case SDL_HAT_DOWN:
558 params.Set("direction", "down");
559 break;
560 case SDL_HAT_LEFT:
561 params.Set("direction", "left");
562 break;
563 case SDL_HAT_RIGHT:
564 params.Set("direction", "right");
565 break;
566 default:
567 return {};
568 } 858 }
569 break; 859 break;
570 } 860 }
571 } 861 }
862 return {};
863}
864
865Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
866 const SDL_GameControllerButtonBind& binding) {
867 switch (binding.bindType) {
868 case SDL_CONTROLLER_BINDTYPE_NONE:
869 break;
870 case SDL_CONTROLLER_BINDTYPE_AXIS:
871 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
872 case SDL_CONTROLLER_BINDTYPE_BUTTON:
873 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
874 case SDL_CONTROLLER_BINDTYPE_HAT:
875 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
876 binding.value.hat.hat_mask);
877 }
878 return {};
879}
880
881Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
882 int axis_y) {
883 Common::ParamPackage params;
884 params.Set("engine", "sdl");
885 params.Set("port", port);
886 params.Set("guid", guid);
887 params.Set("axis_x", axis_x);
888 params.Set("axis_y", axis_y);
572 return params; 889 return params;
573} 890}
891} // Anonymous namespace
574 892
575namespace Polling { 893ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
894 if (!params.Has("guid") || !params.Has("port")) {
895 return {};
896 }
897 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
898 auto* controller = joystick->GetSDLGameController();
899 if (controller == nullptr) {
900 return {};
901 }
576 902
903 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
904 // We will add those afterwards
905 // This list also excludes Screenshot since theres not really a mapping for that
906 using ButtonBindings =
907 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
908 static constexpr ButtonBindings switch_to_sdl_button{{
909 {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
910 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
911 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
912 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
913 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
914 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
915 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
916 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
917 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
918 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
919 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
920 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
921 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
922 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
923 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
924 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
925 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
926 }};
927
928 // Add the missing bindings for ZL/ZR
929 using ZBindings =
930 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
931 static constexpr ZBindings switch_to_sdl_axis{{
932 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
933 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
934 }};
935
936 ButtonMapping mapping;
937 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
938
939 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
940 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
941 mapping.insert_or_assign(
942 switch_button,
943 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
944 }
945 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
946 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
947 mapping.insert_or_assign(
948 switch_button,
949 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
950 }
951
952 return mapping;
953}
954
955AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
956 if (!params.Has("guid") || !params.Has("port")) {
957 return {};
958 }
959 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
960 auto* controller = joystick->GetSDLGameController();
961 if (controller == nullptr) {
962 return {};
963 }
964
965 AnalogMapping mapping = {};
966 const auto& binding_left_x =
967 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
968 const auto& binding_left_y =
969 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
970 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
971 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
972 binding_left_x.value.axis,
973 binding_left_y.value.axis));
974 const auto& binding_right_x =
975 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
976 const auto& binding_right_y =
977 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
978 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
979 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
980 binding_right_x.value.axis,
981 binding_right_y.value.axis));
982 return mapping;
983}
984
985namespace Polling {
577class SDLPoller : public InputCommon::Polling::DevicePoller { 986class SDLPoller : public InputCommon::Polling::DevicePoller {
578public: 987public:
579 explicit SDLPoller(SDLState& state_) : state(state_) {} 988 explicit SDLPoller(SDLState& state_) : state(state_) {}
580 989
581 void Start() override { 990 void Start([[maybe_unused]] const std::string& device_id) override {
582 state.event_queue.Clear(); 991 state.event_queue.Clear();
583 state.polling = true; 992 state.polling = true;
584 } 993 }
@@ -598,70 +1007,116 @@ public:
598 Common::ParamPackage GetNextInput() override { 1007 Common::ParamPackage GetNextInput() override {
599 SDL_Event event; 1008 SDL_Event event;
600 while (state.event_queue.Pop(event)) { 1009 while (state.event_queue.Pop(event)) {
601 switch (event.type) { 1010 const auto package = FromEvent(event);
602 case SDL_JOYAXISMOTION: 1011 if (package) {
603 if (std::abs(event.jaxis.value / 32767.0) < 0.5) { 1012 return *package;
604 break;
605 }
606 case SDL_JOYBUTTONUP:
607 case SDL_JOYHATMOTION:
608 return SDLEventToButtonParamPackage(state, event);
609 } 1013 }
610 } 1014 }
611 return {}; 1015 return {};
612 } 1016 }
1017 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
1018 switch (event.type) {
1019 case SDL_JOYAXISMOTION:
1020 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1021 break;
1022 }
1023 [[fallthrough]];
1024 case SDL_JOYBUTTONUP:
1025 case SDL_JOYHATMOTION:
1026 return {SDLEventToButtonParamPackage(state, event)};
1027 }
1028 return std::nullopt;
1029 }
613}; 1030};
614 1031
615class SDLAnalogPoller final : public SDLPoller { 1032class SDLMotionPoller final : public SDLPoller {
616public: 1033public:
617 explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} 1034 explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
1035
1036 Common::ParamPackage GetNextInput() override {
1037 SDL_Event event;
1038 while (state.event_queue.Pop(event)) {
1039 const auto package = FromEvent(event);
1040 if (package) {
1041 return *package;
1042 }
1043 }
1044 return {};
1045 }
1046 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
1047 switch (event.type) {
1048 case SDL_JOYAXISMOTION:
1049 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1050 break;
1051 }
1052 [[fallthrough]];
1053 case SDL_JOYBUTTONUP:
1054 case SDL_JOYHATMOTION:
1055 return {SDLEventToMotionParamPackage(state, event)};
1056 }
1057 return std::nullopt;
1058 }
1059};
618 1060
619 void Start() override { 1061/**
620 SDLPoller::Start(); 1062 * Attempts to match the press to a controller joy axis (left/right stick) and if a match
1063 * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
1064 * instead
1065 */
1066class SDLAnalogPreferredPoller final : public SDLPoller {
1067public:
1068 explicit SDLAnalogPreferredPoller(SDLState& state_)
1069 : SDLPoller(state_), button_poller(state_) {}
621 1070
1071 void Start(const std::string& device_id) override {
1072 SDLPoller::Start(device_id);
622 // Reset stored axes 1073 // Reset stored axes
623 analog_x_axis = -1; 1074 analog_x_axis = -1;
624 analog_y_axis = -1; 1075 analog_y_axis = -1;
625 analog_axes_joystick = -1;
626 } 1076 }
627 1077
628 Common::ParamPackage GetNextInput() override { 1078 Common::ParamPackage GetNextInput() override {
629 SDL_Event event; 1079 SDL_Event event;
630 while (state.event_queue.Pop(event)) { 1080 while (state.event_queue.Pop(event)) {
631 if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { 1081 // Filter out axis events that are below a threshold
1082 if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
632 continue; 1083 continue;
633 } 1084 }
634 // An analog device needs two axes, so we need to store the axis for later and wait for 1085 if (event.type == SDL_JOYAXISMOTION) {
635 // a second SDL event. The axes also must be from the same joystick. 1086 const auto axis = event.jaxis.axis;
636 const int axis = event.jaxis.axis; 1087 // In order to return a complete analog param, we need inputs for both axes.
637 if (analog_x_axis == -1) { 1088 // First we take the x-axis (horizontal) input, then the y-axis (vertical) input.
638 analog_x_axis = axis; 1089 if (analog_x_axis == -1) {
639 analog_axes_joystick = event.jaxis.which; 1090 analog_x_axis = axis;
640 } else if (analog_y_axis == -1 && analog_x_axis != axis && 1091 } else if (analog_y_axis == -1 && analog_x_axis != axis) {
641 analog_axes_joystick == event.jaxis.which) { 1092 analog_y_axis = axis;
642 analog_y_axis = axis; 1093 }
1094 } else {
1095 // If the press wasn't accepted as a joy axis, check for a button press
1096 auto button_press = button_poller.FromEvent(event);
1097 if (button_press) {
1098 return *button_press;
1099 }
643 } 1100 }
644 } 1101 }
645 Common::ParamPackage params; 1102
646 if (analog_x_axis != -1 && analog_y_axis != -1) { 1103 if (analog_x_axis != -1 && analog_y_axis != -1) {
647 const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); 1104 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
648 params.Set("engine", "sdl"); 1105 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
649 params.Set("port", joystick->GetPort()); 1106 analog_x_axis, analog_y_axis);
650 params.Set("guid", joystick->GetGUID()); 1107 analog_x_axis = -1;
651 params.Set("axis_x", analog_x_axis); 1108 analog_y_axis = -1;
652 params.Set("axis_y", analog_y_axis); 1109 return params;
653 analog_x_axis = -1; 1110 }
654 analog_y_axis = -1;
655 analog_axes_joystick = -1;
656 return params;
657 } 1111 }
658 return params; 1112
1113 return {};
659 } 1114 }
660 1115
661private: 1116private:
662 int analog_x_axis = -1; 1117 int analog_x_axis = -1;
663 int analog_y_axis = -1; 1118 int analog_y_axis = -1;
664 SDL_JoystickID analog_axes_joystick = -1; 1119 SDLButtonPoller button_poller;
665}; 1120};
666} // namespace Polling 1121} // namespace Polling
667 1122
@@ -669,12 +1124,15 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
669 Pollers pollers; 1124 Pollers pollers;
670 1125
671 switch (type) { 1126 switch (type) {
672 case InputCommon::Polling::DeviceType::Analog: 1127 case InputCommon::Polling::DeviceType::AnalogPreferred:
673 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); 1128 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
674 break; 1129 break;
675 case InputCommon::Polling::DeviceType::Button: 1130 case InputCommon::Polling::DeviceType::Button:
676 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); 1131 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
677 break; 1132 break;
1133 case InputCommon::Polling::DeviceType::Motion:
1134 pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
1135 break;
678 } 1136 }
679 1137
680 return pollers; 1138 return pollers;
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
index 606a32c5b..08044b00d 100644
--- a/src/input_common/sdl/sdl_impl.h
+++ b/src/input_common/sdl/sdl_impl.h
@@ -21,6 +21,8 @@ namespace InputCommon::SDL {
21 21
22class SDLAnalogFactory; 22class SDLAnalogFactory;
23class SDLButtonFactory; 23class SDLButtonFactory;
24class SDLMotionFactory;
25class SDLVibrationFactory;
24class SDLJoystick; 26class SDLJoystick;
25 27
26class SDLState : public State { 28class SDLState : public State {
@@ -50,6 +52,11 @@ public:
50 std::atomic<bool> polling = false; 52 std::atomic<bool> polling = false;
51 Common::SPSCQueue<SDL_Event> event_queue; 53 Common::SPSCQueue<SDL_Event> event_queue;
52 54
55 std::vector<Common::ParamPackage> GetInputDevices() override;
56
57 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
58 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
59
53private: 60private:
54 void InitJoystick(int joystick_index); 61 void InitJoystick(int joystick_index);
55 void CloseJoystick(SDL_Joystick* sdl_joystick); 62 void CloseJoystick(SDL_Joystick* sdl_joystick);
@@ -57,12 +64,17 @@ private:
57 /// Needs to be called before SDL_QuitSubSystem. 64 /// Needs to be called before SDL_QuitSubSystem.
58 void CloseJoysticks(); 65 void CloseJoysticks();
59 66
67 // Set to true if SDL supports game controller subsystem
68 bool has_gamecontroller = false;
69
60 /// Map of GUID of a list of corresponding virtual Joysticks 70 /// Map of GUID of a list of corresponding virtual Joysticks
61 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; 71 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
62 std::mutex joystick_map_mutex; 72 std::mutex joystick_map_mutex;
63 73
64 std::shared_ptr<SDLButtonFactory> button_factory; 74 std::shared_ptr<SDLButtonFactory> button_factory;
65 std::shared_ptr<SDLAnalogFactory> analog_factory; 75 std::shared_ptr<SDLAnalogFactory> analog_factory;
76 std::shared_ptr<SDLVibrationFactory> vibration_factory;
77 std::shared_ptr<SDLMotionFactory> motion_factory;
66 78
67 bool start_thread = false; 79 bool start_thread = false;
68 std::atomic<bool> initialized = false; 80 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..557e7a9a0
--- /dev/null
+++ b/src/input_common/settings.cpp
@@ -0,0 +1,47 @@
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 NativeAnalog {
18const std::array<const char*, NumAnalogs> mapping = {{
19 "lstick",
20 "rstick",
21}};
22}
23
24namespace NativeVibration {
25const std::array<const char*, NumVibrations> mapping = {{
26 "left_vibration_device",
27 "right_vibration_device",
28}};
29}
30
31namespace NativeMotion {
32const std::array<const char*, NumMotions> mapping = {{
33 "motionleft",
34 "motionright",
35}};
36}
37
38namespace NativeMouseButton {
39const std::array<const char*, NumMouseButtons> mapping = {{
40 "left",
41 "right",
42 "middle",
43 "forward",
44 "back",
45}};
46}
47} // namespace Settings
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
new file mode 100644
index 000000000..75486554b
--- /dev/null
+++ b/src/input_common/settings.h
@@ -0,0 +1,371 @@
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 NativeVibration {
70enum Values : int {
71 LeftVibrationDevice,
72 RightVibrationDevice,
73
74 NumVibrations,
75};
76
77constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice;
78constexpr int VIBRATION_HID_END = NumVibrations;
79constexpr int NUM_VIBRATIONS_HID = NumVibrations;
80
81extern const std::array<const char*, NumVibrations> mapping;
82}; // namespace NativeVibration
83
84namespace NativeMotion {
85enum Values : int {
86 MotionLeft,
87 MotionRight,
88
89 NumMotions,
90};
91
92constexpr int MOTION_HID_BEGIN = MotionLeft;
93constexpr int MOTION_HID_END = NumMotions;
94constexpr int NUM_MOTIONS_HID = NumMotions;
95
96extern const std::array<const char*, NumMotions> mapping;
97} // namespace NativeMotion
98
99namespace NativeMouseButton {
100enum Values {
101 Left,
102 Right,
103 Middle,
104 Forward,
105 Back,
106
107 NumMouseButtons,
108};
109
110constexpr int MOUSE_HID_BEGIN = Left;
111constexpr int MOUSE_HID_END = NumMouseButtons;
112constexpr int NUM_MOUSE_HID = NumMouseButtons;
113
114extern const std::array<const char*, NumMouseButtons> mapping;
115} // namespace NativeMouseButton
116
117namespace NativeKeyboard {
118enum Keys {
119 None,
120 Error,
121
122 A = 4,
123 B,
124 C,
125 D,
126 E,
127 F,
128 G,
129 H,
130 I,
131 J,
132 K,
133 L,
134 M,
135 N,
136 O,
137 P,
138 Q,
139 R,
140 S,
141 T,
142 U,
143 V,
144 W,
145 X,
146 Y,
147 Z,
148 N1,
149 N2,
150 N3,
151 N4,
152 N5,
153 N6,
154 N7,
155 N8,
156 N9,
157 N0,
158 Enter,
159 Escape,
160 Backspace,
161 Tab,
162 Space,
163 Minus,
164 Equal,
165 LeftBrace,
166 RightBrace,
167 Backslash,
168 Tilde,
169 Semicolon,
170 Apostrophe,
171 Grave,
172 Comma,
173 Dot,
174 Slash,
175 CapsLockKey,
176
177 F1,
178 F2,
179 F3,
180 F4,
181 F5,
182 F6,
183 F7,
184 F8,
185 F9,
186 F10,
187 F11,
188 F12,
189
190 SystemRequest,
191 ScrollLockKey,
192 Pause,
193 Insert,
194 Home,
195 PageUp,
196 Delete,
197 End,
198 PageDown,
199 Right,
200 Left,
201 Down,
202 Up,
203
204 NumLockKey,
205 KPSlash,
206 KPAsterisk,
207 KPMinus,
208 KPPlus,
209 KPEnter,
210 KP1,
211 KP2,
212 KP3,
213 KP4,
214 KP5,
215 KP6,
216 KP7,
217 KP8,
218 KP9,
219 KP0,
220 KPDot,
221
222 Key102,
223 Compose,
224 Power,
225 KPEqual,
226
227 F13,
228 F14,
229 F15,
230 F16,
231 F17,
232 F18,
233 F19,
234 F20,
235 F21,
236 F22,
237 F23,
238 F24,
239
240 Open,
241 Help,
242 Properties,
243 Front,
244 Stop,
245 Repeat,
246 Undo,
247 Cut,
248 Copy,
249 Paste,
250 Find,
251 Mute,
252 VolumeUp,
253 VolumeDown,
254 CapsLockActive,
255 NumLockActive,
256 ScrollLockActive,
257 KPComma,
258
259 KPLeftParenthesis,
260 KPRightParenthesis,
261
262 LeftControlKey = 0xE0,
263 LeftShiftKey,
264 LeftAltKey,
265 LeftMetaKey,
266 RightControlKey,
267 RightShiftKey,
268 RightAltKey,
269 RightMetaKey,
270
271 MediaPlayPause,
272 MediaStopCD,
273 MediaPrevious,
274 MediaNext,
275 MediaEject,
276 MediaVolumeUp,
277 MediaVolumeDown,
278 MediaMute,
279 MediaWebsite,
280 MediaBack,
281 MediaForward,
282 MediaStop,
283 MediaFind,
284 MediaScrollUp,
285 MediaScrollDown,
286 MediaEdit,
287 MediaSleep,
288 MediaCoffee,
289 MediaRefresh,
290 MediaCalculator,
291
292 NumKeyboardKeys,
293};
294
295static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys.");
296
297enum Modifiers {
298 LeftControl,
299 LeftShift,
300 LeftAlt,
301 LeftMeta,
302 RightControl,
303 RightShift,
304 RightAlt,
305 RightMeta,
306 CapsLock,
307 ScrollLock,
308 NumLock,
309
310 NumKeyboardMods,
311};
312
313constexpr int KEYBOARD_KEYS_HID_BEGIN = None;
314constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys;
315constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys;
316
317constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl;
318constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods;
319constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
320
321} // namespace NativeKeyboard
322
323using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
324using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
325using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
326using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
327
328using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
329using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
330using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
331
332constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
333constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
334constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
335constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
336
337enum class ControllerType {
338 ProController,
339 DualJoyconDetached,
340 LeftJoycon,
341 RightJoycon,
342 Handheld,
343};
344
345struct PlayerInput {
346 bool connected;
347 ControllerType controller_type;
348 ButtonsRaw buttons;
349 AnalogsRaw analogs;
350 VibrationsRaw vibrations;
351 MotionsRaw motions;
352
353 bool vibration_enabled;
354 int vibration_strength;
355
356 u32 body_color_left;
357 u32 body_color_right;
358 u32 button_color_left;
359 u32 button_color_right;
360};
361
362struct TouchscreenInput {
363 bool enabled;
364 std::string device;
365
366 u32 finger;
367 u32 diameter_x;
368 u32 diameter_y;
369 u32 rotation_angle;
370};
371} // 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..a07124a86
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,51 @@
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 const auto button_index =
15 static_cast<std::size_t>(Settings::values.touch_from_button_map_index);
16 const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons;
17
18 for (const auto& config_entry : buttons) {
19 const Common::ParamPackage package{config_entry};
20 map.emplace_back(
21 Input::CreateDevice<Input::ButtonDevice>(config_entry),
22 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
23 std::clamp(package.Get("y", 0), 0,
24 static_cast<int>(Layout::ScreenUndocked::Height)));
25 }
26 }
27
28 std::tuple<float, float, bool> GetStatus() const override {
29 for (const auto& m : map) {
30 const bool state = std::get<0>(m)->GetStatus();
31 if (state) {
32 const float x = static_cast<float>(std::get<1>(m)) /
33 static_cast<int>(Layout::ScreenUndocked::Width);
34 const float y = static_cast<float>(std::get<2>(m)) /
35 static_cast<int>(Layout::ScreenUndocked::Height);
36 return {x, y, true};
37 }
38 }
39 return {};
40 }
41
42private:
43 // A vector of the mapped button, its x and its y-coordinate
44 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
45};
46
47std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) {
48 return std::make_unique<TouchFromButtonDevice>();
49}
50
51} // 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 da5227058..c0bb90048 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
@@ -28,11 +26,11 @@ class Socket {
28public: 26public:
29 using clock = std::chrono::system_clock; 27 using clock = std::chrono::system_clock;
30 28
31 explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, 29 explicit Socket(const std::string& host, u16 port, std::size_t pad_index_, u32 client_id_,
32 SocketCallback callback) 30 SocketCallback callback_)
33 : callback(std::move(callback)), timer(io_service), 31 : callback(std::move(callback_)), timer(io_service),
34 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id), 32 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(client_id_),
35 pad_index(pad_index) { 33 pad_index(pad_index_) {
36 boost::system::error_code ec{}; 34 boost::system::error_code ec{};
37 auto ipv4 = boost::asio::ip::make_address_v4(host, ec); 35 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
38 if (ec.value() != boost::system::errc::success) { 36 if (ec.value() != boost::system::errc::success) {
@@ -65,7 +63,7 @@ public:
65 } 63 }
66 64
67private: 65private:
68 void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { 66 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
69 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { 67 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
70 switch (*type) { 68 switch (*type) {
71 case Type::Version: { 69 case Type::Version: {
@@ -92,16 +90,20 @@ private:
92 StartReceive(); 90 StartReceive();
93 } 91 }
94 92
95 void HandleSend(const boost::system::error_code& error) { 93 void HandleSend(const boost::system::error_code&) {
96 boost::system::error_code _ignored{}; 94 boost::system::error_code _ignored{};
97 // Send a request for getting port info for the pad 95 // Send a request for getting port info for the pad
98 Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; 96 const Request::PortInfo port_info{1, {static_cast<u8>(pad_index), 0, 0, 0}};
99 const auto port_message = Request::Create(port_info, client_id); 97 const auto port_message = Request::Create(port_info, client_id);
100 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); 98 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
101 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored); 99 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
102 100
103 // Send a request for getting pad data for the pad 101 // Send a request for getting pad data for the pad
104 Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; 102 const Request::PadData pad_data{
103 Request::PadData::Flags::Id,
104 static_cast<u8>(pad_index),
105 EMPTY_MAC_ADDRESS,
106 };
105 const auto pad_message = Request::Create(pad_data, client_id); 107 const auto pad_message = Request::Create(pad_data, client_id);
106 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); 108 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
107 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored); 109 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
@@ -114,7 +116,7 @@ private:
114 udp::socket socket; 116 udp::socket socket;
115 117
116 u32 client_id{}; 118 u32 client_id{};
117 u8 pad_index{}; 119 std::size_t pad_index{};
118 120
119 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); 121 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
120 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); 122 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
@@ -132,49 +134,100 @@ static void SocketLoop(Socket* socket) {
132 socket->Loop(); 134 socket->Loop();
133} 135}
134 136
135Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, 137Client::Client() {
136 u8 pad_index, u32 client_id) 138 LOG_INFO(Input, "Udp Initialization started");
137 : status(std::move(status)) { 139 for (std::size_t client = 0; client < clients.size(); client++) {
138 StartCommunication(host, port, pad_index, client_id); 140 const auto pad = client % 4;
141 StartCommunication(client, Settings::values.udp_input_address,
142 Settings::values.udp_input_port, pad, 24872);
143 // Set motion parameters
144 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
145 // Real HW values are unknown, 0.0001 is an approximate to Standard
146 clients[client].motion.SetGyroThreshold(0.0001f);
147 }
139} 148}
140 149
141Client::~Client() { 150Client::~Client() {
142 socket->Stop(); 151 Reset();
143 thread.join(); 152}
153
154std::vector<Common::ParamPackage> Client::GetInputDevices() const {
155 std::vector<Common::ParamPackage> devices;
156 for (std::size_t client = 0; client < clients.size(); client++) {
157 if (!DeviceConnected(client)) {
158 continue;
159 }
160 std::string name = fmt::format("UDP Controller {}", client);
161 devices.emplace_back(Common::ParamPackage{
162 {"class", "cemuhookudp"},
163 {"display", std::move(name)},
164 {"port", std::to_string(client)},
165 });
166 }
167 return devices;
144} 168}
145 169
146void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 170bool Client::DeviceConnected(std::size_t pad) const {
147 socket->Stop(); 171 // Use last timestamp to detect if the socket has stopped sending data
148 thread.join(); 172 const auto now = std::chrono::system_clock::now();
149 StartCommunication(host, port, pad_index, client_id); 173 const auto time_difference = static_cast<u64>(
174 std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update)
175 .count());
176 return time_difference < 1000 && clients[pad].active == 1;
150} 177}
151 178
152void Client::OnVersion(Response::Version data) { 179void Client::ReloadUDPClient() {
180 for (std::size_t client = 0; client < clients.size(); client++) {
181 ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client);
182 }
183}
184void Client::ReloadSocket(const std::string& host, u16 port, std::size_t pad_index, u32 client_id) {
185 // client number must be determined from host / port and pad index
186 const std::size_t client = pad_index;
187 clients[client].socket->Stop();
188 clients[client].thread.join();
189 StartCommunication(client, host, port, pad_index, client_id);
190}
191
192void Client::OnVersion([[maybe_unused]] Response::Version data) {
153 LOG_TRACE(Input, "Version packet received: {}", data.version); 193 LOG_TRACE(Input, "Version packet received: {}", data.version);
154} 194}
155 195
156void Client::OnPortInfo(Response::PortInfo data) { 196void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
157 LOG_TRACE(Input, "PortInfo packet received: {}", data.model); 197 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
158} 198}
159 199
160void Client::OnPadData(Response::PadData data) { 200void Client::OnPadData(Response::PadData data) {
201 // Client number must be determined from host / port and pad index
202 const std::size_t client = data.info.id;
161 LOG_TRACE(Input, "PadData packet received"); 203 LOG_TRACE(Input, "PadData packet received");
162 if (data.packet_counter <= packet_sequence) { 204 if (data.packet_counter == clients[client].packet_sequence) {
163 LOG_WARNING( 205 LOG_WARNING(
164 Input, 206 Input,
165 "PadData packet dropped because its stale info. Current count: {} Packet count: {}", 207 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
166 packet_sequence, data.packet_counter); 208 clients[client].packet_sequence, data.packet_counter);
167 return; 209 return;
168 } 210 }
169 packet_sequence = data.packet_counter; 211 clients[client].active = data.info.is_pad_active;
170 // TODO: Check how the Switch handles motions and how the CemuhookUDP motion 212 clients[client].packet_sequence = data.packet_counter;
171 // directions correspond to the ones of the Switch 213 const auto now = std::chrono::system_clock::now();
172 Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); 214 const auto time_difference =
173 Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); 215 static_cast<u64>(std::chrono::duration_cast<std::chrono::microseconds>(
174 { 216 now - clients[client].last_motion_update)
175 std::lock_guard guard(status->update_mutex); 217 .count());
218 clients[client].last_motion_update = now;
219 const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
220 clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
221 // Gyroscope values are not it the correct scale from better joy.
222 // Dividing by 312 allows us to make one full turn = 1 turn
223 // This must be a configurable valued called sensitivity
224 clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f);
225 clients[client].motion.UpdateRotation(time_difference);
226 clients[client].motion.UpdateOrientation(time_difference);
176 227
177 status->motion_status = {accel, gyro}; 228 {
229 std::lock_guard guard(clients[client].status.update_mutex);
230 clients[client].status.motion_status = clients[client].motion.GetMotion();
178 231
179 // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates 232 // 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. 233 // between a simple "tap" and a hard press that causes the touch screen to click.
@@ -183,41 +236,115 @@ void Client::OnPadData(Response::PadData data) {
183 float x = 0; 236 float x = 0;
184 float y = 0; 237 float y = 0;
185 238
186 if (is_active && status->touch_calibration) { 239 if (is_active && clients[client].status.touch_calibration) {
187 const u16 min_x = status->touch_calibration->min_x; 240 const u16 min_x = clients[client].status.touch_calibration->min_x;
188 const u16 max_x = status->touch_calibration->max_x; 241 const u16 max_x = clients[client].status.touch_calibration->max_x;
189 const u16 min_y = status->touch_calibration->min_y; 242 const u16 min_y = clients[client].status.touch_calibration->min_y;
190 const u16 max_y = status->touch_calibration->max_y; 243 const u16 max_y = clients[client].status.touch_calibration->max_y;
191 244
192 x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / 245 x = static_cast<float>(std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) -
246 min_x) /
193 static_cast<float>(max_x - min_x); 247 static_cast<float>(max_x - min_x);
194 y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) / 248 y = static_cast<float>(std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) -
249 min_y) /
195 static_cast<float>(max_y - min_y); 250 static_cast<float>(max_y - min_y);
196 } 251 }
197 252
198 status->touch_status = {x, y, is_active}; 253 clients[client].status.touch_status = {x, y, is_active};
254
255 if (configuring) {
256 const Common::Vec3f gyroscope = clients[client].motion.GetGyroscope();
257 const Common::Vec3f accelerometer = clients[client].motion.GetAcceleration();
258 UpdateYuzuSettings(client, accelerometer, gyroscope, is_active);
259 }
199 } 260 }
200} 261}
201 262
202void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { 263void Client::StartCommunication(std::size_t client, const std::string& host, u16 port,
264 std::size_t pad_index, u32 client_id) {
203 SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, 265 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
204 [this](Response::PortInfo info) { OnPortInfo(info); }, 266 [this](Response::PortInfo info) { OnPortInfo(info); },
205 [this](Response::PadData data) { OnPadData(data); }}; 267 [this](Response::PadData data) { OnPadData(data); }};
206 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); 268 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); 269 clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
208 thread = std::thread{SocketLoop, this->socket.get()}; 270 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
271}
272
273void Client::Reset() {
274 for (auto& client : clients) {
275 client.socket->Stop();
276 client.thread.join();
277 }
278}
279
280void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
281 const Common::Vec3<float>& gyro, bool touch) {
282 if (gyro.Length() > 0.2f) {
283 LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {}), touch={}",
284 client, gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2], touch);
285 }
286 UDPPadStatus pad;
287 if (touch) {
288 pad.touch = PadTouch::Click;
289 pad_queue[client].Push(pad);
290 }
291 for (size_t i = 0; i < 3; ++i) {
292 if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
293 pad.motion = static_cast<PadMotion>(i);
294 pad.motion_value = gyro[i];
295 pad_queue[client].Push(pad);
296 }
297 if (acc[i] > 1.75f || acc[i] < -1.75f) {
298 pad.motion = static_cast<PadMotion>(i + 3);
299 pad.motion_value = acc[i];
300 pad_queue[client].Push(pad);
301 }
302 }
303}
304
305void Client::BeginConfiguration() {
306 for (auto& pq : pad_queue) {
307 pq.Clear();
308 }
309 configuring = true;
310}
311
312void Client::EndConfiguration() {
313 for (auto& pq : pad_queue) {
314 pq.Clear();
315 }
316 configuring = false;
209} 317}
210 318
211void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, 319DeviceStatus& Client::GetPadState(std::size_t pad) {
212 std::function<void()> success_callback, 320 return clients[pad].status;
213 std::function<void()> failure_callback) { 321}
322
323const DeviceStatus& Client::GetPadState(std::size_t pad) const {
324 return clients[pad].status;
325}
326
327std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() {
328 return pad_queue;
329}
330
331const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const {
332 return pad_queue;
333}
334
335void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
336 const std::function<void()>& success_callback,
337 const std::function<void()>& failure_callback) {
214 std::thread([=] { 338 std::thread([=] {
215 Common::Event success_event; 339 Common::Event success_event;
216 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, 340 SocketCallback callback{
217 [&](Response::PadData data) { success_event.Set(); }}; 341 .version = [](Response::Version) {},
342 .port_info = [](Response::PortInfo) {},
343 .pad_data = [&](Response::PadData) { success_event.Set(); },
344 };
218 Socket socket{host, port, pad_index, client_id, std::move(callback)}; 345 Socket socket{host, port, pad_index, client_id, std::move(callback)};
219 std::thread worker_thread{SocketLoop, &socket}; 346 std::thread worker_thread{SocketLoop, &socket};
220 bool result = success_event.WaitFor(std::chrono::seconds(8)); 347 const bool result = success_event.WaitFor(std::chrono::seconds(5));
221 socket.Stop(); 348 socket.Stop();
222 worker_thread.join(); 349 worker_thread.join();
223 if (result) { 350 if (result) {
@@ -225,16 +352,15 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie
225 } else { 352 } else {
226 failure_callback(); 353 failure_callback();
227 } 354 }
228 }) 355 }).detach();
229 .detach();
230} 356}
231 357
232CalibrationConfigurationJob::CalibrationConfigurationJob( 358CalibrationConfigurationJob::CalibrationConfigurationJob(
233 const std::string& host, u16 port, u8 pad_index, u32 client_id, 359 const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
234 std::function<void(Status)> status_callback, 360 std::function<void(Status)> status_callback,
235 std::function<void(u16, u16, u16, u16)> data_callback) { 361 std::function<void(u16, u16, u16, u16)> data_callback) {
236 362
237 std::thread([=] { 363 std::thread([=, this] {
238 constexpr u16 CALIBRATION_THRESHOLD = 100; 364 constexpr u16 CALIBRATION_THRESHOLD = 100;
239 365
240 u16 min_x{UINT16_MAX}; 366 u16 min_x{UINT16_MAX};
@@ -243,14 +369,14 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
243 u16 max_y{}; 369 u16 max_y{};
244 370
245 Status current_status{Status::Initialized}; 371 Status current_status{Status::Initialized};
246 SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, 372 SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
247 [&](Response::PadData data) { 373 [&](Response::PadData data) {
248 if (current_status == Status::Initialized) { 374 if (current_status == Status::Initialized) {
249 // Receiving data means the communication is ready now 375 // Receiving data means the communication is ready now
250 current_status = Status::Ready; 376 current_status = Status::Ready;
251 status_callback(current_status); 377 status_callback(current_status);
252 } 378 }
253 if (!data.touch_1.is_active) { 379 if (data.touch_1.is_active == 0) {
254 return; 380 return;
255 } 381 }
256 LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, 382 LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
@@ -280,8 +406,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
280 complete_event.Wait(); 406 complete_event.Wait();
281 socket.Stop(); 407 socket.Stop();
282 worker_thread.join(); 408 worker_thread.join();
283 }) 409 }).detach();
284 .detach();
285} 410}
286 411
287CalibrationConfigurationJob::~CalibrationConfigurationJob() { 412CalibrationConfigurationJob::~CalibrationConfigurationJob() {
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index b8c654755..747e0c0a2 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();
51 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, 78
52 u32 client_id = 24872); 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();
87 void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760,
88 std::size_t pad_index = 0, u32 client_id = 24872);
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;
53 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 = 0;
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,
117 std::size_t pad_index, 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.
@@ -78,7 +139,7 @@ public:
78 * @param status_callback Callback for job status updates 139 * @param status_callback Callback for job status updates
79 * @param data_callback Called when calibration data is ready 140 * @param data_callback Called when calibration data is ready
80 */ 141 */
81 explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, 142 explicit CalibrationConfigurationJob(const std::string& host, u16 port, std::size_t pad_index,
82 u32 client_id, std::function<void(Status)> status_callback, 143 u32 client_id, std::function<void(Status)> status_callback,
83 std::function<void(u16, u16, u16, u16)> data_callback); 144 std::function<void(u16, u16, u16, u16)> data_callback);
84 ~CalibrationConfigurationJob(); 145 ~CalibrationConfigurationJob();
@@ -88,8 +149,8 @@ private:
88 Common::Event complete_event; 149 Common::Event complete_event;
89}; 150};
90 151
91void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, 152void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
92 std::function<void()> success_callback, 153 const std::function<void()>& success_callback,
93 std::function<void()> failure_callback); 154 const std::function<void()>& failure_callback);
94 155
95} // namespace InputCommon::CemuhookUDP 156} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h
index 3ba4d1fc8..fc1aea4b9 100644
--- a/src/input_common/udp/protocol.h
+++ b/src/input_common/udp/protocol.h
@@ -7,7 +7,16 @@
7#include <array> 7#include <array>
8#include <optional> 8#include <optional>
9#include <type_traits> 9#include <type_traits>
10
11#ifdef _MSC_VER
12#pragma warning(push)
13#pragma warning(disable : 4701)
14#endif
10#include <boost/crc.hpp> 15#include <boost/crc.hpp>
16#ifdef _MSC_VER
17#pragma warning(pop)
18#endif
19
11#include "common/bit_field.h" 20#include "common/bit_field.h"
12#include "common/swap.h" 21#include "common/swap.h"
13 22
@@ -93,7 +102,7 @@ static_assert(std::is_trivially_copyable_v<PadData>,
93 102
94/** 103/**
95 * Creates a message with the proper header data that can be sent to the server. 104 * Creates a message with the proper header data that can be sent to the server.
96 * @param T data Request body to send 105 * @param data Request body to send
97 * @param client_id ID of the udp client (usually not checked on the server) 106 * @param client_id ID of the udp client (usually not checked on the server)
98 */ 107 */
99template <typename T> 108template <typename T>
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index 8c6ef1394..71a76a7aa 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -1,99 +1,142 @@
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 <mutex> 5#include <mutex>
6#include <optional> 6#include <utility>
7#include <tuple> 7#include "common/assert.h"
8 8#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" 9#include "input_common/udp/client.h"
13#include "input_common/udp/udp.h" 10#include "input_common/udp/udp.h"
14 11
15namespace InputCommon::CemuhookUDP { 12namespace InputCommon {
16 13
17class UDPTouchDevice final : public Input::TouchDevice { 14class UDPMotion final : public Input::MotionDevice {
18public: 15public:
19 explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 16 explicit UDPMotion(std::string ip_, int port_, u32 pad_, CemuhookUDP::Client* client_)
20 std::tuple<float, float, bool> GetStatus() const override { 17 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
21 std::lock_guard guard(status->update_mutex); 18
22 return status->touch_status; 19 Input::MotionStatus GetStatus() const override {
20 return client->GetPadState(pad).motion_status;
23 } 21 }
24 22
25private: 23private:
26 std::shared_ptr<DeviceStatus> status; 24 const std::string ip;
25 const int port;
26 const u32 pad;
27 CemuhookUDP::Client* client;
28 mutable std::mutex mutex;
27}; 29};
28 30
29class UDPMotionDevice final : public Input::MotionDevice { 31/// A motion device factory that creates motion devices from JC Adapter
30public: 32UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
31 explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 33 : client(std::move(client_)) {}
32 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { 34
33 std::lock_guard guard(status->update_mutex); 35/**
34 return status->motion_status; 36 * Creates motion device
35 } 37 * @param params contains parameters for creating the device:
38 * - "port": the nth jcpad on the adapter
39 */
40std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
41 auto ip = params.Get("ip", "127.0.0.1");
42 const auto port = params.Get("port", 26760);
43 const auto pad = static_cast<u32>(params.Get("pad_index", 0));
44
45 return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
46}
36 47
37private: 48void UDPMotionFactory::BeginConfiguration() {
38 std::shared_ptr<DeviceStatus> status; 49 polling = true;
39}; 50 client->BeginConfiguration();
51}
40 52
41class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { 53void UDPMotionFactory::EndConfiguration() {
42public: 54 polling = false;
43 explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 55 client->EndConfiguration();
44 56}
45 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { 57
46 { 58Common::ParamPackage UDPMotionFactory::GetNextInput() {
47 std::lock_guard guard(status->update_mutex); 59 Common::ParamPackage params;
48 status->touch_calibration = DeviceStatus::CalibrationData{}; 60 CemuhookUDP::UDPPadStatus pad;
49 // These default values work well for DS4 but probably not other touch inputs 61 auto& queue = client->GetPadQueue();
50 status->touch_calibration->min_x = params.Get("min_x", 100); 62 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
51 status->touch_calibration->min_y = params.Get("min_y", 50); 63 while (queue[pad_number].Pop(pad)) {
52 status->touch_calibration->max_x = params.Get("max_x", 1800); 64 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
53 status->touch_calibration->max_y = params.Get("max_y", 850); 65 continue;
66 }
67 params.Set("engine", "cemuhookudp");
68 params.Set("ip", "127.0.0.1");
69 params.Set("port", 26760);
70 params.Set("pad_index", static_cast<int>(pad_number));
71 params.Set("motion", static_cast<u16>(pad.motion));
72 return params;
54 } 73 }
55 return std::make_unique<UDPTouchDevice>(status);
56 } 74 }
75 return params;
76}
57 77
58private: 78class UDPTouch final : public Input::TouchDevice {
59 std::shared_ptr<DeviceStatus> status;
60};
61
62class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
63public: 79public:
64 explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} 80 explicit UDPTouch(std::string ip_, int port_, u32 pad_, CemuhookUDP::Client* client_)
81 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
65 82
66 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { 83 std::tuple<float, float, bool> GetStatus() const override {
67 return std::make_unique<UDPMotionDevice>(status); 84 return client->GetPadState(pad).touch_status;
68 } 85 }
69 86
70private: 87private:
71 std::shared_ptr<DeviceStatus> status; 88 const std::string ip;
89 const int port;
90 const u32 pad;
91 CemuhookUDP::Client* client;
92 mutable std::mutex mutex;
72}; 93};
73 94
74State::State() { 95/// A motion device factory that creates motion devices from JC Adapter
75 auto status = std::make_shared<DeviceStatus>(); 96UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
76 client = 97 : client(std::move(client_)) {}
77 std::make_unique<Client>(status, Settings::values.udp_input_address, 98
78 Settings::values.udp_input_port, Settings::values.udp_pad_index); 99/**
79 100 * Creates motion device
80 Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", 101 * @param params contains parameters for creating the device:
81 std::make_shared<UDPTouchFactory>(status)); 102 * - "port": the nth jcpad on the adapter
82 Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", 103 */
83 std::make_shared<UDPMotionFactory>(status)); 104std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
105 auto ip = params.Get("ip", "127.0.0.1");
106 const auto port = params.Get("port", 26760);
107 const auto pad = static_cast<u32>(params.Get("pad_index", 0));
108
109 return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
84} 110}
85 111
86State::~State() { 112void UDPTouchFactory::BeginConfiguration() {
87 Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); 113 polling = true;
88 Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); 114 client->BeginConfiguration();
89} 115}
90 116
91void State::ReloadUDPClient() { 117void UDPTouchFactory::EndConfiguration() {
92 client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, 118 polling = false;
93 Settings::values.udp_pad_index); 119 client->EndConfiguration();
94} 120}
95 121
96std::unique_ptr<State> Init() { 122Common::ParamPackage UDPTouchFactory::GetNextInput() {
97 return std::make_unique<State>(); 123 Common::ParamPackage params;
124 CemuhookUDP::UDPPadStatus pad;
125 auto& queue = client->GetPadQueue();
126 for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) {
127 while (queue[pad_number].Pop(pad)) {
128 if (pad.touch == CemuhookUDP::PadTouch::Undefined) {
129 continue;
130 }
131 params.Set("engine", "cemuhookudp");
132 params.Set("ip", "127.0.0.1");
133 params.Set("port", 26760);
134 params.Set("pad_index", static_cast<int>(pad_number));
135 params.Set("touch", static_cast<u16>(pad.touch));
136 return params;
137 }
138 }
139 return params;
98} 140}
99} // namespace InputCommon::CemuhookUDP 141
142} // 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