linux_joystick.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. //========================================================================
  2. // GLFW 3.4 Linux - www.glfw.org
  3. //------------------------------------------------------------------------
  4. // Copyright (c) 2002-2006 Marcus Geelnard
  5. // Copyright (c) 2006-2017 Camilla Löwy <elmindreda@glfw.org>
  6. //
  7. // This software is provided 'as-is', without any express or implied
  8. // warranty. In no event will the authors be held liable for any damages
  9. // arising from the use of this software.
  10. //
  11. // Permission is granted to anyone to use this software for any purpose,
  12. // including commercial applications, and to alter it and redistribute it
  13. // freely, subject to the following restrictions:
  14. //
  15. // 1. The origin of this software must not be misrepresented; you must not
  16. // claim that you wrote the original software. If you use this software
  17. // in a product, an acknowledgment in the product documentation would
  18. // be appreciated but is not required.
  19. //
  20. // 2. Altered source versions must be plainly marked as such, and must not
  21. // be misrepresented as being the original software.
  22. //
  23. // 3. This notice may not be removed or altered from any source
  24. // distribution.
  25. //
  26. //========================================================================
  27. // It is fine to use C99 in this file because it will not be built with VS
  28. //========================================================================
  29. #include "internal.h"
  30. #include <sys/types.h>
  31. #include <sys/stat.h>
  32. #include <sys/inotify.h>
  33. #include <fcntl.h>
  34. #include <errno.h>
  35. #include <dirent.h>
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <string.h>
  39. #include <unistd.h>
  40. #ifndef SYN_DROPPED // < v2.6.39 kernel headers
  41. // Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32
  42. #define SYN_DROPPED 3
  43. #endif
  44. // Apply an EV_KEY event to the specified joystick
  45. //
  46. static void handleKeyEvent(_GLFWjoystick* js, int code, int value)
  47. {
  48. _glfwInputJoystickButton(js,
  49. js->linjs.keyMap[code - BTN_MISC],
  50. value ? GLFW_PRESS : GLFW_RELEASE);
  51. }
  52. // Apply an EV_ABS event to the specified joystick
  53. //
  54. static void handleAbsEvent(_GLFWjoystick* js, int code, int value)
  55. {
  56. const int index = js->linjs.absMap[code];
  57. if (code >= ABS_HAT0X && code <= ABS_HAT3Y)
  58. {
  59. static const char stateMap[3][3] =
  60. {
  61. { GLFW_HAT_CENTERED, GLFW_HAT_UP, GLFW_HAT_DOWN },
  62. { GLFW_HAT_LEFT, GLFW_HAT_LEFT_UP, GLFW_HAT_LEFT_DOWN },
  63. { GLFW_HAT_RIGHT, GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN },
  64. };
  65. const int hat = (code - ABS_HAT0X) / 2;
  66. const int axis = (code - ABS_HAT0X) % 2;
  67. int* state = js->linjs.hats[hat];
  68. // NOTE: Looking at several input drivers, it seems all hat events use
  69. // -1 for left / up, 0 for centered and 1 for right / down
  70. if (value == 0)
  71. state[axis] = 0;
  72. else if (value < 0)
  73. state[axis] = 1;
  74. else if (value > 0)
  75. state[axis] = 2;
  76. _glfwInputJoystickHat(js, index, stateMap[state[0]][state[1]]);
  77. }
  78. else
  79. {
  80. const struct input_absinfo* info = &js->linjs.absInfo[code];
  81. float normalized = value;
  82. const int range = info->maximum - info->minimum;
  83. if (range)
  84. {
  85. // Normalize to 0.0 -> 1.0
  86. normalized = (normalized - info->minimum) / range;
  87. // Normalize to -1.0 -> 1.0
  88. normalized = normalized * 2.0f - 1.0f;
  89. }
  90. _glfwInputJoystickAxis(js, index, normalized);
  91. }
  92. }
  93. // Poll state of absolute axes
  94. //
  95. static void pollAbsState(_GLFWjoystick* js)
  96. {
  97. for (int code = 0; code < ABS_CNT; code++)
  98. {
  99. if (js->linjs.absMap[code] < 0)
  100. continue;
  101. struct input_absinfo* info = &js->linjs.absInfo[code];
  102. if (ioctl(js->linjs.fd, EVIOCGABS(code), info) < 0)
  103. continue;
  104. handleAbsEvent(js, code, info->value);
  105. }
  106. }
  107. #define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8)))
  108. // Attempt to open the specified joystick device
  109. //
  110. static bool openJoystickDevice(const char* path)
  111. {
  112. for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
  113. {
  114. if (!_glfw.joysticks[jid].present)
  115. continue;
  116. if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
  117. return false;
  118. }
  119. _GLFWjoystickLinux linjs = {0};
  120. linjs.fd = open(path, O_RDONLY | O_NONBLOCK);
  121. if (linjs.fd == -1)
  122. return false;
  123. char evBits[(EV_CNT + 7) / 8] = {0};
  124. char keyBits[(KEY_CNT + 7) / 8] = {0};
  125. char absBits[(ABS_CNT + 7) / 8] = {0};
  126. struct input_id id;
  127. if (ioctl(linjs.fd, (int32_t)EVIOCGBIT(0, sizeof(evBits)), evBits) < 0 ||
  128. ioctl(linjs.fd, (int32_t)EVIOCGBIT(EV_KEY, sizeof(keyBits)), keyBits) < 0 ||
  129. ioctl(linjs.fd, (int32_t)EVIOCGBIT(EV_ABS, sizeof(absBits)), absBits) < 0 ||
  130. ioctl(linjs.fd, (int32_t)EVIOCGID, &id) < 0)
  131. {
  132. _glfwInputError(GLFW_PLATFORM_ERROR,
  133. "Linux: Failed to query input device: %s",
  134. strerror(errno));
  135. close(linjs.fd);
  136. return false;
  137. }
  138. // Ensure this device supports the events expected of a joystick
  139. if (!isBitSet(EV_KEY, evBits) || !isBitSet(EV_ABS, evBits))
  140. {
  141. close(linjs.fd);
  142. return false;
  143. }
  144. char name[256] = "";
  145. if (ioctl(linjs.fd, (int32_t)EVIOCGNAME(sizeof(name)), name) < 0)
  146. strncpy(name, "Unknown", sizeof(name));
  147. char guid[33] = "";
  148. // Generate a joystick GUID that matches the SDL 2.0.5+ one
  149. if (id.vendor && id.product && id.version)
  150. {
  151. sprintf(guid, "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000",
  152. id.bustype & 0xff, id.bustype >> 8,
  153. id.vendor & 0xff, id.vendor >> 8,
  154. id.product & 0xff, id.product >> 8,
  155. id.version & 0xff, id.version >> 8);
  156. }
  157. else
  158. {
  159. sprintf(guid, "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00",
  160. id.bustype & 0xff, id.bustype >> 8,
  161. name[0], name[1], name[2], name[3],
  162. name[4], name[5], name[6], name[7],
  163. name[8], name[9], name[10]);
  164. }
  165. int axisCount = 0, buttonCount = 0, hatCount = 0;
  166. for (int code = BTN_MISC; code < KEY_CNT; code++)
  167. {
  168. if (!isBitSet(code, keyBits))
  169. continue;
  170. linjs.keyMap[code - BTN_MISC] = buttonCount;
  171. buttonCount++;
  172. }
  173. for (int code = 0; code < ABS_CNT; code++)
  174. {
  175. linjs.absMap[code] = -1;
  176. if (!isBitSet(code, absBits))
  177. continue;
  178. if (code >= ABS_HAT0X && code <= ABS_HAT3Y)
  179. {
  180. linjs.absMap[code] = hatCount;
  181. hatCount++;
  182. // Skip the Y axis
  183. code++;
  184. }
  185. else
  186. {
  187. if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0)
  188. continue;
  189. linjs.absMap[code] = axisCount;
  190. axisCount++;
  191. }
  192. }
  193. _GLFWjoystick* js =
  194. _glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount);
  195. if (!js)
  196. {
  197. close(linjs.fd);
  198. return false;
  199. }
  200. strncpy(linjs.path, path, sizeof(linjs.path) - 1);
  201. memcpy(&js->linjs, &linjs, sizeof(linjs));
  202. pollAbsState(js);
  203. _glfwInputJoystick(js, GLFW_CONNECTED);
  204. return true;
  205. }
  206. #undef isBitSet
  207. // Frees all resources associated with the specified joystick
  208. //
  209. static void closeJoystick(_GLFWjoystick* js)
  210. {
  211. close(js->linjs.fd);
  212. _glfwFreeJoystick(js);
  213. _glfwInputJoystick(js, GLFW_DISCONNECTED);
  214. }
  215. // Lexically compare joysticks by name; used by qsort
  216. //
  217. static int compareJoysticks(const void* fp, const void* sp)
  218. {
  219. const _GLFWjoystick* fj = fp;
  220. const _GLFWjoystick* sj = sp;
  221. return strcmp(fj->linjs.path, sj->linjs.path);
  222. }
  223. //////////////////////////////////////////////////////////////////////////
  224. ////// GLFW internal API //////
  225. //////////////////////////////////////////////////////////////////////////
  226. void _glfwDetectJoystickConnectionLinux(void)
  227. {
  228. if (_glfw.linjs.inotify <= 0)
  229. return;
  230. ssize_t offset = 0;
  231. char buffer[16384];
  232. const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
  233. while (size > offset)
  234. {
  235. regmatch_t match;
  236. const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
  237. offset += sizeof(struct inotify_event) + e->len;
  238. if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
  239. continue;
  240. char path[PATH_MAX];
  241. snprintf(path, sizeof(path), "/dev/input/%s", e->name);
  242. if (e->mask & (IN_CREATE | IN_ATTRIB))
  243. openJoystickDevice(path);
  244. else if (e->mask & IN_DELETE)
  245. {
  246. for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
  247. {
  248. if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
  249. {
  250. closeJoystick(_glfw.joysticks + jid);
  251. break;
  252. }
  253. }
  254. }
  255. }
  256. }
  257. //////////////////////////////////////////////////////////////////////////
  258. ////// GLFW platform API //////
  259. //////////////////////////////////////////////////////////////////////////
  260. bool _glfwPlatformInitJoysticks(void)
  261. {
  262. const char* dirname = "/dev/input";
  263. _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
  264. if (_glfw.linjs.inotify > 0)
  265. {
  266. // HACK: Register for IN_ATTRIB to get notified when udev is done
  267. // This works well in practice but the true way is libudev
  268. _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify,
  269. dirname,
  270. IN_CREATE | IN_ATTRIB | IN_DELETE);
  271. }
  272. // Continue without device connection notifications if inotify fails
  273. if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0)
  274. {
  275. _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex");
  276. return false;
  277. }
  278. int count = 0;
  279. DIR* dir = opendir(dirname);
  280. if (dir)
  281. {
  282. struct dirent* entry;
  283. while ((entry = readdir(dir)))
  284. {
  285. regmatch_t match;
  286. if (regexec(&_glfw.linjs.regex, entry->d_name, 1, &match, 0) != 0)
  287. continue;
  288. char path[PATH_MAX];
  289. snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name);
  290. if (openJoystickDevice(path))
  291. count++;
  292. }
  293. closedir(dir);
  294. }
  295. // Continue with no joysticks if enumeration fails
  296. qsort(_glfw.joysticks, count, sizeof(_glfw.joysticks[0]), compareJoysticks);
  297. return true;
  298. }
  299. void _glfwPlatformTerminateJoysticks(void)
  300. {
  301. int jid;
  302. for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
  303. {
  304. _GLFWjoystick* js = _glfw.joysticks + jid;
  305. if (js->present)
  306. closeJoystick(js);
  307. }
  308. if (_glfw.linjs.inotify > 0)
  309. {
  310. if (_glfw.linjs.watch > 0)
  311. inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
  312. close(_glfw.linjs.inotify);
  313. regfree(&_glfw.linjs.regex);
  314. }
  315. }
  316. int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED)
  317. {
  318. // Read all queued events (non-blocking)
  319. for (;;)
  320. {
  321. struct input_event e;
  322. errno = 0;
  323. if (read(js->linjs.fd, &e, sizeof(e)) < 0)
  324. {
  325. // Reset the joystick slot if the device was disconnected
  326. if (errno == ENODEV)
  327. closeJoystick(js);
  328. break;
  329. }
  330. if (e.type == EV_SYN)
  331. {
  332. if (e.code == SYN_DROPPED)
  333. _glfw.linjs.dropped = true;
  334. else if (e.code == SYN_REPORT)
  335. {
  336. _glfw.linjs.dropped = false;
  337. pollAbsState(js);
  338. }
  339. }
  340. if (_glfw.linjs.dropped)
  341. continue;
  342. if (e.type == EV_KEY)
  343. handleKeyEvent(js, e.code, e.value);
  344. else if (e.type == EV_ABS)
  345. handleAbsEvent(js, e.code, e.value);
  346. }
  347. return js->present;
  348. }
  349. void _glfwPlatformUpdateGamepadGUID(char* guid UNUSED)
  350. {
  351. }