SDL_udev.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
  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. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "SDL_internal.h"
  19. /*
  20. * To list the properties of a device, try something like:
  21. * udevadm info -a -n snd/hwC0D0 (for a sound card)
  22. * udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)
  23. * udevadm info --query=property -n input/event2
  24. */
  25. #include "SDL_udev.h"
  26. #ifdef SDL_USE_LIBUDEV
  27. #include <linux/input.h>
  28. #include <sys/stat.h>
  29. #include "SDL_evdev_capabilities.h"
  30. #include "../unix/SDL_poll.h"
  31. static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
  32. static SDL_UDEV_PrivateData *_this = NULL;
  33. static bool SDL_UDEV_load_sym(const char *fn, void **addr);
  34. static bool SDL_UDEV_load_syms(void);
  35. static bool SDL_UDEV_hotplug_update_available(void);
  36. static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);
  37. static int guess_device_class(struct udev_device *dev);
  38. static int device_class(struct udev_device *dev);
  39. static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
  40. static bool SDL_UDEV_load_sym(const char *fn, void **addr)
  41. {
  42. *addr = SDL_LoadFunction(_this->udev_handle, fn);
  43. if (!*addr) {
  44. // Don't call SDL_SetError(): SDL_LoadFunction already did.
  45. return false;
  46. }
  47. return true;
  48. }
  49. static bool SDL_UDEV_load_syms(void)
  50. {
  51. /* cast funcs to char* first, to please GCC's strict aliasing rules. */
  52. #define SDL_UDEV_SYM(x) \
  53. if (!SDL_UDEV_load_sym(#x, (void **)(char *)&_this->syms.x)) \
  54. return false
  55. SDL_UDEV_SYM(udev_device_get_action);
  56. SDL_UDEV_SYM(udev_device_get_devnode);
  57. SDL_UDEV_SYM(udev_device_get_syspath);
  58. SDL_UDEV_SYM(udev_device_get_subsystem);
  59. SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
  60. SDL_UDEV_SYM(udev_device_get_property_value);
  61. SDL_UDEV_SYM(udev_device_get_sysattr_value);
  62. SDL_UDEV_SYM(udev_device_new_from_syspath);
  63. SDL_UDEV_SYM(udev_device_unref);
  64. SDL_UDEV_SYM(udev_enumerate_add_match_property);
  65. SDL_UDEV_SYM(udev_enumerate_add_match_subsystem);
  66. SDL_UDEV_SYM(udev_enumerate_get_list_entry);
  67. SDL_UDEV_SYM(udev_enumerate_new);
  68. SDL_UDEV_SYM(udev_enumerate_scan_devices);
  69. SDL_UDEV_SYM(udev_enumerate_unref);
  70. SDL_UDEV_SYM(udev_list_entry_get_name);
  71. SDL_UDEV_SYM(udev_list_entry_get_next);
  72. SDL_UDEV_SYM(udev_monitor_enable_receiving);
  73. SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype);
  74. SDL_UDEV_SYM(udev_monitor_get_fd);
  75. SDL_UDEV_SYM(udev_monitor_new_from_netlink);
  76. SDL_UDEV_SYM(udev_monitor_receive_device);
  77. SDL_UDEV_SYM(udev_monitor_unref);
  78. SDL_UDEV_SYM(udev_new);
  79. SDL_UDEV_SYM(udev_unref);
  80. SDL_UDEV_SYM(udev_device_new_from_devnum);
  81. SDL_UDEV_SYM(udev_device_get_devnum);
  82. #undef SDL_UDEV_SYM
  83. return true;
  84. }
  85. static bool SDL_UDEV_hotplug_update_available(void)
  86. {
  87. if (_this->udev_mon) {
  88. const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon);
  89. if (SDL_IOReady(fd, SDL_IOR_READ, 0)) {
  90. return true;
  91. }
  92. }
  93. return false;
  94. }
  95. bool SDL_UDEV_Init(void)
  96. {
  97. if (!_this) {
  98. _this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));
  99. if (!_this) {
  100. return false;
  101. }
  102. if (!SDL_UDEV_LoadLibrary()) {
  103. SDL_UDEV_Quit();
  104. return false;
  105. }
  106. /* Set up udev monitoring
  107. * Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
  108. */
  109. _this->udev = _this->syms.udev_new();
  110. if (!_this->udev) {
  111. SDL_UDEV_Quit();
  112. return SDL_SetError("udev_new() failed");
  113. }
  114. _this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev");
  115. if (!_this->udev_mon) {
  116. SDL_UDEV_Quit();
  117. return SDL_SetError("udev_monitor_new_from_netlink() failed");
  118. }
  119. _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
  120. _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
  121. _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);
  122. _this->syms.udev_monitor_enable_receiving(_this->udev_mon);
  123. // Do an initial scan of existing devices
  124. SDL_UDEV_Scan();
  125. }
  126. _this->ref_count += 1;
  127. return true;
  128. }
  129. void SDL_UDEV_Quit(void)
  130. {
  131. if (!_this) {
  132. return;
  133. }
  134. _this->ref_count -= 1;
  135. if (_this->ref_count < 1) {
  136. if (_this->udev_mon) {
  137. _this->syms.udev_monitor_unref(_this->udev_mon);
  138. _this->udev_mon = NULL;
  139. }
  140. if (_this->udev) {
  141. _this->syms.udev_unref(_this->udev);
  142. _this->udev = NULL;
  143. }
  144. // Remove existing devices
  145. while (_this->first) {
  146. SDL_UDEV_CallbackList *item = _this->first;
  147. _this->first = _this->first->next;
  148. SDL_free(item);
  149. }
  150. SDL_UDEV_UnloadLibrary();
  151. SDL_free(_this);
  152. _this = NULL;
  153. }
  154. }
  155. bool SDL_UDEV_Scan(void)
  156. {
  157. struct udev_enumerate *enumerate = NULL;
  158. struct udev_list_entry *devs = NULL;
  159. struct udev_list_entry *item = NULL;
  160. if (!_this) {
  161. return true;
  162. }
  163. enumerate = _this->syms.udev_enumerate_new(_this->udev);
  164. if (!enumerate) {
  165. SDL_UDEV_Quit();
  166. return SDL_SetError("udev_enumerate_new() failed");
  167. }
  168. _this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
  169. _this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
  170. _this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux");
  171. _this->syms.udev_enumerate_scan_devices(enumerate);
  172. devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
  173. for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {
  174. const char *path = _this->syms.udev_list_entry_get_name(item);
  175. struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
  176. if (dev) {
  177. device_event(SDL_UDEV_DEVICEADDED, dev);
  178. _this->syms.udev_device_unref(dev);
  179. }
  180. }
  181. _this->syms.udev_enumerate_unref(enumerate);
  182. return true;
  183. }
  184. bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
  185. {
  186. struct stat statbuf;
  187. char type;
  188. struct udev_device *dev;
  189. const char *val;
  190. int class_temp;
  191. if (!_this) {
  192. return false;
  193. }
  194. if (stat(device_path, &statbuf) == -1) {
  195. return false;
  196. }
  197. if (S_ISBLK(statbuf.st_mode)) {
  198. type = 'b';
  199. }
  200. else if (S_ISCHR(statbuf.st_mode)) {
  201. type = 'c';
  202. }
  203. else {
  204. return false;
  205. }
  206. dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);
  207. if (!dev) {
  208. return false;
  209. }
  210. val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
  211. if (val) {
  212. *vendor = (Uint16)SDL_strtol(val, NULL, 16);
  213. }
  214. val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
  215. if (val) {
  216. *product = (Uint16)SDL_strtol(val, NULL, 16);
  217. }
  218. val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
  219. if (val) {
  220. *version = (Uint16)SDL_strtol(val, NULL, 16);
  221. }
  222. class_temp = device_class(dev);
  223. if (class_temp) {
  224. *class = class_temp;
  225. }
  226. _this->syms.udev_device_unref(dev);
  227. return true;
  228. }
  229. void SDL_UDEV_UnloadLibrary(void)
  230. {
  231. if (!_this) {
  232. return;
  233. }
  234. if (_this->udev_handle) {
  235. SDL_UnloadObject(_this->udev_handle);
  236. _this->udev_handle = NULL;
  237. }
  238. }
  239. bool SDL_UDEV_LoadLibrary(void)
  240. {
  241. bool result = true;
  242. if (!_this) {
  243. return SDL_SetError("UDEV not initialized");
  244. }
  245. // See if there is a udev library already loaded
  246. if (SDL_UDEV_load_syms()) {
  247. return true;
  248. }
  249. #ifdef SDL_UDEV_DYNAMIC
  250. // Check for the build environment's libudev first
  251. if (!_this->udev_handle) {
  252. _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
  253. if (_this->udev_handle) {
  254. result = SDL_UDEV_load_syms();
  255. if (!result) {
  256. SDL_UDEV_UnloadLibrary();
  257. }
  258. }
  259. }
  260. #endif
  261. if (!_this->udev_handle) {
  262. for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
  263. _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
  264. if (_this->udev_handle) {
  265. result = SDL_UDEV_load_syms();
  266. if (!result) {
  267. SDL_UDEV_UnloadLibrary();
  268. } else {
  269. break;
  270. }
  271. }
  272. }
  273. if (!_this->udev_handle) {
  274. result = false;
  275. // Don't call SDL_SetError(): SDL_LoadObject already did.
  276. }
  277. }
  278. return result;
  279. }
  280. static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
  281. {
  282. const char *value;
  283. char text[4096];
  284. char *word;
  285. int i;
  286. unsigned long v;
  287. SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));
  288. value = _this->syms.udev_device_get_sysattr_value(pdev, attr);
  289. if (!value) {
  290. return;
  291. }
  292. SDL_strlcpy(text, value, sizeof(text));
  293. i = 0;
  294. while ((word = SDL_strrchr(text, ' ')) != NULL) {
  295. v = SDL_strtoul(word + 1, NULL, 16);
  296. if (i < bitmask_len) {
  297. bitmask[i] = v;
  298. }
  299. ++i;
  300. *word = '\0';
  301. }
  302. v = SDL_strtoul(text, NULL, 16);
  303. if (i < bitmask_len) {
  304. bitmask[i] = v;
  305. }
  306. }
  307. static int guess_device_class(struct udev_device *dev)
  308. {
  309. struct udev_device *pdev;
  310. unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
  311. unsigned long bitmask_ev[NBITS(EV_MAX)];
  312. unsigned long bitmask_abs[NBITS(ABS_MAX)];
  313. unsigned long bitmask_key[NBITS(KEY_MAX)];
  314. unsigned long bitmask_rel[NBITS(REL_MAX)];
  315. /* walk up the parental chain until we find the real input device; the
  316. * argument is very likely a subdevice of this, like eventN */
  317. pdev = dev;
  318. while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
  319. pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
  320. }
  321. if (!pdev) {
  322. return 0;
  323. }
  324. get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));
  325. get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
  326. get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
  327. get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
  328. get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
  329. return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],
  330. &bitmask_ev[0],
  331. &bitmask_abs[0],
  332. &bitmask_key[0],
  333. &bitmask_rel[0]);
  334. }
  335. static int device_class(struct udev_device *dev)
  336. {
  337. const char *subsystem;
  338. const char *val = NULL;
  339. int devclass = 0;
  340. subsystem = _this->syms.udev_device_get_subsystem(dev);
  341. if (!subsystem) {
  342. return 0;
  343. }
  344. if (SDL_strcmp(subsystem, "sound") == 0) {
  345. devclass = SDL_UDEV_DEVICE_SOUND;
  346. } else if (SDL_strcmp(subsystem, "video4linux") == 0) {
  347. val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
  348. if (val && SDL_strcasestr(val, "capture")) {
  349. devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
  350. }
  351. } else if (SDL_strcmp(subsystem, "input") == 0) {
  352. // udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c
  353. val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
  354. if (val && SDL_strcmp(val, "1") == 0) {
  355. devclass |= SDL_UDEV_DEVICE_JOYSTICK;
  356. }
  357. val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
  358. if (val && SDL_strcmp(val, "1") == 0) {
  359. devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;
  360. }
  361. val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
  362. if (val && SDL_strcmp(val, "1") == 0) {
  363. devclass |= SDL_UDEV_DEVICE_MOUSE;
  364. }
  365. val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
  366. if (val && SDL_strcmp(val, "1") == 0) {
  367. devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
  368. }
  369. /* The undocumented rule is:
  370. - All devices with keys get ID_INPUT_KEY
  371. - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
  372. Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
  373. */
  374. val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
  375. if (val && SDL_strcmp(val, "1") == 0) {
  376. devclass |= SDL_UDEV_DEVICE_HAS_KEYS;
  377. }
  378. val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
  379. if (val && SDL_strcmp(val, "1") == 0) {
  380. devclass |= SDL_UDEV_DEVICE_KEYBOARD;
  381. }
  382. if (devclass == 0) {
  383. // Fall back to old style input classes
  384. val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");
  385. if (val) {
  386. if (SDL_strcmp(val, "joystick") == 0) {
  387. devclass = SDL_UDEV_DEVICE_JOYSTICK;
  388. } else if (SDL_strcmp(val, "mouse") == 0) {
  389. devclass = SDL_UDEV_DEVICE_MOUSE;
  390. } else if (SDL_strcmp(val, "kbd") == 0) {
  391. devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;
  392. }
  393. } else {
  394. // We could be linked with libudev on a system that doesn't have udev running
  395. devclass = guess_device_class(dev);
  396. }
  397. }
  398. }
  399. return devclass;
  400. }
  401. static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
  402. {
  403. int devclass = 0;
  404. const char *path;
  405. SDL_UDEV_CallbackList *item;
  406. path = _this->syms.udev_device_get_devnode(dev);
  407. if (!path) {
  408. return;
  409. }
  410. if (type == SDL_UDEV_DEVICEADDED) {
  411. devclass = device_class(dev);
  412. if (!devclass) {
  413. return;
  414. }
  415. } else {
  416. // The device has been removed, the class isn't available
  417. }
  418. // Process callbacks
  419. for (item = _this->first; item; item = item->next) {
  420. item->callback(type, devclass, path);
  421. }
  422. }
  423. void SDL_UDEV_Poll(void)
  424. {
  425. struct udev_device *dev = NULL;
  426. const char *action = NULL;
  427. if (!_this) {
  428. return;
  429. }
  430. while (SDL_UDEV_hotplug_update_available()) {
  431. dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
  432. if (!dev) {
  433. break;
  434. }
  435. action = _this->syms.udev_device_get_action(dev);
  436. if (action) {
  437. if (SDL_strcmp(action, "add") == 0) {
  438. device_event(SDL_UDEV_DEVICEADDED, dev);
  439. } else if (SDL_strcmp(action, "remove") == 0) {
  440. device_event(SDL_UDEV_DEVICEREMOVED, dev);
  441. }
  442. }
  443. _this->syms.udev_device_unref(dev);
  444. }
  445. }
  446. bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
  447. {
  448. SDL_UDEV_CallbackList *item;
  449. item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));
  450. if (!item) {
  451. return false;
  452. }
  453. item->callback = cb;
  454. if (!_this->last) {
  455. _this->first = _this->last = item;
  456. } else {
  457. _this->last->next = item;
  458. _this->last = item;
  459. }
  460. return true;
  461. }
  462. void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
  463. {
  464. SDL_UDEV_CallbackList *item;
  465. SDL_UDEV_CallbackList *prev = NULL;
  466. if (!_this) {
  467. return;
  468. }
  469. for (item = _this->first; item; item = item->next) {
  470. // found it, remove it.
  471. if (item->callback == cb) {
  472. if (prev) {
  473. prev->next = item->next;
  474. } else {
  475. SDL_assert(_this->first == item);
  476. _this->first = item->next;
  477. }
  478. if (item == _this->last) {
  479. _this->last = prev;
  480. }
  481. SDL_free(item);
  482. return;
  483. }
  484. prev = item;
  485. }
  486. }
  487. const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)
  488. {
  489. if (!SDL_UDEV_Init()) {
  490. SDL_SetError("Could not initialize UDEV");
  491. return NULL;
  492. }
  493. return &_this->syms;
  494. }
  495. void SDL_UDEV_ReleaseUdevSyms(void)
  496. {
  497. SDL_UDEV_Quit();
  498. }
  499. #endif // SDL_USE_LIBUDEV