JoystickModule.cpp 12 KB

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