Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/core/linux/SDL_udev.c
21382 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
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
#include "SDL_internal.h"
22
23
/*
24
* To list the properties of a device, try something like:
25
* udevadm info -a -n snd/hwC0D0 (for a sound card)
26
* udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)
27
* udevadm info --query=property -n input/event2
28
*/
29
#include "SDL_udev.h"
30
31
#ifdef SDL_USE_LIBUDEV
32
33
#include <linux/input.h>
34
#include <sys/stat.h>
35
36
#include "SDL_evdev_capabilities.h"
37
#include "../unix/SDL_poll.h"
38
39
static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
40
41
SDL_UDEV_PrivateData *SDL_UDEV_PrivateData_this = NULL;
42
#define _this SDL_UDEV_PrivateData_this
43
44
static bool SDL_UDEV_load_sym(const char *fn, void **addr);
45
static bool SDL_UDEV_load_syms(void);
46
static bool SDL_UDEV_hotplug_update_available(void);
47
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);
48
static int guess_device_class(struct udev_device *dev);
49
static int device_class(struct udev_device *dev);
50
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
51
52
static bool SDL_UDEV_load_sym(const char *fn, void **addr)
53
{
54
*addr = SDL_LoadFunction(_this->udev_handle, fn);
55
if (!*addr) {
56
// Don't call SDL_SetError(): SDL_LoadFunction already did.
57
return false;
58
}
59
60
return true;
61
}
62
63
static bool 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)) \
68
return false
69
70
SDL_UDEV_SYM(udev_device_get_action);
71
SDL_UDEV_SYM(udev_device_get_devnode);
72
SDL_UDEV_SYM(udev_device_get_syspath);
73
SDL_UDEV_SYM(udev_device_get_subsystem);
74
SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
75
SDL_UDEV_SYM(udev_device_get_property_value);
76
SDL_UDEV_SYM(udev_device_get_sysattr_value);
77
SDL_UDEV_SYM(udev_device_new_from_syspath);
78
SDL_UDEV_SYM(udev_device_unref);
79
SDL_UDEV_SYM(udev_enumerate_add_match_property);
80
SDL_UDEV_SYM(udev_enumerate_add_match_subsystem);
81
SDL_UDEV_SYM(udev_enumerate_get_list_entry);
82
SDL_UDEV_SYM(udev_enumerate_new);
83
SDL_UDEV_SYM(udev_enumerate_scan_devices);
84
SDL_UDEV_SYM(udev_enumerate_unref);
85
SDL_UDEV_SYM(udev_list_entry_get_name);
86
SDL_UDEV_SYM(udev_list_entry_get_next);
87
SDL_UDEV_SYM(udev_monitor_enable_receiving);
88
SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype);
89
SDL_UDEV_SYM(udev_monitor_get_fd);
90
SDL_UDEV_SYM(udev_monitor_new_from_netlink);
91
SDL_UDEV_SYM(udev_monitor_receive_device);
92
SDL_UDEV_SYM(udev_monitor_unref);
93
SDL_UDEV_SYM(udev_new);
94
SDL_UDEV_SYM(udev_unref);
95
SDL_UDEV_SYM(udev_device_new_from_devnum);
96
SDL_UDEV_SYM(udev_device_get_devnum);
97
#undef SDL_UDEV_SYM
98
99
return true;
100
}
101
102
static bool SDL_UDEV_hotplug_update_available(void)
103
{
104
if (_this->udev_mon) {
105
const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon);
106
if (SDL_IOReady(fd, SDL_IOR_READ, 0)) {
107
return true;
108
}
109
}
110
return false;
111
}
112
113
bool SDL_UDEV_Init(void)
114
{
115
if (!_this) {
116
_this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));
117
if (!_this) {
118
return false;
119
}
120
121
if (!SDL_UDEV_LoadLibrary()) {
122
SDL_UDEV_Quit();
123
return false;
124
}
125
126
/* Set up udev monitoring
127
* Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
128
*/
129
130
_this->udev = _this->syms.udev_new();
131
if (!_this->udev) {
132
SDL_UDEV_Quit();
133
return SDL_SetError("udev_new() failed");
134
}
135
136
_this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev");
137
if (!_this->udev_mon) {
138
SDL_UDEV_Quit();
139
return SDL_SetError("udev_monitor_new_from_netlink() failed");
140
}
141
142
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
143
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
144
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);
145
_this->syms.udev_monitor_enable_receiving(_this->udev_mon);
146
147
// Do an initial scan of existing devices
148
SDL_UDEV_Scan();
149
}
150
151
_this->ref_count += 1;
152
153
return true;
154
}
155
156
void SDL_UDEV_Quit(void)
157
{
158
if (!_this) {
159
return;
160
}
161
162
_this->ref_count -= 1;
163
164
if (_this->ref_count < 1) {
165
166
if (_this->udev_mon) {
167
_this->syms.udev_monitor_unref(_this->udev_mon);
168
_this->udev_mon = NULL;
169
}
170
if (_this->udev) {
171
_this->syms.udev_unref(_this->udev);
172
_this->udev = NULL;
173
}
174
175
// Remove existing devices
176
while (_this->first) {
177
SDL_UDEV_CallbackList *item = _this->first;
178
_this->first = _this->first->next;
179
SDL_free(item);
180
}
181
182
SDL_UDEV_UnloadLibrary();
183
SDL_free(_this);
184
_this = NULL;
185
}
186
}
187
188
bool SDL_UDEV_Scan(void)
189
{
190
struct udev_enumerate *enumerate = NULL;
191
struct udev_list_entry *devs = NULL;
192
struct udev_list_entry *item = NULL;
193
194
if (!_this) {
195
return true;
196
}
197
198
enumerate = _this->syms.udev_enumerate_new(_this->udev);
199
if (!enumerate) {
200
SDL_UDEV_Quit();
201
return SDL_SetError("udev_enumerate_new() failed");
202
}
203
204
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
205
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
206
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux");
207
208
_this->syms.udev_enumerate_scan_devices(enumerate);
209
devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
210
for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {
211
const char *path = _this->syms.udev_list_entry_get_name(item);
212
struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
213
if (dev) {
214
device_event(SDL_UDEV_DEVICEADDED, dev);
215
_this->syms.udev_device_unref(dev);
216
}
217
}
218
219
_this->syms.udev_enumerate_unref(enumerate);
220
return true;
221
}
222
223
bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
224
{
225
struct stat statbuf;
226
char type;
227
struct udev_device *dev;
228
const char* val;
229
int class_temp;
230
231
if (!_this) {
232
return false;
233
}
234
235
if (stat(device_path, &statbuf) == -1) {
236
return false;
237
}
238
239
if (S_ISBLK(statbuf.st_mode)) {
240
type = 'b';
241
}
242
else if (S_ISCHR(statbuf.st_mode)) {
243
type = 'c';
244
}
245
else {
246
return false;
247
}
248
249
dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);
250
251
if (!dev) {
252
return false;
253
}
254
255
val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
256
if (val) {
257
*vendor = (Uint16)SDL_strtol(val, NULL, 16);
258
}
259
260
val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
261
if (val) {
262
*product = (Uint16)SDL_strtol(val, NULL, 16);
263
}
264
265
val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
266
if (val) {
267
*version = (Uint16)SDL_strtol(val, NULL, 16);
268
}
269
270
class_temp = device_class(dev);
271
if (class_temp) {
272
*class = class_temp;
273
}
274
275
_this->syms.udev_device_unref(dev);
276
277
return true;
278
}
279
280
bool SDL_UDEV_GetProductSerial(const char *device_path, const char **serial)
281
{
282
struct stat statbuf;
283
char type;
284
struct udev_device *dev;
285
const char *val;
286
287
if (!_this) {
288
return false;
289
}
290
291
if (stat(device_path, &statbuf) < 0) {
292
return false;
293
}
294
295
if (S_ISBLK(statbuf.st_mode)) {
296
type = 'b';
297
} else if (S_ISCHR(statbuf.st_mode)) {
298
type = 'c';
299
} else {
300
return false;
301
}
302
303
dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);
304
if (!dev) {
305
return false;
306
}
307
308
val = _this->syms.udev_device_get_property_value(dev, "ID_SERIAL_SHORT");
309
if (val) {
310
*serial = val;
311
return true;
312
}
313
314
return false;
315
}
316
317
void SDL_UDEV_UnloadLibrary(void)
318
{
319
if (!_this) {
320
return;
321
}
322
323
if (_this->udev_handle) {
324
SDL_UnloadObject(_this->udev_handle);
325
_this->udev_handle = NULL;
326
}
327
}
328
329
bool SDL_UDEV_LoadLibrary(void)
330
{
331
bool result = true;
332
333
if (!_this) {
334
return SDL_SetError("UDEV not initialized");
335
}
336
337
// See if there is a udev library already loaded
338
if (SDL_UDEV_load_syms()) {
339
return true;
340
}
341
342
#ifdef SDL_UDEV_DYNAMIC
343
// Check for the build environment's libudev first
344
if (!_this->udev_handle) {
345
_this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
346
if (_this->udev_handle) {
347
result = SDL_UDEV_load_syms();
348
if (!result) {
349
SDL_UDEV_UnloadLibrary();
350
}
351
}
352
}
353
#endif
354
355
if (!_this->udev_handle) {
356
for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
357
_this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
358
if (_this->udev_handle) {
359
result = SDL_UDEV_load_syms();
360
if (!result) {
361
SDL_UDEV_UnloadLibrary();
362
} else {
363
break;
364
}
365
}
366
}
367
368
if (!_this->udev_handle) {
369
result = false;
370
// Don't call SDL_SetError(): SDL_LoadObject already did.
371
}
372
}
373
374
return result;
375
}
376
377
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
378
{
379
const char *value;
380
char text[4096];
381
char *word;
382
int i;
383
unsigned long v;
384
385
SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));
386
value = _this->syms.udev_device_get_sysattr_value(pdev, attr);
387
if (!value) {
388
return;
389
}
390
391
SDL_strlcpy(text, value, sizeof(text));
392
i = 0;
393
while ((word = SDL_strrchr(text, ' ')) != NULL) {
394
v = SDL_strtoul(word + 1, NULL, 16);
395
if (i < bitmask_len) {
396
bitmask[i] = v;
397
}
398
++i;
399
*word = '\0';
400
}
401
v = SDL_strtoul(text, NULL, 16);
402
if (i < bitmask_len) {
403
bitmask[i] = v;
404
}
405
}
406
407
static int guess_device_class(struct udev_device *dev)
408
{
409
struct udev_device *pdev;
410
unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
411
unsigned long bitmask_ev[NBITS(EV_MAX)];
412
unsigned long bitmask_abs[NBITS(ABS_MAX)];
413
unsigned long bitmask_key[NBITS(KEY_MAX)];
414
unsigned long bitmask_rel[NBITS(REL_MAX)];
415
416
/* walk up the parental chain until we find the real input device; the
417
* argument is very likely a subdevice of this, like eventN */
418
pdev = dev;
419
while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
420
pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
421
}
422
if (!pdev) {
423
return 0;
424
}
425
426
get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));
427
get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
428
get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
429
get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
430
get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
431
432
return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],
433
&bitmask_ev[0],
434
&bitmask_abs[0],
435
&bitmask_key[0],
436
&bitmask_rel[0]);
437
}
438
439
static int device_class(struct udev_device *dev)
440
{
441
const char *subsystem;
442
const char *val = NULL;
443
int devclass = 0;
444
445
subsystem = _this->syms.udev_device_get_subsystem(dev);
446
if (!subsystem) {
447
return 0;
448
}
449
450
if (SDL_strcmp(subsystem, "sound") == 0) {
451
devclass = SDL_UDEV_DEVICE_SOUND;
452
} else if (SDL_strcmp(subsystem, "video4linux") == 0) {
453
val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
454
if (val && SDL_strcasestr(val, "capture")) {
455
devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
456
}
457
} else if (SDL_strcmp(subsystem, "input") == 0) {
458
// udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c
459
460
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
461
if (val && SDL_strcmp(val, "1") == 0) {
462
devclass |= SDL_UDEV_DEVICE_JOYSTICK;
463
}
464
465
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
466
if (val && SDL_strcmp(val, "1") == 0) {
467
devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;
468
}
469
470
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
471
if (val && SDL_strcmp(val, "1") == 0) {
472
devclass |= SDL_UDEV_DEVICE_MOUSE;
473
}
474
475
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
476
if (val && SDL_strcmp(val, "1") == 0) {
477
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
478
}
479
480
/* The undocumented rule is:
481
- All devices with keys get ID_INPUT_KEY
482
- From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
483
484
Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
485
*/
486
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
487
if (val && SDL_strcmp(val, "1") == 0) {
488
devclass |= SDL_UDEV_DEVICE_HAS_KEYS;
489
}
490
491
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
492
if (val && SDL_strcmp(val, "1") == 0) {
493
devclass |= SDL_UDEV_DEVICE_KEYBOARD;
494
}
495
496
if (devclass == 0) {
497
// Fall back to old style input classes
498
val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");
499
if (val) {
500
if (SDL_strcmp(val, "joystick") == 0) {
501
devclass = SDL_UDEV_DEVICE_JOYSTICK;
502
} else if (SDL_strcmp(val, "mouse") == 0) {
503
devclass = SDL_UDEV_DEVICE_MOUSE;
504
} else if (SDL_strcmp(val, "kbd") == 0) {
505
devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;
506
}
507
} else {
508
// We could be linked with libudev on a system that doesn't have udev running
509
devclass = guess_device_class(dev);
510
}
511
}
512
}
513
514
return devclass;
515
}
516
517
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
518
{
519
int devclass = 0;
520
const char *path;
521
SDL_UDEV_CallbackList *item;
522
523
path = _this->syms.udev_device_get_devnode(dev);
524
if (!path) {
525
return;
526
}
527
528
if (type == SDL_UDEV_DEVICEADDED) {
529
devclass = device_class(dev);
530
if (!devclass) {
531
return;
532
}
533
} else {
534
// The device has been removed, the class isn't available
535
}
536
537
// Process callbacks
538
for (item = _this->first; item; item = item->next) {
539
item->callback(type, devclass, path);
540
}
541
}
542
543
void SDL_UDEV_Poll(void)
544
{
545
struct udev_device *dev = NULL;
546
const char *action = NULL;
547
548
if (!_this) {
549
return;
550
}
551
552
while (SDL_UDEV_hotplug_update_available()) {
553
dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
554
if (!dev) {
555
break;
556
}
557
action = _this->syms.udev_device_get_action(dev);
558
559
if (action) {
560
if (SDL_strcmp(action, "add") == 0) {
561
device_event(SDL_UDEV_DEVICEADDED, dev);
562
} else if (SDL_strcmp(action, "remove") == 0) {
563
device_event(SDL_UDEV_DEVICEREMOVED, dev);
564
}
565
}
566
567
_this->syms.udev_device_unref(dev);
568
}
569
}
570
571
bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
572
{
573
SDL_UDEV_CallbackList *item;
574
item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));
575
if (!item) {
576
return false;
577
}
578
579
item->callback = cb;
580
581
if (!_this->last) {
582
_this->first = _this->last = item;
583
} else {
584
_this->last->next = item;
585
_this->last = item;
586
}
587
588
return true;
589
}
590
591
void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
592
{
593
SDL_UDEV_CallbackList *item;
594
SDL_UDEV_CallbackList *prev = NULL;
595
596
if (!_this) {
597
return;
598
}
599
600
for (item = _this->first; item; item = item->next) {
601
// found it, remove it.
602
if (item->callback == cb) {
603
if (prev) {
604
prev->next = item->next;
605
} else {
606
SDL_assert(_this->first == item);
607
_this->first = item->next;
608
}
609
if (item == _this->last) {
610
_this->last = prev;
611
}
612
SDL_free(item);
613
return;
614
}
615
prev = item;
616
}
617
}
618
619
const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)
620
{
621
if (!SDL_UDEV_Init()) {
622
SDL_SetError("Could not initialize UDEV");
623
return NULL;
624
}
625
626
return &_this->syms;
627
}
628
629
void SDL_UDEV_ReleaseUdevSyms(void)
630
{
631
SDL_UDEV_Quit();
632
}
633
634
#endif // SDL_USE_LIBUDEV
635
636