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