Atlas - SDL_udev.c

Home / ext / SDL / src / core / linux Lines: 1 | Size: 19470 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23/* 24 * To list the properties of a device, try something like: 25 * udevadm info -a -n snd/hwC0D0 (for a sound card) 26 * udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc) 27 * udevadm info --query=property -n input/event2 28 */ 29#include "SDL_udev.h" 30 31#ifdef SDL_USE_LIBUDEV 32 33#include <linux/input.h> 34#include <sys/stat.h> 35 36#include "SDL_evdev_capabilities.h" 37#include "../unix/SDL_poll.h" 38 39#define SDL_UDEV_FALLBACK_LIBS "libudev.so.1", "libudev.so.0" 40 41static const char *SDL_UDEV_LIBS[] = { SDL_UDEV_FALLBACK_LIBS }; 42 43#ifdef SDL_UDEV_DYNAMIC 44#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_DYNAMIC, SDL_UDEV_FALLBACK_LIBS 45#else 46#define SDL_UDEV_DLNOTE_LIBS SDL_UDEV_FALLBACK_LIBS 47#endif 48 49SDL_ELF_NOTE_DLOPEN( 50 "events-udev", 51 "Support for events through libudev", 52 SDL_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, 53 SDL_UDEV_DLNOTE_LIBS 54) 55 56static SDL_UDEV_PrivateData *_this = NULL; 57 58static bool SDL_UDEV_load_sym(const char *fn, void **addr); 59static bool SDL_UDEV_load_syms(void); 60static bool SDL_UDEV_hotplug_update_available(void); 61static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len); 62static int guess_device_class(struct udev_device *dev); 63static int device_class(struct udev_device *dev); 64static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev); 65 66static bool SDL_UDEV_load_sym(const char *fn, void **addr) 67{ 68 *addr = SDL_LoadFunction(_this->udev_handle, fn); 69 if (!*addr) { 70 // Don't call SDL_SetError(): SDL_LoadFunction already did. 71 return false; 72 } 73 74 return true; 75} 76 77static bool SDL_UDEV_load_syms(void) 78{ 79/* cast funcs to char* first, to please GCC's strict aliasing rules. */ 80#define SDL_UDEV_SYM(x) \ 81 if (!SDL_UDEV_load_sym(#x, (void **)(char *)&_this->syms.x)) \ 82 return false 83 84#define SDL_UDEV_SYM_OPTIONAL(x) \ 85 SDL_UDEV_load_sym(#x, (void **)(char *)&_this->syms.x); 86 87 SDL_UDEV_SYM(udev_device_get_action); 88 SDL_UDEV_SYM(udev_device_get_devnode); 89 SDL_UDEV_SYM(udev_device_get_driver); 90 SDL_UDEV_SYM(udev_device_get_syspath); 91 SDL_UDEV_SYM(udev_device_get_subsystem); 92 SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype); 93 SDL_UDEV_SYM(udev_device_get_property_value); 94 SDL_UDEV_SYM(udev_device_get_sysattr_value); 95 SDL_UDEV_SYM(udev_device_new_from_syspath); 96 SDL_UDEV_SYM(udev_device_unref); 97 SDL_UDEV_SYM(udev_enumerate_add_match_property); 98 SDL_UDEV_SYM(udev_enumerate_add_match_subsystem); 99 SDL_UDEV_SYM(udev_enumerate_get_list_entry); 100 SDL_UDEV_SYM(udev_enumerate_new); 101 SDL_UDEV_SYM(udev_enumerate_scan_devices); 102 SDL_UDEV_SYM(udev_enumerate_unref); 103 SDL_UDEV_SYM(udev_list_entry_get_name); 104 SDL_UDEV_SYM(udev_list_entry_get_value); 105 SDL_UDEV_SYM(udev_list_entry_get_next); 106 SDL_UDEV_SYM(udev_monitor_enable_receiving); 107 SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype); 108 SDL_UDEV_SYM(udev_monitor_get_fd); 109 SDL_UDEV_SYM(udev_monitor_new_from_netlink); 110 SDL_UDEV_SYM(udev_monitor_receive_device); 111 SDL_UDEV_SYM(udev_monitor_unref); 112 SDL_UDEV_SYM(udev_new); 113 SDL_UDEV_SYM(udev_unref); 114 SDL_UDEV_SYM(udev_device_new_from_devnum); 115 SDL_UDEV_SYM(udev_device_get_devnum); 116 117 SDL_UDEV_SYM_OPTIONAL(udev_hwdb_new); 118 SDL_UDEV_SYM_OPTIONAL(udev_hwdb_unref); 119 SDL_UDEV_SYM_OPTIONAL(udev_hwdb_get_properties_list_entry); 120 121#undef SDL_UDEV_SYM 122 123 return true; 124} 125 126static bool SDL_UDEV_hotplug_update_available(void) 127{ 128 if (_this->udev_mon) { 129 const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon); 130 if (SDL_IOReady(fd, SDL_IOR_READ, 0)) { 131 return true; 132 } 133 } 134 return false; 135} 136 137bool SDL_UDEV_Init(void) 138{ 139 if (!_this) { 140 _this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this)); 141 if (!_this) { 142 return false; 143 } 144 145 if (!SDL_UDEV_LoadLibrary()) { 146 SDL_UDEV_Quit(); 147 return false; 148 } 149 150 /* Set up udev monitoring 151 * Listen for input devices (mouse, keyboard, joystick, etc) and sound devices 152 */ 153 154 _this->udev = _this->syms.udev_new(); 155 if (!_this->udev) { 156 SDL_UDEV_Quit(); 157 return SDL_SetError("udev_new() failed"); 158 } 159 160 _this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev"); 161 if (!_this->udev_mon) { 162 SDL_UDEV_Quit(); 163 return SDL_SetError("udev_monitor_new_from_netlink() failed"); 164 } 165 166 _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL); 167 _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL); 168 _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL); 169 _this->syms.udev_monitor_enable_receiving(_this->udev_mon); 170 171 // Do an initial scan of existing devices 172 SDL_UDEV_Scan(); 173 } 174 175 _this->ref_count += 1; 176 177 return true; 178} 179 180void SDL_UDEV_Quit(void) 181{ 182 if (!_this) { 183 return; 184 } 185 186 _this->ref_count -= 1; 187 188 if (_this->ref_count < 1) { 189 190 if (_this->udev_mon) { 191 _this->syms.udev_monitor_unref(_this->udev_mon); 192 _this->udev_mon = NULL; 193 } 194 if (_this->udev) { 195 _this->syms.udev_unref(_this->udev); 196 _this->udev = NULL; 197 } 198 199 // Remove existing devices 200 while (_this->first) { 201 SDL_UDEV_CallbackList *item = _this->first; 202 _this->first = _this->first->next; 203 SDL_free(item); 204 } 205 206 SDL_UDEV_UnloadLibrary(); 207 SDL_free(_this); 208 _this = NULL; 209 } 210} 211 212bool SDL_UDEV_Scan(void) 213{ 214 struct udev_enumerate *enumerate = NULL; 215 struct udev_list_entry *devs = NULL; 216 struct udev_list_entry *item = NULL; 217 218 if (!_this) { 219 return true; 220 } 221 222 enumerate = _this->syms.udev_enumerate_new(_this->udev); 223 if (!enumerate) { 224 SDL_UDEV_Quit(); 225 return SDL_SetError("udev_enumerate_new() failed"); 226 } 227 228 _this->syms.udev_enumerate_add_match_subsystem(enumerate, "input"); 229 _this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound"); 230 _this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux"); 231 232 _this->syms.udev_enumerate_scan_devices(enumerate); 233 devs = _this->syms.udev_enumerate_get_list_entry(enumerate); 234 for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) { 235 const char *path = _this->syms.udev_list_entry_get_name(item); 236 struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path); 237 if (dev) { 238 device_event(SDL_UDEV_DEVICEADDED, dev); 239 _this->syms.udev_device_unref(dev); 240 } 241 } 242 243 _this->syms.udev_enumerate_unref(enumerate); 244 return true; 245} 246 247bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver) 248{ 249 struct stat statbuf; 250 char type; 251 struct udev_device *dev; 252 const char *val; 253 int class_temp; 254 255 if (!_this) { 256 return false; 257 } 258 259 if (stat(device_path, &statbuf) < 0) { 260 return false; 261 } 262 263 if (S_ISBLK(statbuf.st_mode)) { 264 type = 'b'; 265 } else if (S_ISCHR(statbuf.st_mode)) { 266 type = 'c'; 267 } else { 268 return false; 269 } 270 271 dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev); 272 if (!dev) { 273 return false; 274 } 275 276 val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID"); 277 if (val) { 278 inpid->vendor = (Uint16)SDL_strtol(val, NULL, 16); 279 } 280 281 val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID"); 282 if (val) { 283 inpid->product = (Uint16)SDL_strtol(val, NULL, 16); 284 } 285 286 val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION"); 287 if (val) { 288 inpid->version = (Uint16)SDL_strtol(val, NULL, 16); 289 } 290 291 if (driver) { 292 val = _this->syms.udev_device_get_driver(dev); 293 if (!val) { 294 val = _this->syms.udev_device_get_property_value(dev, "ID_USB_DRIVER"); 295 } 296 if (val) { 297 *driver = SDL_strdup(val); 298 } 299 } 300 301 class_temp = device_class(dev); 302 if (class_temp) { 303 *class = class_temp; 304 } 305 306 _this->syms.udev_device_unref(dev); 307 308 return true; 309} 310 311char *SDL_UDEV_GetProductSerial(const char *device_path) 312{ 313 struct stat statbuf; 314 char type; 315 struct udev_device *dev; 316 const char *val; 317 char *result = NULL; 318 319 if (!_this) { 320 return NULL; 321 } 322 323 if (stat(device_path, &statbuf) < 0) { 324 return NULL; 325 } 326 327 if (S_ISBLK(statbuf.st_mode)) { 328 type = 'b'; 329 } else if (S_ISCHR(statbuf.st_mode)) { 330 type = 'c'; 331 } else { 332 return NULL; 333 } 334 335 dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev); 336 if (!dev) { 337 return NULL; 338 } 339 340 val = _this->syms.udev_device_get_property_value(dev, "ID_SERIAL_SHORT"); 341 if (val && *val) { 342 result = SDL_strdup(val); 343 } 344 345 _this->syms.udev_device_unref(dev); 346 347 return result; 348} 349 350void SDL_UDEV_UnloadLibrary(void) 351{ 352 if (!_this) { 353 return; 354 } 355 356 if (_this->udev_handle) { 357 SDL_UnloadObject(_this->udev_handle); 358 _this->udev_handle = NULL; 359 } 360} 361 362bool SDL_UDEV_LoadLibrary(void) 363{ 364 bool result = true; 365 366 if (!_this) { 367 return SDL_SetError("UDEV not initialized"); 368 } 369 370 // See if there is a udev library already loaded 371 if (SDL_UDEV_load_syms()) { 372 return true; 373 } 374 375#ifdef SDL_UDEV_DYNAMIC 376 // Check for the build environment's libudev first 377 if (!_this->udev_handle) { 378 _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC); 379 if (_this->udev_handle) { 380 result = SDL_UDEV_load_syms(); 381 if (!result) { 382 SDL_UDEV_UnloadLibrary(); 383 } 384 } 385 } 386#endif 387 388 if (!_this->udev_handle) { 389 for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) { 390 _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]); 391 if (_this->udev_handle) { 392 result = SDL_UDEV_load_syms(); 393 if (!result) { 394 SDL_UDEV_UnloadLibrary(); 395 } else { 396 break; 397 } 398 } 399 } 400 401 if (!_this->udev_handle) { 402 result = false; 403 // Don't call SDL_SetError(): SDL_LoadObject already did. 404 } 405 } 406 407 return result; 408} 409 410static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len) 411{ 412 const char *value; 413 char text[4096]; 414 char *word; 415 int i; 416 unsigned long v; 417 418 SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask)); 419 value = _this->syms.udev_device_get_sysattr_value(pdev, attr); 420 if (!value) { 421 return; 422 } 423 424 SDL_strlcpy(text, value, sizeof(text)); 425 i = 0; 426 while ((word = SDL_strrchr(text, ' ')) != NULL) { 427 v = SDL_strtoul(word + 1, NULL, 16); 428 if (i < bitmask_len) { 429 bitmask[i] = v; 430 } 431 ++i; 432 *word = '\0'; 433 } 434 v = SDL_strtoul(text, NULL, 16); 435 if (i < bitmask_len) { 436 bitmask[i] = v; 437 } 438} 439 440static int guess_device_class(struct udev_device *dev) 441{ 442 struct udev_device *pdev; 443 unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)]; 444 unsigned long bitmask_ev[NBITS(EV_MAX)]; 445 unsigned long bitmask_abs[NBITS(ABS_MAX)]; 446 unsigned long bitmask_key[NBITS(KEY_MAX)]; 447 unsigned long bitmask_rel[NBITS(REL_MAX)]; 448 449 /* walk up the parental chain until we find the real input device; the 450 * argument is very likely a subdevice of this, like eventN */ 451 pdev = dev; 452 while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) { 453 pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL); 454 } 455 if (!pdev) { 456 return 0; 457 } 458 459 get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props)); 460 get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev)); 461 get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs)); 462 get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel)); 463 get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key)); 464 465 return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0], 466 &bitmask_ev[0], 467 &bitmask_abs[0], 468 &bitmask_key[0], 469 &bitmask_rel[0]); 470} 471 472static int device_class(struct udev_device *dev) 473{ 474 const char *subsystem; 475 const char *val = NULL; 476 int devclass = 0; 477 478 subsystem = _this->syms.udev_device_get_subsystem(dev); 479 if (!subsystem) { 480 return 0; 481 } 482 483 if (SDL_strcmp(subsystem, "sound") == 0) { 484 devclass = SDL_UDEV_DEVICE_SOUND; 485 } else if (SDL_strcmp(subsystem, "video4linux") == 0) { 486 val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES"); 487 if (val && SDL_strcasestr(val, "capture")) { 488 devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE; 489 } 490 } else if (SDL_strcmp(subsystem, "input") == 0) { 491 // udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c 492 493 val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"); 494 if (val && SDL_strcmp(val, "1") == 0) { 495 devclass |= SDL_UDEV_DEVICE_JOYSTICK; 496 } 497 498 val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER"); 499 if (val && SDL_strcmp(val, "1") == 0) { 500 devclass |= SDL_UDEV_DEVICE_ACCELEROMETER; 501 } 502 503 val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE"); 504 if (val && SDL_strcmp(val, "1") == 0) { 505 devclass |= SDL_UDEV_DEVICE_MOUSE; 506 } 507 508 val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN"); 509 if (val && SDL_strcmp(val, "1") == 0) { 510 devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; 511 } 512 513 /* The undocumented rule is: 514 - All devices with keys get ID_INPUT_KEY 515 - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD 516 517 Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183 518 */ 519 val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY"); 520 if (val && SDL_strcmp(val, "1") == 0) { 521 devclass |= SDL_UDEV_DEVICE_HAS_KEYS; 522 } 523 524 val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD"); 525 if (val && SDL_strcmp(val, "1") == 0) { 526 devclass |= SDL_UDEV_DEVICE_KEYBOARD; 527 } 528 529 if (devclass == 0) { 530 // Fall back to old style input classes 531 val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS"); 532 if (val) { 533 if (SDL_strcmp(val, "joystick") == 0) { 534 devclass = SDL_UDEV_DEVICE_JOYSTICK; 535 } else if (SDL_strcmp(val, "mouse") == 0) { 536 devclass = SDL_UDEV_DEVICE_MOUSE; 537 } else if (SDL_strcmp(val, "kbd") == 0) { 538 devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD; 539 } 540 } else { 541 // We could be linked with libudev on a system that doesn't have udev running 542 devclass = guess_device_class(dev); 543 } 544 } 545 } 546 547 return devclass; 548} 549 550static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) 551{ 552 int devclass = 0; 553 const char *path; 554 SDL_UDEV_CallbackList *item; 555 556 path = _this->syms.udev_device_get_devnode(dev); 557 if (!path) { 558 return; 559 } 560 561 if (type == SDL_UDEV_DEVICEADDED) { 562 devclass = device_class(dev); 563 if (!devclass) { 564 return; 565 } 566 } else { 567 // The device has been removed, the class isn't available 568 } 569 570 // Process callbacks 571 for (item = _this->first; item; item = item->next) { 572 item->callback(type, devclass, path); 573 } 574} 575 576void SDL_UDEV_Poll(void) 577{ 578 struct udev_device *dev = NULL; 579 const char *action = NULL; 580 581 if (!_this) { 582 return; 583 } 584 585 while (SDL_UDEV_hotplug_update_available()) { 586 dev = _this->syms.udev_monitor_receive_device(_this->udev_mon); 587 if (!dev) { 588 break; 589 } 590 action = _this->syms.udev_device_get_action(dev); 591 592 if (action) { 593 if (SDL_strcmp(action, "add") == 0) { 594 device_event(SDL_UDEV_DEVICEADDED, dev); 595 } else if (SDL_strcmp(action, "remove") == 0) { 596 device_event(SDL_UDEV_DEVICEREMOVED, dev); 597 } 598 } 599 600 _this->syms.udev_device_unref(dev); 601 } 602} 603 604bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb) 605{ 606 SDL_UDEV_CallbackList *item; 607 item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList)); 608 if (!item) { 609 return false; 610 } 611 612 item->callback = cb; 613 614 if (!_this->last) { 615 _this->first = _this->last = item; 616 } else { 617 _this->last->next = item; 618 _this->last = item; 619 } 620 621 return true; 622} 623 624void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb) 625{ 626 SDL_UDEV_CallbackList *item; 627 SDL_UDEV_CallbackList *prev = NULL; 628 629 if (!_this) { 630 return; 631 } 632 633 for (item = _this->first; item; item = item->next) { 634 // found it, remove it. 635 if (item->callback == cb) { 636 if (prev) { 637 prev->next = item->next; 638 } else { 639 SDL_assert(_this->first == item); 640 _this->first = item->next; 641 } 642 if (item == _this->last) { 643 _this->last = prev; 644 } 645 SDL_free(item); 646 return; 647 } 648 prev = item; 649 } 650} 651 652const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void) 653{ 654 if (!SDL_UDEV_Init()) { 655 SDL_SetError("Could not initialize UDEV"); 656 return NULL; 657 } 658 659 return &_this->syms; 660} 661 662void SDL_UDEV_ReleaseUdevSyms(void) 663{ 664 SDL_UDEV_Quit(); 665} 666 667#endif // SDL_USE_LIBUDEV 668
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.