summaryrefslogtreecommitdiff
path: root/src/input_common/drivers
diff options
context:
space:
mode:
authorGravatar Feng Chen2021-12-18 13:57:14 +0800
committerGravatar GitHub2021-12-18 13:57:14 +0800
commite49184e6069a9d791d2df3c1958f5c4b1187e124 (patch)
treeb776caf722e0be0e680f67b0ad0842628162ef1c /src/input_common/drivers
parentImplement convert legacy to generic (diff)
parentMerge pull request #7570 from ameerj/favorites-expanded (diff)
downloadyuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.gz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.tar.xz
yuzu-e49184e6069a9d791d2df3c1958f5c4b1187e124.zip
Merge branch 'yuzu-emu:master' into convert_legacy
Diffstat (limited to 'src/input_common/drivers')
-rw-r--r--src/input_common/drivers/gc_adapter.cpp527
-rw-r--r--src/input_common/drivers/gc_adapter.h135
-rw-r--r--src/input_common/drivers/keyboard.cpp112
-rw-r--r--src/input_common/drivers/keyboard.h56
-rw-r--r--src/input_common/drivers/mouse.cpp185
-rw-r--r--src/input_common/drivers/mouse.h81
-rw-r--r--src/input_common/drivers/sdl_driver.cpp926
-rw-r--r--src/input_common/drivers/sdl_driver.h117
-rw-r--r--src/input_common/drivers/tas_input.cpp320
-rw-r--r--src/input_common/drivers/tas_input.h201
-rw-r--r--src/input_common/drivers/touch_screen.cpp53
-rw-r--r--src/input_common/drivers/touch_screen.h44
-rw-r--r--src/input_common/drivers/udp_client.cpp591
-rw-r--r--src/input_common/drivers/udp_client.h185
14 files changed, 3533 insertions, 0 deletions
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
new file mode 100644
index 000000000..7ab4540a8
--- /dev/null
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -0,0 +1,527 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <fmt/format.h>
6#include <libusb.h>
7
8#include "common/logging/log.h"
9#include "common/param_package.h"
10#include "common/settings_input.h"
11#include "common/thread.h"
12#include "input_common/drivers/gc_adapter.h"
13
14namespace InputCommon {
15
16class LibUSBContext {
17public:
18 explicit LibUSBContext() {
19 init_result = libusb_init(&ctx);
20 }
21
22 ~LibUSBContext() {
23 libusb_exit(ctx);
24 }
25
26 LibUSBContext& operator=(const LibUSBContext&) = delete;
27 LibUSBContext(const LibUSBContext&) = delete;
28
29 LibUSBContext& operator=(LibUSBContext&&) noexcept = delete;
30 LibUSBContext(LibUSBContext&&) noexcept = delete;
31
32 [[nodiscard]] int InitResult() const noexcept {
33 return init_result;
34 }
35
36 [[nodiscard]] libusb_context* get() noexcept {
37 return ctx;
38 }
39
40private:
41 libusb_context* ctx;
42 int init_result{};
43};
44
45class LibUSBDeviceHandle {
46public:
47 explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept {
48 handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
49 }
50
51 ~LibUSBDeviceHandle() noexcept {
52 if (handle) {
53 libusb_release_interface(handle, 1);
54 libusb_close(handle);
55 }
56 }
57
58 LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete;
59 LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete;
60
61 LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete;
62 LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete;
63
64 [[nodiscard]] libusb_device_handle* get() noexcept {
65 return handle;
66 }
67
68private:
69 libusb_device_handle* handle{};
70};
71
72GCAdapter::GCAdapter(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
73 if (usb_adapter_handle) {
74 return;
75 }
76 LOG_DEBUG(Input, "Initialization started");
77
78 libusb_ctx = std::make_unique<LibUSBContext>();
79 const int init_res = libusb_ctx->InitResult();
80 if (init_res == LIBUSB_SUCCESS) {
81 adapter_scan_thread =
82 std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); });
83 } else {
84 LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
85 }
86}
87
88GCAdapter::~GCAdapter() {
89 Reset();
90}
91
92void GCAdapter::AdapterInputThread(std::stop_token stop_token) {
93 LOG_DEBUG(Input, "Input thread started");
94 Common::SetCurrentThreadName("yuzu:input:GCAdapter");
95 s32 payload_size{};
96 AdapterPayload adapter_payload{};
97
98 adapter_scan_thread = {};
99
100 while (!stop_token.stop_requested()) {
101 libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(),
102 static_cast<s32>(adapter_payload.size()), &payload_size, 16);
103 if (IsPayloadCorrect(adapter_payload, payload_size)) {
104 UpdateControllers(adapter_payload);
105 UpdateVibrations();
106 }
107 std::this_thread::yield();
108 }
109
110 if (restart_scan_thread) {
111 adapter_scan_thread =
112 std::jthread([this](std::stop_token token) { AdapterScanThread(token); });
113 restart_scan_thread = false;
114 }
115}
116
117bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) {
118 if (payload_size != static_cast<s32>(adapter_payload.size()) ||
119 adapter_payload[0] != LIBUSB_DT_HID) {
120 LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size,
121 adapter_payload[0]);
122 if (input_error_counter++ > 20) {
123 LOG_ERROR(Input, "Timeout, Is the adapter connected?");
124 adapter_input_thread.request_stop();
125 restart_scan_thread = true;
126 }
127 return false;
128 }
129
130 input_error_counter = 0;
131 return true;
132}
133
134void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) {
135 for (std::size_t port = 0; port < pads.size(); ++port) {
136 const std::size_t offset = 1 + (9 * port);
137 const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4);
138 UpdatePadType(port, type);
139 if (DeviceConnected(port)) {
140 const u8 b1 = adapter_payload[offset + 1];
141 const u8 b2 = adapter_payload[offset + 2];
142 UpdateStateButtons(port, b1, b2);
143 UpdateStateAxes(port, adapter_payload);
144 }
145 }
146}
147
148void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) {
149 if (pads[port].type == pad_type) {
150 return;
151 }
152 // Device changed reset device and set new type
153 pads[port].axis_origin = {};
154 pads[port].reset_origin_counter = {};
155 pads[port].enable_vibration = {};
156 pads[port].rumble_amplitude = {};
157 pads[port].type = pad_type;
158}
159
160void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1,
161 [[maybe_unused]] u8 b2) {
162 if (port >= pads.size()) {
163 return;
164 }
165
166 static constexpr std::array<PadButton, 8> b1_buttons{
167 PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY,
168 PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp,
169 };
170
171 static constexpr std::array<PadButton, 4> b2_buttons{
172 PadButton::ButtonStart,
173 PadButton::TriggerZ,
174 PadButton::TriggerR,
175 PadButton::TriggerL,
176 };
177
178 for (std::size_t i = 0; i < b1_buttons.size(); ++i) {
179 const bool button_status = (b1 & (1U << i)) != 0;
180 const int button = static_cast<int>(b1_buttons[i]);
181 SetButton(pads[port].identifier, button, button_status);
182 }
183
184 for (std::size_t j = 0; j < b2_buttons.size(); ++j) {
185 const bool button_status = (b2 & (1U << j)) != 0;
186 const int button = static_cast<int>(b2_buttons[j]);
187 SetButton(pads[port].identifier, button, button_status);
188 }
189}
190
191void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) {
192 if (port >= pads.size()) {
193 return;
194 }
195
196 const std::size_t offset = 1 + (9 * port);
197 static constexpr std::array<PadAxes, 6> axes{
198 PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX,
199 PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight,
200 };
201
202 for (const PadAxes axis : axes) {
203 const auto index = static_cast<std::size_t>(axis);
204 const u8 axis_value = adapter_payload[offset + 3 + index];
205 if (pads[port].reset_origin_counter <= 18) {
206 if (pads[port].axis_origin[index] != axis_value) {
207 pads[port].reset_origin_counter = 0;
208 }
209 pads[port].axis_origin[index] = axis_value;
210 pads[port].reset_origin_counter++;
211 }
212 const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f;
213 SetAxis(pads[port].identifier, static_cast<int>(index), axis_status);
214 }
215}
216
217void GCAdapter::AdapterScanThread(std::stop_token stop_token) {
218 Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter");
219 usb_adapter_handle = nullptr;
220 pads = {};
221 while (!stop_token.stop_requested() && !Setup()) {
222 std::this_thread::sleep_for(std::chrono::seconds(2));
223 }
224}
225
226bool GCAdapter::Setup() {
227 constexpr u16 nintendo_vid = 0x057e;
228 constexpr u16 gc_adapter_pid = 0x0337;
229 usb_adapter_handle =
230 std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid);
231 if (!usb_adapter_handle->get()) {
232 return false;
233 }
234 if (!CheckDeviceAccess()) {
235 usb_adapter_handle = nullptr;
236 return false;
237 }
238
239 libusb_device* const device = libusb_get_device(usb_adapter_handle->get());
240
241 LOG_INFO(Input, "GC adapter is now connected");
242 // GC Adapter found and accessible, registering it
243 if (GetGCEndpoint(device)) {
244 rumble_enabled = true;
245 input_error_counter = 0;
246 output_error_counter = 0;
247
248 std::size_t port = 0;
249 for (GCController& pad : pads) {
250 pad.identifier = {
251 .guid = Common::UUID{Common::INVALID_UUID},
252 .port = port++,
253 .pad = 0,
254 };
255 PreSetController(pad.identifier);
256 }
257
258 adapter_input_thread =
259 std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); });
260 return true;
261 }
262 return false;
263}
264
265bool GCAdapter::CheckDeviceAccess() {
266 s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0);
267 if (kernel_driver_error == 1) {
268 kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0);
269 if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
270 LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}",
271 kernel_driver_error);
272 }
273 }
274
275 if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) {
276 usb_adapter_handle = nullptr;
277 return false;
278 }
279
280 const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0);
281 if (interface_claim_error) {
282 LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error);
283 usb_adapter_handle = nullptr;
284 return false;
285 }
286
287 // This fixes payload problems from offbrand GCAdapters
288 const s32 control_transfer_error =
289 libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000);
290 if (control_transfer_error < 0) {
291 LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error);
292 }
293
294 return true;
295}
296
297bool GCAdapter::GetGCEndpoint(libusb_device* device) {
298 libusb_config_descriptor* config = nullptr;
299 const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
300 if (config_descriptor_return != LIBUSB_SUCCESS) {
301 LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
302 config_descriptor_return);
303 return false;
304 }
305
306 for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
307 const libusb_interface* interfaceContainer = &config->interface[ic];
308 for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
309 const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i];
310 for (u8 e = 0; e < interface->bNumEndpoints; e++) {
311 const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e];
312 if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) {
313 input_endpoint = endpoint->bEndpointAddress;
314 } else {
315 output_endpoint = endpoint->bEndpointAddress;
316 }
317 }
318 }
319 }
320 // This transfer seems to be responsible for clearing the state of the adapter
321 // Used to clear the "busy" state of when the device is unexpectedly unplugged
322 unsigned char clear_payload = 0x13;
323 libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload,
324 sizeof(clear_payload), nullptr, 16);
325 return true;
326}
327
328Common::Input::VibrationError GCAdapter::SetRumble(
329 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
330 const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
331 const auto processed_amplitude =
332 static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8);
333
334 pads[identifier.port].rumble_amplitude = processed_amplitude;
335
336 if (!rumble_enabled) {
337 return Common::Input::VibrationError::Disabled;
338 }
339 return Common::Input::VibrationError::None;
340}
341
342void GCAdapter::UpdateVibrations() {
343 // Use 8 states to keep the switching between on/off fast enough for
344 // a human to feel different vibration strenght
345 // More states == more rumble strengths == slower update time
346 constexpr u8 vibration_states = 8;
347
348 vibration_counter = (vibration_counter + 1) % vibration_states;
349
350 for (GCController& pad : pads) {
351 const bool vibrate = pad.rumble_amplitude > vibration_counter;
352 vibration_changed |= vibrate != pad.enable_vibration;
353 pad.enable_vibration = vibrate;
354 }
355 SendVibrations();
356}
357
358void GCAdapter::SendVibrations() {
359 if (!rumble_enabled || !vibration_changed) {
360 return;
361 }
362 s32 size{};
363 constexpr u8 rumble_command = 0x11;
364 const u8 p1 = pads[0].enable_vibration;
365 const u8 p2 = pads[1].enable_vibration;
366 const u8 p3 = pads[2].enable_vibration;
367 const u8 p4 = pads[3].enable_vibration;
368 std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4};
369 const int err =
370 libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(),
371 static_cast<s32>(payload.size()), &size, 16);
372 if (err) {
373 LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err));
374 if (output_error_counter++ > 5) {
375 LOG_ERROR(Input, "Output timeout, Rumble disabled");
376 rumble_enabled = false;
377 }
378 return;
379 }
380 output_error_counter = 0;
381 vibration_changed = false;
382}
383
384bool GCAdapter::DeviceConnected(std::size_t port) const {
385 return pads[port].type != ControllerTypes::None;
386}
387
388void GCAdapter::Reset() {
389 adapter_scan_thread = {};
390 adapter_input_thread = {};
391 usb_adapter_handle = nullptr;
392 pads = {};
393 libusb_ctx = nullptr;
394}
395
396std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const {
397 std::vector<Common::ParamPackage> devices;
398 for (std::size_t port = 0; port < pads.size(); ++port) {
399 if (!DeviceConnected(port)) {
400 continue;
401 }
402 Common::ParamPackage identifier{};
403 identifier.Set("engine", GetEngineName());
404 identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1));
405 identifier.Set("port", static_cast<int>(port));
406 devices.emplace_back(identifier);
407 }
408 return devices;
409}
410
411ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) {
412 // This list is missing ZL/ZR since those are not considered buttons.
413 // We will add those afterwards
414 // This list also excludes any button that can't be really mapped
415 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12>
416 switch_to_gcadapter_button = {
417 std::pair{Settings::NativeButton::A, PadButton::ButtonA},
418 {Settings::NativeButton::B, PadButton::ButtonB},
419 {Settings::NativeButton::X, PadButton::ButtonX},
420 {Settings::NativeButton::Y, PadButton::ButtonY},
421 {Settings::NativeButton::Plus, PadButton::ButtonStart},
422 {Settings::NativeButton::DLeft, PadButton::ButtonLeft},
423 {Settings::NativeButton::DUp, PadButton::ButtonUp},
424 {Settings::NativeButton::DRight, PadButton::ButtonRight},
425 {Settings::NativeButton::DDown, PadButton::ButtonDown},
426 {Settings::NativeButton::SL, PadButton::TriggerL},
427 {Settings::NativeButton::SR, PadButton::TriggerR},
428 {Settings::NativeButton::R, PadButton::TriggerZ},
429 };
430 if (!params.Has("port")) {
431 return {};
432 }
433
434 ButtonMapping mapping{};
435 for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) {
436 Common::ParamPackage button_params{};
437 button_params.Set("engine", GetEngineName());
438 button_params.Set("port", params.Get("port", 0));
439 button_params.Set("button", static_cast<int>(gcadapter_button));
440 mapping.insert_or_assign(switch_button, std::move(button_params));
441 }
442
443 // Add the missing bindings for ZL/ZR
444 static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2>
445 switch_to_gcadapter_axis = {
446 std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft},
447 {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight},
448 };
449 for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) {
450 Common::ParamPackage button_params{};
451 button_params.Set("engine", GetEngineName());
452 button_params.Set("port", params.Get("port", 0));
453 button_params.Set("button", static_cast<s32>(gcadapter_buton));
454 button_params.Set("axis", static_cast<s32>(gcadapter_axis));
455 button_params.Set("threshold", 0.5f);
456 button_params.Set("range", 1.9f);
457 button_params.Set("direction", "+");
458 mapping.insert_or_assign(switch_button, std::move(button_params));
459 }
460 return mapping;
461}
462
463AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
464 if (!params.Has("port")) {
465 return {};
466 }
467
468 AnalogMapping mapping = {};
469 Common::ParamPackage left_analog_params;
470 left_analog_params.Set("engine", GetEngineName());
471 left_analog_params.Set("port", params.Get("port", 0));
472 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX));
473 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY));
474 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
475 Common::ParamPackage right_analog_params;
476 right_analog_params.Set("engine", GetEngineName());
477 right_analog_params.Set("port", params.Get("port", 0));
478 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX));
479 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY));
480 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
481 return mapping;
482}
483
484Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const {
485 PadButton button = static_cast<PadButton>(params.Get("button", 0));
486 switch (button) {
487 case PadButton::ButtonLeft:
488 return Common::Input::ButtonNames::ButtonLeft;
489 case PadButton::ButtonRight:
490 return Common::Input::ButtonNames::ButtonRight;
491 case PadButton::ButtonDown:
492 return Common::Input::ButtonNames::ButtonDown;
493 case PadButton::ButtonUp:
494 return Common::Input::ButtonNames::ButtonUp;
495 case PadButton::TriggerZ:
496 return Common::Input::ButtonNames::TriggerZ;
497 case PadButton::TriggerR:
498 return Common::Input::ButtonNames::TriggerR;
499 case PadButton::TriggerL:
500 return Common::Input::ButtonNames::TriggerL;
501 case PadButton::ButtonA:
502 return Common::Input::ButtonNames::ButtonA;
503 case PadButton::ButtonB:
504 return Common::Input::ButtonNames::ButtonB;
505 case PadButton::ButtonX:
506 return Common::Input::ButtonNames::ButtonX;
507 case PadButton::ButtonY:
508 return Common::Input::ButtonNames::ButtonY;
509 case PadButton::ButtonStart:
510 return Common::Input::ButtonNames::ButtonStart;
511 default:
512 return Common::Input::ButtonNames::Undefined;
513 }
514}
515
516Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const {
517 if (params.Has("button")) {
518 return GetUIButtonName(params);
519 }
520 if (params.Has("axis")) {
521 return Common::Input::ButtonNames::Value;
522 }
523
524 return Common::Input::ButtonNames::Invalid;
525}
526
527} // namespace InputCommon
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
new file mode 100644
index 000000000..7ce1912a3
--- /dev/null
+++ b/src/input_common/drivers/gc_adapter.h
@@ -0,0 +1,135 @@
1// Copyright 2014 Dolphin Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <mutex>
10#include <stop_token>
11#include <string>
12#include <thread>
13
14#include "input_common/input_engine.h"
15
16struct libusb_context;
17struct libusb_device;
18struct libusb_device_handle;
19
20namespace InputCommon {
21
22class LibUSBContext;
23class LibUSBDeviceHandle;
24
25class GCAdapter : public InputEngine {
26public:
27 explicit GCAdapter(std::string input_engine_);
28 ~GCAdapter() override;
29
30 Common::Input::VibrationError SetRumble(
31 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
32
33 /// Used for automapping features
34 std::vector<Common::ParamPackage> GetInputDevices() const override;
35 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
36 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
37 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
38
39private:
40 enum class PadButton {
41 Undefined = 0x0000,
42 ButtonLeft = 0x0001,
43 ButtonRight = 0x0002,
44 ButtonDown = 0x0004,
45 ButtonUp = 0x0008,
46 TriggerZ = 0x0010,
47 TriggerR = 0x0020,
48 TriggerL = 0x0040,
49 ButtonA = 0x0100,
50 ButtonB = 0x0200,
51 ButtonX = 0x0400,
52 ButtonY = 0x0800,
53 ButtonStart = 0x1000,
54 };
55
56 enum class PadAxes : u8 {
57 StickX,
58 StickY,
59 SubstickX,
60 SubstickY,
61 TriggerLeft,
62 TriggerRight,
63 Undefined,
64 };
65
66 enum class ControllerTypes {
67 None,
68 Wired,
69 Wireless,
70 };
71
72 struct GCController {
73 ControllerTypes type = ControllerTypes::None;
74 PadIdentifier identifier{};
75 bool enable_vibration = false;
76 u8 rumble_amplitude{};
77 std::array<u8, 6> axis_origin{};
78 u8 reset_origin_counter{};
79 };
80
81 using AdapterPayload = std::array<u8, 37>;
82
83 void UpdatePadType(std::size_t port, ControllerTypes pad_type);
84 void UpdateControllers(const AdapterPayload& adapter_payload);
85 void UpdateStateButtons(std::size_t port, u8 b1, u8 b2);
86 void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload);
87
88 void AdapterInputThread(std::stop_token stop_token);
89
90 void AdapterScanThread(std::stop_token stop_token);
91
92 bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size);
93
94 /// For use in initialization, querying devices to find the adapter
95 bool Setup();
96
97 /// Returns true if we successfully gain access to GC Adapter
98 bool CheckDeviceAccess();
99
100 /// Captures GC Adapter endpoint address
101 /// Returns true if the endpoint was set correctly
102 bool GetGCEndpoint(libusb_device* device);
103
104 /// Returns true if there is a device connected to port
105 bool DeviceConnected(std::size_t port) const;
106
107 /// For shutting down, clear all data, join all threads, release usb
108 void Reset();
109
110 void UpdateVibrations();
111
112 /// Updates vibration state of all controllers
113 void SendVibrations();
114
115 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
116
117 std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle;
118 std::array<GCController, 4> pads;
119
120 std::jthread adapter_input_thread;
121 std::jthread adapter_scan_thread;
122 bool restart_scan_thread{};
123
124 std::unique_ptr<LibUSBContext> libusb_ctx;
125
126 u8 input_endpoint{0};
127 u8 output_endpoint{0};
128 u8 input_error_counter{0};
129 u8 output_error_counter{0};
130 int vibration_counter{0};
131
132 bool rumble_enabled{true};
133 bool vibration_changed{true};
134};
135} // namespace InputCommon
diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp
new file mode 100644
index 000000000..4c1e5bbec
--- /dev/null
+++ b/src/input_common/drivers/keyboard.cpp
@@ -0,0 +1,112 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/param_package.h"
6#include "common/settings_input.h"
7#include "input_common/drivers/keyboard.h"
8
9namespace InputCommon {
10
11constexpr PadIdentifier key_identifier = {
12 .guid = Common::UUID{Common::INVALID_UUID},
13 .port = 0,
14 .pad = 0,
15};
16constexpr PadIdentifier keyboard_key_identifier = {
17 .guid = Common::UUID{Common::INVALID_UUID},
18 .port = 1,
19 .pad = 0,
20};
21constexpr PadIdentifier keyboard_modifier_identifier = {
22 .guid = Common::UUID{Common::INVALID_UUID},
23 .port = 1,
24 .pad = 1,
25};
26
27Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
28 // Keyboard is broken into 3 diferent sets:
29 // key: Unfiltered intended for controllers.
30 // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation.
31 // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard
32 // emulation.
33 PreSetController(key_identifier);
34 PreSetController(keyboard_key_identifier);
35 PreSetController(keyboard_modifier_identifier);
36}
37
38void Keyboard::PressKey(int key_code) {
39 SetButton(key_identifier, key_code, true);
40}
41
42void Keyboard::ReleaseKey(int key_code) {
43 SetButton(key_identifier, key_code, false);
44}
45
46void Keyboard::PressKeyboardKey(int key_index) {
47 if (key_index == Settings::NativeKeyboard::None) {
48 return;
49 }
50 SetButton(keyboard_key_identifier, key_index, true);
51}
52
53void Keyboard::ReleaseKeyboardKey(int key_index) {
54 if (key_index == Settings::NativeKeyboard::None) {
55 return;
56 }
57 SetButton(keyboard_key_identifier, key_index, false);
58}
59
60void Keyboard::SetKeyboardModifiers(int key_modifiers) {
61 for (int i = 0; i < 32; ++i) {
62 bool key_value = ((key_modifiers >> i) & 0x1) != 0;
63 SetButton(keyboard_modifier_identifier, i, key_value);
64 // Use the modifier to press the key button equivalent
65 switch (i) {
66 case Settings::NativeKeyboard::LeftControl:
67 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value);
68 break;
69 case Settings::NativeKeyboard::LeftShift:
70 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value);
71 break;
72 case Settings::NativeKeyboard::LeftAlt:
73 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value);
74 break;
75 case Settings::NativeKeyboard::LeftMeta:
76 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value);
77 break;
78 case Settings::NativeKeyboard::RightControl:
79 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey,
80 key_value);
81 break;
82 case Settings::NativeKeyboard::RightShift:
83 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value);
84 break;
85 case Settings::NativeKeyboard::RightAlt:
86 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value);
87 break;
88 case Settings::NativeKeyboard::RightMeta:
89 SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value);
90 break;
91 default:
92 // Other modifier keys should be pressed with PressKey since they stay enabled until
93 // next press
94 break;
95 }
96 }
97}
98
99void Keyboard::ReleaseAllKeys() {
100 ResetButtonState();
101}
102
103std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const {
104 std::vector<Common::ParamPackage> devices;
105 devices.emplace_back(Common::ParamPackage{
106 {"engine", GetEngineName()},
107 {"display", "Keyboard Only"},
108 });
109 return devices;
110}
111
112} // namespace InputCommon
diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h
new file mode 100644
index 000000000..3856c882c
--- /dev/null
+++ b/src/input_common/drivers/keyboard.h
@@ -0,0 +1,56 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11/**
12 * A button device factory representing a keyboard. It receives keyboard events and forward them
13 * to all button devices it created.
14 */
15class Keyboard final : public InputEngine {
16public:
17 explicit Keyboard(std::string input_engine_);
18
19 /**
20 * Sets the status of all buttons bound with the key to pressed
21 * @param key_code the code of the key to press
22 */
23 void PressKey(int key_code);
24
25 /**
26 * Sets the status of all buttons bound with the key to released
27 * @param key_code the code of the key to release
28 */
29 void ReleaseKey(int key_code);
30
31 /**
32 * Sets the status of the keyboard key to pressed
33 * @param key_index index of the key to press
34 */
35 void PressKeyboardKey(int key_index);
36
37 /**
38 * Sets the status of the keyboard key to released
39 * @param key_index index of the key to release
40 */
41 void ReleaseKeyboardKey(int key_index);
42
43 /**
44 * Sets the status of all keyboard modifier keys
45 * @param key_modifiers the code of the key to release
46 */
47 void SetKeyboardModifiers(int key_modifiers);
48
49 /// Sets all keys to the non pressed state
50 void ReleaseAllKeys();
51
52 /// Used for automapping features
53 std::vector<Common::ParamPackage> GetInputDevices() const override;
54};
55
56} // namespace InputCommon
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp
new file mode 100644
index 000000000..aa69216c8
--- /dev/null
+++ b/src/input_common/drivers/mouse.cpp
@@ -0,0 +1,185 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include <stop_token>
6#include <thread>
7#include <fmt/format.h>
8
9#include "common/param_package.h"
10#include "common/settings.h"
11#include "common/thread.h"
12#include "input_common/drivers/mouse.h"
13
14namespace InputCommon {
15constexpr int mouse_axis_x = 0;
16constexpr int mouse_axis_y = 1;
17constexpr int wheel_axis_x = 2;
18constexpr int wheel_axis_y = 3;
19constexpr int touch_axis_x = 10;
20constexpr int touch_axis_y = 11;
21constexpr PadIdentifier identifier = {
22 .guid = Common::UUID{Common::INVALID_UUID},
23 .port = 0,
24 .pad = 0,
25};
26
27Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
28 PreSetController(identifier);
29 PreSetAxis(identifier, mouse_axis_x);
30 PreSetAxis(identifier, mouse_axis_y);
31 PreSetAxis(identifier, wheel_axis_x);
32 PreSetAxis(identifier, wheel_axis_y);
33 PreSetAxis(identifier, touch_axis_x);
34 PreSetAxis(identifier, touch_axis_x);
35 update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
36}
37
38void Mouse::UpdateThread(std::stop_token stop_token) {
39 Common::SetCurrentThreadName("yuzu:input:Mouse");
40 constexpr int update_time = 10;
41 while (!stop_token.stop_requested()) {
42 if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
43 // Slow movement by 4%
44 last_mouse_change *= 0.96f;
45 const float sensitivity =
46 Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f;
47 SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity);
48 SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity);
49 }
50
51 if (mouse_panning_timout++ > 20) {
52 StopPanning();
53 }
54 std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
55 }
56}
57
58void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
59 // If native mouse is enabled just set the screen coordinates
60 if (Settings::values.mouse_enabled) {
61 SetAxis(identifier, mouse_axis_x, touch_x);
62 SetAxis(identifier, mouse_axis_y, touch_y);
63 return;
64 }
65
66 SetAxis(identifier, touch_axis_x, touch_x);
67 SetAxis(identifier, touch_axis_y, touch_y);
68
69 if (Settings::values.mouse_panning) {
70 auto mouse_change =
71 (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
72 mouse_panning_timout = 0;
73
74 const auto move_distance = mouse_change.Length();
75 if (move_distance == 0) {
76 return;
77 }
78
79 // Make slow movements at least 3 units on lenght
80 if (move_distance < 3.0f) {
81 // Normalize value
82 mouse_change /= move_distance;
83 mouse_change *= 3.0f;
84 }
85
86 // Average mouse movements
87 last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f);
88
89 const auto last_move_distance = last_mouse_change.Length();
90
91 // Make fast movements clamp to 8 units on lenght
92 if (last_move_distance > 8.0f) {
93 // Normalize value
94 last_mouse_change /= last_move_distance;
95 last_mouse_change *= 8.0f;
96 }
97
98 // Ignore average if it's less than 1 unit and use current movement value
99 if (last_move_distance < 1.0f) {
100 last_mouse_change = mouse_change / mouse_change.Length();
101 }
102
103 return;
104 }
105
106 if (button_pressed) {
107 const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin;
108 const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
109 SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
110 SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity);
111 }
112}
113
114void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
115 SetAxis(identifier, touch_axis_x, touch_x);
116 SetAxis(identifier, touch_axis_y, touch_y);
117 SetButton(identifier, static_cast<int>(button), true);
118 // Set initial analog parameters
119 mouse_origin = {x, y};
120 last_mouse_position = {x, y};
121 button_pressed = true;
122}
123
124void Mouse::ReleaseButton(MouseButton button) {
125 SetButton(identifier, static_cast<int>(button), false);
126
127 if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
128 SetAxis(identifier, mouse_axis_x, 0);
129 SetAxis(identifier, mouse_axis_y, 0);
130 }
131 button_pressed = false;
132}
133
134void Mouse::MouseWheelChange(int x, int y) {
135 wheel_position.x += x;
136 wheel_position.y += y;
137 SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x));
138 SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y));
139}
140
141void Mouse::ReleaseAllButtons() {
142 ResetButtonState();
143 button_pressed = false;
144}
145
146void Mouse::StopPanning() {
147 last_mouse_change = {};
148}
149
150std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
151 std::vector<Common::ParamPackage> devices;
152 devices.emplace_back(Common::ParamPackage{
153 {"engine", GetEngineName()},
154 {"display", "Keyboard/Mouse"},
155 });
156 return devices;
157}
158
159AnalogMapping Mouse::GetAnalogMappingForDevice(
160 [[maybe_unused]] const Common::ParamPackage& params) {
161 // Only overwrite different buttons from default
162 AnalogMapping mapping = {};
163 Common::ParamPackage right_analog_params;
164 right_analog_params.Set("engine", GetEngineName());
165 right_analog_params.Set("axis_x", 0);
166 right_analog_params.Set("axis_y", 1);
167 right_analog_params.Set("threshold", 0.5f);
168 right_analog_params.Set("range", 1.0f);
169 right_analog_params.Set("deadzone", 0.0f);
170 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
171 return mapping;
172}
173
174Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const {
175 if (params.Has("button")) {
176 return Common::Input::ButtonNames::Value;
177 }
178 if (params.Has("axis")) {
179 return Common::Input::ButtonNames::Value;
180 }
181
182 return Common::Input::ButtonNames::Invalid;
183}
184
185} // namespace InputCommon
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h
new file mode 100644
index 000000000..040446178
--- /dev/null
+++ b/src/input_common/drivers/mouse.h
@@ -0,0 +1,81 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include <stop_token>
8#include <thread>
9
10#include "common/vector_math.h"
11#include "input_common/input_engine.h"
12
13namespace InputCommon {
14
15enum class MouseButton {
16 Left,
17 Right,
18 Wheel,
19 Backward,
20 Forward,
21 Task,
22 Extra,
23 Undefined,
24};
25
26/**
27 * A button device factory representing a keyboard. It receives keyboard events and forward them
28 * to all button devices it created.
29 */
30class Mouse final : public InputEngine {
31public:
32 explicit Mouse(std::string input_engine_);
33
34 /**
35 * Signals that mouse has moved.
36 * @param x the x-coordinate of the cursor
37 * @param y the y-coordinate of the cursor
38 * @param center_x the x-coordinate of the middle of the screen
39 * @param center_y the y-coordinate of the middle of the screen
40 */
41 void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
42
43 /**
44 * Sets the status of all buttons bound with the key to pressed
45 * @param key_code the code of the key to press
46 */
47 void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
48
49 /**
50 * Sets the status of all buttons bound with the key to released
51 * @param key_code the code of the key to release
52 */
53 void ReleaseButton(MouseButton button);
54
55 /**
56 * Sets the status of the mouse wheel
57 * @param x delta movement in the x direction
58 * @param y delta movement in the y direction
59 */
60 void MouseWheelChange(int x, int y);
61
62 void ReleaseAllButtons();
63
64 std::vector<Common::ParamPackage> GetInputDevices() const override;
65 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
66 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
67
68private:
69 void UpdateThread(std::stop_token stop_token);
70 void StopPanning();
71
72 Common::Vec2<int> mouse_origin;
73 Common::Vec2<int> last_mouse_position;
74 Common::Vec2<float> last_mouse_change;
75 Common::Vec2<int> wheel_position;
76 bool button_pressed;
77 int mouse_panning_timout{};
78 std::jthread update_thread;
79};
80
81} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
new file mode 100644
index 000000000..0cda9df62
--- /dev/null
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -0,0 +1,926 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/logging/log.h"
6#include "common/math_util.h"
7#include "common/param_package.h"
8#include "common/settings.h"
9#include "common/thread.h"
10#include "common/vector_math.h"
11#include "input_common/drivers/sdl_driver.h"
12
13namespace InputCommon {
14
15namespace {
16std::string GetGUID(SDL_Joystick* joystick) {
17 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
18 char guid_str[33];
19 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
20 return guid_str;
21}
22} // Anonymous namespace
23
24static int SDLEventWatcher(void* user_data, SDL_Event* event) {
25 auto* const sdl_state = static_cast<SDLDriver*>(user_data);
26
27 sdl_state->HandleGameControllerEvent(*event);
28
29 return 0;
30}
31
32class SDLJoystick {
33public:
34 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
35 SDL_GameController* game_controller)
36 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
37 sdl_controller{game_controller, &SDL_GameControllerClose} {
38 EnableMotion();
39 }
40
41 void EnableMotion() {
42 if (sdl_controller) {
43 SDL_GameController* controller = sdl_controller.get();
44 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
45 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
46 has_accel = true;
47 }
48 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
49 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
50 has_gyro = true;
51 }
52 }
53 }
54
55 bool HasGyro() const {
56 return has_gyro;
57 }
58
59 bool HasAccel() const {
60 return has_accel;
61 }
62
63 bool UpdateMotion(SDL_ControllerSensorEvent event) {
64 constexpr float gravity_constant = 9.80665f;
65 std::lock_guard lock{mutex};
66 const u64 time_difference = event.timestamp - last_motion_update;
67 last_motion_update = event.timestamp;
68 switch (event.sensor) {
69 case SDL_SENSOR_ACCEL: {
70 motion.accel_x = -event.data[0] / gravity_constant;
71 motion.accel_y = event.data[2] / gravity_constant;
72 motion.accel_z = -event.data[1] / gravity_constant;
73 break;
74 }
75 case SDL_SENSOR_GYRO: {
76 motion.gyro_x = event.data[0] / (Common::PI * 2);
77 motion.gyro_y = -event.data[2] / (Common::PI * 2);
78 motion.gyro_z = event.data[1] / (Common::PI * 2);
79 break;
80 }
81 }
82
83 // Ignore duplicated timestamps
84 if (time_difference == 0) {
85 return false;
86 }
87 motion.delta_timestamp = time_difference * 1000;
88 return true;
89 }
90
91 const BasicMotion& GetMotion() const {
92 return motion;
93 }
94
95 bool RumblePlay(const Common::Input::VibrationStatus vibration) {
96 constexpr u32 rumble_max_duration_ms = 1000;
97 if (sdl_controller) {
98 return SDL_GameControllerRumble(
99 sdl_controller.get(), static_cast<u16>(vibration.low_amplitude),
100 static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
101 } else if (sdl_joystick) {
102 return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
103 static_cast<u16>(vibration.high_amplitude),
104 rumble_max_duration_ms) != -1;
105 }
106
107 return false;
108 }
109
110 bool HasHDRumble() const {
111 if (sdl_controller) {
112 return (SDL_GameControllerGetType(sdl_controller.get()) ==
113 SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO);
114 }
115 return false;
116 }
117 /**
118 * The Pad identifier of the joystick
119 */
120 const PadIdentifier GetPadIdentifier() const {
121 return {
122 .guid = Common::UUID{guid},
123 .port = static_cast<std::size_t>(port),
124 .pad = 0,
125 };
126 }
127
128 /**
129 * The guid of the joystick
130 */
131 const std::string& GetGUID() const {
132 return guid;
133 }
134
135 /**
136 * The number of joystick from the same type that were connected before this joystick
137 */
138 int GetPort() const {
139 return port;
140 }
141
142 SDL_Joystick* GetSDLJoystick() const {
143 return sdl_joystick.get();
144 }
145
146 SDL_GameController* GetSDLGameController() const {
147 return sdl_controller.get();
148 }
149
150 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
151 sdl_joystick.reset(joystick);
152 sdl_controller.reset(controller);
153 }
154
155 bool IsJoyconLeft() const {
156 const std::string controller_name = GetControllerName();
157 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
158 return true;
159 }
160 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
161 return true;
162 }
163 return false;
164 }
165
166 bool IsJoyconRight() const {
167 const std::string controller_name = GetControllerName();
168 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
169 return true;
170 }
171 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
172 return true;
173 }
174 return false;
175 }
176
177 BatteryLevel GetBatteryLevel() {
178 const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get());
179 switch (level) {
180 case SDL_JOYSTICK_POWER_EMPTY:
181 return BatteryLevel::Empty;
182 case SDL_JOYSTICK_POWER_LOW:
183 return BatteryLevel::Critical;
184 case SDL_JOYSTICK_POWER_MEDIUM:
185 return BatteryLevel::Low;
186 case SDL_JOYSTICK_POWER_FULL:
187 return BatteryLevel::Medium;
188 case SDL_JOYSTICK_POWER_MAX:
189 return BatteryLevel::Full;
190 case SDL_JOYSTICK_POWER_UNKNOWN:
191 case SDL_JOYSTICK_POWER_WIRED:
192 default:
193 return BatteryLevel::Charging;
194 }
195 }
196
197 std::string GetControllerName() const {
198 if (sdl_controller) {
199 switch (SDL_GameControllerGetType(sdl_controller.get())) {
200 case SDL_CONTROLLER_TYPE_XBOX360:
201 return "XBox 360 Controller";
202 case SDL_CONTROLLER_TYPE_XBOXONE:
203 return "XBox One Controller";
204 default:
205 break;
206 }
207 const auto name = SDL_GameControllerName(sdl_controller.get());
208 if (name) {
209 return name;
210 }
211 }
212
213 if (sdl_joystick) {
214 const auto name = SDL_JoystickName(sdl_joystick.get());
215 if (name) {
216 return name;
217 }
218 }
219
220 return "Unknown";
221 }
222
223private:
224 std::string guid;
225 int port;
226 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
227 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
228 mutable std::mutex mutex;
229
230 u64 last_motion_update{};
231 bool has_gyro{false};
232 bool has_accel{false};
233 BasicMotion motion;
234};
235
236std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) {
237 std::lock_guard lock{joystick_map_mutex};
238 const auto it = joystick_map.find(guid);
239
240 if (it != joystick_map.end()) {
241 while (it->second.size() <= static_cast<std::size_t>(port)) {
242 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
243 nullptr, nullptr);
244 it->second.emplace_back(std::move(joystick));
245 }
246
247 return it->second[static_cast<std::size_t>(port)];
248 }
249
250 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
251
252 return joystick_map[guid].emplace_back(std::move(joystick));
253}
254
255std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
256 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
257 const std::string guid = GetGUID(sdl_joystick);
258
259 std::lock_guard lock{joystick_map_mutex};
260 const auto map_it = joystick_map.find(guid);
261
262 if (map_it == joystick_map.end()) {
263 return nullptr;
264 }
265
266 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
267 [&sdl_joystick](const auto& joystick) {
268 return joystick->GetSDLJoystick() == sdl_joystick;
269 });
270
271 if (vec_it == map_it->second.end()) {
272 return nullptr;
273 }
274
275 return *vec_it;
276}
277
278void SDLDriver::InitJoystick(int joystick_index) {
279 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
280 SDL_GameController* sdl_gamecontroller = nullptr;
281
282 if (SDL_IsGameController(joystick_index)) {
283 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
284 }
285
286 if (!sdl_joystick) {
287 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
288 return;
289 }
290
291 const std::string guid = GetGUID(sdl_joystick);
292
293 std::lock_guard lock{joystick_map_mutex};
294 if (joystick_map.find(guid) == joystick_map.end()) {
295 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
296 PreSetController(joystick->GetPadIdentifier());
297 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
298 joystick_map[guid].emplace_back(std::move(joystick));
299 return;
300 }
301
302 auto& joystick_guid_list = joystick_map[guid];
303 const auto joystick_it =
304 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
305 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
306
307 if (joystick_it != joystick_guid_list.end()) {
308 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
309 return;
310 }
311
312 const int port = static_cast<int>(joystick_guid_list.size());
313 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
314 PreSetController(joystick->GetPadIdentifier());
315 SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel());
316 joystick_guid_list.emplace_back(std::move(joystick));
317}
318
319void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) {
320 const std::string guid = GetGUID(sdl_joystick);
321
322 std::lock_guard lock{joystick_map_mutex};
323 // This call to guid is safe since the joystick is guaranteed to be in the map
324 const auto& joystick_guid_list = joystick_map[guid];
325 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
326 [&sdl_joystick](const auto& joystick) {
327 return joystick->GetSDLJoystick() == sdl_joystick;
328 });
329
330 if (joystick_it != joystick_guid_list.end()) {
331 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
332 }
333}
334
335void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) {
336 switch (event.type) {
337 case SDL_JOYBUTTONUP: {
338 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
339 const PadIdentifier identifier = joystick->GetPadIdentifier();
340 SetButton(identifier, event.jbutton.button, false);
341 }
342 break;
343 }
344 case SDL_JOYBUTTONDOWN: {
345 if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
346 const PadIdentifier identifier = joystick->GetPadIdentifier();
347 SetButton(identifier, event.jbutton.button, true);
348 }
349 break;
350 }
351 case SDL_JOYHATMOTION: {
352 if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
353 const PadIdentifier identifier = joystick->GetPadIdentifier();
354 SetHatButton(identifier, event.jhat.hat, event.jhat.value);
355 }
356 break;
357 }
358 case SDL_JOYAXISMOTION: {
359 if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
360 const PadIdentifier identifier = joystick->GetPadIdentifier();
361 SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f);
362 }
363 break;
364 }
365 case SDL_CONTROLLERSENSORUPDATE: {
366 if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
367 if (joystick->UpdateMotion(event.csensor)) {
368 const PadIdentifier identifier = joystick->GetPadIdentifier();
369 SetMotion(identifier, 0, joystick->GetMotion());
370 }
371 }
372 break;
373 }
374 case SDL_JOYDEVICEREMOVED:
375 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
376 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
377 break;
378 case SDL_JOYDEVICEADDED:
379 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
380 InitJoystick(event.jdevice.which);
381 break;
382 }
383}
384
385void SDLDriver::CloseJoysticks() {
386 std::lock_guard lock{joystick_map_mutex};
387 joystick_map.clear();
388}
389
390SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
391 if (!Settings::values.enable_raw_input) {
392 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
393 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
394 }
395
396 // Prevent SDL from adding undesired axis
397 SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
398
399 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
400 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
401 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
402 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
403
404 // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
405 // not a generic one
406 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
407
408 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
409 // driver on Linux.
410 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
411
412 // If the frontend is going to manage the event loop, then we don't start one here
413 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0;
414 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
415 LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError());
416 return;
417 }
418
419 SDL_AddEventWatch(&SDLEventWatcher, this);
420
421 initialized = true;
422 if (start_thread) {
423 poll_thread = std::thread([this] {
424 Common::SetCurrentThreadName("yuzu:input:SDL");
425 using namespace std::chrono_literals;
426 while (initialized) {
427 SDL_PumpEvents();
428 std::this_thread::sleep_for(1ms);
429 }
430 });
431 }
432 // Because the events for joystick connection happens before we have our event watcher added, we
433 // can just open all the joysticks right here
434 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
435 InitJoystick(i);
436 }
437}
438
439SDLDriver::~SDLDriver() {
440 CloseJoysticks();
441 SDL_DelEventWatch(&SDLEventWatcher, this);
442
443 initialized = false;
444 if (start_thread) {
445 poll_thread.join();
446 SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
447 }
448}
449
450std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
451 std::vector<Common::ParamPackage> devices;
452 std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
453 for (const auto& [key, value] : joystick_map) {
454 for (const auto& joystick : value) {
455 if (!joystick->GetSDLJoystick()) {
456 continue;
457 }
458 const std::string name =
459 fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
460 devices.emplace_back(Common::ParamPackage{
461 {"engine", GetEngineName()},
462 {"display", std::move(name)},
463 {"guid", joystick->GetGUID()},
464 {"port", std::to_string(joystick->GetPort())},
465 });
466 if (joystick->IsJoyconLeft()) {
467 joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
468 }
469 }
470 }
471
472 // Add dual controllers
473 for (const auto& [key, value] : joystick_map) {
474 for (const auto& joystick : value) {
475 if (joystick->IsJoyconRight()) {
476 if (!joycon_pairs.contains(joystick->GetPort())) {
477 continue;
478 }
479 const auto joystick2 = joycon_pairs.at(joystick->GetPort());
480
481 const std::string name =
482 fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
483 devices.emplace_back(Common::ParamPackage{
484 {"engine", GetEngineName()},
485 {"display", std::move(name)},
486 {"guid", joystick->GetGUID()},
487 {"guid2", joystick2->GetGUID()},
488 {"port", std::to_string(joystick->GetPort())},
489 });
490 }
491 }
492 }
493 return devices;
494}
495
496Common::Input::VibrationError SDLDriver::SetRumble(
497 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
498 const auto joystick =
499 GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port));
500 const auto process_amplitude_exp = [](f32 amplitude, f32 factor) {
501 return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF;
502 };
503
504 // Default exponential curve for rumble
505 f32 factor = 0.35f;
506
507 // If vibration is set as a linear output use a flatter value
508 if (vibration.type == Common::Input::VibrationAmplificationType::Linear) {
509 factor = 0.5f;
510 }
511
512 // Amplitude for HD rumble needs no modification
513 if (joystick->HasHDRumble()) {
514 factor = 1.0f;
515 }
516
517 const Common::Input::VibrationStatus new_vibration{
518 .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor),
519 .low_frequency = vibration.low_frequency,
520 .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor),
521 .high_frequency = vibration.high_frequency,
522 .type = Common::Input::VibrationAmplificationType::Exponential,
523 };
524
525 if (!joystick->RumblePlay(new_vibration)) {
526 return Common::Input::VibrationError::Unknown;
527 }
528
529 return Common::Input::VibrationError::None;
530}
531
532Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid,
533 s32 axis, float value) const {
534 Common::ParamPackage params{};
535 params.Set("engine", GetEngineName());
536 params.Set("port", port);
537 params.Set("guid", std::move(guid));
538 params.Set("axis", axis);
539 params.Set("threshold", "0.5");
540 params.Set("invert", value < 0 ? "-" : "+");
541 return params;
542}
543
544Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid,
545 s32 button) const {
546 Common::ParamPackage params{};
547 params.Set("engine", GetEngineName());
548 params.Set("port", port);
549 params.Set("guid", std::move(guid));
550 params.Set("button", button);
551 return params;
552}
553
554Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
555 u8 value) const {
556 Common::ParamPackage params{};
557 params.Set("engine", GetEngineName());
558 params.Set("port", port);
559 params.Set("guid", std::move(guid));
560 params.Set("hat", hat);
561 params.Set("direction", GetHatButtonName(value));
562 return params;
563}
564
565Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const {
566 Common::ParamPackage params{};
567 params.Set("engine", GetEngineName());
568 params.Set("motion", 0);
569 params.Set("port", port);
570 params.Set("guid", std::move(guid));
571 return params;
572}
573
574Common::ParamPackage SDLDriver::BuildParamPackageForBinding(
575 int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const {
576 switch (binding.bindType) {
577 case SDL_CONTROLLER_BINDTYPE_NONE:
578 break;
579 case SDL_CONTROLLER_BINDTYPE_AXIS:
580 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
581 case SDL_CONTROLLER_BINDTYPE_BUTTON:
582 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
583 case SDL_CONTROLLER_BINDTYPE_HAT:
584 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
585 static_cast<u8>(binding.value.hat.hat_mask));
586 }
587 return {};
588}
589
590Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
591 int axis_y, float offset_x,
592 float offset_y) const {
593 Common::ParamPackage params;
594 params.Set("engine", GetEngineName());
595 params.Set("port", static_cast<int>(identifier.port));
596 params.Set("guid", identifier.guid.Format());
597 params.Set("axis_x", axis_x);
598 params.Set("axis_y", axis_y);
599 params.Set("offset_x", offset_x);
600 params.Set("offset_y", offset_y);
601 params.Set("invert_x", "+");
602 params.Set("invert_y", "+");
603 return params;
604}
605
606ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) {
607 if (!params.Has("guid") || !params.Has("port")) {
608 return {};
609 }
610 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
611
612 auto* controller = joystick->GetSDLGameController();
613 if (controller == nullptr) {
614 return {};
615 }
616
617 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
618 // We will add those afterwards
619 // This list also excludes Screenshot since theres not really a mapping for that
620 ButtonBindings switch_to_sdl_button;
621
622 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
623 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
624 } else {
625 switch_to_sdl_button = GetDefaultButtonBinding();
626 }
627
628 // Add the missing bindings for ZL/ZR
629 static constexpr ZButtonBindings switch_to_sdl_axis{{
630 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
631 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
632 }};
633
634 // Parameters contain two joysticks return dual
635 if (params.Has("guid2")) {
636 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
637
638 if (joystick2->GetSDLGameController() != nullptr) {
639 return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
640 switch_to_sdl_axis);
641 }
642 }
643
644 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
645}
646
647ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
648 return {
649 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
650 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
651 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
652 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
653 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
654 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
655 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
656 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
657 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
658 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
659 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
660 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
661 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
662 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
663 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
664 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
665 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
666 };
667}
668
669ButtonBindings SDLDriver::GetNintendoButtonBinding(
670 const std::shared_ptr<SDLJoystick>& joystick) const {
671 // Default SL/SR mapping for pro controllers
672 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
673 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
674
675 if (joystick->IsJoyconLeft()) {
676 sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
677 sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
678 }
679 if (joystick->IsJoyconRight()) {
680 sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
681 sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
682 }
683
684 return {
685 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
686 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
687 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
688 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
689 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
690 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
691 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
692 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
693 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
694 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
695 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
696 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
697 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
698 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
699 {Settings::NativeButton::SL, sl_button},
700 {Settings::NativeButton::SR, sr_button},
701 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
702 };
703}
704
705ButtonMapping SDLDriver::GetSingleControllerMapping(
706 const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
707 const ZButtonBindings& switch_to_sdl_axis) const {
708 ButtonMapping mapping;
709 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
710 auto* controller = joystick->GetSDLGameController();
711
712 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
713 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
714 mapping.insert_or_assign(
715 switch_button,
716 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
717 }
718 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
719 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
720 mapping.insert_or_assign(
721 switch_button,
722 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
723 }
724
725 return mapping;
726}
727
728ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
729 const std::shared_ptr<SDLJoystick>& joystick2,
730 const ButtonBindings& switch_to_sdl_button,
731 const ZButtonBindings& switch_to_sdl_axis) const {
732 ButtonMapping mapping;
733 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
734 auto* controller = joystick->GetSDLGameController();
735 auto* controller2 = joystick2->GetSDLGameController();
736
737 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
738 if (IsButtonOnLeftSide(switch_button)) {
739 const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
740 mapping.insert_or_assign(
741 switch_button,
742 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
743 continue;
744 }
745 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
746 mapping.insert_or_assign(
747 switch_button,
748 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
749 }
750 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
751 if (IsButtonOnLeftSide(switch_button)) {
752 const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
753 mapping.insert_or_assign(
754 switch_button,
755 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
756 continue;
757 }
758 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
759 mapping.insert_or_assign(
760 switch_button,
761 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
762 }
763
764 return mapping;
765}
766
767bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
768 switch (button) {
769 case Settings::NativeButton::DDown:
770 case Settings::NativeButton::DLeft:
771 case Settings::NativeButton::DRight:
772 case Settings::NativeButton::DUp:
773 case Settings::NativeButton::L:
774 case Settings::NativeButton::LStick:
775 case Settings::NativeButton::Minus:
776 case Settings::NativeButton::Screenshot:
777 case Settings::NativeButton::ZL:
778 return true;
779 default:
780 return false;
781 }
782}
783
784AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
785 if (!params.Has("guid") || !params.Has("port")) {
786 return {};
787 }
788 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
789 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
790 auto* controller = joystick->GetSDLGameController();
791 if (controller == nullptr) {
792 return {};
793 }
794
795 AnalogMapping mapping = {};
796 const auto& binding_left_x =
797 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
798 const auto& binding_left_y =
799 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
800 if (params.Has("guid2")) {
801 const auto identifier = joystick2->GetPadIdentifier();
802 PreSetController(identifier);
803 PreSetAxis(identifier, binding_left_x.value.axis);
804 PreSetAxis(identifier, binding_left_y.value.axis);
805 const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
806 const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
807 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
808 BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
809 binding_left_y.value.axis,
810 left_offset_x, left_offset_y));
811 } else {
812 const auto identifier = joystick->GetPadIdentifier();
813 PreSetController(identifier);
814 PreSetAxis(identifier, binding_left_x.value.axis);
815 PreSetAxis(identifier, binding_left_y.value.axis);
816 const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis);
817 const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis);
818 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
819 BuildParamPackageForAnalog(identifier, binding_left_x.value.axis,
820 binding_left_y.value.axis,
821 left_offset_x, left_offset_y));
822 }
823 const auto& binding_right_x =
824 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
825 const auto& binding_right_y =
826 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
827 const auto identifier = joystick->GetPadIdentifier();
828 PreSetController(identifier);
829 PreSetAxis(identifier, binding_right_x.value.axis);
830 PreSetAxis(identifier, binding_right_y.value.axis);
831 const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis);
832 const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis);
833 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
834 BuildParamPackageForAnalog(identifier, binding_right_x.value.axis,
835 binding_right_y.value.axis, right_offset_x,
836 right_offset_y));
837 return mapping;
838}
839
840MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) {
841 if (!params.Has("guid") || !params.Has("port")) {
842 return {};
843 }
844 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
845 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
846 auto* controller = joystick->GetSDLGameController();
847 if (controller == nullptr) {
848 return {};
849 }
850
851 MotionMapping mapping = {};
852 joystick->EnableMotion();
853
854 if (joystick->HasGyro() || joystick->HasAccel()) {
855 mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
856 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
857 }
858 if (params.Has("guid2")) {
859 joystick2->EnableMotion();
860 if (joystick2->HasGyro() || joystick2->HasAccel()) {
861 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
862 BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
863 }
864 } else {
865 if (joystick->HasGyro() || joystick->HasAccel()) {
866 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
867 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
868 }
869 }
870
871 return mapping;
872}
873
874Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const {
875 if (params.Has("button")) {
876 // TODO(German77): Find how to substitue the values for real button names
877 return Common::Input::ButtonNames::Value;
878 }
879 if (params.Has("hat")) {
880 return Common::Input::ButtonNames::Value;
881 }
882 if (params.Has("axis")) {
883 return Common::Input::ButtonNames::Value;
884 }
885 if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) {
886 return Common::Input::ButtonNames::Value;
887 }
888 if (params.Has("motion")) {
889 return Common::Input::ButtonNames::Engine;
890 }
891
892 return Common::Input::ButtonNames::Invalid;
893}
894
895std::string SDLDriver::GetHatButtonName(u8 direction_value) const {
896 switch (direction_value) {
897 case SDL_HAT_UP:
898 return "up";
899 case SDL_HAT_DOWN:
900 return "down";
901 case SDL_HAT_LEFT:
902 return "left";
903 case SDL_HAT_RIGHT:
904 return "right";
905 default:
906 return {};
907 }
908}
909
910u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const {
911 Uint8 direction;
912 if (direction_name == "up") {
913 direction = SDL_HAT_UP;
914 } else if (direction_name == "down") {
915 direction = SDL_HAT_DOWN;
916 } else if (direction_name == "left") {
917 direction = SDL_HAT_LEFT;
918 } else if (direction_name == "right") {
919 direction = SDL_HAT_RIGHT;
920 } else {
921 direction = 0;
922 }
923 return direction;
924}
925
926} // namespace InputCommon
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
new file mode 100644
index 000000000..e9a5d2e26
--- /dev/null
+++ b/src/input_common/drivers/sdl_driver.h
@@ -0,0 +1,117 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <mutex>
9#include <thread>
10#include <unordered_map>
11
12#include <SDL.h>
13
14#include "common/common_types.h"
15#include "input_common/input_engine.h"
16
17union SDL_Event;
18using SDL_GameController = struct _SDL_GameController;
19using SDL_Joystick = struct _SDL_Joystick;
20using SDL_JoystickID = s32;
21
22namespace InputCommon {
23
24class SDLJoystick;
25
26using ButtonBindings =
27 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
28using ZButtonBindings =
29 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
30
31class SDLDriver : public InputEngine {
32public:
33 /// Initializes and registers SDL device factories
34 explicit SDLDriver(std::string input_engine_);
35
36 /// Unregisters SDL device factories and shut them down.
37 ~SDLDriver() override;
38
39 /// Handle SDL_Events for joysticks from SDL_PollEvent
40 void HandleGameControllerEvent(const SDL_Event& event);
41
42 /// Get the nth joystick with the corresponding GUID
43 std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
44
45 /**
46 * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so
47 * tie it to a SDLJoystick with the same guid and that port
48 */
49 std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
50
51 std::vector<Common::ParamPackage> GetInputDevices() const override;
52
53 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
54 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
55 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
56 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
57
58 std::string GetHatButtonName(u8 direction_value) const override;
59 u8 GetHatButtonId(const std::string& direction_name) const override;
60
61 Common::Input::VibrationError SetRumble(
62 const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
63
64private:
65 void InitJoystick(int joystick_index);
66 void CloseJoystick(SDL_Joystick* sdl_joystick);
67
68 /// Needs to be called before SDL_QuitSubSystem.
69 void CloseJoysticks();
70
71 Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
72 float value = 0.1f) const;
73 Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid,
74 s32 button) const;
75
76 Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat,
77 u8 value) const;
78
79 Common::ParamPackage BuildMotionParam(int port, std::string guid) const;
80
81 Common::ParamPackage BuildParamPackageForBinding(
82 int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const;
83
84 Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x,
85 int axis_y, float offset_x,
86 float offset_y) const;
87
88 /// Returns the default button bindings list for generic controllers
89 ButtonBindings GetDefaultButtonBinding() const;
90
91 /// Returns the default button bindings list for nintendo controllers
92 ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
93
94 /// Returns the button mappings from a single controller
95 ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
96 const ButtonBindings& switch_to_sdl_button,
97 const ZButtonBindings& switch_to_sdl_axis) const;
98
99 /// Returns the button mappings from two different controllers
100 ButtonMapping GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
101 const std::shared_ptr<SDLJoystick>& joystick2,
102 const ButtonBindings& switch_to_sdl_button,
103 const ZButtonBindings& switch_to_sdl_axis) const;
104
105 /// Returns true if the button is on the left joycon
106 bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const;
107
108 /// Map of GUID of a list of corresponding virtual Joysticks
109 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
110 std::mutex joystick_map_mutex;
111
112 bool start_thread = false;
113 std::atomic<bool> initialized = false;
114
115 std::thread poll_thread;
116};
117} // namespace InputCommon
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
new file mode 100644
index 000000000..5bdd5dac3
--- /dev/null
+++ b/src/input_common/drivers/tas_input.cpp
@@ -0,0 +1,320 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <fmt/format.h>
7
8#include "common/fs/file.h"
9#include "common/fs/fs_types.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/drivers/tas_input.h"
14
15namespace InputCommon::TasInput {
16
17enum class Tas::TasAxis : u8 {
18 StickX,
19 StickY,
20 SubstickX,
21 SubstickY,
22 Undefined,
23};
24
25// Supported keywords and buttons from a TAS file
26constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
27 std::pair{"KEY_A", TasButton::BUTTON_A},
28 {"KEY_B", TasButton::BUTTON_B},
29 {"KEY_X", TasButton::BUTTON_X},
30 {"KEY_Y", TasButton::BUTTON_Y},
31 {"KEY_LSTICK", TasButton::STICK_L},
32 {"KEY_RSTICK", TasButton::STICK_R},
33 {"KEY_L", TasButton::TRIGGER_L},
34 {"KEY_R", TasButton::TRIGGER_R},
35 {"KEY_PLUS", TasButton::BUTTON_PLUS},
36 {"KEY_MINUS", TasButton::BUTTON_MINUS},
37 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
38 {"KEY_DUP", TasButton::BUTTON_UP},
39 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
40 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
41 {"KEY_SL", TasButton::BUTTON_SL},
42 {"KEY_SR", TasButton::BUTTON_SR},
43 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
44 {"KEY_HOME", TasButton::BUTTON_HOME},
45 {"KEY_ZL", TasButton::TRIGGER_ZL},
46 {"KEY_ZR", TasButton::TRIGGER_ZR},
47};
48
49Tas::Tas(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
50 for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
51 PadIdentifier identifier{
52 .guid = Common::UUID{},
53 .port = player_index,
54 .pad = 0,
55 };
56 PreSetController(identifier);
57 }
58 ClearInput();
59 if (!Settings::values.tas_enable) {
60 needs_reset = true;
61 return;
62 }
63 LoadTasFiles();
64}
65
66Tas::~Tas() {
67 Stop();
68}
69
70void Tas::LoadTasFiles() {
71 script_length = 0;
72 for (size_t i = 0; i < commands.size(); i++) {
73 LoadTasFile(i, 0);
74 if (commands[i].size() > script_length) {
75 script_length = commands[i].size();
76 }
77 }
78}
79
80void Tas::LoadTasFile(size_t player_index, size_t file_index) {
81 commands[player_index].clear();
82
83 std::string file = Common::FS::ReadStringFromFile(
84 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
85 fmt::format("script{}-{}.txt", file_index, player_index + 1),
86 Common::FS::FileType::BinaryFile);
87 std::istringstream command_line(file);
88 std::string line;
89 int frame_no = 0;
90 while (std::getline(command_line, line, '\n')) {
91 if (line.empty()) {
92 continue;
93 }
94
95 std::vector<std::string> seg_list;
96 {
97 std::istringstream line_stream(line);
98 std::string segment;
99 while (std::getline(line_stream, segment, ' ')) {
100 seg_list.push_back(std::move(segment));
101 }
102 }
103
104 if (seg_list.size() < 4) {
105 continue;
106 }
107
108 const auto num_frames = std::stoi(seg_list[0]);
109 while (frame_no < num_frames) {
110 commands[player_index].emplace_back();
111 frame_no++;
112 }
113
114 TASCommand command = {
115 .buttons = ReadCommandButtons(seg_list[1]),
116 .l_axis = ReadCommandAxis(seg_list[2]),
117 .r_axis = ReadCommandAxis(seg_list[3]),
118 };
119 commands[player_index].push_back(command);
120 frame_no++;
121 }
122 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
123}
124
125void Tas::WriteTasFile(std::u8string_view file_name) {
126 std::string output_text;
127 for (size_t frame = 0; frame < record_commands.size(); frame++) {
128 const TASCommand& line = record_commands[frame];
129 output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
130 WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
131 }
132
133 const auto tas_file_name = Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name;
134 const auto bytes_written =
135 Common::FS::WriteStringToFile(tas_file_name, Common::FS::FileType::TextFile, output_text);
136 if (bytes_written == output_text.size()) {
137 LOG_INFO(Input, "TAS file written to file!");
138 } else {
139 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
140 output_text.size());
141 }
142}
143
144void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
145 last_input = {
146 .buttons = buttons,
147 .l_axis = left_axis,
148 .r_axis = right_axis,
149 };
150}
151
152std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
153 TasState state;
154 if (is_recording) {
155 return {TasState::Recording, 0, record_commands.size()};
156 }
157
158 if (is_running) {
159 state = TasState::Running;
160 } else {
161 state = TasState::Stopped;
162 }
163
164 return {state, current_command, script_length};
165}
166
167void Tas::UpdateThread() {
168 if (!Settings::values.tas_enable) {
169 if (is_running) {
170 Stop();
171 }
172 return;
173 }
174
175 if (is_recording) {
176 record_commands.push_back(last_input);
177 }
178 if (needs_reset) {
179 current_command = 0;
180 needs_reset = false;
181 LoadTasFiles();
182 LOG_DEBUG(Input, "tas_reset done");
183 }
184
185 if (!is_running) {
186 ClearInput();
187 return;
188 }
189 if (current_command < script_length) {
190 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
191 const size_t frame = current_command++;
192 for (size_t player_index = 0; player_index < commands.size(); player_index++) {
193 TASCommand command{};
194 if (frame < commands[player_index].size()) {
195 command = commands[player_index][frame];
196 }
197
198 PadIdentifier identifier{
199 .guid = Common::UUID{},
200 .port = player_index,
201 .pad = 0,
202 };
203 for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
204 const bool button_status = (command.buttons & (1LLU << i)) != 0;
205 const int button = static_cast<int>(i);
206 SetButton(identifier, button, button_status);
207 }
208 SetTasAxis(identifier, TasAxis::StickX, command.l_axis.x);
209 SetTasAxis(identifier, TasAxis::StickY, command.l_axis.y);
210 SetTasAxis(identifier, TasAxis::SubstickX, command.r_axis.x);
211 SetTasAxis(identifier, TasAxis::SubstickY, command.r_axis.y);
212 }
213 } else {
214 is_running = Settings::values.tas_loop.GetValue();
215 LoadTasFiles();
216 current_command = 0;
217 ClearInput();
218 }
219}
220
221void Tas::ClearInput() {
222 ResetButtonState();
223 ResetAnalogState();
224}
225
226TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
227 std::vector<std::string> seg_list;
228 {
229 std::istringstream line_stream(line);
230 std::string segment;
231 while (std::getline(line_stream, segment, ';')) {
232 seg_list.push_back(std::move(segment));
233 }
234 }
235
236 const float x = std::stof(seg_list.at(0)) / 32767.0f;
237 const float y = std::stof(seg_list.at(1)) / 32767.0f;
238
239 return {x, y};
240}
241
242u64 Tas::ReadCommandButtons(const std::string& line) const {
243 std::istringstream button_text(line);
244 std::string button_line;
245 u64 buttons = 0;
246 while (std::getline(button_text, button_line, ';')) {
247 for (const auto& [text, tas_button] : text_to_tas_button) {
248 if (text == button_line) {
249 buttons |= static_cast<u64>(tas_button);
250 break;
251 }
252 }
253 }
254 return buttons;
255}
256
257std::string Tas::WriteCommandButtons(u64 buttons) const {
258 std::string returns;
259 for (const auto& [text_button, tas_button] : text_to_tas_button) {
260 if ((buttons & static_cast<u64>(tas_button)) != 0) {
261 returns += fmt::format("{};", text_button);
262 }
263 }
264 return returns.empty() ? "NONE" : returns;
265}
266
267std::string Tas::WriteCommandAxis(TasAnalog analog) const {
268 return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
269}
270
271void Tas::SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value) {
272 SetAxis(identifier, static_cast<int>(axis), value);
273}
274
275void Tas::StartStop() {
276 if (!Settings::values.tas_enable) {
277 return;
278 }
279 if (is_running) {
280 Stop();
281 } else {
282 is_running = true;
283 }
284}
285
286void Tas::Stop() {
287 is_running = false;
288}
289
290void Tas::Reset() {
291 if (!Settings::values.tas_enable) {
292 return;
293 }
294 needs_reset = true;
295}
296
297bool Tas::Record() {
298 if (!Settings::values.tas_enable) {
299 return true;
300 }
301 is_recording = !is_recording;
302 return is_recording;
303}
304
305void Tas::SaveRecording(bool overwrite_file) {
306 if (is_recording) {
307 return;
308 }
309 if (record_commands.empty()) {
310 return;
311 }
312 WriteTasFile(u8"record.txt");
313 if (overwrite_file) {
314 WriteTasFile(u8"script0-1.txt");
315 }
316 needs_reset = true;
317 record_commands.clear();
318}
319
320} // namespace InputCommon::TasInput
diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h
new file mode 100644
index 000000000..4b4e6c417
--- /dev/null
+++ b/src/input_common/drivers/tas_input.h
@@ -0,0 +1,201 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <string>
9#include <vector>
10
11#include "common/common_types.h"
12#include "input_common/input_engine.h"
13
14/*
15To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
16Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
17for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
18
19A script file has the same format as TAS-nx uses, so final files will look like this:
20
211 KEY_B 0;0 0;0
226 KEY_ZL 0;0 0;0
2341 KEY_ZL;KEY_Y 0;0 0;0
2443 KEY_X;KEY_A 32767;0 0;0
2544 KEY_A 32767;0 0;0
2645 KEY_A 32767;0 0;0
2746 KEY_A 32767;0 0;0
2847 KEY_A 32767;0 0;0
29
30After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
31CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
32has. Playback can be started or stopped using CTRL+F5.
33
34However, for playback to actually work, the correct input device has to be selected: In the Controls
35menu, select TAS from the device list for the controller that the script should be played on.
36
37Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
38connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
39again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
40record.txt.
41
42For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
43P1).
44*/
45
46namespace InputCommon::TasInput {
47
48constexpr size_t PLAYER_NUMBER = 10;
49
50enum class TasButton : u64 {
51 BUTTON_A = 1U << 0,
52 BUTTON_B = 1U << 1,
53 BUTTON_X = 1U << 2,
54 BUTTON_Y = 1U << 3,
55 STICK_L = 1U << 4,
56 STICK_R = 1U << 5,
57 TRIGGER_L = 1U << 6,
58 TRIGGER_R = 1U << 7,
59 TRIGGER_ZL = 1U << 8,
60 TRIGGER_ZR = 1U << 9,
61 BUTTON_PLUS = 1U << 10,
62 BUTTON_MINUS = 1U << 11,
63 BUTTON_LEFT = 1U << 12,
64 BUTTON_UP = 1U << 13,
65 BUTTON_RIGHT = 1U << 14,
66 BUTTON_DOWN = 1U << 15,
67 BUTTON_SL = 1U << 16,
68 BUTTON_SR = 1U << 17,
69 BUTTON_HOME = 1U << 18,
70 BUTTON_CAPTURE = 1U << 19,
71};
72
73struct TasAnalog {
74 float x{};
75 float y{};
76};
77
78enum class TasState {
79 Running,
80 Recording,
81 Stopped,
82};
83
84class Tas final : public InputEngine {
85public:
86 explicit Tas(std::string input_engine_);
87 ~Tas() override;
88
89 /**
90 * Changes the input status that will be stored in each frame
91 * @param buttons Bitfield with the status of the buttons
92 * @param left_axis Value of the left axis
93 * @param right_axis Value of the right axis
94 */
95 void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis);
96
97 // Main loop that records or executes input
98 void UpdateThread();
99
100 // Sets the flag to start or stop the TAS command execution and swaps controllers profiles
101 void StartStop();
102
103 // Stop the TAS and reverts any controller profile
104 void Stop();
105
106 // Sets the flag to reload the file and start from the beginning in the next update
107 void Reset();
108
109 /**
110 * Sets the flag to enable or disable recording of inputs
111 * @returns true if the current recording status is enabled
112 */
113 bool Record();
114
115 /**
116 * Saves contents of record_commands on a file
117 * @param overwrite_file Indicates if player 1 should be overwritten
118 */
119 void SaveRecording(bool overwrite_file);
120
121 /**
122 * Returns the current status values of TAS playback/recording
123 * @returns A Tuple of
124 * TasState indicating the current state out of Running ;
125 * Current playback progress ;
126 * Total length of script file currently loaded or being recorded
127 */
128 std::tuple<TasState, size_t, size_t> GetStatus() const;
129
130private:
131 enum class TasAxis : u8;
132
133 struct TASCommand {
134 u64 buttons{};
135 TasAnalog l_axis{};
136 TasAnalog r_axis{};
137 };
138
139 /// Loads TAS files from all players
140 void LoadTasFiles();
141
142 /**
143 * Loads TAS file from the specified player
144 * @param player_index Player number to save the script
145 * @param file_index Script number of the file
146 */
147 void LoadTasFile(size_t player_index, size_t file_index);
148
149 /**
150 * Writes a TAS file from the recorded commands
151 * @param file_name Name of the file to be written
152 */
153 void WriteTasFile(std::u8string_view file_name);
154
155 /**
156 * Parses a string containing the axis values. X and Y have a range from -32767 to 32767
157 * @param line String containing axis values with the following format "x;y"
158 * @returns A TAS analog object with axis values with range from -1.0 to 1.0
159 */
160 TasAnalog ReadCommandAxis(const std::string& line) const;
161
162 /**
163 * Parses a string containing the button values. Each button is represented by it's text format
164 * specified in text_to_tas_button array
165 * @param line string containing button name with the following format "a;b;c;d..."
166 * @returns A u64 with each bit representing the status of a button
167 */
168 u64 ReadCommandButtons(const std::string& line) const;
169
170 /**
171 * Reset state of all players
172 */
173 void ClearInput();
174
175 /**
176 * Converts an u64 containing the button status into the text equivalent
177 * @param buttons Bitfield with the status of the buttons
178 * @returns A string with the name of the buttons to be written to the file
179 */
180 std::string WriteCommandButtons(u64 buttons) const;
181
182 /**
183 * Converts an TAS analog object containing the axis status into the text equivalent
184 * @param analog Value of the axis
185 * @returns A string with the value of the axis to be written to the file
186 */
187 std::string WriteCommandAxis(TasAnalog analog) const;
188
189 /// Sets an axis for a particular pad to the given value.
190 void SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value);
191
192 size_t script_length{0};
193 bool is_recording{false};
194 bool is_running{false};
195 bool needs_reset{false};
196 std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
197 std::vector<TASCommand> record_commands{};
198 size_t current_command{0};
199 TASCommand last_input{}; // only used for recording
200};
201} // namespace InputCommon::TasInput
diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp
new file mode 100644
index 000000000..880781825
--- /dev/null
+++ b/src/input_common/drivers/touch_screen.cpp
@@ -0,0 +1,53 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/param_package.h"
6#include "input_common/drivers/touch_screen.h"
7
8namespace InputCommon {
9
10constexpr PadIdentifier identifier = {
11 .guid = Common::UUID{Common::INVALID_UUID},
12 .port = 0,
13 .pad = 0,
14};
15
16TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
17 PreSetController(identifier);
18}
19
20void TouchScreen::TouchMoved(float x, float y, std::size_t finger) {
21 if (finger >= 16) {
22 return;
23 }
24 TouchPressed(x, y, finger);
25}
26
27void TouchScreen::TouchPressed(float x, float y, std::size_t finger) {
28 if (finger >= 16) {
29 return;
30 }
31 SetButton(identifier, static_cast<int>(finger), true);
32 SetAxis(identifier, static_cast<int>(finger * 2), x);
33 SetAxis(identifier, static_cast<int>(finger * 2 + 1), y);
34}
35
36void TouchScreen::TouchReleased(std::size_t finger) {
37 if (finger >= 16) {
38 return;
39 }
40 SetButton(identifier, static_cast<int>(finger), false);
41 SetAxis(identifier, static_cast<int>(finger * 2), 0.0f);
42 SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f);
43}
44
45void TouchScreen::ReleaseAllTouch() {
46 for (int index = 0; index < 16; ++index) {
47 SetButton(identifier, index, false);
48 SetAxis(identifier, index * 2, 0.0f);
49 SetAxis(identifier, index * 2 + 1, 0.0f);
50 }
51}
52
53} // namespace InputCommon
diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h
new file mode 100644
index 000000000..bf395c40b
--- /dev/null
+++ b/src/input_common/drivers/touch_screen.h
@@ -0,0 +1,44 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#pragma once
6
7#include "input_common/input_engine.h"
8
9namespace InputCommon {
10
11/**
12 * A button device factory representing a keyboard. It receives keyboard events and forward them
13 * to all button devices it created.
14 */
15class TouchScreen final : public InputEngine {
16public:
17 explicit TouchScreen(std::string input_engine_);
18
19 /**
20 * Signals that mouse has moved.
21 * @param x the x-coordinate of the cursor
22 * @param y the y-coordinate of the cursor
23 * @param center_x the x-coordinate of the middle of the screen
24 * @param center_y the y-coordinate of the middle of the screen
25 */
26 void TouchMoved(float x, float y, std::size_t finger);
27
28 /**
29 * Sets the status of all buttons bound with the key to pressed
30 * @param key_code the code of the key to press
31 */
32 void TouchPressed(float x, float y, std::size_t finger);
33
34 /**
35 * Sets the status of all buttons bound with the key to released
36 * @param key_code the code of the key to release
37 */
38 void TouchReleased(std::size_t finger);
39
40 /// Resets all inputs to their initial value
41 void ReleaseAllTouch();
42};
43
44} // namespace InputCommon
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
new file mode 100644
index 000000000..4ab991a7d
--- /dev/null
+++ b/src/input_common/drivers/udp_client.cpp
@@ -0,0 +1,591 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <random>
6#include <boost/asio.hpp>
7#include <fmt/format.h>
8
9#include "common/logging/log.h"
10#include "common/param_package.h"
11#include "common/settings.h"
12#include "input_common/drivers/udp_client.h"
13#include "input_common/helpers/udp_protocol.h"
14
15using boost::asio::ip::udp;
16
17namespace InputCommon::CemuhookUDP {
18
19struct SocketCallback {
20 std::function<void(Response::Version)> version;
21 std::function<void(Response::PortInfo)> port_info;
22 std::function<void(Response::PadData)> pad_data;
23};
24
25class Socket {
26public:
27 using clock = std::chrono::system_clock;
28
29 explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
30 : callback(std::move(callback_)), timer(io_service),
31 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
32 boost::system::error_code ec{};
33 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
34 if (ec.value() != boost::system::errc::success) {
35 LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
36 ipv4 = boost::asio::ip::address_v4{};
37 }
38
39 send_endpoint = {udp::endpoint(ipv4, port)};
40 }
41
42 void Stop() {
43 io_service.stop();
44 }
45
46 void Loop() {
47 io_service.run();
48 }
49
50 void StartSend(const clock::time_point& from) {
51 timer.expires_at(from + std::chrono::seconds(3));
52 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
53 }
54
55 void StartReceive() {
56 socket.async_receive_from(
57 boost::asio::buffer(receive_buffer), receive_endpoint,
58 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
59 HandleReceive(error, bytes_transferred);
60 });
61 }
62
63private:
64 u32 GenerateRandomClientId() const {
65 std::random_device device;
66 return device();
67 }
68
69 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
70 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
71 switch (*type) {
72 case Type::Version: {
73 Response::Version version;
74 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
75 callback.version(std::move(version));
76 break;
77 }
78 case Type::PortInfo: {
79 Response::PortInfo port_info;
80 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
81 sizeof(Response::PortInfo));
82 callback.port_info(std::move(port_info));
83 break;
84 }
85 case Type::PadData: {
86 Response::PadData pad_data;
87 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
88 callback.pad_data(std::move(pad_data));
89 break;
90 }
91 }
92 }
93 StartReceive();
94 }
95
96 void HandleSend(const boost::system::error_code&) {
97 boost::system::error_code _ignored{};
98 // Send a request for getting port info for the pad
99 const Request::PortInfo port_info{4, {0, 1, 2, 3}};
100 const auto port_message = Request::Create(port_info, client_id);
101 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
102 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
103
104 // Send a request for getting pad data for the pad
105 const Request::PadData pad_data{
106 Request::RegisterFlags::AllPads,
107 0,
108 EMPTY_MAC_ADDRESS,
109 };
110 const auto pad_message = Request::Create(pad_data, client_id);
111 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
112 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
113 StartSend(timer.expiry());
114 }
115
116 SocketCallback callback;
117 boost::asio::io_service io_service;
118 boost::asio::basic_waitable_timer<clock> timer;
119 udp::socket socket;
120
121 const u32 client_id;
122
123 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
124 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
125 std::array<u8, PORT_INFO_SIZE> send_buffer1;
126 std::array<u8, PAD_DATA_SIZE> send_buffer2;
127 udp::endpoint send_endpoint;
128
129 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
130 udp::endpoint receive_endpoint;
131};
132
133static void SocketLoop(Socket* socket) {
134 socket->StartReceive();
135 socket->StartSend(Socket::clock::now());
136 socket->Loop();
137}
138
139UDPClient::UDPClient(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
140 LOG_INFO(Input, "Udp Initialization started");
141 ReloadSockets();
142}
143
144UDPClient::~UDPClient() {
145 Reset();
146}
147
148UDPClient::ClientConnection::ClientConnection() = default;
149
150UDPClient::ClientConnection::~ClientConnection() = default;
151
152void UDPClient::ReloadSockets() {
153 Reset();
154
155 std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
156 std::string server_token;
157 std::size_t client = 0;
158 while (std::getline(servers_ss, server_token, ',')) {
159 if (client == MAX_UDP_CLIENTS) {
160 break;
161 }
162 std::stringstream server_ss(server_token);
163 std::string token;
164 std::getline(server_ss, token, ':');
165 std::string udp_input_address = token;
166 std::getline(server_ss, token, ':');
167 char* temp;
168 const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
169 if (*temp != '\0') {
170 LOG_ERROR(Input, "Port number is not valid {}", token);
171 continue;
172 }
173
174 const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
175 if (client_number != MAX_UDP_CLIENTS) {
176 LOG_ERROR(Input, "Duplicated UDP servers found");
177 continue;
178 }
179 StartCommunication(client++, udp_input_address, udp_input_port);
180 }
181}
182
183std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
184 for (std::size_t client = 0; client < clients.size(); client++) {
185 if (clients[client].active == -1) {
186 continue;
187 }
188 if (clients[client].host == host && clients[client].port == port) {
189 return client;
190 }
191 }
192 return MAX_UDP_CLIENTS;
193}
194
195void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
196 LOG_TRACE(Input, "Version packet received: {}", data.version);
197}
198
199void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
200 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
201}
202
203void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
204 const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
205
206 if (pad_index >= pads.size()) {
207 LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
208 return;
209 }
210
211 LOG_TRACE(Input, "PadData packet received");
212 if (data.packet_counter == pads[pad_index].packet_sequence) {
213 LOG_WARNING(
214 Input,
215 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
216 pads[pad_index].packet_sequence, data.packet_counter);
217 pads[pad_index].connected = false;
218 return;
219 }
220
221 clients[client].active = 1;
222 pads[pad_index].connected = true;
223 pads[pad_index].packet_sequence = data.packet_counter;
224
225 const auto now = std::chrono::steady_clock::now();
226 const auto time_difference = static_cast<u64>(
227 std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
228 .count());
229 pads[pad_index].last_update = now;
230
231 // Gyroscope values are not it the correct scale from better joy.
232 // Dividing by 312 allows us to make one full turn = 1 turn
233 // This must be a configurable valued called sensitivity
234 const float gyro_scale = 1.0f / 312.0f;
235
236 const BasicMotion motion{
237 .gyro_x = data.gyro.pitch * gyro_scale,
238 .gyro_y = data.gyro.roll * gyro_scale,
239 .gyro_z = -data.gyro.yaw * gyro_scale,
240 .accel_x = data.accel.x,
241 .accel_y = -data.accel.z,
242 .accel_z = data.accel.y,
243 .delta_timestamp = time_difference,
244 };
245 const PadIdentifier identifier = GetPadIdentifier(pad_index);
246 SetMotion(identifier, 0, motion);
247
248 for (std::size_t id = 0; id < data.touch.size(); ++id) {
249 const auto touch_pad = data.touch[id];
250 const auto touch_axis_x_id =
251 static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
252 const auto touch_axis_y_id =
253 static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
254 const auto touch_button_id =
255 static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::touch2);
256
257 // TODO: Use custom calibration per device
258 const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
259 const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
260 const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
261 const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
262 const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
263
264 const f32 x =
265 static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
266 static_cast<f32>(max_x - min_x);
267 const f32 y =
268 static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
269 static_cast<f32>(max_y - min_y);
270
271 if (touch_pad.is_active) {
272 SetAxis(identifier, touch_axis_x_id, x);
273 SetAxis(identifier, touch_axis_y_id, y);
274 SetButton(identifier, touch_button_id, true);
275 continue;
276 }
277 SetAxis(identifier, touch_axis_x_id, 0);
278 SetAxis(identifier, touch_axis_y_id, 0);
279 SetButton(identifier, touch_button_id, false);
280 }
281
282 SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX),
283 (data.left_stick_x - 127.0f) / 127.0f);
284 SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
285 (data.left_stick_y - 127.0f) / 127.0f);
286 SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
287 (data.right_stick_x - 127.0f) / 127.0f);
288 SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
289 (data.right_stick_y - 127.0f) / 127.0f);
290
291 static constexpr std::array<PadButton, 16> buttons{
292 PadButton::Share, PadButton::L3, PadButton::R3, PadButton::Options,
293 PadButton::Up, PadButton::Right, PadButton::Down, PadButton::Left,
294 PadButton::L2, PadButton::R2, PadButton::L1, PadButton::R1,
295 PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square};
296
297 for (std::size_t i = 0; i < buttons.size(); ++i) {
298 const bool button_status = (data.digital_button & (1U << i)) != 0;
299 const int button = static_cast<int>(buttons[i]);
300 SetButton(identifier, button, button_status);
301 }
302}
303
304void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
305 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
306 [this](Response::PortInfo info) { OnPortInfo(info); },
307 [this, client](Response::PadData data) { OnPadData(data, client); }};
308 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
309 clients[client].uuid = GetHostUUID(host);
310 clients[client].host = host;
311 clients[client].port = port;
312 clients[client].active = 0;
313 clients[client].socket = std::make_unique<Socket>(host, port, callback);
314 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
315 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
316 const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
317 PreSetController(identifier);
318 }
319}
320
321const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
322 const std::size_t client = pad_index / PADS_PER_CLIENT;
323 return {
324 .guid = clients[client].uuid,
325 .port = static_cast<std::size_t>(clients[client].port),
326 .pad = pad_index,
327 };
328}
329
330const Common::UUID UDPClient::GetHostUUID(const std::string host) const {
331 const auto ip = boost::asio::ip::address_v4::from_string(host);
332 const auto hex_host = fmt::format("{:06x}", ip.to_ulong());
333 return Common::UUID{hex_host};
334}
335
336void UDPClient::Reset() {
337 for (auto& client : clients) {
338 if (client.thread.joinable()) {
339 client.active = -1;
340 client.socket->Stop();
341 client.thread.join();
342 }
343 }
344}
345
346std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
347 std::vector<Common::ParamPackage> devices;
348 if (!Settings::values.enable_udp_controller) {
349 return devices;
350 }
351 for (std::size_t client = 0; client < clients.size(); client++) {
352 if (clients[client].active != 1) {
353 continue;
354 }
355 for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
356 const std::size_t pad_index = client * PADS_PER_CLIENT + index;
357 if (!pads[pad_index].connected) {
358 continue;
359 }
360 const auto pad_identifier = GetPadIdentifier(pad_index);
361 Common::ParamPackage identifier{};
362 identifier.Set("engine", GetEngineName());
363 identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad));
364 identifier.Set("guid", pad_identifier.guid.Format());
365 identifier.Set("port", static_cast<int>(pad_identifier.port));
366 identifier.Set("pad", static_cast<int>(pad_identifier.pad));
367 devices.emplace_back(identifier);
368 }
369 }
370 return devices;
371}
372
373ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
374 // This list excludes any button that can't be really mapped
375 static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 18>
376 switch_to_dsu_button = {
377 std::pair{Settings::NativeButton::A, PadButton::Circle},
378 {Settings::NativeButton::B, PadButton::Cross},
379 {Settings::NativeButton::X, PadButton::Triangle},
380 {Settings::NativeButton::Y, PadButton::Square},
381 {Settings::NativeButton::Plus, PadButton::Options},
382 {Settings::NativeButton::Minus, PadButton::Share},
383 {Settings::NativeButton::DLeft, PadButton::Left},
384 {Settings::NativeButton::DUp, PadButton::Up},
385 {Settings::NativeButton::DRight, PadButton::Right},
386 {Settings::NativeButton::DDown, PadButton::Down},
387 {Settings::NativeButton::L, PadButton::L1},
388 {Settings::NativeButton::R, PadButton::R1},
389 {Settings::NativeButton::ZL, PadButton::L2},
390 {Settings::NativeButton::ZR, PadButton::R2},
391 {Settings::NativeButton::SL, PadButton::L2},
392 {Settings::NativeButton::SR, PadButton::R2},
393 {Settings::NativeButton::LStick, PadButton::L3},
394 {Settings::NativeButton::RStick, PadButton::R3},
395 };
396 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
397 return {};
398 }
399
400 ButtonMapping mapping{};
401 for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) {
402 Common::ParamPackage button_params{};
403 button_params.Set("engine", GetEngineName());
404 button_params.Set("guid", params.Get("guid", ""));
405 button_params.Set("port", params.Get("port", 0));
406 button_params.Set("pad", params.Get("pad", 0));
407 button_params.Set("button", static_cast<int>(dsu_button));
408 mapping.insert_or_assign(switch_button, std::move(button_params));
409 }
410
411 return mapping;
412}
413
414AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
415 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
416 return {};
417 }
418
419 AnalogMapping mapping = {};
420 Common::ParamPackage left_analog_params;
421 left_analog_params.Set("engine", GetEngineName());
422 left_analog_params.Set("guid", params.Get("guid", ""));
423 left_analog_params.Set("port", params.Get("port", 0));
424 left_analog_params.Set("pad", params.Get("pad", 0));
425 left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
426 left_analog_params.Set("axis_y", static_cast<int>(PadAxes::LeftStickY));
427 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
428 Common::ParamPackage right_analog_params;
429 right_analog_params.Set("engine", GetEngineName());
430 right_analog_params.Set("guid", params.Get("guid", ""));
431 right_analog_params.Set("port", params.Get("port", 0));
432 right_analog_params.Set("pad", params.Get("pad", 0));
433 right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
434 right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY));
435 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
436 return mapping;
437}
438
439MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) {
440 if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
441 return {};
442 }
443
444 MotionMapping mapping = {};
445 Common::ParamPackage motion_params;
446 motion_params.Set("engine", GetEngineName());
447 motion_params.Set("guid", params.Get("guid", ""));
448 motion_params.Set("port", params.Get("port", 0));
449 motion_params.Set("pad", params.Get("pad", 0));
450 motion_params.Set("motion", 0);
451 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params));
452 mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params));
453 return mapping;
454}
455
456Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const {
457 PadButton button = static_cast<PadButton>(params.Get("button", 0));
458 switch (button) {
459 case PadButton::Left:
460 return Common::Input::ButtonNames::ButtonLeft;
461 case PadButton::Right:
462 return Common::Input::ButtonNames::ButtonRight;
463 case PadButton::Down:
464 return Common::Input::ButtonNames::ButtonDown;
465 case PadButton::Up:
466 return Common::Input::ButtonNames::ButtonUp;
467 case PadButton::L1:
468 return Common::Input::ButtonNames::L1;
469 case PadButton::L2:
470 return Common::Input::ButtonNames::L2;
471 case PadButton::L3:
472 return Common::Input::ButtonNames::L3;
473 case PadButton::R1:
474 return Common::Input::ButtonNames::R1;
475 case PadButton::R2:
476 return Common::Input::ButtonNames::R2;
477 case PadButton::R3:
478 return Common::Input::ButtonNames::R3;
479 case PadButton::Circle:
480 return Common::Input::ButtonNames::Circle;
481 case PadButton::Cross:
482 return Common::Input::ButtonNames::Cross;
483 case PadButton::Square:
484 return Common::Input::ButtonNames::Square;
485 case PadButton::Triangle:
486 return Common::Input::ButtonNames::Triangle;
487 case PadButton::Share:
488 return Common::Input::ButtonNames::Share;
489 case PadButton::Options:
490 return Common::Input::ButtonNames::Options;
491 default:
492 return Common::Input::ButtonNames::Undefined;
493 }
494}
495
496Common::Input::ButtonNames UDPClient::GetUIName(const Common::ParamPackage& params) const {
497 if (params.Has("button")) {
498 return GetUIButtonName(params);
499 }
500 if (params.Has("axis")) {
501 return Common::Input::ButtonNames::Value;
502 }
503 if (params.Has("motion")) {
504 return Common::Input::ButtonNames::Engine;
505 }
506
507 return Common::Input::ButtonNames::Invalid;
508}
509
510void TestCommunication(const std::string& host, u16 port,
511 const std::function<void()>& success_callback,
512 const std::function<void()>& failure_callback) {
513 std::thread([=] {
514 Common::Event success_event;
515 SocketCallback callback{
516 .version = [](Response::Version) {},
517 .port_info = [](Response::PortInfo) {},
518 .pad_data = [&](Response::PadData) { success_event.Set(); },
519 };
520 Socket socket{host, port, std::move(callback)};
521 std::thread worker_thread{SocketLoop, &socket};
522 const bool result =
523 success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
524 socket.Stop();
525 worker_thread.join();
526 if (result) {
527 success_callback();
528 } else {
529 failure_callback();
530 }
531 }).detach();
532}
533
534CalibrationConfigurationJob::CalibrationConfigurationJob(
535 const std::string& host, u16 port, std::function<void(Status)> status_callback,
536 std::function<void(u16, u16, u16, u16)> data_callback) {
537
538 std::thread([=, this] {
539 Status current_status{Status::Initialized};
540 SocketCallback callback{
541 [](Response::Version) {}, [](Response::PortInfo) {},
542 [&](Response::PadData data) {
543 static constexpr u16 CALIBRATION_THRESHOLD = 100;
544 static constexpr u16 MAX_VALUE = UINT16_MAX;
545
546 if (current_status == Status::Initialized) {
547 // Receiving data means the communication is ready now
548 current_status = Status::Ready;
549 status_callback(current_status);
550 }
551 const auto& touchpad_0 = data.touch[0];
552 if (touchpad_0.is_active == 0) {
553 return;
554 }
555 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
556 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
557 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
558 if (current_status == Status::Ready) {
559 // First touch - min data (min_x/min_y)
560 current_status = Status::Stage1Completed;
561 status_callback(current_status);
562 }
563 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
564 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
565 // Set the current position as max value and finishes configuration
566 const u16 max_x = touchpad_0.x;
567 const u16 max_y = touchpad_0.y;
568 current_status = Status::Completed;
569 data_callback(min_x, min_y, max_x, max_y);
570 status_callback(current_status);
571
572 complete_event.Set();
573 }
574 }};
575 Socket socket{host, port, std::move(callback)};
576 std::thread worker_thread{SocketLoop, &socket};
577 complete_event.Wait();
578 socket.Stop();
579 worker_thread.join();
580 }).detach();
581}
582
583CalibrationConfigurationJob::~CalibrationConfigurationJob() {
584 Stop();
585}
586
587void CalibrationConfigurationJob::Stop() {
588 complete_event.Set();
589}
590
591} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h
new file mode 100644
index 000000000..1adc947c4
--- /dev/null
+++ b/src/input_common/drivers/udp_client.h
@@ -0,0 +1,185 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <optional>
8
9#include "common/common_types.h"
10#include "common/thread.h"
11#include "input_common/input_engine.h"
12
13namespace InputCommon::CemuhookUDP {
14
15class Socket;
16
17namespace Response {
18struct PadData;
19struct PortInfo;
20struct TouchPad;
21struct Version;
22} // namespace Response
23
24enum class PadTouch {
25 Click,
26 Undefined,
27};
28
29struct UDPPadStatus {
30 std::string host{"127.0.0.1"};
31 u16 port{26760};
32 std::size_t pad_index{};
33};
34
35struct DeviceStatus {
36 std::mutex update_mutex;
37
38 // calibration data for scaling the device's touch area to 3ds
39 struct CalibrationData {
40 u16 min_x{};
41 u16 min_y{};
42 u16 max_x{};
43 u16 max_y{};
44 };
45 std::optional<CalibrationData> touch_calibration;
46};
47
48/**
49 * A button device factory representing a keyboard. It receives keyboard events and forward them
50 * to all button devices it created.
51 */
52class UDPClient final : public InputEngine {
53public:
54 explicit UDPClient(std::string input_engine_);
55 ~UDPClient() override;
56
57 void ReloadSockets();
58
59 /// Used for automapping features
60 std::vector<Common::ParamPackage> GetInputDevices() const override;
61 ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
62 AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
63 MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
64 Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
65
66private:
67 enum class PadButton {
68 Undefined = 0x0000,
69 Share = 0x0001,
70 L3 = 0x0002,
71 R3 = 0x0004,
72 Options = 0x0008,
73 Up = 0x0010,
74 Right = 0x0020,
75 Down = 0x0040,
76 Left = 0x0080,
77 L2 = 0x0100,
78 R2 = 0x0200,
79 L1 = 0x0400,
80 R1 = 0x0800,
81 Triangle = 0x1000,
82 Circle = 0x2000,
83 Cross = 0x4000,
84 Square = 0x8000,
85 Touch1 = 0x10000,
86 touch2 = 0x20000,
87 };
88
89 enum class PadAxes : u8 {
90 LeftStickX,
91 LeftStickY,
92 RightStickX,
93 RightStickY,
94 AnalogLeft,
95 AnalogDown,
96 AnalogRight,
97 AnalogUp,
98 AnalogSquare,
99 AnalogCross,
100 AnalogCircle,
101 AnalogTriangle,
102 AnalogR1,
103 AnalogL1,
104 AnalogR2,
105 AnalogL3,
106 AnalogR3,
107 Touch1X,
108 Touch1Y,
109 Touch2X,
110 Touch2Y,
111 Undefined,
112 };
113
114 struct PadData {
115 std::size_t pad_index{};
116 bool connected{};
117 DeviceStatus status;
118 u64 packet_sequence{};
119
120 std::chrono::time_point<std::chrono::steady_clock> last_update;
121 };
122
123 struct ClientConnection {
124 ClientConnection();
125 ~ClientConnection();
126 Common::UUID uuid{"7F000001"};
127 std::string host{"127.0.0.1"};
128 u16 port{26760};
129 s8 active{-1};
130 std::unique_ptr<Socket> socket;
131 std::thread thread;
132 };
133
134 // For shutting down, clear all data, join all threads, release usb
135 void Reset();
136
137 // Translates configuration to client number
138 std::size_t GetClientNumber(std::string_view host, u16 port) const;
139
140 void OnVersion(Response::Version);
141 void OnPortInfo(Response::PortInfo);
142 void OnPadData(Response::PadData, std::size_t client);
143 void StartCommunication(std::size_t client, const std::string& host, u16 port);
144 const PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
145 const Common::UUID GetHostUUID(const std::string host) const;
146
147 Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
148
149 // Allocate clients for 8 udp servers
150 static constexpr std::size_t MAX_UDP_CLIENTS = 8;
151 static constexpr std::size_t PADS_PER_CLIENT = 4;
152 std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
153 std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
154};
155
156/// An async job allowing configuration of the touchpad calibration.
157class CalibrationConfigurationJob {
158public:
159 enum class Status {
160 Initialized,
161 Ready,
162 Stage1Completed,
163 Completed,
164 };
165 /**
166 * Constructs and starts the job with the specified parameter.
167 *
168 * @param status_callback Callback for job status updates
169 * @param data_callback Called when calibration data is ready
170 */
171 explicit CalibrationConfigurationJob(const std::string& host, u16 port,
172 std::function<void(Status)> status_callback,
173 std::function<void(u16, u16, u16, u16)> data_callback);
174 ~CalibrationConfigurationJob();
175 void Stop();
176
177private:
178 Common::Event complete_event;
179};
180
181void TestCommunication(const std::string& host, u16 port,
182 const std::function<void()>& success_callback,
183 const std::function<void()>& failure_callback);
184
185} // namespace InputCommon::CemuhookUDP