Atlas - SDL_udev.c

Home / ext / SDL2 / src / core / linux Lines: 1 | Size: 17112 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 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 22/* 23 * To list the properties of a device, try something like: 24 * udevadm info -a -n snd/hwC0D0 (for a sound card) 25 * udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc) 26 * udevadm info --query=property -n input/event2 27 */ 28#include "SDL_udev.h" 29 30#ifdef SDL_USE_LIBUDEV 31 32#include <linux/input.h> 33 34#include "SDL_assert.h" 35#include "SDL_loadso.h" 36#include "SDL_timer.h" 37#include "../unix/SDL_poll.h" 38 39static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" }; 40 41#define _THIS SDL_UDEV_PrivateData *_this 42static _THIS = NULL; 43 44static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr); 45static int SDL_UDEV_load_syms(void); 46static SDL_bool SDL_UDEV_hotplug_update_available(void); 47static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev); 48 49static SDL_bool 50SDL_UDEV_load_sym(const char *fn, void **addr) 51{ 52 *addr = SDL_LoadFunction(_this->udev_handle, fn); 53 if (*addr == NULL) { 54 /* Don't call SDL_SetError(): SDL_LoadFunction already did. */ 55 return SDL_FALSE; 56 } 57 58 return SDL_TRUE; 59} 60 61static int 62SDL_UDEV_load_syms(void) 63{ 64 /* cast funcs to char* first, to please GCC's strict aliasing rules. */ 65 #define SDL_UDEV_SYM(x) \ 66 if (!SDL_UDEV_load_sym(#x, (void **) (char *) & _this->x)) return -1 67 68 SDL_UDEV_SYM(udev_device_get_action); 69 SDL_UDEV_SYM(udev_device_get_devnode); 70 SDL_UDEV_SYM(udev_device_get_subsystem); 71 SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype); 72 SDL_UDEV_SYM(udev_device_get_property_value); 73 SDL_UDEV_SYM(udev_device_get_sysattr_value); 74 SDL_UDEV_SYM(udev_device_new_from_syspath); 75 SDL_UDEV_SYM(udev_device_unref); 76 SDL_UDEV_SYM(udev_enumerate_add_match_property); 77 SDL_UDEV_SYM(udev_enumerate_add_match_subsystem); 78 SDL_UDEV_SYM(udev_enumerate_get_list_entry); 79 SDL_UDEV_SYM(udev_enumerate_new); 80 SDL_UDEV_SYM(udev_enumerate_scan_devices); 81 SDL_UDEV_SYM(udev_enumerate_unref); 82 SDL_UDEV_SYM(udev_list_entry_get_name); 83 SDL_UDEV_SYM(udev_list_entry_get_next); 84 SDL_UDEV_SYM(udev_monitor_enable_receiving); 85 SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype); 86 SDL_UDEV_SYM(udev_monitor_get_fd); 87 SDL_UDEV_SYM(udev_monitor_new_from_netlink); 88 SDL_UDEV_SYM(udev_monitor_receive_device); 89 SDL_UDEV_SYM(udev_monitor_unref); 90 SDL_UDEV_SYM(udev_new); 91 SDL_UDEV_SYM(udev_unref); 92 SDL_UDEV_SYM(udev_device_new_from_devnum); 93 SDL_UDEV_SYM(udev_device_get_devnum); 94 #undef SDL_UDEV_SYM 95 96 return 0; 97} 98 99static SDL_bool 100SDL_UDEV_hotplug_update_available(void) 101{ 102 if (_this->udev_mon != NULL) { 103 const int fd = _this->udev_monitor_get_fd(_this->udev_mon); 104 if (SDL_IOReady(fd, SDL_FALSE, 0)) { 105 return SDL_TRUE; 106 } 107 } 108 return SDL_FALSE; 109} 110 111 112int 113SDL_UDEV_Init(void) 114{ 115 int retval = 0; 116 117 if (_this == NULL) { 118 _this = (SDL_UDEV_PrivateData *) SDL_calloc(1, sizeof(*_this)); 119 if(_this == NULL) { 120 return SDL_OutOfMemory(); 121 } 122 123 retval = SDL_UDEV_LoadLibrary(); 124 if (retval < 0) { 125 SDL_UDEV_Quit(); 126 return retval; 127 } 128 129 /* Set up udev monitoring 130 * Listen for input devices (mouse, keyboard, joystick, etc) and sound devices 131 */ 132 133 _this->udev = _this->udev_new(); 134 if (_this->udev == NULL) { 135 SDL_UDEV_Quit(); 136 return SDL_SetError("udev_new() failed"); 137 } 138 139 _this->udev_mon = _this->udev_monitor_new_from_netlink(_this->udev, "udev"); 140 if (_this->udev_mon == NULL) { 141 SDL_UDEV_Quit(); 142 return SDL_SetError("udev_monitor_new_from_netlink() failed"); 143 } 144 145 _this->udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL); 146 _this->udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL); 147 _this->udev_monitor_enable_receiving(_this->udev_mon); 148 149 /* Do an initial scan of existing devices */ 150 SDL_UDEV_Scan(); 151 152 } 153 154 _this->ref_count += 1; 155 156 return retval; 157} 158 159void 160SDL_UDEV_Quit(void) 161{ 162 SDL_UDEV_CallbackList *item; 163 164 if (_this == NULL) { 165 return; 166 } 167 168 _this->ref_count -= 1; 169 170 if (_this->ref_count < 1) { 171 172 if (_this->udev_mon != NULL) { 173 _this->udev_monitor_unref(_this->udev_mon); 174 _this->udev_mon = NULL; 175 } 176 if (_this->udev != NULL) { 177 _this->udev_unref(_this->udev); 178 _this->udev = NULL; 179 } 180 181 /* Remove existing devices */ 182 while (_this->first != NULL) { 183 item = _this->first; 184 _this->first = _this->first->next; 185 SDL_free(item); 186 } 187 188 SDL_UDEV_UnloadLibrary(); 189 SDL_free(_this); 190 _this = NULL; 191 } 192} 193 194void 195SDL_UDEV_Scan(void) 196{ 197 struct udev_enumerate *enumerate = NULL; 198 struct udev_list_entry *devs = NULL; 199 struct udev_list_entry *item = NULL; 200 201 if (_this == NULL) { 202 return; 203 } 204 205 enumerate = _this->udev_enumerate_new(_this->udev); 206 if (enumerate == NULL) { 207 SDL_UDEV_Quit(); 208 SDL_SetError("udev_enumerate_new() failed"); 209 return; 210 } 211 212 _this->udev_enumerate_add_match_subsystem(enumerate, "input"); 213 _this->udev_enumerate_add_match_subsystem(enumerate, "sound"); 214 215 _this->udev_enumerate_scan_devices(enumerate); 216 devs = _this->udev_enumerate_get_list_entry(enumerate); 217 for (item = devs; item; item = _this->udev_list_entry_get_next(item)) { 218 const char *path = _this->udev_list_entry_get_name(item); 219 struct udev_device *dev = _this->udev_device_new_from_syspath(_this->udev, path); 220 if (dev != NULL) { 221 device_event(SDL_UDEV_DEVICEADDED, dev); 222 _this->udev_device_unref(dev); 223 } 224 } 225 226 _this->udev_enumerate_unref(enumerate); 227} 228 229 230void 231SDL_UDEV_UnloadLibrary(void) 232{ 233 if (_this == NULL) { 234 return; 235 } 236 237 if (_this->udev_handle != NULL) { 238 SDL_UnloadObject(_this->udev_handle); 239 _this->udev_handle = NULL; 240 } 241} 242 243int 244SDL_UDEV_LoadLibrary(void) 245{ 246 int retval = 0, i; 247 248 if (_this == NULL) { 249 return SDL_SetError("UDEV not initialized"); 250 } 251 252 /* See if there is a udev library already loaded */ 253 if (SDL_UDEV_load_syms() == 0) { 254 return 0; 255 } 256 257#ifdef SDL_UDEV_DYNAMIC 258 /* Check for the build environment's libudev first */ 259 if (_this->udev_handle == NULL) { 260 _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC); 261 if (_this->udev_handle != NULL) { 262 retval = SDL_UDEV_load_syms(); 263 if (retval < 0) { 264 SDL_UDEV_UnloadLibrary(); 265 } 266 } 267 } 268#endif 269 270 if (_this->udev_handle == NULL) { 271 for( i = 0 ; i < SDL_arraysize(SDL_UDEV_LIBS); i++) { 272 _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]); 273 if (_this->udev_handle != NULL) { 274 retval = SDL_UDEV_load_syms(); 275 if (retval < 0) { 276 SDL_UDEV_UnloadLibrary(); 277 } 278 else { 279 break; 280 } 281 } 282 } 283 284 if (_this->udev_handle == NULL) { 285 retval = -1; 286 /* Don't call SDL_SetError(): SDL_LoadObject already did. */ 287 } 288 } 289 290 return retval; 291} 292 293#define BITS_PER_LONG (sizeof(unsigned long) * 8) 294#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1) 295#define OFF(x) ((x)%BITS_PER_LONG) 296#define LONG(x) ((x)/BITS_PER_LONG) 297#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1) 298 299static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len) 300{ 301 const char *value; 302 char text[4096]; 303 char *word; 304 int i; 305 unsigned long v; 306 307 SDL_memset(bitmask, 0, bitmask_len*sizeof(*bitmask)); 308 value = _this->udev_device_get_sysattr_value(pdev, attr); 309 if (!value) { 310 return; 311 } 312 313 SDL_strlcpy(text, value, sizeof(text)); 314 i = 0; 315 while ((word = SDL_strrchr(text, ' ')) != NULL) { 316 v = SDL_strtoul(word+1, NULL, 16); 317 if (i < bitmask_len) { 318 bitmask[i] = v; 319 } 320 ++i; 321 *word = '\0'; 322 } 323 v = SDL_strtoul(text, NULL, 16); 324 if (i < bitmask_len) { 325 bitmask[i] = v; 326 } 327} 328 329static int 330guess_device_class(struct udev_device *dev) 331{ 332 int devclass = 0; 333 struct udev_device *pdev; 334 unsigned long bitmask_ev[NBITS(EV_MAX)]; 335 unsigned long bitmask_abs[NBITS(ABS_MAX)]; 336 unsigned long bitmask_key[NBITS(KEY_MAX)]; 337 unsigned long bitmask_rel[NBITS(REL_MAX)]; 338 unsigned long keyboard_mask; 339 340 /* walk up the parental chain until we find the real input device; the 341 * argument is very likely a subdevice of this, like eventN */ 342 pdev = dev; 343 while (pdev && !_this->udev_device_get_sysattr_value(pdev, "capabilities/ev")) { 344 pdev = _this->udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL); 345 } 346 if (!pdev) { 347 return 0; 348 } 349 350 get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev)); 351 get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs)); 352 get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel)); 353 get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key)); 354 355 if (test_bit(EV_ABS, bitmask_ev) && 356 test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs)) { 357 if (test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key)) { 358 ; /* ID_INPUT_TABLET */ 359 } else if (test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key)) { 360 ; /* ID_INPUT_TOUCHPAD */ 361 } else if (test_bit(BTN_MOUSE, bitmask_key)) { 362 devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */ 363 } else if (test_bit(BTN_TOUCH, bitmask_key)) { 364 /* TODO: better determining between touchscreen and multitouch touchpad, 365 see https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-input_id.c */ 366 devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; /* ID_INPUT_TOUCHSCREEN */ 367 } 368 369 if (test_bit(BTN_TRIGGER, bitmask_key) || 370 test_bit(BTN_A, bitmask_key) || 371 test_bit(BTN_1, bitmask_key) || 372 test_bit(ABS_RX, bitmask_abs) || 373 test_bit(ABS_RY, bitmask_abs) || 374 test_bit(ABS_RZ, bitmask_abs) || 375 test_bit(ABS_THROTTLE, bitmask_abs) || 376 test_bit(ABS_RUDDER, bitmask_abs) || 377 test_bit(ABS_WHEEL, bitmask_abs) || 378 test_bit(ABS_GAS, bitmask_abs) || 379 test_bit(ABS_BRAKE, bitmask_abs)) { 380 devclass |= SDL_UDEV_DEVICE_JOYSTICK; /* ID_INPUT_JOYSTICK */ 381 } 382 } 383 384 if (test_bit(EV_REL, bitmask_ev) && 385 test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) && 386 test_bit(BTN_MOUSE, bitmask_key)) { 387 devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */ 388 } 389 390 /* the first 32 bits are ESC, numbers, and Q to D; if we have any of 391 * those, consider it a keyboard device; do not test KEY_RESERVED, though */ 392 keyboard_mask = 0xFFFFFFFE; 393 if ((bitmask_key[0] & keyboard_mask) != 0) 394 devclass |= SDL_UDEV_DEVICE_KEYBOARD; /* ID_INPUT_KEYBOARD */ 395 396 return devclass; 397} 398 399static void 400device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) 401{ 402 const char *subsystem; 403 const char *val = NULL; 404 int devclass = 0; 405 const char *path; 406 SDL_UDEV_CallbackList *item; 407 408 path = _this->udev_device_get_devnode(dev); 409 if (path == NULL) { 410 return; 411 } 412 413 subsystem = _this->udev_device_get_subsystem(dev); 414 if (SDL_strcmp(subsystem, "sound") == 0) { 415 devclass = SDL_UDEV_DEVICE_SOUND; 416 } else if (SDL_strcmp(subsystem, "input") == 0) { 417 /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */ 418 419 val = _this->udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK"); 420 if (val != NULL && SDL_strcmp(val, "1") == 0 ) { 421 devclass |= SDL_UDEV_DEVICE_JOYSTICK; 422 } 423 424 val = _this->udev_device_get_property_value(dev, "ID_INPUT_MOUSE"); 425 if (val != NULL && SDL_strcmp(val, "1") == 0 ) { 426 devclass |= SDL_UDEV_DEVICE_MOUSE; 427 } 428 429 val = _this->udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN"); 430 if (val != NULL && SDL_strcmp(val, "1") == 0 ) { 431 devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; 432 } 433 434 /* The undocumented rule is: 435 - All devices with keys get ID_INPUT_KEY 436 - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD 437 438 Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183 439 */ 440 val = _this->udev_device_get_property_value(dev, "ID_INPUT_KEY"); 441 if (val != NULL && SDL_strcmp(val, "1") == 0 ) { 442 devclass |= SDL_UDEV_DEVICE_KEYBOARD; 443 } 444 445 if (devclass == 0) { 446 /* Fall back to old style input classes */ 447 val = _this->udev_device_get_property_value(dev, "ID_CLASS"); 448 if (val != NULL) { 449 if (SDL_strcmp(val, "joystick") == 0) { 450 devclass = SDL_UDEV_DEVICE_JOYSTICK; 451 } else if (SDL_strcmp(val, "mouse") == 0) { 452 devclass = SDL_UDEV_DEVICE_MOUSE; 453 } else if (SDL_strcmp(val, "kbd") == 0) { 454 devclass = SDL_UDEV_DEVICE_KEYBOARD; 455 } else { 456 return; 457 } 458 } else { 459 /* We could be linked with libudev on a system that doesn't have udev running */ 460 devclass = guess_device_class(dev); 461 } 462 } 463 } else { 464 return; 465 } 466 467 /* Process callbacks */ 468 for (item = _this->first; item != NULL; item = item->next) { 469 item->callback(type, devclass, path); 470 } 471} 472 473void 474SDL_UDEV_Poll(void) 475{ 476 struct udev_device *dev = NULL; 477 const char *action = NULL; 478 479 if (_this == NULL) { 480 return; 481 } 482 483 while (SDL_UDEV_hotplug_update_available()) { 484 dev = _this->udev_monitor_receive_device(_this->udev_mon); 485 if (dev == NULL) { 486 break; 487 } 488 action = _this->udev_device_get_action(dev); 489 490 if (SDL_strcmp(action, "add") == 0) { 491 /* Wait for the device to finish initialization */ 492 SDL_Delay(100); 493 494 device_event(SDL_UDEV_DEVICEADDED, dev); 495 } else if (SDL_strcmp(action, "remove") == 0) { 496 device_event(SDL_UDEV_DEVICEREMOVED, dev); 497 } 498 499 _this->udev_device_unref(dev); 500 } 501} 502 503int 504SDL_UDEV_AddCallback(SDL_UDEV_Callback cb) 505{ 506 SDL_UDEV_CallbackList *item; 507 item = (SDL_UDEV_CallbackList *) SDL_calloc(1, sizeof (SDL_UDEV_CallbackList)); 508 if (item == NULL) { 509 return SDL_OutOfMemory(); 510 } 511 512 item->callback = cb; 513 514 if (_this->last == NULL) { 515 _this->first = _this->last = item; 516 } else { 517 _this->last->next = item; 518 _this->last = item; 519 } 520 521 return 1; 522} 523 524void 525SDL_UDEV_DelCallback(SDL_UDEV_Callback cb) 526{ 527 SDL_UDEV_CallbackList *item; 528 SDL_UDEV_CallbackList *prev = NULL; 529 530 for (item = _this->first; item != NULL; item = item->next) { 531 /* found it, remove it. */ 532 if (item->callback == cb) { 533 if (prev != NULL) { 534 prev->next = item->next; 535 } else { 536 SDL_assert(_this->first == item); 537 _this->first = item->next; 538 } 539 if (item == _this->last) { 540 _this->last = prev; 541 } 542 SDL_free(item); 543 return; 544 } 545 prev = item; 546 } 547 548} 549 550 551#endif /* SDL_USE_LIBUDEV */ 552 553/* vi: set ts=4 sw=4 expandtab: */ 554
[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.