Joystick.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /**
  2. * Copyright (c) 2006-2019 LOVE Development Team
  3. *
  4. * This software is provided 'as-is', without any express or implied
  5. * warranty. In no event will the authors be held liable for any damages
  6. * arising from the use of this software.
  7. *
  8. * Permission is granted to anyone to use this software for any purpose,
  9. * including commercial applications, and to alter it and redistribute it
  10. * freely, subject to the following restrictions:
  11. *
  12. * 1. The origin of this software must not be misrepresented; you must not
  13. * claim that you wrote the original software. If you use this software
  14. * in a product, an acknowledgment in the product documentation would be
  15. * appreciated but is not required.
  16. * 2. Altered source versions must be plainly marked as such, and must not be
  17. * misrepresented as being the original software.
  18. * 3. This notice may not be removed or altered from any source distribution.
  19. **/
  20. // LOVE
  21. #include "common/config.h"
  22. #include "Joystick.h"
  23. #include "common/int.h"
  24. // C++
  25. #include <algorithm>
  26. #include <limits>
  27. #ifndef SDL_TICKS_PASSED
  28. #define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
  29. #endif
  30. namespace love
  31. {
  32. namespace joystick
  33. {
  34. namespace sdl
  35. {
  36. Joystick::Joystick(int id)
  37. : joyhandle(nullptr)
  38. , controller(nullptr)
  39. , haptic(nullptr)
  40. , instanceid(-1)
  41. , id(id)
  42. , vibration()
  43. {
  44. }
  45. Joystick::Joystick(int id, int joyindex)
  46. : joyhandle(nullptr)
  47. , controller(nullptr)
  48. , haptic(nullptr)
  49. , instanceid(-1)
  50. , id(id)
  51. , vibration()
  52. {
  53. open(joyindex);
  54. }
  55. Joystick::~Joystick()
  56. {
  57. close();
  58. }
  59. bool Joystick::open(int deviceindex)
  60. {
  61. close();
  62. joyhandle = SDL_JoystickOpen(deviceindex);
  63. if (joyhandle)
  64. {
  65. instanceid = SDL_JoystickInstanceID(joyhandle);
  66. // SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
  67. char cstr[33];
  68. SDL_JoystickGUID sdlguid = SDL_JoystickGetGUID(joyhandle);
  69. SDL_JoystickGetGUIDString(sdlguid, cstr, (int) sizeof(cstr));
  70. pguid = std::string(cstr);
  71. // See if SDL thinks this is a Game Controller.
  72. openGamepad(deviceindex);
  73. // Prefer the Joystick name for consistency.
  74. const char *joyname = SDL_JoystickName(joyhandle);
  75. if (!joyname && controller)
  76. joyname = SDL_GameControllerName(controller);
  77. if (joyname)
  78. name = joyname;
  79. }
  80. return isConnected();
  81. }
  82. void Joystick::close()
  83. {
  84. if (haptic)
  85. SDL_HapticClose(haptic);
  86. if (controller)
  87. SDL_GameControllerClose(controller);
  88. if (joyhandle)
  89. SDL_JoystickClose(joyhandle);
  90. joyhandle = nullptr;
  91. controller = nullptr;
  92. haptic = nullptr;
  93. instanceid = -1;
  94. vibration = Vibration();
  95. }
  96. bool Joystick::isConnected() const
  97. {
  98. return joyhandle != nullptr && SDL_JoystickGetAttached(joyhandle);
  99. }
  100. const char *Joystick::getName() const
  101. {
  102. return name.c_str();
  103. }
  104. int Joystick::getAxisCount() const
  105. {
  106. return isConnected() ? SDL_JoystickNumAxes(joyhandle) : 0;
  107. }
  108. int Joystick::getButtonCount() const
  109. {
  110. return isConnected() ? SDL_JoystickNumButtons(joyhandle) : 0;
  111. }
  112. int Joystick::getHatCount() const
  113. {
  114. return isConnected() ? SDL_JoystickNumHats(joyhandle) : 0;
  115. }
  116. float Joystick::getAxis(int axisindex) const
  117. {
  118. if (!isConnected() || axisindex < 0 || axisindex >= getAxisCount())
  119. return 0;
  120. return clampval(((float) SDL_JoystickGetAxis(joyhandle, axisindex))/32768.0f);
  121. }
  122. std::vector<float> Joystick::getAxes() const
  123. {
  124. std::vector<float> axes;
  125. int count = getAxisCount();
  126. if (!isConnected() || count <= 0)
  127. return axes;
  128. axes.reserve(count);
  129. for (int i = 0; i < count; i++)
  130. axes.push_back(clampval(((float) SDL_JoystickGetAxis(joyhandle, i))/32768.0f));
  131. return axes;
  132. }
  133. Joystick::Hat Joystick::getHat(int hatindex) const
  134. {
  135. Hat h = HAT_INVALID;
  136. if (!isConnected() || hatindex < 0 || hatindex >= getHatCount())
  137. return h;
  138. getConstant(SDL_JoystickGetHat(joyhandle, hatindex), h);
  139. return h;
  140. }
  141. bool Joystick::isDown(const std::vector<int> &buttonlist) const
  142. {
  143. if (!isConnected())
  144. return false;
  145. int numbuttons = getButtonCount();
  146. for (int button : buttonlist)
  147. {
  148. if (button < 0 || button >= numbuttons)
  149. continue;
  150. if (SDL_JoystickGetButton(joyhandle, button) == 1)
  151. return true;
  152. }
  153. return false;
  154. }
  155. bool Joystick::openGamepad(int deviceindex)
  156. {
  157. if (!SDL_IsGameController(deviceindex))
  158. return false;
  159. if (isGamepad())
  160. {
  161. SDL_GameControllerClose(controller);
  162. controller = nullptr;
  163. }
  164. controller = SDL_GameControllerOpen(deviceindex);
  165. return isGamepad();
  166. }
  167. bool Joystick::isGamepad() const
  168. {
  169. return controller != nullptr;
  170. }
  171. float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
  172. {
  173. if (!isConnected() || !isGamepad())
  174. return 0.f;
  175. SDL_GameControllerAxis sdlaxis;
  176. if (!getConstant(axis, sdlaxis))
  177. return 0.f;
  178. Sint16 value = SDL_GameControllerGetAxis(controller, sdlaxis);
  179. return clampval((float) value / 32768.0f);
  180. }
  181. bool Joystick::isGamepadDown(const std::vector<GamepadButton> &blist) const
  182. {
  183. if (!isConnected() || !isGamepad())
  184. return false;
  185. SDL_GameControllerButton sdlbutton;
  186. for (GamepadButton button : blist)
  187. {
  188. if (!getConstant(button, sdlbutton))
  189. continue;
  190. if (SDL_GameControllerGetButton(controller, sdlbutton) == 1)
  191. return true;
  192. }
  193. return false;
  194. }
  195. Joystick::JoystickInput Joystick::getGamepadMapping(const GamepadInput &input) const
  196. {
  197. Joystick::JoystickInput jinput;
  198. jinput.type = INPUT_TYPE_MAX_ENUM;
  199. if (!isGamepad())
  200. return jinput;
  201. SDL_GameControllerButtonBind sdlbind = {};
  202. sdlbind.bindType = SDL_CONTROLLER_BINDTYPE_NONE;
  203. SDL_GameControllerButton sdlbutton;
  204. SDL_GameControllerAxis sdlaxis;
  205. switch (input.type)
  206. {
  207. case INPUT_TYPE_BUTTON:
  208. if (getConstant(input.button, sdlbutton))
  209. sdlbind = SDL_GameControllerGetBindForButton(controller, sdlbutton);
  210. break;
  211. case INPUT_TYPE_AXIS:
  212. if (getConstant(input.axis, sdlaxis))
  213. sdlbind = SDL_GameControllerGetBindForAxis(controller, sdlaxis);
  214. break;
  215. default:
  216. break;
  217. }
  218. switch (sdlbind.bindType)
  219. {
  220. case SDL_CONTROLLER_BINDTYPE_BUTTON:
  221. jinput.type = INPUT_TYPE_BUTTON;
  222. jinput.button = sdlbind.value.button;
  223. break;
  224. case SDL_CONTROLLER_BINDTYPE_AXIS:
  225. jinput.type = INPUT_TYPE_AXIS;
  226. jinput.axis = sdlbind.value.axis;
  227. break;
  228. case SDL_CONTROLLER_BINDTYPE_HAT:
  229. if (getConstant(sdlbind.value.hat.hat_mask, jinput.hat.value))
  230. {
  231. jinput.type = INPUT_TYPE_HAT;
  232. jinput.hat.index = sdlbind.value.hat.hat;
  233. }
  234. break;
  235. case SDL_CONTROLLER_BINDTYPE_NONE:
  236. default:
  237. break;
  238. }
  239. return jinput;
  240. }
  241. std::string Joystick::getGamepadMappingString() const
  242. {
  243. char *sdlmapping = nullptr;
  244. if (controller != nullptr)
  245. sdlmapping = SDL_GameControllerMapping(controller);
  246. if (sdlmapping == nullptr)
  247. {
  248. SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(pguid.c_str());
  249. sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
  250. }
  251. if (sdlmapping == nullptr)
  252. return "";
  253. std::string mappingstr(sdlmapping);
  254. SDL_free(sdlmapping);
  255. // Matches SDL_GameControllerAddMappingsFromRW.
  256. if (mappingstr.find_last_of(',') != mappingstr.length() - 1)
  257. mappingstr += ",";
  258. mappingstr += "platform:" + std::string(SDL_GetPlatform());
  259. return mappingstr;
  260. }
  261. void *Joystick::getHandle() const
  262. {
  263. return joyhandle;
  264. }
  265. std::string Joystick::getGUID() const
  266. {
  267. // SDL2's GUIDs identify *classes* of devices, instead of unique devices.
  268. return pguid;
  269. }
  270. int Joystick::getInstanceID() const
  271. {
  272. return instanceid;
  273. }
  274. int Joystick::getID() const
  275. {
  276. return id;
  277. }
  278. void Joystick::getDeviceInfo(int &vendorID, int &productID, int &productVersion) const
  279. {
  280. #if SDL_VERSION_ATLEAST(2, 0, 6)
  281. if (joyhandle != nullptr)
  282. {
  283. vendorID = SDL_JoystickGetVendor(joyhandle);
  284. productID = SDL_JoystickGetProduct(joyhandle);
  285. productVersion = SDL_JoystickGetProductVersion(joyhandle);
  286. }
  287. else
  288. #endif
  289. {
  290. vendorID = 0;
  291. productID = 0;
  292. productVersion = 0;
  293. }
  294. }
  295. bool Joystick::checkCreateHaptic()
  296. {
  297. if (!isConnected())
  298. return false;
  299. if (!SDL_WasInit(SDL_INIT_HAPTIC) && SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0)
  300. return false;
  301. if (haptic && SDL_HapticIndex(haptic) != -1)
  302. return true;
  303. if (haptic)
  304. {
  305. SDL_HapticClose(haptic);
  306. haptic = nullptr;
  307. }
  308. haptic = SDL_HapticOpenFromJoystick(joyhandle);
  309. vibration = Vibration();
  310. return haptic != nullptr;
  311. }
  312. bool Joystick::isVibrationSupported()
  313. {
  314. if (!checkCreateHaptic())
  315. return false;
  316. unsigned int features = SDL_HapticQuery(haptic);
  317. if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
  318. return true;
  319. // Some gamepad drivers only support left/right motors via a custom effect.
  320. if (isGamepad() && (features & SDL_HAPTIC_CUSTOM) != 0)
  321. return true;
  322. // Test for simple sine wave support as a last resort.
  323. if ((features & SDL_HAPTIC_SINE) != 0)
  324. return true;
  325. return false;
  326. }
  327. bool Joystick::runVibrationEffect()
  328. {
  329. if (vibration.id != -1)
  330. {
  331. if (SDL_HapticUpdateEffect(haptic, vibration.id, &vibration.effect) == 0)
  332. {
  333. if (SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
  334. return true;
  335. }
  336. // If the effect fails to update, we should destroy and re-create it.
  337. SDL_HapticDestroyEffect(haptic, vibration.id);
  338. vibration.id = -1;
  339. }
  340. vibration.id = SDL_HapticNewEffect(haptic, &vibration.effect);
  341. if (vibration.id != -1 && SDL_HapticRunEffect(haptic, vibration.id, 1) == 0)
  342. return true;
  343. return false;
  344. }
  345. bool Joystick::setVibration(float left, float right, float duration)
  346. {
  347. left = std::min(std::max(left, 0.0f), 1.0f);
  348. right = std::min(std::max(right, 0.0f), 1.0f);
  349. if (left == 0.0f && right == 0.0f)
  350. return setVibration();
  351. if (!checkCreateHaptic())
  352. return false;
  353. Uint32 length = SDL_HAPTIC_INFINITY;
  354. if (duration >= 0.0f)
  355. {
  356. float maxduration = std::numeric_limits<Uint32>::max() / 1000.0f;
  357. length = Uint32(std::min(duration, maxduration) * 1000);
  358. }
  359. bool success = false;
  360. unsigned int features = SDL_HapticQuery(haptic);
  361. int axes = SDL_HapticNumAxes(haptic);
  362. if ((features & SDL_HAPTIC_LEFTRIGHT) != 0)
  363. {
  364. memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
  365. vibration.effect.type = SDL_HAPTIC_LEFTRIGHT;
  366. vibration.effect.leftright.length = length;
  367. vibration.effect.leftright.large_magnitude = Uint16(left * LOVE_UINT16_MAX);
  368. vibration.effect.leftright.small_magnitude = Uint16(right * LOVE_UINT16_MAX);
  369. success = runVibrationEffect();
  370. }
  371. // Some gamepad drivers only give support for controlling individual motors
  372. // through a custom FF effect.
  373. if (!success && isGamepad() && (features & SDL_HAPTIC_CUSTOM) && axes == 2)
  374. {
  375. // NOTE: this may cause issues with drivers which support custom effects
  376. // but aren't similar to https://github.com/d235j/360Controller .
  377. // Custom effect data is clamped to 0x7FFF in SDL.
  378. vibration.data[0] = vibration.data[2] = Uint16(left * 0x7FFF);
  379. vibration.data[1] = vibration.data[3] = Uint16(right * 0x7FFF);
  380. memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
  381. vibration.effect.type = SDL_HAPTIC_CUSTOM;
  382. vibration.effect.custom.length = length;
  383. vibration.effect.custom.channels = 2;
  384. vibration.effect.custom.period = 10;
  385. vibration.effect.custom.samples = 2;
  386. vibration.effect.custom.data = vibration.data;
  387. success = runVibrationEffect();
  388. }
  389. // Fall back to a simple sine wave if all else fails. This only supports a
  390. // single strength value.
  391. if (!success && (features & SDL_HAPTIC_SINE) != 0)
  392. {
  393. memset(&vibration.effect, 0, sizeof(SDL_HapticEffect));
  394. vibration.effect.type = SDL_HAPTIC_SINE;
  395. vibration.effect.periodic.length = length;
  396. vibration.effect.periodic.period = 10;
  397. float strength = std::max(left, right);
  398. vibration.effect.periodic.magnitude = Sint16(strength * 0x7FFF);
  399. success = runVibrationEffect();
  400. }
  401. if (success)
  402. {
  403. vibration.left = left;
  404. vibration.right = right;
  405. if (length == SDL_HAPTIC_INFINITY)
  406. vibration.endtime = SDL_HAPTIC_INFINITY;
  407. else
  408. vibration.endtime = SDL_GetTicks() + length;
  409. }
  410. else
  411. {
  412. vibration.left = vibration.right = 0.0f;
  413. vibration.endtime = SDL_HAPTIC_INFINITY;
  414. }
  415. return success;
  416. }
  417. bool Joystick::setVibration()
  418. {
  419. bool success = true;
  420. if (SDL_WasInit(SDL_INIT_HAPTIC) && haptic && SDL_HapticIndex(haptic) != -1)
  421. success = (SDL_HapticStopEffect(haptic, vibration.id) == 0);
  422. if (success)
  423. vibration.left = vibration.right = 0.0f;
  424. return success;
  425. }
  426. void Joystick::getVibration(float &left, float &right)
  427. {
  428. if (vibration.endtime != SDL_HAPTIC_INFINITY)
  429. {
  430. // With some drivers, the effect physically stops at the right time, but
  431. // SDL_HapticGetEffectStatus still thinks it's playing. So we explicitly
  432. // stop it once it's done, just to be sure.
  433. if (SDL_TICKS_PASSED(SDL_GetTicks(), vibration.endtime))
  434. {
  435. setVibration();
  436. vibration.endtime = SDL_HAPTIC_INFINITY;
  437. }
  438. }
  439. // Check if the haptic effect has stopped playing.
  440. int id = vibration.id;
  441. if (!haptic || id == -1 || SDL_HapticGetEffectStatus(haptic, id) != 1)
  442. vibration.left = vibration.right = 0.0f;
  443. left = vibration.left;
  444. right = vibration.right;
  445. }
  446. bool Joystick::getConstant(Uint8 in, Joystick::Hat &out)
  447. {
  448. return hats.find(in, out);
  449. }
  450. bool Joystick::getConstant(Joystick::Hat in, Uint8 &out)
  451. {
  452. return hats.find(in, out);
  453. }
  454. bool Joystick::getConstant(SDL_GameControllerAxis in, Joystick::GamepadAxis &out)
  455. {
  456. return gpAxes.find(in, out);
  457. }
  458. bool Joystick::getConstant(Joystick::GamepadAxis in, SDL_GameControllerAxis &out)
  459. {
  460. return gpAxes.find(in, out);
  461. }
  462. bool Joystick::getConstant(SDL_GameControllerButton in, Joystick::GamepadButton &out)
  463. {
  464. return gpButtons.find(in, out);
  465. }
  466. bool Joystick::getConstant(Joystick::GamepadButton in, SDL_GameControllerButton &out)
  467. {
  468. return gpButtons.find(in, out);
  469. }
  470. EnumMap<Joystick::Hat, Uint8, Joystick::HAT_MAX_ENUM>::Entry Joystick::hatEntries[] =
  471. {
  472. {Joystick::HAT_CENTERED, SDL_HAT_CENTERED},
  473. {Joystick::HAT_UP, SDL_HAT_UP},
  474. {Joystick::HAT_RIGHT, SDL_HAT_RIGHT},
  475. {Joystick::HAT_DOWN, SDL_HAT_DOWN},
  476. {Joystick::HAT_LEFT, SDL_HAT_LEFT},
  477. {Joystick::HAT_RIGHTUP, SDL_HAT_RIGHTUP},
  478. {Joystick::HAT_RIGHTDOWN, SDL_HAT_RIGHTDOWN},
  479. {Joystick::HAT_LEFTUP, SDL_HAT_LEFTUP},
  480. {Joystick::HAT_LEFTDOWN, SDL_HAT_LEFTDOWN},
  481. };
  482. EnumMap<Joystick::Hat, Uint8, Joystick::HAT_MAX_ENUM> Joystick::hats(Joystick::hatEntries, sizeof(Joystick::hatEntries));
  483. EnumMap<Joystick::GamepadAxis, SDL_GameControllerAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM>::Entry Joystick::gpAxisEntries[] =
  484. {
  485. {Joystick::GAMEPAD_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTX},
  486. {Joystick::GAMEPAD_AXIS_LEFTY, SDL_CONTROLLER_AXIS_LEFTY},
  487. {Joystick::GAMEPAD_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTX},
  488. {Joystick::GAMEPAD_AXIS_RIGHTY, SDL_CONTROLLER_AXIS_RIGHTY},
  489. {Joystick::GAMEPAD_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
  490. {Joystick::GAMEPAD_AXIS_TRIGGERRIGHT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
  491. };
  492. EnumMap<Joystick::GamepadAxis, SDL_GameControllerAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM> Joystick::gpAxes(Joystick::gpAxisEntries, sizeof(Joystick::gpAxisEntries));
  493. EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM>::Entry Joystick::gpButtonEntries[] =
  494. {
  495. {Joystick::GAMEPAD_BUTTON_A, SDL_CONTROLLER_BUTTON_A},
  496. {Joystick::GAMEPAD_BUTTON_B, SDL_CONTROLLER_BUTTON_B},
  497. {Joystick::GAMEPAD_BUTTON_X, SDL_CONTROLLER_BUTTON_X},
  498. {Joystick::GAMEPAD_BUTTON_Y, SDL_CONTROLLER_BUTTON_Y},
  499. {Joystick::GAMEPAD_BUTTON_BACK, SDL_CONTROLLER_BUTTON_BACK},
  500. {Joystick::GAMEPAD_BUTTON_GUIDE, SDL_CONTROLLER_BUTTON_GUIDE},
  501. {Joystick::GAMEPAD_BUTTON_START, SDL_CONTROLLER_BUTTON_START},
  502. {Joystick::GAMEPAD_BUTTON_LEFTSTICK, SDL_CONTROLLER_BUTTON_LEFTSTICK},
  503. {Joystick::GAMEPAD_BUTTON_RIGHTSTICK, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
  504. {Joystick::GAMEPAD_BUTTON_LEFTSHOULDER, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
  505. {Joystick::GAMEPAD_BUTTON_RIGHTSHOULDER, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
  506. {Joystick::GAMEPAD_BUTTON_DPAD_UP, SDL_CONTROLLER_BUTTON_DPAD_UP},
  507. {Joystick::GAMEPAD_BUTTON_DPAD_DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
  508. {Joystick::GAMEPAD_BUTTON_DPAD_LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
  509. {Joystick::GAMEPAD_BUTTON_DPAD_RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
  510. };
  511. EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM> Joystick::gpButtons(Joystick::gpButtonEntries, sizeof(Joystick::gpButtonEntries));
  512. } // sdl
  513. } // joystick
  514. } // love