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