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