JoystickModule.cpp 12 KB

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