summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt15
-rw-r--r--src/input_common/keyboard.cpp8
-rw-r--r--src/input_common/main.cpp23
-rw-r--r--src/input_common/main.h2
-rw-r--r--src/input_common/motion_emu.cpp38
-rw-r--r--src/input_common/sdl/sdl.cpp636
-rw-r--r--src/input_common/sdl/sdl.h53
-rw-r--r--src/input_common/sdl/sdl_impl.cpp671
-rw-r--r--src/input_common/sdl/sdl_impl.h63
9 files changed, 805 insertions, 704 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 1c7db28c0..5b4e032bd 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,15 +7,18 @@ add_library(input_common STATIC
7 main.h 7 main.h
8 motion_emu.cpp 8 motion_emu.cpp
9 motion_emu.h 9 motion_emu.h
10 10 sdl/sdl.cpp
11 $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h> 11 sdl/sdl.h
12) 12)
13 13
14create_target_directory_groups(input_common)
15
16target_link_libraries(input_common PUBLIC core PRIVATE common)
17
18if(SDL2_FOUND) 14if(SDL2_FOUND)
15 target_sources(input_common PRIVATE
16 sdl/sdl_impl.cpp
17 sdl/sdl_impl.h
18 )
19 target_link_libraries(input_common PRIVATE SDL2) 19 target_link_libraries(input_common PRIVATE SDL2)
20 target_compile_definitions(input_common PRIVATE HAVE_SDL2) 20 target_compile_definitions(input_common PRIVATE HAVE_SDL2)
21endif() 21endif()
22
23create_target_directory_groups(input_common)
24target_link_libraries(input_common PUBLIC core PRIVATE common)
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp
index 525fe6abc..078374be5 100644
--- a/src/input_common/keyboard.cpp
+++ b/src/input_common/keyboard.cpp
@@ -36,18 +36,18 @@ struct KeyButtonPair {
36class KeyButtonList { 36class KeyButtonList {
37public: 37public:
38 void AddKeyButton(int key_code, KeyButton* key_button) { 38 void AddKeyButton(int key_code, KeyButton* key_button) {
39 std::lock_guard<std::mutex> guard(mutex); 39 std::lock_guard guard{mutex};
40 list.push_back(KeyButtonPair{key_code, key_button}); 40 list.push_back(KeyButtonPair{key_code, key_button});
41 } 41 }
42 42
43 void RemoveKeyButton(const KeyButton* key_button) { 43 void RemoveKeyButton(const KeyButton* key_button) {
44 std::lock_guard<std::mutex> guard(mutex); 44 std::lock_guard guard{mutex};
45 list.remove_if( 45 list.remove_if(
46 [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; }); 46 [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; });
47 } 47 }
48 48
49 void ChangeKeyStatus(int key_code, bool pressed) { 49 void ChangeKeyStatus(int key_code, bool pressed) {
50 std::lock_guard<std::mutex> 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);
@@ -55,7 +55,7 @@ public:
55 } 55 }
56 56
57 void ChangeAllKeyStatus(bool pressed) { 57 void ChangeAllKeyStatus(bool pressed) {
58 std::lock_guard<std::mutex> guard(mutex); 58 std::lock_guard guard{mutex};
59 for (const KeyButtonPair& pair : list) { 59 for (const KeyButtonPair& pair : list) {
60 pair.key_button->status.store(pressed); 60 pair.key_button->status.store(pressed);
61 } 61 }
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 37f572853..8e66c1b15 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -17,10 +17,7 @@ namespace InputCommon {
17 17
18static std::shared_ptr<Keyboard> keyboard; 18static std::shared_ptr<Keyboard> keyboard;
19static std::shared_ptr<MotionEmu> motion_emu; 19static std::shared_ptr<MotionEmu> motion_emu;
20 20static std::unique_ptr<SDL::State> sdl;
21#ifdef HAVE_SDL2
22static std::thread poll_thread;
23#endif
24 21
25void Init() { 22void Init() {
26 keyboard = std::make_shared<Keyboard>(); 23 keyboard = std::make_shared<Keyboard>();
@@ -30,15 +27,7 @@ void Init() {
30 motion_emu = std::make_shared<MotionEmu>(); 27 motion_emu = std::make_shared<MotionEmu>();
31 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); 28 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
32 29
33#ifdef HAVE_SDL2 30 sdl = SDL::Init();
34 SDL::Init();
35#endif
36}
37
38void StartJoystickEventHandler() {
39#ifdef HAVE_SDL2
40 poll_thread = std::thread(SDL::PollLoop);
41#endif
42} 31}
43 32
44void Shutdown() { 33void Shutdown() {
@@ -47,11 +36,7 @@ void Shutdown() {
47 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); 36 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
48 Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); 37 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
49 motion_emu.reset(); 38 motion_emu.reset();
50 39 sdl.reset();
51#ifdef HAVE_SDL2
52 SDL::Shutdown();
53 poll_thread.join();
54#endif
55} 40}
56 41
57Keyboard* GetKeyboard() { 42Keyboard* GetKeyboard() {
@@ -88,7 +73,7 @@ namespace Polling {
88 73
89std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { 74std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
90#ifdef HAVE_SDL2 75#ifdef HAVE_SDL2
91 return SDL::Polling::GetPollers(type); 76 return sdl->GetPollers(type);
92#else 77#else
93 return {}; 78 return {};
94#endif 79#endif
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 9eb13106e..77a0ce90b 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -20,8 +20,6 @@ void Init();
20/// Deregisters all built-in input device factories and shuts them down. 20/// Deregisters all built-in input device factories and shuts them down.
21void Shutdown(); 21void Shutdown();
22 22
23void StartJoystickEventHandler();
24
25class Keyboard; 23class Keyboard;
26 24
27/// Gets the keyboard button device factory. 25/// Gets the keyboard button device factory.
diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp
index 9570c060e..868251628 100644
--- a/src/input_common/motion_emu.cpp
+++ b/src/input_common/motion_emu.cpp
@@ -32,32 +32,32 @@ public:
32 } 32 }
33 33
34 void BeginTilt(int x, int y) { 34 void BeginTilt(int x, int y) {
35 mouse_origin = Math::MakeVec(x, y); 35 mouse_origin = Common::MakeVec(x, y);
36 is_tilting = true; 36 is_tilting = true;
37 } 37 }
38 38
39 void Tilt(int x, int y) { 39 void Tilt(int x, int y) {
40 auto mouse_move = Math::MakeVec(x, y) - mouse_origin; 40 auto mouse_move = Common::MakeVec(x, y) - mouse_origin;
41 if (is_tilting) { 41 if (is_tilting) {
42 std::lock_guard<std::mutex> guard(tilt_mutex); 42 std::lock_guard guard{tilt_mutex};
43 if (mouse_move.x == 0 && mouse_move.y == 0) { 43 if (mouse_move.x == 0 && mouse_move.y == 0) {
44 tilt_angle = 0; 44 tilt_angle = 0;
45 } else { 45 } else {
46 tilt_direction = mouse_move.Cast<float>(); 46 tilt_direction = mouse_move.Cast<float>();
47 tilt_angle = 47 tilt_angle =
48 std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, MathUtil::PI * 0.5f); 48 std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f);
49 } 49 }
50 } 50 }
51 } 51 }
52 52
53 void EndTilt() { 53 void EndTilt() {
54 std::lock_guard<std::mutex> guard(tilt_mutex); 54 std::lock_guard guard{tilt_mutex};
55 tilt_angle = 0; 55 tilt_angle = 0;
56 is_tilting = false; 56 is_tilting = false;
57 } 57 }
58 58
59 std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() { 59 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() {
60 std::lock_guard<std::mutex> guard(status_mutex); 60 std::lock_guard guard{status_mutex};
61 return status; 61 return status;
62 } 62 }
63 63
@@ -66,17 +66,17 @@ private:
66 const std::chrono::steady_clock::duration update_duration; 66 const std::chrono::steady_clock::duration update_duration;
67 const float sensitivity; 67 const float sensitivity;
68 68
69 Math::Vec2<int> mouse_origin; 69 Common::Vec2<int> mouse_origin;
70 70
71 std::mutex tilt_mutex; 71 std::mutex tilt_mutex;
72 Math::Vec2<float> tilt_direction; 72 Common::Vec2<float> tilt_direction;
73 float tilt_angle = 0; 73 float tilt_angle = 0;
74 74
75 bool is_tilting = false; 75 bool is_tilting = false;
76 76
77 Common::Event shutdown_event; 77 Common::Event shutdown_event;
78 78
79 std::tuple<Math::Vec3<float>, Math::Vec3<float>> status; 79 std::tuple<Common::Vec3<float>, Common::Vec3<float>> status;
80 std::mutex status_mutex; 80 std::mutex status_mutex;
81 81
82 // Note: always keep the thread declaration at the end so that other objects are initialized 82 // Note: always keep the thread declaration at the end so that other objects are initialized
@@ -85,29 +85,29 @@ private:
85 85
86 void MotionEmuThread() { 86 void MotionEmuThread() {
87 auto update_time = std::chrono::steady_clock::now(); 87 auto update_time = std::chrono::steady_clock::now();
88 Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0); 88 Common::Quaternion<float> q = Common::MakeQuaternion(Common::Vec3<float>(), 0);
89 Math::Quaternion<float> old_q; 89 Common::Quaternion<float> old_q;
90 90
91 while (!shutdown_event.WaitUntil(update_time)) { 91 while (!shutdown_event.WaitUntil(update_time)) {
92 update_time += update_duration; 92 update_time += update_duration;
93 old_q = q; 93 old_q = q;
94 94
95 { 95 {
96 std::lock_guard<std::mutex> guard(tilt_mutex); 96 std::lock_guard guard{tilt_mutex};
97 97
98 // Find the quaternion describing current 3DS tilting 98 // Find the quaternion describing current 3DS tilting
99 q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), 99 q = Common::MakeQuaternion(
100 tilt_angle); 100 Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle);
101 } 101 }
102 102
103 auto inv_q = q.Inverse(); 103 auto inv_q = q.Inverse();
104 104
105 // Set the gravity vector in world space 105 // Set the gravity vector in world space
106 auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f); 106 auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f);
107 107
108 // Find the angular rate vector in world space 108 // Find the angular rate vector in world space
109 auto angular_rate = ((q - old_q) * inv_q).xyz * 2; 109 auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
110 angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180; 110 angular_rate *= 1000 / update_millisecond / Common::PI * 180;
111 111
112 // Transform the two vectors from world space to 3DS space 112 // Transform the two vectors from world space to 3DS space
113 gravity = QuaternionRotate(inv_q, gravity); 113 gravity = QuaternionRotate(inv_q, gravity);
@@ -115,7 +115,7 @@ private:
115 115
116 // Update the sensor state 116 // Update the sensor state
117 { 117 {
118 std::lock_guard<std::mutex> guard(status_mutex); 118 std::lock_guard guard{status_mutex};
119 status = std::make_tuple(gravity, angular_rate); 119 status = std::make_tuple(gravity, angular_rate);
120 } 120 }
121 } 121 }
@@ -131,7 +131,7 @@ public:
131 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); 131 device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity);
132 } 132 }
133 133
134 std::tuple<Math::Vec3<float>, Math::Vec3<float>> GetStatus() const override { 134 std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override {
135 return device->GetStatus(); 135 return device->GetStatus();
136 } 136 }
137 137
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
index faf3c1fa3..644db3448 100644
--- a/src/input_common/sdl/sdl.cpp
+++ b/src/input_common/sdl/sdl.cpp
@@ -1,631 +1,19 @@
1// Copyright 2017 Citra Emulator Project 1// Copyright 2018 Citra 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 <algorithm>
6#include <atomic>
7#include <cmath>
8#include <functional>
9#include <iterator>
10#include <mutex>
11#include <string>
12#include <thread>
13#include <tuple>
14#include <unordered_map>
15#include <utility>
16#include <vector>
17#include <SDL.h>
18#include "common/assert.h"
19#include "common/logging/log.h"
20#include "common/math_util.h"
21#include "common/param_package.h"
22#include "common/threadsafe_queue.h"
23#include "input_common/main.h"
24#include "input_common/sdl/sdl.h" 5#include "input_common/sdl/sdl.h"
6#ifdef HAVE_SDL2
7#include "input_common/sdl/sdl_impl.h"
8#endif
25 9
26namespace InputCommon { 10namespace InputCommon::SDL {
27 11
28namespace SDL { 12std::unique_ptr<State> Init() {
29 13#ifdef HAVE_SDL2
30class SDLJoystick; 14 return std::make_unique<SDLState>();
31class SDLButtonFactory; 15#else
32class SDLAnalogFactory; 16 return std::make_unique<NullState>();
33 17#endif
34/// Map of GUID of a list of corresponding virtual Joysticks
35static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
36static std::mutex joystick_map_mutex;
37
38static std::shared_ptr<SDLButtonFactory> button_factory;
39static std::shared_ptr<SDLAnalogFactory> analog_factory;
40
41/// Used by the Pollers during config
42static std::atomic<bool> polling;
43static Common::SPSCQueue<SDL_Event> event_queue;
44
45static std::atomic<bool> initialized = false;
46
47static std::string GetGUID(SDL_Joystick* joystick) {
48 SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
49 char guid_str[33];
50 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
51 return guid_str;
52}
53
54class SDLJoystick {
55public:
56 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
57 decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
58 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
59
60 void SetButton(int button, bool value) {
61 std::lock_guard<std::mutex> lock(mutex);
62 state.buttons[button] = value;
63 }
64
65 bool GetButton(int button) const {
66 std::lock_guard<std::mutex> lock(mutex);
67 return state.buttons.at(button);
68 }
69
70 void SetAxis(int axis, Sint16 value) {
71 std::lock_guard<std::mutex> lock(mutex);
72 state.axes[axis] = value;
73 }
74
75 float GetAxis(int axis) const {
76 std::lock_guard<std::mutex> lock(mutex);
77 return state.axes.at(axis) / 32767.0f;
78 }
79
80 std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
81 float x = GetAxis(axis_x);
82 float y = GetAxis(axis_y);
83 y = -y; // 3DS uses an y-axis inverse from SDL
84
85 // Make sure the coordinates are in the unit circle,
86 // otherwise normalize it.
87 float r = x * x + y * y;
88 if (r > 1.0f) {
89 r = std::sqrt(r);
90 x /= r;
91 y /= r;
92 }
93
94 return std::make_tuple(x, y);
95 }
96
97 void SetHat(int hat, Uint8 direction) {
98 std::lock_guard<std::mutex> lock(mutex);
99 state.hats[hat] = direction;
100 }
101
102 bool GetHatDirection(int hat, Uint8 direction) const {
103 std::lock_guard<std::mutex> lock(mutex);
104 return (state.hats.at(hat) & direction) != 0;
105 }
106 /**
107 * The guid of the joystick
108 */
109 const std::string& GetGUID() const {
110 return guid;
111 }
112
113 /**
114 * The number of joystick from the same type that were connected before this joystick
115 */
116 int GetPort() const {
117 return port;
118 }
119
120 SDL_Joystick* GetSDLJoystick() const {
121 return sdl_joystick.get();
122 }
123
124 void SetSDLJoystick(SDL_Joystick* joystick,
125 decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
126 sdl_joystick =
127 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
128 }
129
130private:
131 struct State {
132 std::unordered_map<int, bool> buttons;
133 std::unordered_map<int, Sint16> axes;
134 std::unordered_map<int, Uint8> hats;
135 } state;
136 std::string guid;
137 int port;
138 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
139 mutable std::mutex mutex;
140};
141
142/**
143 * Get the nth joystick with the corresponding GUID
144 */
145static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) {
146 std::lock_guard<std::mutex> lock(joystick_map_mutex);
147 const auto it = joystick_map.find(guid);
148 if (it != joystick_map.end()) {
149 while (it->second.size() <= port) {
150 auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
151 [](SDL_Joystick*) {});
152 it->second.emplace_back(std::move(joystick));
153 }
154 return it->second[port];
155 }
156 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
157 return joystick_map[guid].emplace_back(std::move(joystick));
158}
159
160/**
161 * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
162 * it to a SDLJoystick with the same guid and that port
163 */
164static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
165 std::lock_guard<std::mutex> lock(joystick_map_mutex);
166 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
167 const std::string guid = GetGUID(sdl_joystick);
168 auto map_it = joystick_map.find(guid);
169 if (map_it != joystick_map.end()) {
170 auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
171 [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
172 return sdl_joystick == joystick->GetSDLJoystick();
173 });
174 if (vec_it != map_it->second.end()) {
175 // This is the common case: There is already an existing SDL_Joystick maped to a
176 // SDLJoystick. return the SDLJoystick
177 return *vec_it;
178 }
179 // Search for a SDLJoystick without a mapped SDL_Joystick...
180 auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
181 [](const std::shared_ptr<SDLJoystick>& joystick) {
182 return !joystick->GetSDLJoystick();
183 });
184 if (nullptr_it != map_it->second.end()) {
185 // ... and map it
186 (*nullptr_it)->SetSDLJoystick(sdl_joystick);
187 return *nullptr_it;
188 }
189 // There is no SDLJoystick without a mapped SDL_Joystick
190 // Create a new SDLJoystick
191 auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
192 return map_it->second.emplace_back(std::move(joystick));
193 }
194 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
195 return joystick_map[guid].emplace_back(std::move(joystick));
196}
197
198void InitJoystick(int joystick_index) {
199 std::lock_guard<std::mutex> lock(joystick_map_mutex);
200 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
201 if (!sdl_joystick) {
202 LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
203 return;
204 }
205 std::string guid = GetGUID(sdl_joystick);
206 if (joystick_map.find(guid) == joystick_map.end()) {
207 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
208 joystick_map[guid].emplace_back(std::move(joystick));
209 return;
210 }
211 auto& joystick_guid_list = joystick_map[guid];
212 const auto it = std::find_if(
213 joystick_guid_list.begin(), joystick_guid_list.end(),
214 [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
215 if (it != joystick_guid_list.end()) {
216 (*it)->SetSDLJoystick(sdl_joystick);
217 return;
218 }
219 auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
220 joystick_guid_list.emplace_back(std::move(joystick));
221}
222
223void CloseJoystick(SDL_Joystick* sdl_joystick) {
224 std::lock_guard<std::mutex> lock(joystick_map_mutex);
225 std::string guid = GetGUID(sdl_joystick);
226 // This call to guid is save since the joystick is guranteed to be in that map
227 auto& joystick_guid_list = joystick_map[guid];
228 const auto joystick_it =
229 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
230 [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
231 return joystick->GetSDLJoystick() == sdl_joystick;
232 });
233 (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
234}
235
236void HandleGameControllerEvent(const SDL_Event& event) {
237 switch (event.type) {
238 case SDL_JOYBUTTONUP: {
239 auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
240 if (joystick) {
241 joystick->SetButton(event.jbutton.button, false);
242 }
243 break;
244 }
245 case SDL_JOYBUTTONDOWN: {
246 auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
247 if (joystick) {
248 joystick->SetButton(event.jbutton.button, true);
249 }
250 break;
251 }
252 case SDL_JOYHATMOTION: {
253 auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
254 if (joystick) {
255 joystick->SetHat(event.jhat.hat, event.jhat.value);
256 }
257 break;
258 }
259 case SDL_JOYAXISMOTION: {
260 auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
261 if (joystick) {
262 joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
263 }
264 break;
265 }
266 case SDL_JOYDEVICEREMOVED:
267 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
268 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
269 break;
270 case SDL_JOYDEVICEADDED:
271 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
272 InitJoystick(event.jdevice.which);
273 break;
274 }
275}
276
277void CloseSDLJoysticks() {
278 std::lock_guard<std::mutex> lock(joystick_map_mutex);
279 joystick_map.clear();
280}
281
282void PollLoop() {
283 if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
284 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
285 return;
286 }
287
288 SDL_Event event;
289 while (initialized) {
290 // Wait for 10 ms or until an event happens
291 if (SDL_WaitEventTimeout(&event, 10)) {
292 // Don't handle the event if we are configuring
293 if (polling) {
294 event_queue.Push(event);
295 } else {
296 HandleGameControllerEvent(event);
297 }
298 }
299 }
300 CloseSDLJoysticks();
301 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
302}
303
304class SDLButton final : public Input::ButtonDevice {
305public:
306 explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
307 : joystick(std::move(joystick_)), button(button_) {}
308
309 bool GetStatus() const override {
310 return joystick->GetButton(button);
311 }
312
313private:
314 std::shared_ptr<SDLJoystick> joystick;
315 int button;
316};
317
318class SDLDirectionButton final : public Input::ButtonDevice {
319public:
320 explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
321 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
322
323 bool GetStatus() const override {
324 return joystick->GetHatDirection(hat, direction);
325 }
326
327private:
328 std::shared_ptr<SDLJoystick> joystick;
329 int hat;
330 Uint8 direction;
331};
332
333class SDLAxisButton final : public Input::ButtonDevice {
334public:
335 explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
336 bool trigger_if_greater_)
337 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
338 trigger_if_greater(trigger_if_greater_) {}
339
340 bool GetStatus() const override {
341 float axis_value = joystick->GetAxis(axis);
342 if (trigger_if_greater)
343 return axis_value > threshold;
344 return axis_value < threshold;
345 }
346
347private:
348 std::shared_ptr<SDLJoystick> joystick;
349 int axis;
350 float threshold;
351 bool trigger_if_greater;
352};
353
354class SDLAnalog final : public Input::AnalogDevice {
355public:
356 SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
357 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {}
358
359 std::tuple<float, float> GetStatus() const override {
360 return joystick->GetAnalog(axis_x, axis_y);
361 }
362
363private:
364 std::shared_ptr<SDLJoystick> joystick;
365 int axis_x;
366 int axis_y;
367};
368
369/// A button device factory that creates button devices from SDL joystick
370class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
371public:
372 /**
373 * Creates a button device from a joystick button
374 * @param params contains parameters for creating the device:
375 * - "guid": the guid of the joystick to bind
376 * - "port": the nth joystick of the same type to bind
377 * - "button"(optional): the index of the button to bind
378 * - "hat"(optional): the index of the hat to bind as direction buttons
379 * - "axis"(optional): the index of the axis to bind
380 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
381 * "down", "left" or "right"
382 * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
383 * triggered if the axis value crosses
384 * - "direction"(only used for axis): "+" means the button is triggered when the axis
385 * value is greater than the threshold; "-" means the button is triggered when the axis
386 * value is smaller than the threshold
387 */
388 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
389 const std::string guid = params.Get("guid", "0");
390 const int port = params.Get("port", 0);
391
392 auto joystick = GetSDLJoystickByGUID(guid, port);
393
394 if (params.Has("hat")) {
395 const int hat = params.Get("hat", 0);
396 const std::string direction_name = params.Get("direction", "");
397 Uint8 direction;
398 if (direction_name == "up") {
399 direction = SDL_HAT_UP;
400 } else if (direction_name == "down") {
401 direction = SDL_HAT_DOWN;
402 } else if (direction_name == "left") {
403 direction = SDL_HAT_LEFT;
404 } else if (direction_name == "right") {
405 direction = SDL_HAT_RIGHT;
406 } else {
407 direction = 0;
408 }
409 // This is necessary so accessing GetHat with hat won't crash
410 joystick->SetHat(hat, SDL_HAT_CENTERED);
411 return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
412 }
413
414 if (params.Has("axis")) {
415 const int axis = params.Get("axis", 0);
416 const float threshold = params.Get("threshold", 0.5f);
417 const std::string direction_name = params.Get("direction", "");
418 bool trigger_if_greater;
419 if (direction_name == "+") {
420 trigger_if_greater = true;
421 } else if (direction_name == "-") {
422 trigger_if_greater = false;
423 } else {
424 trigger_if_greater = true;
425 LOG_ERROR(Input, "Unknown direction '{}'", direction_name);
426 }
427 // This is necessary so accessing GetAxis with axis won't crash
428 joystick->SetAxis(axis, 0);
429 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
430 }
431
432 const int button = params.Get("button", 0);
433 // This is necessary so accessing GetButton with button won't crash
434 joystick->SetButton(button, false);
435 return std::make_unique<SDLButton>(joystick, button);
436 }
437};
438
439/// An analog device factory that creates analog devices from SDL joystick
440class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
441public:
442 /**
443 * Creates analog device from joystick axes
444 * @param params contains parameters for creating the device:
445 * - "guid": the guid of the joystick to bind
446 * - "port": the nth joystick of the same type
447 * - "axis_x": the index of the axis to be bind as x-axis
448 * - "axis_y": the index of the axis to be bind as y-axis
449 */
450 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
451 const std::string guid = params.Get("guid", "0");
452 const int port = params.Get("port", 0);
453 const int axis_x = params.Get("axis_x", 0);
454 const int axis_y = params.Get("axis_y", 1);
455
456 auto joystick = GetSDLJoystickByGUID(guid, port);
457
458 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
459 joystick->SetAxis(axis_x, 0);
460 joystick->SetAxis(axis_y, 0);
461 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y);
462 }
463};
464
465void Init() {
466 using namespace Input;
467 RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
468 RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
469 polling = false;
470 initialized = true;
471}
472
473void Shutdown() {
474 if (initialized) {
475 using namespace Input;
476 UnregisterFactory<ButtonDevice>("sdl");
477 UnregisterFactory<AnalogDevice>("sdl");
478 initialized = false;
479 }
480}
481
482Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) {
483 Common::ParamPackage params({{"engine", "sdl"}});
484 switch (event.type) {
485 case SDL_JOYAXISMOTION: {
486 auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
487 params.Set("port", joystick->GetPort());
488 params.Set("guid", joystick->GetGUID());
489 params.Set("axis", event.jaxis.axis);
490 if (event.jaxis.value > 0) {
491 params.Set("direction", "+");
492 params.Set("threshold", "0.5");
493 } else {
494 params.Set("direction", "-");
495 params.Set("threshold", "-0.5");
496 }
497 break;
498 }
499 case SDL_JOYBUTTONUP: {
500 auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
501 params.Set("port", joystick->GetPort());
502 params.Set("guid", joystick->GetGUID());
503 params.Set("button", event.jbutton.button);
504 break;
505 }
506 case SDL_JOYHATMOTION: {
507 auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
508 params.Set("port", joystick->GetPort());
509 params.Set("guid", joystick->GetGUID());
510 params.Set("hat", event.jhat.hat);
511 switch (event.jhat.value) {
512 case SDL_HAT_UP:
513 params.Set("direction", "up");
514 break;
515 case SDL_HAT_DOWN:
516 params.Set("direction", "down");
517 break;
518 case SDL_HAT_LEFT:
519 params.Set("direction", "left");
520 break;
521 case SDL_HAT_RIGHT:
522 params.Set("direction", "right");
523 break;
524 default:
525 return {};
526 }
527 break;
528 }
529 }
530 return params;
531}
532
533namespace Polling {
534
535class SDLPoller : public InputCommon::Polling::DevicePoller {
536public:
537 void Start() override {
538 event_queue.Clear();
539 polling = true;
540 }
541
542 void Stop() override {
543 polling = false;
544 }
545};
546
547class SDLButtonPoller final : public SDLPoller {
548public:
549 Common::ParamPackage GetNextInput() override {
550 SDL_Event event;
551 while (event_queue.Pop(event)) {
552 switch (event.type) {
553 case SDL_JOYAXISMOTION:
554 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
555 break;
556 }
557 case SDL_JOYBUTTONUP:
558 case SDL_JOYHATMOTION:
559 return SDLEventToButtonParamPackage(event);
560 }
561 }
562 return {};
563 }
564};
565
566class SDLAnalogPoller final : public SDLPoller {
567public:
568 void Start() override {
569 SDLPoller::Start();
570
571 // Reset stored axes
572 analog_xaxis = -1;
573 analog_yaxis = -1;
574 analog_axes_joystick = -1;
575 }
576
577 Common::ParamPackage GetNextInput() override {
578 SDL_Event event;
579 while (event_queue.Pop(event)) {
580 if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
581 continue;
582 }
583 // An analog device needs two axes, so we need to store the axis for later and wait for
584 // a second SDL event. The axes also must be from the same joystick.
585 int axis = event.jaxis.axis;
586 if (analog_xaxis == -1) {
587 analog_xaxis = axis;
588 analog_axes_joystick = event.jaxis.which;
589 } else if (analog_yaxis == -1 && analog_xaxis != axis &&
590 analog_axes_joystick == event.jaxis.which) {
591 analog_yaxis = axis;
592 }
593 }
594 Common::ParamPackage params;
595 if (analog_xaxis != -1 && analog_yaxis != -1) {
596 auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
597 params.Set("engine", "sdl");
598 params.Set("port", joystick->GetPort());
599 params.Set("guid", joystick->GetGUID());
600 params.Set("axis_x", analog_xaxis);
601 params.Set("axis_y", analog_yaxis);
602 analog_xaxis = -1;
603 analog_yaxis = -1;
604 analog_axes_joystick = -1;
605 return params;
606 }
607 return params;
608 }
609
610private:
611 int analog_xaxis = -1;
612 int analog_yaxis = -1;
613 SDL_JoystickID analog_axes_joystick = -1;
614};
615
616std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
617 InputCommon::Polling::DeviceType type) {
618 std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
619 switch (type) {
620 case InputCommon::Polling::DeviceType::Analog:
621 pollers.push_back(std::make_unique<SDLAnalogPoller>());
622 break;
623 case InputCommon::Polling::DeviceType::Button:
624 pollers.push_back(std::make_unique<SDLButtonPoller>());
625 break;
626 }
627 return pollers;
628} 18}
629} // namespace Polling 19} // namespace InputCommon::SDL
630} // namespace SDL
631} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 0206860d3..d7f24c68a 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -1,4 +1,4 @@
1// Copyright 2017 Citra Emulator Project 1// Copyright 2018 Citra 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
@@ -7,45 +7,38 @@
7#include <memory> 7#include <memory>
8#include <vector> 8#include <vector>
9#include "core/frontend/input.h" 9#include "core/frontend/input.h"
10#include "input_common/main.h"
10 11
11union SDL_Event; 12union SDL_Event;
13
12namespace Common { 14namespace Common {
13class ParamPackage; 15class ParamPackage;
14} 16} // namespace Common
15namespace InputCommon { 17
16namespace Polling { 18namespace InputCommon::Polling {
17class DevicePoller; 19class DevicePoller;
18enum class DeviceType; 20enum class DeviceType;
19} // namespace Polling 21} // namespace InputCommon::Polling
20} // namespace InputCommon
21
22namespace InputCommon {
23namespace SDL {
24
25/// Initializes and registers SDL device factories
26void Init();
27
28/// Unresisters SDL device factories and shut them down.
29void Shutdown();
30 22
31/// Needs to be called before SDL_QuitSubSystem. 23namespace InputCommon::SDL {
32void CloseSDLJoysticks();
33 24
34/// Handle SDL_Events for joysticks from SDL_PollEvent 25class State {
35void HandleGameControllerEvent(const SDL_Event& event); 26public:
27 using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
36 28
37/// A Loop that calls HandleGameControllerEvent until Shutdown is called 29 /// Unregisters SDL device factories and shut them down.
38void PollLoop(); 30 virtual ~State() = default;
39 31
40/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice 32 virtual Pollers GetPollers(Polling::DeviceType type) = 0;
41Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); 33};
42 34
43namespace Polling { 35class NullState : public State {
36public:
37 Pollers GetPollers(Polling::DeviceType type) override {
38 return {};
39 }
40};
44 41
45/// Get all DevicePoller that use the SDL backend for a specific device type 42std::unique_ptr<State> Init();
46std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
47 InputCommon::Polling::DeviceType type);
48 43
49} // namespace Polling 44} // namespace InputCommon::SDL
50} // namespace SDL
51} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
new file mode 100644
index 000000000..5949ecbae
--- /dev/null
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -0,0 +1,671 @@
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 <atomic>
7#include <cmath>
8#include <functional>
9#include <iterator>
10#include <mutex>
11#include <string>
12#include <thread>
13#include <tuple>
14#include <unordered_map>
15#include <utility>
16#include <vector>
17#include <SDL.h>
18#include "common/assert.h"
19#include "common/logging/log.h"
20#include "common/math_util.h"
21#include "common/param_package.h"
22#include "common/threadsafe_queue.h"
23#include "core/frontend/input.h"
24#include "input_common/sdl/sdl_impl.h"
25
26namespace InputCommon {
27
28namespace SDL {
29
30static std::string GetGUID(SDL_Joystick* joystick) {
31 SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
32 char guid_str[33];
33 SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
34 return guid_str;
35}
36
37/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
38static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
39
40static int SDLEventWatcher(void* userdata, SDL_Event* event) {
41 SDLState* sdl_state = reinterpret_cast<SDLState*>(userdata);
42 // Don't handle the event if we are configuring
43 if (sdl_state->polling) {
44 sdl_state->event_queue.Push(*event);
45 } else {
46 sdl_state->HandleGameControllerEvent(*event);
47 }
48 return 0;
49}
50
51class SDLJoystick {
52public:
53 SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
54 decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
55 : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
56
57 void SetButton(int button, bool value) {
58 std::lock_guard lock{mutex};
59 state.buttons[button] = value;
60 }
61
62 bool GetButton(int button) const {
63 std::lock_guard lock{mutex};
64 return state.buttons.at(button);
65 }
66
67 void SetAxis(int axis, Sint16 value) {
68 std::lock_guard lock{mutex};
69 state.axes[axis] = value;
70 }
71
72 float GetAxis(int axis) const {
73 std::lock_guard lock{mutex};
74 return state.axes.at(axis) / 32767.0f;
75 }
76
77 std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
78 float x = GetAxis(axis_x);
79 float y = GetAxis(axis_y);
80 y = -y; // 3DS uses an y-axis inverse from SDL
81
82 // Make sure the coordinates are in the unit circle,
83 // otherwise normalize it.
84 float r = x * x + y * y;
85 if (r > 1.0f) {
86 r = std::sqrt(r);
87 x /= r;
88 y /= r;
89 }
90
91 return std::make_tuple(x, y);
92 }
93
94 void SetHat(int hat, Uint8 direction) {
95 std::lock_guard lock{mutex};
96 state.hats[hat] = direction;
97 }
98
99 bool GetHatDirection(int hat, Uint8 direction) const {
100 std::lock_guard lock{mutex};
101 return (state.hats.at(hat) & direction) != 0;
102 }
103 /**
104 * The guid of the joystick
105 */
106 const std::string& GetGUID() const {
107 return guid;
108 }
109
110 /**
111 * The number of joystick from the same type that were connected before this joystick
112 */
113 int GetPort() const {
114 return port;
115 }
116
117 SDL_Joystick* GetSDLJoystick() const {
118 return sdl_joystick.get();
119 }
120
121 void SetSDLJoystick(SDL_Joystick* joystick,
122 decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
123 sdl_joystick =
124 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
125 }
126
127private:
128 struct State {
129 std::unordered_map<int, bool> buttons;
130 std::unordered_map<int, Sint16> axes;
131 std::unordered_map<int, Uint8> hats;
132 } state;
133 std::string guid;
134 int port;
135 std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
136 mutable std::mutex mutex;
137};
138
139/**
140 * Get the nth joystick with the corresponding GUID
141 */
142std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
143 std::lock_guard lock{joystick_map_mutex};
144 const auto it = joystick_map.find(guid);
145 if (it != joystick_map.end()) {
146 while (it->second.size() <= port) {
147 auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
148 [](SDL_Joystick*) {});
149 it->second.emplace_back(std::move(joystick));
150 }
151 return it->second[port];
152 }
153 auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
154 return joystick_map[guid].emplace_back(std::move(joystick));
155}
156
157/**
158 * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
159 * it to a SDLJoystick with the same guid and that port
160 */
161std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
162 auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
163 const std::string guid = GetGUID(sdl_joystick);
164
165 std::lock_guard lock{joystick_map_mutex};
166 auto map_it = joystick_map.find(guid);
167 if (map_it != joystick_map.end()) {
168 auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
169 [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
170 return sdl_joystick == joystick->GetSDLJoystick();
171 });
172 if (vec_it != map_it->second.end()) {
173 // This is the common case: There is already an existing SDL_Joystick maped to a
174 // SDLJoystick. return the SDLJoystick
175 return *vec_it;
176 }
177 // Search for a SDLJoystick without a mapped SDL_Joystick...
178 auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
179 [](const std::shared_ptr<SDLJoystick>& joystick) {
180 return !joystick->GetSDLJoystick();
181 });
182 if (nullptr_it != map_it->second.end()) {
183 // ... and map it
184 (*nullptr_it)->SetSDLJoystick(sdl_joystick);
185 return *nullptr_it;
186 }
187 // There is no SDLJoystick without a mapped SDL_Joystick
188 // Create a new SDLJoystick
189 auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
190 return map_it->second.emplace_back(std::move(joystick));
191 }
192 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
193 return joystick_map[guid].emplace_back(std::move(joystick));
194}
195
196void SDLState::InitJoystick(int joystick_index) {
197 SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
198 if (!sdl_joystick) {
199 LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
200 return;
201 }
202 const std::string guid = GetGUID(sdl_joystick);
203
204 std::lock_guard lock{joystick_map_mutex};
205 if (joystick_map.find(guid) == joystick_map.end()) {
206 auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
207 joystick_map[guid].emplace_back(std::move(joystick));
208 return;
209 }
210 auto& joystick_guid_list = joystick_map[guid];
211 const auto it = std::find_if(
212 joystick_guid_list.begin(), joystick_guid_list.end(),
213 [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
214 if (it != joystick_guid_list.end()) {
215 (*it)->SetSDLJoystick(sdl_joystick);
216 return;
217 }
218 auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
219 joystick_guid_list.emplace_back(std::move(joystick));
220}
221
222void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
223 std::string guid = GetGUID(sdl_joystick);
224 std::shared_ptr<SDLJoystick> joystick;
225 {
226 std::lock_guard lock{joystick_map_mutex};
227 // This call to guid is safe since the joystick is guaranteed to be in the map
228 auto& joystick_guid_list = joystick_map[guid];
229 const auto joystick_it =
230 std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
231 [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
232 return joystick->GetSDLJoystick() == sdl_joystick;
233 });
234 joystick = *joystick_it;
235 }
236 // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback
237 // which locks the mutex again
238 joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
239}
240
241void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
242 switch (event.type) {
243 case SDL_JOYBUTTONUP: {
244 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
245 joystick->SetButton(event.jbutton.button, false);
246 }
247 break;
248 }
249 case SDL_JOYBUTTONDOWN: {
250 if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
251 joystick->SetButton(event.jbutton.button, true);
252 }
253 break;
254 }
255 case SDL_JOYHATMOTION: {
256 if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
257 joystick->SetHat(event.jhat.hat, event.jhat.value);
258 }
259 break;
260 }
261 case SDL_JOYAXISMOTION: {
262 if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
263 joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
264 }
265 break;
266 }
267 case SDL_JOYDEVICEREMOVED:
268 LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
269 CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
270 break;
271 case SDL_JOYDEVICEADDED:
272 LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
273 InitJoystick(event.jdevice.which);
274 break;
275 }
276}
277
278void SDLState::CloseJoysticks() {
279 std::lock_guard lock{joystick_map_mutex};
280 joystick_map.clear();
281}
282
283class SDLButton final : public Input::ButtonDevice {
284public:
285 explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
286 : joystick(std::move(joystick_)), button(button_) {}
287
288 bool GetStatus() const override {
289 return joystick->GetButton(button);
290 }
291
292private:
293 std::shared_ptr<SDLJoystick> joystick;
294 int button;
295};
296
297class SDLDirectionButton final : public Input::ButtonDevice {
298public:
299 explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
300 : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
301
302 bool GetStatus() const override {
303 return joystick->GetHatDirection(hat, direction);
304 }
305
306private:
307 std::shared_ptr<SDLJoystick> joystick;
308 int hat;
309 Uint8 direction;
310};
311
312class SDLAxisButton final : public Input::ButtonDevice {
313public:
314 explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
315 bool trigger_if_greater_)
316 : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
317 trigger_if_greater(trigger_if_greater_) {}
318
319 bool GetStatus() const override {
320 float axis_value = joystick->GetAxis(axis);
321 if (trigger_if_greater)
322 return axis_value > threshold;
323 return axis_value < threshold;
324 }
325
326private:
327 std::shared_ptr<SDLJoystick> joystick;
328 int axis;
329 float threshold;
330 bool trigger_if_greater;
331};
332
333class SDLAnalog final : public Input::AnalogDevice {
334public:
335 SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
336 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
337
338 std::tuple<float, float> GetStatus() const override {
339 const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
340 const float r = std::sqrt((x * x) + (y * y));
341 if (r > deadzone) {
342 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
343 y / r * (r - deadzone) / (1 - deadzone));
344 }
345 return std::make_tuple<float, float>(0.0f, 0.0f);
346 }
347
348private:
349 std::shared_ptr<SDLJoystick> joystick;
350 const int axis_x;
351 const int axis_y;
352 const float deadzone;
353};
354
355/// A button device factory that creates button devices from SDL joystick
356class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
357public:
358 explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
359
360 /**
361 * Creates a button device from a joystick button
362 * @param params contains parameters for creating the device:
363 * - "guid": the guid of the joystick to bind
364 * - "port": the nth joystick of the same type to bind
365 * - "button"(optional): the index of the button to bind
366 * - "hat"(optional): the index of the hat to bind as direction buttons
367 * - "axis"(optional): the index of the axis to bind
368 * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
369 * "down", "left" or "right"
370 * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
371 * triggered if the axis value crosses
372 * - "direction"(only used for axis): "+" means the button is triggered when the axis
373 * value is greater than the threshold; "-" means the button is triggered when the axis
374 * value is smaller than the threshold
375 */
376 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
377 const std::string guid = params.Get("guid", "0");
378 const int port = params.Get("port", 0);
379
380 auto joystick = state.GetSDLJoystickByGUID(guid, port);
381
382 if (params.Has("hat")) {
383 const int hat = params.Get("hat", 0);
384 const std::string direction_name = params.Get("direction", "");
385 Uint8 direction;
386 if (direction_name == "up") {
387 direction = SDL_HAT_UP;
388 } else if (direction_name == "down") {
389 direction = SDL_HAT_DOWN;
390 } else if (direction_name == "left") {
391 direction = SDL_HAT_LEFT;
392 } else if (direction_name == "right") {
393 direction = SDL_HAT_RIGHT;
394 } else {
395 direction = 0;
396 }
397 // This is necessary so accessing GetHat with hat won't crash
398 joystick->SetHat(hat, SDL_HAT_CENTERED);
399 return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
400 }
401
402 if (params.Has("axis")) {
403 const int axis = params.Get("axis", 0);
404 const float threshold = params.Get("threshold", 0.5f);
405 const std::string direction_name = params.Get("direction", "");
406 bool trigger_if_greater;
407 if (direction_name == "+") {
408 trigger_if_greater = true;
409 } else if (direction_name == "-") {
410 trigger_if_greater = false;
411 } else {
412 trigger_if_greater = true;
413 LOG_ERROR(Input, "Unknown direction {}", direction_name);
414 }
415 // This is necessary so accessing GetAxis with axis won't crash
416 joystick->SetAxis(axis, 0);
417 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
418 }
419
420 const int button = params.Get("button", 0);
421 // This is necessary so accessing GetButton with button won't crash
422 joystick->SetButton(button, false);
423 return std::make_unique<SDLButton>(joystick, button);
424 }
425
426private:
427 SDLState& state;
428};
429
430/// An analog device factory that creates analog devices from SDL joystick
431class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
432public:
433 explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
434 /**
435 * Creates analog device from joystick axes
436 * @param params contains parameters for creating the device:
437 * - "guid": the guid of the joystick to bind
438 * - "port": the nth joystick of the same type
439 * - "axis_x": the index of the axis to be bind as x-axis
440 * - "axis_y": the index of the axis to be bind as y-axis
441 */
442 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
443 const std::string guid = params.Get("guid", "0");
444 const int port = params.Get("port", 0);
445 const int axis_x = params.Get("axis_x", 0);
446 const int axis_y = params.Get("axis_y", 1);
447 float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
448
449 auto joystick = state.GetSDLJoystickByGUID(guid, port);
450
451 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
452 joystick->SetAxis(axis_x, 0);
453 joystick->SetAxis(axis_y, 0);
454 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone);
455 }
456
457private:
458 SDLState& state;
459};
460
461SDLState::SDLState() {
462 using namespace Input;
463 RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
464 RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
465
466 // If the frontend is going to manage the event loop, then we dont start one here
467 start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
468 if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
469 LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
470 return;
471 }
472 if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
473 LOG_ERROR(Input, "Failed to set Hint for background events", SDL_GetError());
474 }
475
476 SDL_AddEventWatch(&SDLEventWatcher, this);
477
478 initialized = true;
479 if (start_thread) {
480 poll_thread = std::thread([this] {
481 using namespace std::chrono_literals;
482 while (initialized) {
483 SDL_PumpEvents();
484 std::this_thread::sleep_for(10ms);
485 }
486 });
487 }
488 // Because the events for joystick connection happens before we have our event watcher added, we
489 // can just open all the joysticks right here
490 for (int i = 0; i < SDL_NumJoysticks(); ++i) {
491 InitJoystick(i);
492 }
493}
494
495SDLState::~SDLState() {
496 using namespace Input;
497 UnregisterFactory<ButtonDevice>("sdl");
498 UnregisterFactory<AnalogDevice>("sdl");
499
500 CloseJoysticks();
501 SDL_DelEventWatch(&SDLEventWatcher, this);
502
503 initialized = false;
504 if (start_thread) {
505 poll_thread.join();
506 SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
507 }
508}
509
510Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
511 Common::ParamPackage params({{"engine", "sdl"}});
512
513 switch (event.type) {
514 case SDL_JOYAXISMOTION: {
515 auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
516 params.Set("port", joystick->GetPort());
517 params.Set("guid", joystick->GetGUID());
518 params.Set("axis", event.jaxis.axis);
519 if (event.jaxis.value > 0) {
520 params.Set("direction", "+");
521 params.Set("threshold", "0.5");
522 } else {
523 params.Set("direction", "-");
524 params.Set("threshold", "-0.5");
525 }
526 break;
527 }
528 case SDL_JOYBUTTONUP: {
529 auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
530 params.Set("port", joystick->GetPort());
531 params.Set("guid", joystick->GetGUID());
532 params.Set("button", event.jbutton.button);
533 break;
534 }
535 case SDL_JOYHATMOTION: {
536 auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
537 params.Set("port", joystick->GetPort());
538 params.Set("guid", joystick->GetGUID());
539 params.Set("hat", event.jhat.hat);
540 switch (event.jhat.value) {
541 case SDL_HAT_UP:
542 params.Set("direction", "up");
543 break;
544 case SDL_HAT_DOWN:
545 params.Set("direction", "down");
546 break;
547 case SDL_HAT_LEFT:
548 params.Set("direction", "left");
549 break;
550 case SDL_HAT_RIGHT:
551 params.Set("direction", "right");
552 break;
553 default:
554 return {};
555 }
556 break;
557 }
558 }
559 return params;
560}
561
562namespace Polling {
563
564class SDLPoller : public InputCommon::Polling::DevicePoller {
565public:
566 explicit SDLPoller(SDLState& state_) : state(state_) {}
567
568 void Start() override {
569 state.event_queue.Clear();
570 state.polling = true;
571 }
572
573 void Stop() override {
574 state.polling = false;
575 }
576
577protected:
578 SDLState& state;
579};
580
581class SDLButtonPoller final : public SDLPoller {
582public:
583 explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
584
585 Common::ParamPackage GetNextInput() override {
586 SDL_Event event;
587 while (state.event_queue.Pop(event)) {
588 switch (event.type) {
589 case SDL_JOYAXISMOTION:
590 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
591 break;
592 }
593 case SDL_JOYBUTTONUP:
594 case SDL_JOYHATMOTION:
595 return SDLEventToButtonParamPackage(state, event);
596 }
597 }
598 return {};
599 }
600};
601
602class SDLAnalogPoller final : public SDLPoller {
603public:
604 explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
605
606 void Start() override {
607 SDLPoller::Start();
608
609 // Reset stored axes
610 analog_xaxis = -1;
611 analog_yaxis = -1;
612 analog_axes_joystick = -1;
613 }
614
615 Common::ParamPackage GetNextInput() override {
616 SDL_Event event;
617 while (state.event_queue.Pop(event)) {
618 if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
619 continue;
620 }
621 // An analog device needs two axes, so we need to store the axis for later and wait for
622 // a second SDL event. The axes also must be from the same joystick.
623 int axis = event.jaxis.axis;
624 if (analog_xaxis == -1) {
625 analog_xaxis = axis;
626 analog_axes_joystick = event.jaxis.which;
627 } else if (analog_yaxis == -1 && analog_xaxis != axis &&
628 analog_axes_joystick == event.jaxis.which) {
629 analog_yaxis = axis;
630 }
631 }
632 Common::ParamPackage params;
633 if (analog_xaxis != -1 && analog_yaxis != -1) {
634 auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
635 params.Set("engine", "sdl");
636 params.Set("port", joystick->GetPort());
637 params.Set("guid", joystick->GetGUID());
638 params.Set("axis_x", analog_xaxis);
639 params.Set("axis_y", analog_yaxis);
640 analog_xaxis = -1;
641 analog_yaxis = -1;
642 analog_axes_joystick = -1;
643 return params;
644 }
645 return params;
646 }
647
648private:
649 int analog_xaxis = -1;
650 int analog_yaxis = -1;
651 SDL_JoystickID analog_axes_joystick = -1;
652};
653} // namespace Polling
654
655SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
656 Pollers pollers;
657
658 switch (type) {
659 case InputCommon::Polling::DeviceType::Analog:
660 pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
661 break;
662 case InputCommon::Polling::DeviceType::Button:
663 pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
664 break;
665 }
666
667 return pollers;
668}
669
670} // namespace SDL
671} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
new file mode 100644
index 000000000..2579741d6
--- /dev/null
+++ b/src/input_common/sdl/sdl_impl.h
@@ -0,0 +1,63 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <atomic>
8#include <memory>
9#include <thread>
10#include "common/threadsafe_queue.h"
11#include "input_common/sdl/sdl.h"
12
13union SDL_Event;
14using SDL_Joystick = struct _SDL_Joystick;
15using SDL_JoystickID = s32;
16
17namespace InputCommon::SDL {
18
19class SDLJoystick;
20class SDLButtonFactory;
21class SDLAnalogFactory;
22
23class SDLState : public State {
24public:
25 /// Initializes and registers SDL device factories
26 SDLState();
27
28 /// Unregisters SDL device factories and shut them down.
29 ~SDLState() override;
30
31 /// Handle SDL_Events for joysticks from SDL_PollEvent
32 void HandleGameControllerEvent(const SDL_Event& event);
33
34 std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
35 std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
36
37 /// Get all DevicePoller that use the SDL backend for a specific device type
38 Pollers GetPollers(Polling::DeviceType type) override;
39
40 /// Used by the Pollers during config
41 std::atomic<bool> polling = false;
42 Common::SPSCQueue<SDL_Event> event_queue;
43
44private:
45 void InitJoystick(int joystick_index);
46 void CloseJoystick(SDL_Joystick* sdl_joystick);
47
48 /// Needs to be called before SDL_QuitSubSystem.
49 void CloseJoysticks();
50
51 /// Map of GUID of a list of corresponding virtual Joysticks
52 std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
53 std::mutex joystick_map_mutex;
54
55 std::shared_ptr<SDLButtonFactory> button_factory;
56 std::shared_ptr<SDLAnalogFactory> analog_factory;
57
58 bool start_thread = false;
59 std::atomic<bool> initialized = false;
60
61 std::thread poll_thread;
62};
63} // namespace InputCommon::SDL