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