linux_joystick.c 12 KB

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