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