JoystickModule.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /**
  2. * Copyright (c) 2006-2014 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. #include "common/config.h"
  21. #include "JoystickModule.h"
  22. #include "Joystick.h"
  23. // SDL
  24. #include <SDL.h>
  25. // C++
  26. #include <sstream>
  27. #include <algorithm>
  28. // C
  29. #include <cstdlib>
  30. namespace love
  31. {
  32. namespace joystick
  33. {
  34. namespace sdl
  35. {
  36. JoystickModule::JoystickModule()
  37. {
  38. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0)
  39. throw love::Exception("%s", SDL_GetError());
  40. // Initialize any joysticks which are already connected.
  41. for (int i = 0; i < SDL_NumJoysticks(); i++)
  42. addJoystick(i);
  43. // Start joystick event watching. Joysticks are automatically added and
  44. // removed via love.event.
  45. SDL_JoystickEventState(SDL_ENABLE);
  46. SDL_GameControllerEventState(SDL_ENABLE);
  47. }
  48. JoystickModule::~JoystickModule()
  49. {
  50. // Close any open Joysticks.
  51. for (auto stick : joysticks)
  52. {
  53. stick->close();
  54. stick->release();
  55. }
  56. if (SDL_WasInit(SDL_INIT_HAPTIC) != 0)
  57. SDL_QuitSubSystem(SDL_INIT_HAPTIC);
  58. SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  59. }
  60. const char *JoystickModule::getName() const
  61. {
  62. return "love.joystick.sdl";
  63. }
  64. love::joystick::Joystick *JoystickModule::getJoystick(int joyindex)
  65. {
  66. if (joyindex < 0 || (size_t) joyindex >= activeSticks.size())
  67. return nullptr;
  68. return activeSticks[joyindex];
  69. }
  70. int JoystickModule::getIndex(const love::joystick::Joystick *joystick)
  71. {
  72. for (size_t i = 0; i < activeSticks.size(); i++)
  73. {
  74. if (activeSticks[i] == joystick)
  75. return i;
  76. }
  77. // Joystick is not connected.
  78. return -1;
  79. }
  80. int JoystickModule::getJoystickCount() const
  81. {
  82. return (int) activeSticks.size();
  83. }
  84. love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
  85. {
  86. for (auto stick : activeSticks)
  87. {
  88. if (stick->getInstanceID() == instanceid)
  89. return stick;
  90. }
  91. return nullptr;
  92. }
  93. love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
  94. {
  95. if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
  96. return nullptr;
  97. std::string guidstr = getDeviceGUID(deviceindex);
  98. joystick::Joystick *joystick = 0;
  99. bool reused = false;
  100. for (auto stick : joysticks)
  101. {
  102. // Try to re-use a disconnected Joystick with the same GUID.
  103. if (!stick->isConnected() && stick->getGUID() == guidstr)
  104. {
  105. joystick = stick;
  106. reused = true;
  107. break;
  108. }
  109. }
  110. if (!joystick)
  111. {
  112. joystick = new Joystick(joysticks.size());
  113. joysticks.push_back(joystick);
  114. }
  115. // Make sure the Joystick object isn't in the active list already.
  116. removeJoystick(joystick);
  117. if (!joystick->open(deviceindex))
  118. return nullptr;
  119. // Make sure multiple instances of the same physical joystick aren't added
  120. // to the active list.
  121. for (auto activestick : activeSticks)
  122. {
  123. if (joystick->getHandle() == activestick->getHandle())
  124. {
  125. joystick->close();
  126. // If we just created the stick, remove it since it's a duplicate.
  127. if (!reused)
  128. {
  129. joysticks.remove(joystick);
  130. joystick->release();
  131. }
  132. return activestick;
  133. }
  134. }
  135. activeSticks.push_back(joystick);
  136. return joystick;
  137. }
  138. void JoystickModule::removeJoystick(love::joystick::Joystick *joystick)
  139. {
  140. if (!joystick)
  141. return;
  142. // Close the Joystick and remove it from the active joystick list.
  143. auto it = std::find(activeSticks.begin(), activeSticks.end(), joystick);
  144. if (it != activeSticks.end())
  145. {
  146. (*it)->close();
  147. activeSticks.erase(it);
  148. }
  149. }
  150. bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput, Joystick::JoystickInput joyinput)
  151. {
  152. // All SDL joystick GUID strings are 32 characters.
  153. if (guid.length() != 32)
  154. throw love::Exception("Invalid joystick GUID: %s", guid.c_str());
  155. SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
  156. std::string mapstr;
  157. char *sdlmapstr = SDL_GameControllerMappingForGUID(sdlguid);
  158. if (sdlmapstr)
  159. {
  160. mapstr = sdlmapstr;
  161. SDL_free(sdlmapstr);
  162. }
  163. else
  164. {
  165. // Use a generic name if we have to create a new mapping string.
  166. mapstr = guid + ",Controller,";
  167. }
  168. std::stringstream joyinputstream;
  169. Uint8 sdlhat;
  170. // We can't have negative int values in the bind string.
  171. switch (joyinput.type)
  172. {
  173. case Joystick::INPUT_TYPE_AXIS:
  174. if (joyinput.axis >= 0)
  175. joyinputstream << "a" << joyinput.axis;
  176. break;
  177. case Joystick::INPUT_TYPE_BUTTON:
  178. if (joyinput.button >= 0)
  179. joyinputstream << "b" << joyinput.button;
  180. break;
  181. case Joystick::INPUT_TYPE_HAT:
  182. if (joyinput.hat.value >= 0 && Joystick::getConstant(joyinput.hat.value, sdlhat))
  183. joyinputstream << "h" << joyinput.hat.value << "." << int(sdlhat);
  184. break;
  185. default:
  186. break;
  187. }
  188. std::string joyinputstr = joyinputstream.str();
  189. if (joyinputstr.length() == 0)
  190. throw love::Exception("Invalid joystick input value.");
  191. // SDL's name for the gamepad input value, e.g. "guide".
  192. std::string gpinputname = stringFromGamepadInput(gpinput);
  193. // We should remove any existing joystick bind for this gamepad buttton/axis
  194. // so SDL's parser doesn't get mixed up.
  195. removeBindFromMapString(mapstr, joyinputstr);
  196. // The string we'll be adding to the mapping string, e.g. "guide:b10,"
  197. std::string insertstr = gpinputname + ":" + joyinputstr + ",";
  198. // We should replace any existing gamepad bind.
  199. size_t findpos = mapstr.find(gpinputname + ":");
  200. if (findpos != std::string::npos)
  201. {
  202. // The bind string ends at the next comma, or the end of the string.
  203. size_t endpos = mapstr.find_first_of(',', findpos);
  204. if (endpos == std::string::npos)
  205. endpos = mapstr.length() - 1;
  206. mapstr.replace(findpos, endpos - findpos + 1, insertstr);
  207. }
  208. else
  209. {
  210. // Just append to the end if we don't need to replace anything.
  211. mapstr += insertstr;
  212. }
  213. // 1 == added, 0 == updated, -1 == error.
  214. int status = SDL_GameControllerAddMapping(mapstr.c_str());
  215. // FIXME: massive hack until missing APIs are added to SDL 2:
  216. // https://bugzilla.libsdl.org/show_bug.cgi?id=1975
  217. if (status == 1)
  218. checkGamepads(guid);
  219. return status >= 0;
  220. }
  221. Joystick::JoystickInput JoystickModule::getGamepadMapping(const std::string &guid, Joystick::GamepadInput gpinput)
  222. {
  223. // All SDL joystick GUID strings are 32 characters.
  224. if (guid.length() != 32)
  225. throw love::Exception("Invalid joystick GUID: %s", guid.c_str());
  226. Joystick::JoystickInput jinput;
  227. jinput.type = Joystick::INPUT_TYPE_MAX_ENUM;
  228. SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
  229. std::string mapstr;
  230. char *sdlmapstr = SDL_GameControllerMappingForGUID(sdlguid);
  231. if (!sdlmapstr)
  232. return jinput;
  233. mapstr = sdlmapstr;
  234. SDL_free(sdlmapstr);
  235. std::string gpbindname = stringFromGamepadInput(gpinput);
  236. size_t findpos = mapstr.find(std::string(",") + gpbindname + ":");
  237. if (findpos == std::string::npos)
  238. return jinput;
  239. size_t endpos = mapstr.find_first_of(',', findpos);
  240. if (endpos == std::string::npos)
  241. {
  242. // Assume end-of-string if we can't find the next comma.
  243. endpos = mapstr.length() - 1;
  244. }
  245. if (endpos >= mapstr.length())
  246. return jinput; // Something went wrong.
  247. // Strip out the trailing comma from our search position, if it exists.
  248. if (mapstr[endpos] == ',')
  249. endpos--;
  250. // New start position: comma + gamepadinputlength + ":".
  251. findpos += 1 + gpbindname.length() + 1;
  252. std::string jbindstr = mapstr.substr(findpos, endpos - findpos + 1);
  253. jinput = JoystickInputFromString(jbindstr);
  254. return jinput;
  255. }
  256. std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinput) const
  257. {
  258. SDL_GameControllerAxis sdlaxis;
  259. SDL_GameControllerButton sdlbutton;
  260. const char *gpinputname = nullptr;
  261. switch (gpinput.type)
  262. {
  263. case Joystick::INPUT_TYPE_AXIS:
  264. if (Joystick::getConstant(gpinput.axis, sdlaxis))
  265. gpinputname = SDL_GameControllerGetStringForAxis(sdlaxis);
  266. break;
  267. case Joystick::INPUT_TYPE_BUTTON:
  268. if (Joystick::getConstant(gpinput.button, sdlbutton))
  269. gpinputname = SDL_GameControllerGetStringForButton(sdlbutton);
  270. break;
  271. default:
  272. break;
  273. }
  274. if (!gpinputname)
  275. throw love::Exception("Invalid gamepad axis/button.");
  276. return std::string(gpinputname);
  277. }
  278. Joystick::JoystickInput JoystickModule::JoystickInputFromString(const std::string &str) const
  279. {
  280. Joystick::JoystickInput jinput;
  281. jinput.type = Joystick::INPUT_TYPE_MAX_ENUM;
  282. // Return an invalid value rather than throwing an exception.
  283. if (str.length() < 2)
  284. return jinput;
  285. // The input type will always be the first character in the string.
  286. char inputtype = str[0];
  287. std::string bindvalues = str.substr(1);
  288. Uint8 sdlhat;
  289. switch (inputtype)
  290. {
  291. case 'a':
  292. jinput.type = Joystick::INPUT_TYPE_AXIS;
  293. jinput.axis = atoi(bindvalues.c_str());
  294. break;
  295. case 'b':
  296. jinput.type = Joystick::INPUT_TYPE_BUTTON;
  297. jinput.button = atoi(bindvalues.c_str());
  298. break;
  299. case 'h':
  300. // Hat string syntax is "index.value".
  301. if (bindvalues.length() < 3)
  302. break;
  303. jinput.type = Joystick::INPUT_TYPE_HAT;
  304. jinput.hat.index = atoi(bindvalues.substr(0, 1).c_str());
  305. sdlhat = (Uint8) atoi(bindvalues.substr(2, 1).c_str());
  306. if (!Joystick::getConstant(sdlhat, jinput.hat.value))
  307. {
  308. // Return an invalid value if we can't find the hat constant.
  309. jinput.type = Joystick::INPUT_TYPE_MAX_ENUM;
  310. return jinput;
  311. }
  312. break;
  313. default:
  314. break;
  315. }
  316. return jinput;
  317. }
  318. void JoystickModule::removeBindFromMapString(std::string &mapstr, const std::string &joybindstr) const
  319. {
  320. // Find the joystick part of the bind in the string.
  321. size_t joybindpos = mapstr.find(joybindstr + ",");
  322. if (joybindpos == std::string::npos)
  323. {
  324. joybindpos = mapstr.rfind(joybindstr);
  325. if (joybindpos != mapstr.length() - joybindstr.length())
  326. return;
  327. }
  328. if (joybindpos == std::string::npos)
  329. return;
  330. // Find the start of the entire bind.
  331. size_t bindstart = mapstr.rfind(',', joybindpos);
  332. if (bindstart != std::string::npos && bindstart < mapstr.length() - 1)
  333. {
  334. size_t bindend = mapstr.find(',', bindstart + 1);
  335. if (bindend == std::string::npos)
  336. bindend = mapstr.length() - 1;
  337. // Replace it with an empty string (remove it.)
  338. mapstr.replace(bindstart, bindend - bindstart + 1, "");
  339. }
  340. }
  341. void JoystickModule::checkGamepads(const std::string &guid) const
  342. {
  343. // FIXME: massive hack until missing APIs are added to SDL 2:
  344. // https://bugzilla.libsdl.org/show_bug.cgi?id=1975
  345. // Make sure all connected joysticks of a certain guid that are
  346. // gamepad-capable are opened as such.
  347. for (int d_index = 0; d_index < SDL_NumJoysticks(); d_index++)
  348. {
  349. if (!SDL_IsGameController(d_index))
  350. continue;
  351. if (guid.compare(getDeviceGUID(d_index)) != 0)
  352. continue;
  353. for (auto stick : activeSticks)
  354. {
  355. if (stick->isGamepad() || guid.compare(stick->getGUID()) != 0)
  356. continue;
  357. // Big hack time: open the index as a game controller and compare
  358. // the underlying joystick handle to the active stick's.
  359. SDL_GameController *controller = SDL_GameControllerOpen(d_index);
  360. if (controller == nullptr)
  361. continue;
  362. SDL_Joystick *sdlstick = SDL_GameControllerGetJoystick(controller);
  363. if (sdlstick == (SDL_Joystick *) stick->getHandle())
  364. stick->openGamepad(d_index);
  365. // GameController objects are reference-counted in SDL.
  366. SDL_GameControllerClose(controller);
  367. }
  368. }
  369. }
  370. std::string JoystickModule::getDeviceGUID(int deviceindex) const
  371. {
  372. if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
  373. return std::string("");
  374. // SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
  375. char guidstr[33] = {'\0'};
  376. // SDL2's GUIDs identify *classes* of devices, instead of unique devices.
  377. SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(deviceindex);
  378. SDL_JoystickGetGUIDString(guid, guidstr, sizeof(guidstr));
  379. return std::string(guidstr);
  380. }
  381. void JoystickModule::loadGamepadMappings(const std::string &mappings)
  382. {
  383. // TODO: We should use SDL_GameControllerAddMappingsFromRW. We're
  384. // duplicating its functionality for now because it was added after
  385. // SDL 2.0.0's release, and we want runtime compat with 2.0.0 on Linux...
  386. std::stringstream ss(mappings);
  387. std::string mapping;
  388. bool success = false;
  389. // The mappings string contains newline-separated mappings.
  390. while (std::getline(ss, mapping))
  391. {
  392. if (mapping.empty())
  393. continue;
  394. // Strip out and compare any "platform:XYZ," in the mapping.
  395. size_t pstartpos = mapping.find("platform:");
  396. if (pstartpos != std::string::npos)
  397. {
  398. pstartpos += strlen("platform:");
  399. size_t pendpos = mapping.find_first_of(',', pstartpos);
  400. std::string platform = mapping.substr(pstartpos, pendpos - pstartpos);
  401. if (platform.compare(SDL_GetPlatform()) != 0)
  402. continue;
  403. pstartpos -= strlen("platform:");
  404. mapping.erase(pstartpos, pendpos - pstartpos + 1);
  405. }
  406. success = success || (SDL_GameControllerAddMapping(mapping.c_str()) != -1);
  407. }
  408. if (!success)
  409. throw love::Exception("Invalid gamepad mappings.");
  410. }
  411. std::string JoystickModule::saveGamepadMapping(const std::string &pguid)
  412. {
  413. SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(pguid.c_str());
  414. std::string mapping;
  415. char *sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
  416. if (sdlmapping == nullptr)
  417. throw love::Exception("The specified Joystick GUID string has no gamepad mapping.");
  418. mapping = sdlmapping;
  419. SDL_free(sdlmapping);
  420. if (mapping.find_last_of(',') != mapping.size() - 1)
  421. mapping += ",";
  422. // Matches SDL_GameControllerAddMappingsFromRW.
  423. mapping += "platform:" + std::string(SDL_GetPlatform()) + ",";
  424. return mapping;
  425. }
  426. } // sdl
  427. } // joystick
  428. } // love