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