sdl.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. // Copyright 2017 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <cmath>
  5. #include <string>
  6. #include <tuple>
  7. #include <unordered_map>
  8. #include <utility>
  9. #include <SDL.h>
  10. #include "common/logging/log.h"
  11. #include "common/math_util.h"
  12. #include "common/param_package.h"
  13. #include "input_common/main.h"
  14. #include "input_common/sdl/sdl.h"
  15. namespace InputCommon {
  16. namespace SDL {
  17. class SDLJoystick;
  18. class SDLButtonFactory;
  19. class SDLAnalogFactory;
  20. static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list;
  21. static std::shared_ptr<SDLButtonFactory> button_factory;
  22. static std::shared_ptr<SDLAnalogFactory> analog_factory;
  23. static bool initialized = false;
  24. class SDLJoystick {
  25. public:
  26. explicit SDLJoystick(int joystick_index)
  27. : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} {
  28. if (!joystick) {
  29. NGLOG_ERROR(Input, "failed to open joystick {}", joystick_index);
  30. }
  31. }
  32. bool GetButton(int button) const {
  33. if (!joystick)
  34. return {};
  35. SDL_JoystickUpdate();
  36. return SDL_JoystickGetButton(joystick.get(), button) == 1;
  37. }
  38. float GetAxis(int axis) const {
  39. if (!joystick)
  40. return {};
  41. SDL_JoystickUpdate();
  42. return SDL_JoystickGetAxis(joystick.get(), axis) / 32767.0f;
  43. }
  44. std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
  45. float x = GetAxis(axis_x);
  46. float y = GetAxis(axis_y);
  47. y = -y; // 3DS uses an y-axis inverse from SDL
  48. // Make sure the coordinates are in the unit circle,
  49. // otherwise normalize it.
  50. float r = x * x + y * y;
  51. if (r > 1.0f) {
  52. r = std::sqrt(r);
  53. x /= r;
  54. y /= r;
  55. }
  56. return std::make_tuple(x, y);
  57. }
  58. bool GetHatDirection(int hat, Uint8 direction) const {
  59. return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0;
  60. }
  61. SDL_JoystickID GetJoystickID() const {
  62. return SDL_JoystickInstanceID(joystick.get());
  63. }
  64. private:
  65. std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick;
  66. };
  67. class SDLButton final : public Input::ButtonDevice {
  68. public:
  69. explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
  70. : joystick(joystick_), button(button_) {}
  71. bool GetStatus() const override {
  72. return joystick->GetButton(button);
  73. }
  74. private:
  75. std::shared_ptr<SDLJoystick> joystick;
  76. int button;
  77. };
  78. class SDLDirectionButton final : public Input::ButtonDevice {
  79. public:
  80. explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
  81. : joystick(joystick_), hat(hat_), direction(direction_) {}
  82. bool GetStatus() const override {
  83. return joystick->GetHatDirection(hat, direction);
  84. }
  85. private:
  86. std::shared_ptr<SDLJoystick> joystick;
  87. int hat;
  88. Uint8 direction;
  89. };
  90. class SDLAxisButton final : public Input::ButtonDevice {
  91. public:
  92. explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
  93. bool trigger_if_greater_)
  94. : joystick(joystick_), axis(axis_), threshold(threshold_),
  95. trigger_if_greater(trigger_if_greater_) {}
  96. bool GetStatus() const override {
  97. float axis_value = joystick->GetAxis(axis);
  98. if (trigger_if_greater)
  99. return axis_value > threshold;
  100. return axis_value < threshold;
  101. }
  102. private:
  103. std::shared_ptr<SDLJoystick> joystick;
  104. int axis;
  105. float threshold;
  106. bool trigger_if_greater;
  107. };
  108. class SDLAnalog final : public Input::AnalogDevice {
  109. public:
  110. SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
  111. : joystick(joystick_), axis_x(axis_x_), axis_y(axis_y_) {}
  112. std::tuple<float, float> GetStatus() const override {
  113. return joystick->GetAnalog(axis_x, axis_y);
  114. }
  115. private:
  116. std::shared_ptr<SDLJoystick> joystick;
  117. int axis_x;
  118. int axis_y;
  119. };
  120. static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) {
  121. std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock();
  122. if (!joystick) {
  123. joystick = std::make_shared<SDLJoystick>(joystick_index);
  124. joystick_list[joystick_index] = joystick;
  125. }
  126. return joystick;
  127. }
  128. /// A button device factory that creates button devices from SDL joystick
  129. class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
  130. public:
  131. /**
  132. * Creates a button device from a joystick button
  133. * @param params contains parameters for creating the device:
  134. * - "joystick": the index of the joystick to bind
  135. * - "button"(optional): the index of the button to bind
  136. * - "hat"(optional): the index of the hat to bind as direction buttons
  137. * - "axis"(optional): the index of the axis to bind
  138. * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
  139. * "down", "left" or "right"
  140. * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
  141. * triggered if the axis value crosses
  142. * - "direction"(only used for axis): "+" means the button is triggered when the axis value
  143. * is greater than the threshold; "-" means the button is triggered when the axis value
  144. * is smaller than the threshold
  145. */
  146. std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
  147. const int joystick_index = params.Get("joystick", 0);
  148. if (params.Has("hat")) {
  149. const int hat = params.Get("hat", 0);
  150. const std::string direction_name = params.Get("direction", "");
  151. Uint8 direction;
  152. if (direction_name == "up") {
  153. direction = SDL_HAT_UP;
  154. } else if (direction_name == "down") {
  155. direction = SDL_HAT_DOWN;
  156. } else if (direction_name == "left") {
  157. direction = SDL_HAT_LEFT;
  158. } else if (direction_name == "right") {
  159. direction = SDL_HAT_RIGHT;
  160. } else {
  161. direction = 0;
  162. }
  163. return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat,
  164. direction);
  165. }
  166. if (params.Has("axis")) {
  167. const int axis = params.Get("axis", 0);
  168. const float threshold = params.Get("threshold", 0.5f);
  169. const std::string direction_name = params.Get("direction", "");
  170. bool trigger_if_greater;
  171. if (direction_name == "+") {
  172. trigger_if_greater = true;
  173. } else if (direction_name == "-") {
  174. trigger_if_greater = false;
  175. } else {
  176. trigger_if_greater = true;
  177. NGLOG_ERROR(Input, "Unknown direction '{}'", direction_name);
  178. }
  179. return std::make_unique<SDLAxisButton>(GetJoystick(joystick_index), axis, threshold,
  180. trigger_if_greater);
  181. }
  182. const int button = params.Get("button", 0);
  183. return std::make_unique<SDLButton>(GetJoystick(joystick_index), button);
  184. }
  185. };
  186. /// An analog device factory that creates analog devices from SDL joystick
  187. class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
  188. public:
  189. /**
  190. * Creates analog device from joystick axes
  191. * @param params contains parameters for creating the device:
  192. * - "joystick": the index of the joystick to bind
  193. * - "axis_x": the index of the axis to be bind as x-axis
  194. * - "axis_y": the index of the axis to be bind as y-axis
  195. */
  196. std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
  197. const int joystick_index = params.Get("joystick", 0);
  198. const int axis_x = params.Get("axis_x", 0);
  199. const int axis_y = params.Get("axis_y", 1);
  200. return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y);
  201. }
  202. };
  203. void Init() {
  204. if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
  205. NGLOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
  206. } else {
  207. using namespace Input;
  208. RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
  209. RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
  210. initialized = true;
  211. }
  212. }
  213. void Shutdown() {
  214. if (initialized) {
  215. using namespace Input;
  216. UnregisterFactory<ButtonDevice>("sdl");
  217. UnregisterFactory<AnalogDevice>("sdl");
  218. SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
  219. }
  220. }
  221. /**
  222. * This function converts a joystick ID used in SDL events to the device index. This is necessary
  223. * because Citra opens joysticks using their indices, not their IDs.
  224. */
  225. static int JoystickIDToDeviceIndex(SDL_JoystickID id) {
  226. int num_joysticks = SDL_NumJoysticks();
  227. for (int i = 0; i < num_joysticks; i++) {
  228. auto joystick = GetJoystick(i);
  229. if (joystick->GetJoystickID() == id) {
  230. return i;
  231. }
  232. }
  233. return -1;
  234. }
  235. Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) {
  236. Common::ParamPackage params({{"engine", "sdl"}});
  237. switch (event.type) {
  238. case SDL_JOYAXISMOTION:
  239. params.Set("joystick", JoystickIDToDeviceIndex(event.jaxis.which));
  240. params.Set("axis", event.jaxis.axis);
  241. if (event.jaxis.value > 0) {
  242. params.Set("direction", "+");
  243. params.Set("threshold", "0.5");
  244. } else {
  245. params.Set("direction", "-");
  246. params.Set("threshold", "-0.5");
  247. }
  248. break;
  249. case SDL_JOYBUTTONUP:
  250. params.Set("joystick", JoystickIDToDeviceIndex(event.jbutton.which));
  251. params.Set("button", event.jbutton.button);
  252. break;
  253. case SDL_JOYHATMOTION:
  254. params.Set("joystick", JoystickIDToDeviceIndex(event.jhat.which));
  255. params.Set("hat", event.jhat.hat);
  256. switch (event.jhat.value) {
  257. case SDL_HAT_UP:
  258. params.Set("direction", "up");
  259. break;
  260. case SDL_HAT_DOWN:
  261. params.Set("direction", "down");
  262. break;
  263. case SDL_HAT_LEFT:
  264. params.Set("direction", "left");
  265. break;
  266. case SDL_HAT_RIGHT:
  267. params.Set("direction", "right");
  268. break;
  269. default:
  270. return {};
  271. }
  272. break;
  273. }
  274. return params;
  275. }
  276. namespace Polling {
  277. class SDLPoller : public InputCommon::Polling::DevicePoller {
  278. public:
  279. SDLPoller() = default;
  280. ~SDLPoller() = default;
  281. void Start() override {
  282. // SDL joysticks must be opened, otherwise they don't generate events
  283. SDL_JoystickUpdate();
  284. int num_joysticks = SDL_NumJoysticks();
  285. for (int i = 0; i < num_joysticks; i++) {
  286. joysticks_opened.emplace_back(GetJoystick(i));
  287. }
  288. // Empty event queue to get rid of old events. citra-qt doesn't use the queue
  289. SDL_Event dummy;
  290. while (SDL_PollEvent(&dummy)) {
  291. }
  292. }
  293. void Stop() override {
  294. joysticks_opened.clear();
  295. }
  296. private:
  297. std::vector<std::shared_ptr<SDLJoystick>> joysticks_opened;
  298. };
  299. class SDLButtonPoller final : public SDLPoller {
  300. public:
  301. SDLButtonPoller() = default;
  302. ~SDLButtonPoller() = default;
  303. Common::ParamPackage GetNextInput() override {
  304. SDL_Event event;
  305. while (SDL_PollEvent(&event)) {
  306. switch (event.type) {
  307. case SDL_JOYAXISMOTION:
  308. if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
  309. break;
  310. }
  311. case SDL_JOYBUTTONUP:
  312. case SDL_JOYHATMOTION:
  313. return SDLEventToButtonParamPackage(event);
  314. }
  315. }
  316. return {};
  317. }
  318. };
  319. class SDLAnalogPoller final : public SDLPoller {
  320. public:
  321. SDLAnalogPoller() = default;
  322. ~SDLAnalogPoller() = default;
  323. void Start() override {
  324. SDLPoller::Start();
  325. // Reset stored axes
  326. analog_xaxis = -1;
  327. analog_yaxis = -1;
  328. analog_axes_joystick = -1;
  329. }
  330. Common::ParamPackage GetNextInput() override {
  331. SDL_Event event;
  332. while (SDL_PollEvent(&event)) {
  333. if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
  334. continue;
  335. }
  336. // An analog device needs two axes, so we need to store the axis for later and wait for
  337. // a second SDL event. The axes also must be from the same joystick.
  338. int axis = event.jaxis.axis;
  339. if (analog_xaxis == -1) {
  340. analog_xaxis = axis;
  341. analog_axes_joystick = event.jaxis.which;
  342. } else if (analog_yaxis == -1 && analog_xaxis != axis &&
  343. analog_axes_joystick == event.jaxis.which) {
  344. analog_yaxis = axis;
  345. }
  346. }
  347. Common::ParamPackage params;
  348. if (analog_xaxis != -1 && analog_yaxis != -1) {
  349. params.Set("engine", "sdl");
  350. params.Set("joystick", JoystickIDToDeviceIndex(analog_axes_joystick));
  351. params.Set("axis_x", analog_xaxis);
  352. params.Set("axis_y", analog_yaxis);
  353. analog_xaxis = -1;
  354. analog_yaxis = -1;
  355. analog_axes_joystick = -1;
  356. return params;
  357. }
  358. return params;
  359. }
  360. private:
  361. int analog_xaxis = -1;
  362. int analog_yaxis = -1;
  363. SDL_JoystickID analog_axes_joystick = -1;
  364. };
  365. std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
  366. InputCommon::Polling::DeviceType type) {
  367. std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
  368. switch (type) {
  369. case InputCommon::Polling::DeviceType::Analog:
  370. pollers.push_back(std::make_unique<SDLAnalogPoller>());
  371. break;
  372. case InputCommon::Polling::DeviceType::Button:
  373. pollers.push_back(std::make_unique<SDLButtonPoller>());
  374. break;
  375. }
  376. return pollers;
  377. }
  378. } // namespace Polling
  379. } // namespace SDL
  380. } // namespace InputCommon