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