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