summaryrefslogtreecommitdiff
path: root/src/input_common/sdl/sdl_impl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/sdl/sdl_impl.cpp')
-rw-r--r--src/input_common/sdl/sdl_impl.cpp1658
1 files changed, 0 insertions, 1658 deletions
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
deleted file mode 100644
index ecb00d428..000000000
--- a/src/input_common/sdl/sdl_impl.cpp
+++ /dev/null
@@ -1,1658 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <array>
7#include <atomic>
8#include <chrono>
9#include <cmath>
10#include <functional>
11#include <mutex>
12#include <optional>
13#include <sstream>
14#include <string>
15#include <thread>
16#include <tuple>
17#include <unordered_map>
18#include <utility>
19#include <vector>
20
21#include "common/logging/log.h"
22#include "common/math_util.h"
23#include "common/param_package.h"
24#include "common/settings.h"
25#include "common/threadsafe_queue.h"
26#include "core/frontend/input.h"
27#include "input_common/motion_input.h"
28#include "input_common/sdl/sdl_impl.h"
29
30namespace InputCommon::SDL {
31
32namespace {
33std::string GetGUID(SDL_Joystick* joystick) {
34 const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
35 char guid_str[33];
36 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
37 return guid_str;
38}
39
40/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
41Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
42} // Anonymous namespace
43
44static int SDLEventWatcher(void* user_data, SDL_Event* event) {
45 auto* const sdl_state = static_cast<SDLState*>(user_data);
46
47 // Don't handle the event if we are configuring
48 if (sdl_state->polling) {
49 sdl_state->event_queue.Push(*event);
50 } else {
51 sdl_state->HandleGameControllerEvent(*event);
52 }
53
54 return 0;
55}
56
57class SDLJoystick {
58public:
59 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
60 SDL_GameController* game_controller)
61 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose},
62 sdl_controller{game_controller, &SDL_GameControllerClose} {
63 EnableMotion();
64 }
65
66 void EnableMotion() {
67 if (sdl_controller) {
68 SDL_GameController* controller = sdl_controller.get();
69 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) {
70 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE);
71 has_accel = true;
72 }
73 if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) {
74 SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE);
75 has_gyro = true;
76 }
77 }
78 }
79
80 void SetButton(int button, bool value) {
81 std::lock_guard lock{mutex};
82 state.buttons.insert_or_assign(button, value);
83 }
84
85 void PreSetButton(int button) {
86 if (!state.buttons.contains(button)) {
87 SetButton(button, false);
88 }
89 }
90
91 void SetMotion(SDL_ControllerSensorEvent event) {
92 constexpr float gravity_constant = 9.80665f;
93 std::lock_guard lock{mutex};
94 u64 time_difference = event.timestamp - last_motion_update;
95 last_motion_update = event.timestamp;
96 switch (event.sensor) {
97 case SDL_SENSOR_ACCEL: {
98 const Common::Vec3f acceleration = {-event.data[0], event.data[2], -event.data[1]};
99 motion.SetAcceleration(acceleration / gravity_constant);
100 break;
101 }
102 case SDL_SENSOR_GYRO: {
103 const Common::Vec3f gyroscope = {event.data[0], -event.data[2], event.data[1]};
104 motion.SetGyroscope(gyroscope / (Common::PI * 2));
105 break;
106 }
107 }
108
109 // Ignore duplicated timestamps
110 if (time_difference == 0) {
111 return;
112 }
113
114 motion.SetGyroThreshold(0.0001f);
115 motion.UpdateRotation(time_difference * 1000);
116 motion.UpdateOrientation(time_difference * 1000);
117 }
118
119 bool GetButton(int button) const {
120 std::lock_guard lock{mutex};
121 return state.buttons.at(button);
122 }
123
124 bool ToggleButton(int button) {
125 std::lock_guard lock{mutex};
126
127 if (!state.toggle_buttons.contains(button) || !state.lock_buttons.contains(button)) {
128 state.toggle_buttons.insert_or_assign(button, false);
129 state.lock_buttons.insert_or_assign(button, false);
130 }
131
132 const bool button_state = state.toggle_buttons.at(button);
133 const bool button_lock = state.lock_buttons.at(button);
134
135 if (button_lock) {
136 return button_state;
137 }
138
139 state.lock_buttons.insert_or_assign(button, true);
140
141 if (button_state) {
142 state.toggle_buttons.insert_or_assign(button, false);
143 } else {
144 state.toggle_buttons.insert_or_assign(button, true);
145 }
146
147 return !button_state;
148 }
149
150 bool UnlockButton(int button) {
151 std::lock_guard lock{mutex};
152 if (!state.toggle_buttons.contains(button)) {
153 return false;
154 }
155 state.lock_buttons.insert_or_assign(button, false);
156 return state.toggle_buttons.at(button);
157 }
158
159 void SetAxis(int axis, Sint16 value) {
160 std::lock_guard lock{mutex};
161 state.axes.insert_or_assign(axis, value);
162 }
163
164 void PreSetAxis(int axis) {
165 if (!state.axes.contains(axis)) {
166 SetAxis(axis, 0);
167 }
168 }
169
170 float GetAxis(int axis, float range, float offset) const {
171 std::lock_guard lock{mutex};
172 const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f;
173 const float offset_scale = (value + offset) > 0.0f ? 1.0f + offset : 1.0f - offset;
174 return (value + offset) / range / offset_scale;
175 }
176
177 bool RumblePlay(u16 amp_low, u16 amp_high) {
178 constexpr u32 rumble_max_duration_ms = 1000;
179
180 if (sdl_controller) {
181 return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high,
182 rumble_max_duration_ms) != -1;
183 } else if (sdl_joystick) {
184 return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high,
185 rumble_max_duration_ms) != -1;
186 }
187
188 return false;
189 }
190
191 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x,
192 float offset_y) const {
193 float x = GetAxis(axis_x, range, offset_x);
194 float y = GetAxis(axis_y, range, offset_y);
195 y = -y; // 3DS uses an y-axis inverse from SDL
196
197 // Make sure the coordinates are in the unit circle,
198 // otherwise normalize it.
199 float r = x * x + y * y;
200 if (r > 1.0f) {
201 r = std::sqrt(r);
202 x /= r;
203 y /= r;
204 }
205
206 return std::make_tuple(x, y);
207 }
208
209 bool HasGyro() const {
210 return has_gyro;
211 }
212
213 bool HasAccel() const {
214 return has_accel;
215 }
216
217 const MotionInput& GetMotion() const {
218 return motion;
219 }
220
221 void SetHat(int hat, Uint8 direction) {
222 std::lock_guard lock{mutex};
223 state.hats.insert_or_assign(hat, direction);
224 }
225
226 bool GetHatDirection(int hat, Uint8 direction) const {
227 std::lock_guard lock{mutex};
228 return (state.hats.at(hat) & direction) != 0;
229 }
230 /**
231 * The guid of the joystick
232 */
233 const std::string& GetGUID() const {
234 return guid;
235 }
236
237 /**
238 * The number of joystick from the same type that were connected before this joystick
239 */
240 int GetPort() const {
241 return port;
242 }
243
244 SDL_Joystick* GetSDLJoystick() const {
245 return sdl_joystick.get();
246 }
247
248 SDL_GameController* GetSDLGameController() const {
249 return sdl_controller.get();
250 }
251
252 void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) {
253 sdl_joystick.reset(joystick);
254 sdl_controller.reset(controller);
255 }
256
257 bool IsJoyconLeft() const {
258 const std::string controller_name = GetControllerName();
259 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
260 return true;
261 }
262 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
263 return true;
264 }
265 return false;
266 }
267
268 bool IsJoyconRight() const {
269 const std::string controller_name = GetControllerName();
270 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
271 return true;
272 }
273 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
274 return true;
275 }
276 return false;
277 }
278
279 std::string GetControllerName() const {
280 if (sdl_controller) {
281 switch (SDL_GameControllerGetType(sdl_controller.get())) {
282 case SDL_CONTROLLER_TYPE_XBOX360:
283 return "XBox 360 Controller";
284 case SDL_CONTROLLER_TYPE_XBOXONE:
285 return "XBox One Controller";
286 default:
287 break;
288 }
289 const auto name = SDL_GameControllerName(sdl_controller.get());
290 if (name) {
291 return name;
292 }
293 }
294
295 if (sdl_joystick) {
296 const auto name = SDL_JoystickName(sdl_joystick.get());
297 if (name) {
298 return name;
299 }
300 }
301
302 return "Unknown";
303 }
304
305private:
306 struct State {
307 std::unordered_map<int, bool> buttons;
308 std::unordered_map<int, bool> toggle_buttons{};
309 std::unordered_map<int, bool> lock_buttons{};
310 std::unordered_map<int, Sint16> axes;
311 std::unordered_map<int, Uint8> hats;
312 } state;
313 std::string guid;
314 int port;
315 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
316 std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
317 mutable std::mutex mutex;
318
319 // Motion is initialized with the PID values
320 MotionInput motion{0.3f, 0.005f, 0.0f};
321 u64 last_motion_update{};
322 bool has_gyro{false};
323 bool has_accel{false};
324};
325
326std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
327 std::lock_guard lock{joystick_map_mutex};
328 const auto it = joystick_map.find(guid);
329
330 if (it != joystick_map.end()) {
331 while (it->second.size() <= static_cast<std::size_t>(port)) {
332 auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()),
333 nullptr, nullptr);
334 it->second.emplace_back(std::move(joystick));
335 }
336
337 return it->second[static_cast<std::size_t>(port)];
338 }
339
340 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr);
341
342 return joystick_map[guid].emplace_back(std::move(joystick));
343}
344
345std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
346 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
347 const std::string guid = GetGUID(sdl_joystick);
348
349 std::lock_guard lock{joystick_map_mutex};
350 const auto map_it = joystick_map.find(guid);
351
352 if (map_it == joystick_map.end()) {
353 return nullptr;
354 }
355
356 const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
357 [&sdl_joystick](const auto& joystick) {
358 return joystick->GetSDLJoystick() == sdl_joystick;
359 });
360
361 if (vec_it == map_it->second.end()) {
362 return nullptr;
363 }
364
365 return *vec_it;
366}
367
368void SDLState::InitJoystick(int joystick_index) {
369 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
370 SDL_GameController* sdl_gamecontroller = nullptr;
371
372 if (SDL_IsGameController(joystick_index)) {
373 sdl_gamecontroller = SDL_GameControllerOpen(joystick_index);
374 }
375
376 if (!sdl_joystick) {
377 LOG_ERROR(Input, "Failed to open joystick {}", joystick_index);
378 return;
379 }
380
381 const std::string guid = GetGUID(sdl_joystick);
382
383 std::lock_guard lock{joystick_map_mutex};
384 if (joystick_map.find(guid) == joystick_map.end()) {
385 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller);
386 joystick_map[guid].emplace_back(std::move(joystick));
387 return;
388 }
389
390 auto& joystick_guid_list = joystick_map[guid];
391 const auto joystick_it =
392 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
393 [](const auto& joystick) { return !joystick->GetSDLJoystick(); });
394
395 if (joystick_it != joystick_guid_list.end()) {
396 (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller);
397 return;
398 }
399
400 const int port = static_cast<int>(joystick_guid_list.size());
401 auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller);
402 joystick_guid_list.emplace_back(std::move(joystick));
403}
404
405void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
406 const std::string guid = GetGUID(sdl_joystick);
407
408 std::lock_guard lock{joystick_map_mutex};
409 // This call to guid is safe since the joystick is guaranteed to be in the map
410 const auto& joystick_guid_list = joystick_map[guid];
411 const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
412 [&sdl_joystick](const auto& joystick) {
413 return joystick->GetSDLJoystick() == sdl_joystick;
414 });
415
416 if (joystick_it != joystick_guid_list.end()) {
417 (*joystick_it)->SetSDLJoystick(nullptr, nullptr);
418 }
419}
420
421void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
422 switch (event.type) {
423 case SDL_JOYBUTTONUP: {
424 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
425 joystick->SetButton(event.jbutton.button, false);
426 }
427 break;
428 }
429 case SDL_JOYBUTTONDOWN: {
430 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
431 joystick->SetButton(event.jbutton.button, true);
432 }
433 break;
434 }
435 case SDL_JOYHATMOTION: {
436 if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
437 joystick->SetHat(event.jhat.hat, event.jhat.value);
438 }
439 break;
440 }
441 case SDL_JOYAXISMOTION: {
442 if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
443 joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
444 }
445 break;
446 }
447 case SDL_CONTROLLERSENSORUPDATE: {
448 if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) {
449 joystick->SetMotion(event.csensor);
450 }
451 break;
452 }
453 case SDL_JOYDEVICEREMOVED:
454 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
455 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
456 break;
457 case SDL_JOYDEVICEADDED:
458 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
459 InitJoystick(event.jdevice.which);
460 break;
461 }
462}
463
464void SDLState::CloseJoysticks() {
465 std::lock_guard lock{joystick_map_mutex};
466 joystick_map.clear();
467}
468
469class SDLButton final : public Input::ButtonDevice {
470public:
471 explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_, bool toggle_)
472 : joystick(std::move(joystick_)), button(button_), toggle(toggle_) {}
473
474 bool GetStatus() const override {
475 const bool button_state = joystick->GetButton(button);
476 if (!toggle) {
477 return button_state;
478 }
479
480 if (button_state) {
481 return joystick->ToggleButton(button);
482 }
483 return joystick->UnlockButton(button);
484 }
485
486private:
487 std::shared_ptr<SDLJoystick> joystick;
488 int button;
489 bool toggle;
490};
491
492class SDLDirectionButton final : public Input::ButtonDevice {
493public:
494 explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
495 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
496
497 bool GetStatus() const override {
498 return joystick->GetHatDirection(hat, direction);
499 }
500
501private:
502 std::shared_ptr<SDLJoystick> joystick;
503 int hat;
504 Uint8 direction;
505};
506
507class SDLAxisButton final : public Input::ButtonDevice {
508public:
509 explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
510 bool trigger_if_greater_)
511 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
512 trigger_if_greater(trigger_if_greater_) {}
513
514 bool GetStatus() const override {
515 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
516 if (trigger_if_greater) {
517 return axis_value > threshold;
518 }
519 return axis_value < threshold;
520 }
521
522private:
523 std::shared_ptr<SDLJoystick> joystick;
524 int axis;
525 float threshold;
526 bool trigger_if_greater;
527};
528
529class SDLAnalog final : public Input::AnalogDevice {
530public:
531 explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
532 bool invert_x_, bool invert_y_, float deadzone_, float range_,
533 float offset_x_, float offset_y_)
534 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_),
535 invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_),
536 offset_y(offset_y_) {}
537
538 std::tuple<float, float> GetStatus() const override {
539 auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y);
540 const float r = std::sqrt((x * x) + (y * y));
541 if (invert_x) {
542 x = -x;
543 }
544 if (invert_y) {
545 y = -y;
546 }
547
548 if (r > deadzone) {
549 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
550 y / r * (r - deadzone) / (1 - deadzone));
551 }
552 return {};
553 }
554
555 std::tuple<float, float> GetRawStatus() const override {
556 const float x = joystick->GetAxis(axis_x, range, offset_x);
557 const float y = joystick->GetAxis(axis_y, range, offset_y);
558 return {x, -y};
559 }
560
561 Input::AnalogProperties GetAnalogProperties() const override {
562 return {deadzone, range, 0.5f};
563 }
564
565 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
566 const auto [x, y] = GetStatus();
567 const float directional_deadzone = 0.5f;
568 switch (direction) {
569 case Input::AnalogDirection::RIGHT:
570 return x > directional_deadzone;
571 case Input::AnalogDirection::LEFT:
572 return x < -directional_deadzone;
573 case Input::AnalogDirection::UP:
574 return y > directional_deadzone;
575 case Input::AnalogDirection::DOWN:
576 return y < -directional_deadzone;
577 }
578 return false;
579 }
580
581private:
582 std::shared_ptr<SDLJoystick> joystick;
583 const int axis_x;
584 const int axis_y;
585 const bool invert_x;
586 const bool invert_y;
587 const float deadzone;
588 const float range;
589 const float offset_x;
590 const float offset_y;
591};
592
593class SDLVibration final : public Input::VibrationDevice {
594public:
595 explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
596 : joystick(std::move(joystick_)) {}
597
598 u8 GetStatus() const override {
599 joystick->RumblePlay(1, 1);
600 return joystick->RumblePlay(0, 0);
601 }
602
603 bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high,
604 [[maybe_unused]] f32 freq_high) const override {
605 const auto process_amplitude = [](f32 amplitude) {
606 return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF);
607 };
608
609 const auto processed_amp_low = process_amplitude(amp_low);
610 const auto processed_amp_high = process_amplitude(amp_high);
611
612 return joystick->RumblePlay(processed_amp_low, processed_amp_high);
613 }
614
615private:
616 std::shared_ptr<SDLJoystick> joystick;
617};
618
619class SDLMotion final : public Input::MotionDevice {
620public:
621 explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {}
622
623 Input::MotionStatus GetStatus() const override {
624 return joystick->GetMotion().GetMotion();
625 }
626
627private:
628 std::shared_ptr<SDLJoystick> joystick;
629};
630
631class SDLDirectionMotion final : public Input::MotionDevice {
632public:
633 explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
634 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
635
636 Input::MotionStatus GetStatus() const override {
637 if (joystick->GetHatDirection(hat, direction)) {
638 return joystick->GetMotion().GetRandomMotion(2, 6);
639 }
640 return joystick->GetMotion().GetRandomMotion(0, 0);
641 }
642
643private:
644 std::shared_ptr<SDLJoystick> joystick;
645 int hat;
646 Uint8 direction;
647};
648
649class SDLAxisMotion final : public Input::MotionDevice {
650public:
651 explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
652 bool trigger_if_greater_)
653 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
654 trigger_if_greater(trigger_if_greater_) {}
655
656 Input::MotionStatus GetStatus() const override {
657 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
658 bool trigger = axis_value < threshold;
659 if (trigger_if_greater) {
660 trigger = axis_value > threshold;
661 }
662
663 if (trigger) {
664 return joystick->GetMotion().GetRandomMotion(2, 6);
665 }
666 return joystick->GetMotion().GetRandomMotion(0, 0);
667 }
668
669private:
670 std::shared_ptr<SDLJoystick> joystick;
671 int axis;
672 float threshold;
673 bool trigger_if_greater;
674};
675
676class SDLButtonMotion final : public Input::MotionDevice {
677public:
678 explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
679 : joystick(std::move(joystick_)), button(button_) {}
680
681 Input::MotionStatus GetStatus() const override {
682 if (joystick->GetButton(button)) {
683 return joystick->GetMotion().GetRandomMotion(2, 6);
684 }
685 return joystick->GetMotion().GetRandomMotion(0, 0);
686 }
687
688private:
689 std::shared_ptr<SDLJoystick> joystick;
690 int button;
691};
692
693/// A button device factory that creates button devices from SDL joystick
694class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
695public:
696 explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
697
698 /**
699 * Creates a button device from a joystick button
700 * @param params contains parameters for creating the device:
701 * - "guid": the guid of the joystick to bind
702 * - "port": the nth joystick of the same type to bind
703 * - "button"(optional): the index of the button to bind
704 * - "hat"(optional): the index of the hat to bind as direction buttons
705 * - "axis"(optional): the index of the axis to bind
706 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
707 * "down", "left" or "right"
708 * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
709 * triggered if the axis value crosses
710 * - "direction"(only used for axis): "+" means the button is triggered when the axis
711 * value is greater than the threshold; "-" means the button is triggered when the axis
712 * value is smaller than the threshold
713 */
714 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
715 const std::string guid = params.Get("guid", "0");
716 const int port = params.Get("port", 0);
717 const auto toggle = params.Get("toggle", false);
718
719 auto joystick = state.GetSDLJoystickByGUID(guid, port);
720
721 if (params.Has("hat")) {
722 const int hat = params.Get("hat", 0);
723 const std::string direction_name = params.Get("direction", "");
724 Uint8 direction;
725 if (direction_name == "up") {
726 direction = SDL_HAT_UP;
727 } else if (direction_name == "down") {
728 direction = SDL_HAT_DOWN;
729 } else if (direction_name == "left") {
730 direction = SDL_HAT_LEFT;
731 } else if (direction_name == "right") {
732 direction = SDL_HAT_RIGHT;
733 } else {
734 direction = 0;
735 }
736 // This is necessary so accessing GetHat with hat won't crash
737 joystick->SetHat(hat, SDL_HAT_CENTERED);
738 return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
739 }
740
741 if (params.Has("axis")) {
742 const int axis = params.Get("axis", 0);
743 // Convert range from (0.0, 1.0) to (-1.0, 1.0)
744 const float threshold = (params.Get("threshold", 0.5f) - 0.5f) * 2.0f;
745 const std::string direction_name = params.Get("direction", "");
746 bool trigger_if_greater;
747 if (direction_name == "+") {
748 trigger_if_greater = true;
749 } else if (direction_name == "-") {
750 trigger_if_greater = false;
751 } else {
752 trigger_if_greater = true;
753 LOG_ERROR(Input, "Unknown direction {}", direction_name);
754 }
755 // This is necessary so accessing GetAxis with axis won't crash
756 joystick->PreSetAxis(axis);
757 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
758 }
759
760 const int button = params.Get("button", 0);
761 // This is necessary so accessing GetButton with button won't crash
762 joystick->PreSetButton(button);
763 return std::make_unique<SDLButton>(joystick, button, toggle);
764 }
765
766private:
767 SDLState& state;
768};
769
770/// An analog device factory that creates analog devices from SDL joystick
771class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
772public:
773 explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
774 /**
775 * Creates an analog device from joystick axes
776 * @param params contains parameters for creating the device:
777 * - "guid": the guid of the joystick to bind
778 * - "port": the nth joystick of the same type
779 * - "axis_x": the index of the axis to be bind as x-axis
780 * - "axis_y": the index of the axis to be bind as y-axis
781 */
782 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
783 const std::string guid = params.Get("guid", "0");
784 const int port = params.Get("port", 0);
785 const int axis_x = params.Get("axis_x", 0);
786 const int axis_y = params.Get("axis_y", 1);
787 const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
788 const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f);
789 const std::string invert_x_value = params.Get("invert_x", "+");
790 const std::string invert_y_value = params.Get("invert_y", "+");
791 const bool invert_x = invert_x_value == "-";
792 const bool invert_y = invert_y_value == "-";
793 const float offset_x = std::clamp(params.Get("offset_x", 0.0f), -0.99f, 0.99f);
794 const float offset_y = std::clamp(params.Get("offset_y", 0.0f), -0.99f, 0.99f);
795 auto joystick = state.GetSDLJoystickByGUID(guid, port);
796
797 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
798 joystick->PreSetAxis(axis_x);
799 joystick->PreSetAxis(axis_y);
800 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone,
801 range, offset_x, offset_y);
802 }
803
804private:
805 SDLState& state;
806};
807
808/// An vibration device factory that creates vibration devices from SDL joystick
809class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
810public:
811 explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
812 /**
813 * Creates a vibration device from a joystick
814 * @param params contains parameters for creating the device:
815 * - "guid": the guid of the joystick to bind
816 * - "port": the nth joystick of the same type
817 */
818 std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
819 const std::string guid = params.Get("guid", "0");
820 const int port = params.Get("port", 0);
821 return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
822 }
823
824private:
825 SDLState& state;
826};
827
828/// A motion device factory that creates motion devices from SDL joystick
829class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
830public:
831 explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
832 /**
833 * Creates motion device from joystick axes
834 * @param params contains parameters for creating the device:
835 * - "guid": the guid of the joystick to bind
836 * - "port": the nth joystick of the same type
837 */
838 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
839 const std::string guid = params.Get("guid", "0");
840 const int port = params.Get("port", 0);
841
842 auto joystick = state.GetSDLJoystickByGUID(guid, port);
843
844 if (params.Has("motion")) {
845 return std::make_unique<SDLMotion>(joystick);
846 }
847
848 if (params.Has("hat")) {
849 const int hat = params.Get("hat", 0);
850 const std::string direction_name = params.Get("direction", "");
851 Uint8 direction;
852 if (direction_name == "up") {
853 direction = SDL_HAT_UP;
854 } else if (direction_name == "down") {
855 direction = SDL_HAT_DOWN;
856 } else if (direction_name == "left") {
857 direction = SDL_HAT_LEFT;
858 } else if (direction_name == "right") {
859 direction = SDL_HAT_RIGHT;
860 } else {
861 direction = 0;
862 }
863 // This is necessary so accessing GetHat with hat won't crash
864 joystick->SetHat(hat, SDL_HAT_CENTERED);
865 return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
866 }
867
868 if (params.Has("axis")) {
869 const int axis = params.Get("axis", 0);
870 const float threshold = params.Get("threshold", 0.5f);
871 const std::string direction_name = params.Get("direction", "");
872 bool trigger_if_greater;
873 if (direction_name == "+") {
874 trigger_if_greater = true;
875 } else if (direction_name == "-") {
876 trigger_if_greater = false;
877 } else {
878 trigger_if_greater = true;
879 LOG_ERROR(Input, "Unknown direction {}", direction_name);
880 }
881 // This is necessary so accessing GetAxis with axis won't crash
882 joystick->PreSetAxis(axis);
883 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
884 }
885
886 const int button = params.Get("button", 0);
887 // This is necessary so accessing GetButton with button won't crash
888 joystick->PreSetButton(button);
889 return std::make_unique<SDLButtonMotion>(joystick, button);
890 }
891
892private:
893 SDLState& state;
894};
895
896SDLState::SDLState() {
897 using namespace Input;
898 button_factory = std::make_shared<SDLButtonFactory>(*this);
899 analog_factory = std::make_shared<SDLAnalogFactory>(*this);
900 vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
901 motion_factory = std::make_shared<SDLMotionFactory>(*this);
902 RegisterFactory<ButtonDevice>("sdl", button_factory);
903 RegisterFactory<AnalogDevice>("sdl", analog_factory);
904 RegisterFactory<VibrationDevice>("sdl", vibration_factory);
905 RegisterFactory<MotionDevice>("sdl", motion_factory);
906
907 if (!Settings::values.enable_raw_input) {
908 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
909 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
910 }
911
912 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
913 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
914 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
915
916 // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a
917 // GameController and not a generic one
918 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
919
920 // Turn off Pro controller home led
921 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
922
923 // If the frontend is going to manage the event loop, then we don't start one here
924 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
925 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
926 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
927 return;
928 }
929 has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0;
930 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
931 LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError());
932 }
933
934 SDL_AddEventWatch(&SDLEventWatcher, this);
935
936 initialized = true;
937 if (start_thread) {
938 poll_thread = std::thread([this] {
939 using namespace std::chrono_literals;
940 while (initialized) {
941 SDL_PumpEvents();
942 std::this_thread::sleep_for(1ms);
943 }
944 });
945 }
946 // Because the events for joystick connection happens before we have our event watcher added, we
947 // can just open all the joysticks right here
948 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
949 InitJoystick(i);
950 }
951}
952
953SDLState::~SDLState() {
954 using namespace Input;
955 UnregisterFactory<ButtonDevice>("sdl");
956 UnregisterFactory<AnalogDevice>("sdl");
957 UnregisterFactory<VibrationDevice>("sdl");
958 UnregisterFactory<MotionDevice>("sdl");
959
960 CloseJoysticks();
961 SDL_DelEventWatch(&SDLEventWatcher, this);
962
963 initialized = false;
964 if (start_thread) {
965 poll_thread.join();
966 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
967 }
968}
969
970std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
971 std::scoped_lock lock(joystick_map_mutex);
972 std::vector<Common::ParamPackage> devices;
973 std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs;
974 for (const auto& [key, value] : joystick_map) {
975 for (const auto& joystick : value) {
976 if (!joystick->GetSDLJoystick()) {
977 continue;
978 }
979 std::string name =
980 fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort());
981 devices.emplace_back(Common::ParamPackage{
982 {"class", "sdl"},
983 {"display", std::move(name)},
984 {"guid", joystick->GetGUID()},
985 {"port", std::to_string(joystick->GetPort())},
986 });
987 if (joystick->IsJoyconLeft()) {
988 joycon_pairs.insert_or_assign(joystick->GetPort(), joystick);
989 }
990 }
991 }
992
993 // Add dual controllers
994 for (const auto& [key, value] : joystick_map) {
995 for (const auto& joystick : value) {
996 if (joystick->IsJoyconRight()) {
997 if (!joycon_pairs.contains(joystick->GetPort())) {
998 continue;
999 }
1000 const auto joystick2 = joycon_pairs.at(joystick->GetPort());
1001
1002 std::string name =
1003 fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort());
1004 devices.emplace_back(Common::ParamPackage{
1005 {"class", "sdl"},
1006 {"display", std::move(name)},
1007 {"guid", joystick->GetGUID()},
1008 {"guid2", joystick2->GetGUID()},
1009 {"port", std::to_string(joystick->GetPort())},
1010 });
1011 }
1012 }
1013 }
1014 return devices;
1015}
1016
1017namespace {
1018Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis,
1019 float value = 0.1f) {
1020 Common::ParamPackage params({{"engine", "sdl"}});
1021 params.Set("port", port);
1022 params.Set("guid", std::move(guid));
1023 params.Set("axis", axis);
1024 params.Set("threshold", "0.5");
1025 if (value > 0) {
1026 params.Set("direction", "+");
1027 } else {
1028 params.Set("direction", "-");
1029 }
1030 return params;
1031}
1032
1033Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) {
1034 Common::ParamPackage params({{"engine", "sdl"}});
1035 params.Set("port", port);
1036 params.Set("guid", std::move(guid));
1037 params.Set("button", button);
1038 params.Set("toggle", false);
1039 return params;
1040}
1041
1042Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) {
1043 Common::ParamPackage params({{"engine", "sdl"}});
1044
1045 params.Set("port", port);
1046 params.Set("guid", std::move(guid));
1047 params.Set("hat", hat);
1048 switch (value) {
1049 case SDL_HAT_UP:
1050 params.Set("direction", "up");
1051 break;
1052 case SDL_HAT_DOWN:
1053 params.Set("direction", "down");
1054 break;
1055 case SDL_HAT_LEFT:
1056 params.Set("direction", "left");
1057 break;
1058 case SDL_HAT_RIGHT:
1059 params.Set("direction", "right");
1060 break;
1061 default:
1062 return {};
1063 }
1064 return params;
1065}
1066
1067Common::ParamPackage BuildMotionParam(int port, std::string guid) {
1068 Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}});
1069 params.Set("port", port);
1070 params.Set("guid", std::move(guid));
1071 return params;
1072}
1073
1074Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
1075 switch (event.type) {
1076 case SDL_JOYAXISMOTION: {
1077 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1078 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1079 static_cast<s32>(event.jaxis.axis),
1080 event.jaxis.value);
1081 }
1082 break;
1083 }
1084 case SDL_JOYBUTTONUP: {
1085 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
1086 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1087 static_cast<s32>(event.jbutton.button));
1088 }
1089 break;
1090 }
1091 case SDL_JOYHATMOTION: {
1092 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
1093 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1094 static_cast<s32>(event.jhat.hat),
1095 static_cast<s32>(event.jhat.value));
1096 }
1097 break;
1098 }
1099 }
1100 return {};
1101}
1102
1103Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
1104 switch (event.type) {
1105 case SDL_JOYAXISMOTION: {
1106 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1107 return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1108 static_cast<s32>(event.jaxis.axis),
1109 event.jaxis.value);
1110 }
1111 break;
1112 }
1113 case SDL_JOYBUTTONUP: {
1114 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) {
1115 return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1116 static_cast<s32>(event.jbutton.button));
1117 }
1118 break;
1119 }
1120 case SDL_JOYHATMOTION: {
1121 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) {
1122 return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
1123 static_cast<s32>(event.jhat.hat),
1124 static_cast<s32>(event.jhat.value));
1125 }
1126 break;
1127 }
1128 case SDL_CONTROLLERSENSORUPDATE: {
1129 bool is_motion_shaking = false;
1130 constexpr float gyro_threshold = 5.0f;
1131 constexpr float accel_threshold = 11.0f;
1132 if (event.csensor.sensor == SDL_SENSOR_ACCEL) {
1133 const Common::Vec3f acceleration = {-event.csensor.data[0], event.csensor.data[2],
1134 -event.csensor.data[1]};
1135 if (acceleration.Length() > accel_threshold) {
1136 is_motion_shaking = true;
1137 }
1138 }
1139
1140 if (event.csensor.sensor == SDL_SENSOR_GYRO) {
1141 const Common::Vec3f gyroscope = {event.csensor.data[0], -event.csensor.data[2],
1142 event.csensor.data[1]};
1143 if (gyroscope.Length() > gyro_threshold) {
1144 is_motion_shaking = true;
1145 }
1146 }
1147
1148 if (!is_motion_shaking) {
1149 break;
1150 }
1151
1152 if (const auto joystick = state.GetSDLJoystickBySDLID(event.csensor.which)) {
1153 return BuildMotionParam(joystick->GetPort(), joystick->GetGUID());
1154 }
1155 break;
1156 }
1157 }
1158 return {};
1159}
1160
1161Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
1162 const SDL_GameControllerButtonBind& binding) {
1163 switch (binding.bindType) {
1164 case SDL_CONTROLLER_BINDTYPE_NONE:
1165 break;
1166 case SDL_CONTROLLER_BINDTYPE_AXIS:
1167 return BuildAnalogParamPackageForButton(port, guid, binding.value.axis);
1168 case SDL_CONTROLLER_BINDTYPE_BUTTON:
1169 return BuildButtonParamPackageForButton(port, guid, binding.value.button);
1170 case SDL_CONTROLLER_BINDTYPE_HAT:
1171 return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat,
1172 binding.value.hat.hat_mask);
1173 }
1174 return {};
1175}
1176
1177Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
1178 int axis_y, float offset_x, float offset_y) {
1179 Common::ParamPackage params;
1180 params.Set("engine", "sdl");
1181 params.Set("port", port);
1182 params.Set("guid", guid);
1183 params.Set("axis_x", axis_x);
1184 params.Set("axis_y", axis_y);
1185 params.Set("offset_x", offset_x);
1186 params.Set("offset_y", offset_y);
1187 params.Set("invert_x", "+");
1188 params.Set("invert_y", "+");
1189 return params;
1190}
1191} // Anonymous namespace
1192
1193ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
1194 if (!params.Has("guid") || !params.Has("port")) {
1195 return {};
1196 }
1197 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1198
1199 auto* controller = joystick->GetSDLGameController();
1200 if (controller == nullptr) {
1201 return {};
1202 }
1203
1204 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
1205 // We will add those afterwards
1206 // This list also excludes Screenshot since theres not really a mapping for that
1207 ButtonBindings switch_to_sdl_button;
1208
1209 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) {
1210 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
1211 } else {
1212 switch_to_sdl_button = GetDefaultButtonBinding();
1213 }
1214
1215 // Add the missing bindings for ZL/ZR
1216 static constexpr ZButtonBindings switch_to_sdl_axis{{
1217 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
1218 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
1219 }};
1220
1221 // Parameters contain two joysticks return dual
1222 if (params.Has("guid2")) {
1223 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1224
1225 if (joystick2->GetSDLGameController() != nullptr) {
1226 return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button,
1227 switch_to_sdl_axis);
1228 }
1229 }
1230
1231 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
1232}
1233
1234ButtonBindings SDLState::GetDefaultButtonBinding() const {
1235 return {
1236 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
1237 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
1238 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
1239 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
1240 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
1241 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
1242 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1243 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1244 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
1245 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
1246 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
1247 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
1248 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
1249 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
1250 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1251 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1252 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
1253 };
1254}
1255
1256ButtonBindings SDLState::GetNintendoButtonBinding(
1257 const std::shared_ptr<SDLJoystick>& joystick) const {
1258 // Default SL/SR mapping for pro controllers
1259 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
1260 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
1261
1262 if (joystick->IsJoyconLeft()) {
1263 sl_button = SDL_CONTROLLER_BUTTON_PADDLE2;
1264 sr_button = SDL_CONTROLLER_BUTTON_PADDLE4;
1265 }
1266 if (joystick->IsJoyconRight()) {
1267 sl_button = SDL_CONTROLLER_BUTTON_PADDLE3;
1268 sr_button = SDL_CONTROLLER_BUTTON_PADDLE1;
1269 }
1270
1271 return {
1272 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A},
1273 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B},
1274 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X},
1275 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y},
1276 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
1277 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
1278 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
1279 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
1280 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
1281 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
1282 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
1283 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
1284 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
1285 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
1286 {Settings::NativeButton::SL, sl_button},
1287 {Settings::NativeButton::SR, sr_button},
1288 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
1289 };
1290}
1291
1292ButtonMapping SDLState::GetSingleControllerMapping(
1293 const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button,
1294 const ZButtonBindings& switch_to_sdl_axis) const {
1295 ButtonMapping mapping;
1296 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
1297 auto* controller = joystick->GetSDLGameController();
1298
1299 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
1300 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
1301 mapping.insert_or_assign(
1302 switch_button,
1303 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1304 }
1305 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
1306 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
1307 mapping.insert_or_assign(
1308 switch_button,
1309 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1310 }
1311
1312 return mapping;
1313}
1314
1315ButtonMapping SDLState::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
1316 const std::shared_ptr<SDLJoystick>& joystick2,
1317 const ButtonBindings& switch_to_sdl_button,
1318 const ZButtonBindings& switch_to_sdl_axis) const {
1319 ButtonMapping mapping;
1320 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
1321 auto* controller = joystick->GetSDLGameController();
1322 auto* controller2 = joystick2->GetSDLGameController();
1323
1324 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
1325 if (IsButtonOnLeftSide(switch_button)) {
1326 const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button);
1327 mapping.insert_or_assign(
1328 switch_button,
1329 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
1330 continue;
1331 }
1332 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
1333 mapping.insert_or_assign(
1334 switch_button,
1335 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1336 }
1337 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
1338 if (IsButtonOnLeftSide(switch_button)) {
1339 const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis);
1340 mapping.insert_or_assign(
1341 switch_button,
1342 BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding));
1343 continue;
1344 }
1345 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
1346 mapping.insert_or_assign(
1347 switch_button,
1348 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
1349 }
1350
1351 return mapping;
1352}
1353
1354bool SDLState::IsButtonOnLeftSide(Settings::NativeButton::Values button) const {
1355 switch (button) {
1356 case Settings::NativeButton::DDown:
1357 case Settings::NativeButton::DLeft:
1358 case Settings::NativeButton::DRight:
1359 case Settings::NativeButton::DUp:
1360 case Settings::NativeButton::L:
1361 case Settings::NativeButton::LStick:
1362 case Settings::NativeButton::Minus:
1363 case Settings::NativeButton::Screenshot:
1364 case Settings::NativeButton::ZL:
1365 return true;
1366 default:
1367 return false;
1368 }
1369}
1370
1371AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
1372 if (!params.Has("guid") || !params.Has("port")) {
1373 return {};
1374 }
1375 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1376 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1377 auto* controller = joystick->GetSDLGameController();
1378 if (controller == nullptr) {
1379 return {};
1380 }
1381
1382 AnalogMapping mapping = {};
1383 const auto& binding_left_x =
1384 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
1385 const auto& binding_left_y =
1386 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
1387 if (params.Has("guid2")) {
1388 joystick2->PreSetAxis(binding_left_x.value.axis);
1389 joystick2->PreSetAxis(binding_left_y.value.axis);
1390 const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1391 const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1392 mapping.insert_or_assign(
1393 Settings::NativeAnalog::LStick,
1394 BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(),
1395 binding_left_x.value.axis, binding_left_y.value.axis,
1396 left_offset_x, left_offset_y));
1397 } else {
1398 joystick->PreSetAxis(binding_left_x.value.axis);
1399 joystick->PreSetAxis(binding_left_y.value.axis);
1400 const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1401 const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1402 mapping.insert_or_assign(
1403 Settings::NativeAnalog::LStick,
1404 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1405 binding_left_x.value.axis, binding_left_y.value.axis,
1406 left_offset_x, left_offset_y));
1407 }
1408 const auto& binding_right_x =
1409 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
1410 const auto& binding_right_y =
1411 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
1412 joystick->PreSetAxis(binding_right_x.value.axis);
1413 joystick->PreSetAxis(binding_right_y.value.axis);
1414 const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0);
1415 const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0);
1416 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
1417 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1418 binding_right_x.value.axis,
1419 binding_right_y.value.axis, right_offset_x,
1420 right_offset_y));
1421 return mapping;
1422}
1423
1424MotionMapping SDLState::GetMotionMappingForDevice(const Common::ParamPackage& params) {
1425 if (!params.Has("guid") || !params.Has("port")) {
1426 return {};
1427 }
1428 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
1429 const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0));
1430 auto* controller = joystick->GetSDLGameController();
1431 if (controller == nullptr) {
1432 return {};
1433 }
1434
1435 MotionMapping mapping = {};
1436 joystick->EnableMotion();
1437
1438 if (joystick->HasGyro() || joystick->HasAccel()) {
1439 mapping.insert_or_assign(Settings::NativeMotion::MotionRight,
1440 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
1441 }
1442 if (params.Has("guid2")) {
1443 joystick2->EnableMotion();
1444 if (joystick2->HasGyro() || joystick2->HasAccel()) {
1445 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
1446 BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID()));
1447 }
1448 } else {
1449 if (joystick->HasGyro() || joystick->HasAccel()) {
1450 mapping.insert_or_assign(Settings::NativeMotion::MotionLeft,
1451 BuildMotionParam(joystick->GetPort(), joystick->GetGUID()));
1452 }
1453 }
1454
1455 return mapping;
1456}
1457namespace Polling {
1458class SDLPoller : public InputCommon::Polling::DevicePoller {
1459public:
1460 explicit SDLPoller(SDLState& state_) : state(state_) {}
1461
1462 void Start([[maybe_unused]] const std::string& device_id) override {
1463 state.event_queue.Clear();
1464 state.polling = true;
1465 }
1466
1467 void Stop() override {
1468 state.polling = false;
1469 }
1470
1471protected:
1472 SDLState& state;
1473};
1474
1475class SDLButtonPoller final : public SDLPoller {
1476public:
1477 explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
1478
1479 Common::ParamPackage GetNextInput() override {
1480 SDL_Event event;
1481 while (state.event_queue.Pop(event)) {
1482 const auto package = FromEvent(event);
1483 if (package) {
1484 return *package;
1485 }
1486 }
1487 return {};
1488 }
1489 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) {
1490 switch (event.type) {
1491 case SDL_JOYAXISMOTION:
1492 if (!axis_memory.count(event.jaxis.which) ||
1493 !axis_memory[event.jaxis.which].count(event.jaxis.axis)) {
1494 axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value;
1495 axis_event_count[event.jaxis.which][event.jaxis.axis] = 1;
1496 break;
1497 } else {
1498 axis_event_count[event.jaxis.which][event.jaxis.axis]++;
1499 // The joystick and axis exist in our map if we take this branch, so no checks
1500 // needed
1501 if (std::abs(
1502 (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) /
1503 32767.0) < 0.5) {
1504 break;
1505 } else {
1506 if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 &&
1507 IsAxisAtPole(event.jaxis.value) &&
1508 IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) {
1509 // If we have exactly two events and both are near a pole, this is
1510 // likely a digital input masquerading as an analog axis; Instead of
1511 // trying to look at the direction the axis travelled, assume the first
1512 // event was press and the second was release; This should handle most
1513 // digital axes while deferring to the direction of travel for analog
1514 // axes
1515 event.jaxis.value = static_cast<Sint16>(
1516 std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis]));
1517 } else {
1518 // There are more than two events, so this is likely a true analog axis,
1519 // check the direction it travelled
1520 event.jaxis.value = static_cast<Sint16>(std::copysign(
1521 32767,
1522 event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]));
1523 }
1524 axis_memory.clear();
1525 axis_event_count.clear();
1526 }
1527 }
1528 [[fallthrough]];
1529 case SDL_JOYBUTTONUP:
1530 case SDL_JOYHATMOTION:
1531 return {SDLEventToButtonParamPackage(state, event)};
1532 }
1533 return std::nullopt;
1534 }
1535
1536private:
1537 // Determine whether an axis value is close to an extreme or center
1538 // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per
1539 // axis, which is why the center must be considered a pole
1540 bool IsAxisAtPole(int16_t value) const {
1541 return std::abs(value) >= 32767 || std::abs(value) < 327;
1542 }
1543 std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory;
1544 std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count;
1545};
1546
1547class SDLMotionPoller final : public SDLPoller {
1548public:
1549 explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
1550
1551 Common::ParamPackage GetNextInput() override {
1552 SDL_Event event;
1553 while (state.event_queue.Pop(event)) {
1554 const auto package = FromEvent(event);
1555 if (package) {
1556 return *package;
1557 }
1558 }
1559 return {};
1560 }
1561 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
1562 switch (event.type) {
1563 case SDL_JOYAXISMOTION:
1564 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1565 break;
1566 }
1567 [[fallthrough]];
1568 case SDL_JOYBUTTONUP:
1569 case SDL_JOYHATMOTION:
1570 case SDL_CONTROLLERSENSORUPDATE:
1571 return {SDLEventToMotionParamPackage(state, event)};
1572 }
1573 return std::nullopt;
1574 }
1575};
1576
1577/**
1578 * Attempts to match the press to a controller joy axis (left/right stick) and if a match
1579 * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
1580 * instead
1581 */
1582class SDLAnalogPreferredPoller final : public SDLPoller {
1583public:
1584 explicit SDLAnalogPreferredPoller(SDLState& state_)
1585 : SDLPoller(state_), button_poller(state_) {}
1586
1587 void Start(const std::string& device_id) override {
1588 SDLPoller::Start(device_id);
1589 // Reset stored axes
1590 first_axis = -1;
1591 }
1592
1593 Common::ParamPackage GetNextInput() override {
1594 SDL_Event event;
1595 while (state.event_queue.Pop(event)) {
1596 if (event.type != SDL_JOYAXISMOTION) {
1597 // Check for a button press
1598 auto button_press = button_poller.FromEvent(event);
1599 if (button_press) {
1600 return *button_press;
1601 }
1602 continue;
1603 }
1604 const auto axis = event.jaxis.axis;
1605
1606 // Filter out axis events that are below a threshold
1607 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
1608 continue;
1609 }
1610
1611 // Filter out axis events that are the same
1612 if (first_axis == axis) {
1613 continue;
1614 }
1615
1616 // In order to return a complete analog param, we need inputs for both axes.
1617 // If the first axis isn't set we set the value then wait till next event
1618 if (first_axis == -1) {
1619 first_axis = axis;
1620 continue;
1621 }
1622
1623 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1624 // Set offset to zero since the joystick is not on center
1625 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1626 first_axis, axis, 0, 0);
1627 first_axis = -1;
1628 return params;
1629 }
1630 }
1631 return {};
1632 }
1633
1634private:
1635 int first_axis = -1;
1636 SDLButtonPoller button_poller;
1637};
1638} // namespace Polling
1639
1640SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
1641 Pollers pollers;
1642
1643 switch (type) {
1644 case InputCommon::Polling::DeviceType::AnalogPreferred:
1645 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this));
1646 break;
1647 case InputCommon::Polling::DeviceType::Button:
1648 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
1649 break;
1650 case InputCommon::Polling::DeviceType::Motion:
1651 pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
1652 break;
1653 }
1654
1655 return pollers;
1656}
1657
1658} // namespace InputCommon::SDL