• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 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 
36 static const char* SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
37 
38 #define _THIS SDL_UDEV_PrivateData *_this
39 static _THIS = NULL;
40 
41 static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr);
42 static int SDL_UDEV_load_syms(void);
43 static SDL_bool SDL_UDEV_hotplug_update_available(void);
44 static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
45 
46 static SDL_bool
SDL_UDEV_load_sym(const char * fn,void ** addr)47 SDL_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 
58 static int
SDL_UDEV_load_syms(void)59 SDL_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 
96 static SDL_bool
SDL_UDEV_hotplug_update_available(void)97 SDL_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 
116 int
SDL_UDEV_Init(void)117 SDL_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 
163 void
SDL_UDEV_Quit(void)164 SDL_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 
198 void
SDL_UDEV_Scan(void)199 SDL_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 
234 void
SDL_UDEV_UnloadLibrary(void)235 SDL_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 
247 int
SDL_UDEV_LoadLibrary(void)248 SDL_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 
get_caps(struct udev_device * dev,struct udev_device * pdev,const char * attr,unsigned long * bitmask,size_t bitmask_len)287 static 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 
317 static int
guess_device_class(struct udev_device * dev)318 guess_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             /* TODO: better determining between touchscreen and multitouch touchpad,
353                see https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-input_id.c */
354             devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; /* ID_INPUT_TOUCHSCREEN */
355         }
356 
357         if (test_bit(BTN_TRIGGER, bitmask_key) ||
358             test_bit(BTN_A, bitmask_key) ||
359             test_bit(BTN_1, bitmask_key) ||
360             test_bit(ABS_RX, bitmask_abs) ||
361             test_bit(ABS_RY, bitmask_abs) ||
362             test_bit(ABS_RZ, bitmask_abs) ||
363             test_bit(ABS_THROTTLE, bitmask_abs) ||
364             test_bit(ABS_RUDDER, bitmask_abs) ||
365             test_bit(ABS_WHEEL, bitmask_abs) ||
366             test_bit(ABS_GAS, bitmask_abs) ||
367             test_bit(ABS_BRAKE, bitmask_abs)) {
368             devclass |= SDL_UDEV_DEVICE_JOYSTICK; /* ID_INPUT_JOYSTICK */
369         }
370     }
371 
372     if (test_bit(EV_REL, bitmask_ev) &&
373         test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
374         test_bit(BTN_MOUSE, bitmask_key)) {
375         devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
376     }
377 
378     /* the first 32 bits are ESC, numbers, and Q to D; if we have any of
379      * those, consider it a keyboard device; do not test KEY_RESERVED, though */
380     keyboard_mask = 0xFFFFFFFE;
381     if ((bitmask_key[0] & keyboard_mask) != 0)
382         devclass |= SDL_UDEV_DEVICE_KEYBOARD; /* ID_INPUT_KEYBOARD */
383 
384     return devclass;
385 }
386 
387 static void
device_event(SDL_UDEV_deviceevent type,struct udev_device * dev)388 device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
389 {
390     const char *subsystem;
391     const char *val = NULL;
392     int devclass = 0;
393     const char *path;
394     SDL_UDEV_CallbackList *item;
395 
396     path = _this->udev_device_get_devnode(dev);
397     if (path == NULL) {
398         return;
399     }
400 
401     subsystem = _this->udev_device_get_subsystem(dev);
402     if (SDL_strcmp(subsystem, "sound") == 0) {
403         devclass = SDL_UDEV_DEVICE_SOUND;
404     } else if (SDL_strcmp(subsystem, "input") == 0) {
405         /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
406 
407         val = _this->udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
408         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
409             devclass |= SDL_UDEV_DEVICE_JOYSTICK;
410         }
411 
412         val = _this->udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
413         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
414             devclass |= SDL_UDEV_DEVICE_MOUSE;
415         }
416 
417         val = _this->udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
418         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
419             devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
420         }
421 
422         /* The undocumented rule is:
423            - All devices with keys get ID_INPUT_KEY
424            - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
425 
426            Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
427         */
428         val = _this->udev_device_get_property_value(dev, "ID_INPUT_KEY");
429         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
430             devclass |= SDL_UDEV_DEVICE_KEYBOARD;
431         }
432 
433         if (devclass == 0) {
434             /* Fall back to old style input classes */
435             val = _this->udev_device_get_property_value(dev, "ID_CLASS");
436             if (val != NULL) {
437                 if (SDL_strcmp(val, "joystick") == 0) {
438                     devclass = SDL_UDEV_DEVICE_JOYSTICK;
439                 } else if (SDL_strcmp(val, "mouse") == 0) {
440                     devclass = SDL_UDEV_DEVICE_MOUSE;
441                 } else if (SDL_strcmp(val, "kbd") == 0) {
442                     devclass = SDL_UDEV_DEVICE_KEYBOARD;
443                 } else {
444                     return;
445                 }
446             } else {
447                 /* We could be linked with libudev on a system that doesn't have udev running */
448                 devclass = guess_device_class(dev);
449             }
450         }
451     } else {
452         return;
453     }
454 
455     /* Process callbacks */
456     for (item = _this->first; item != NULL; item = item->next) {
457         item->callback(type, devclass, path);
458     }
459 }
460 
461 void
SDL_UDEV_Poll(void)462 SDL_UDEV_Poll(void)
463 {
464     struct udev_device *dev = NULL;
465     const char *action = NULL;
466 
467     if (_this == NULL) {
468         return;
469     }
470 
471     while (SDL_UDEV_hotplug_update_available()) {
472         dev = _this->udev_monitor_receive_device(_this->udev_mon);
473         if (dev == NULL) {
474             break;
475         }
476         action = _this->udev_device_get_action(dev);
477 
478         if (SDL_strcmp(action, "add") == 0) {
479             /* Wait for the device to finish initialization */
480             SDL_Delay(100);
481 
482             device_event(SDL_UDEV_DEVICEADDED, dev);
483         } else if (SDL_strcmp(action, "remove") == 0) {
484             device_event(SDL_UDEV_DEVICEREMOVED, dev);
485         }
486 
487         _this->udev_device_unref(dev);
488     }
489 }
490 
491 int
SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)492 SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
493 {
494     SDL_UDEV_CallbackList *item;
495     item = (SDL_UDEV_CallbackList *) SDL_calloc(1, sizeof (SDL_UDEV_CallbackList));
496     if (item == NULL) {
497         return SDL_OutOfMemory();
498     }
499 
500     item->callback = cb;
501 
502     if (_this->last == NULL) {
503         _this->first = _this->last = item;
504     } else {
505         _this->last->next = item;
506         _this->last = item;
507     }
508 
509     return 1;
510 }
511 
512 void
SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)513 SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
514 {
515     SDL_UDEV_CallbackList *item;
516     SDL_UDEV_CallbackList *prev = NULL;
517 
518     for (item = _this->first; item != NULL; item = item->next) {
519         /* found it, remove it. */
520         if (item->callback == cb) {
521             if (prev != NULL) {
522                 prev->next = item->next;
523             } else {
524                 SDL_assert(_this->first == item);
525                 _this->first = item->next;
526             }
527             if (item == _this->last) {
528                 _this->last = prev;
529             }
530             SDL_free(item);
531             return;
532         }
533         prev = item;
534     }
535 
536 }
537 
538 
539 #endif /* SDL_USE_LIBUDEV */
540