Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/core/linux/SDL_udev.c
10279 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
void SDL_UDEV_UnloadLibrary(void)
281
{
282
if (!_this) {
283
return;
284
}
285
286
if (_this->udev_handle) {
287
SDL_UnloadObject(_this->udev_handle);
288
_this->udev_handle = NULL;
289
}
290
}
291
292
bool SDL_UDEV_LoadLibrary(void)
293
{
294
bool result = true;
295
296
if (!_this) {
297
return SDL_SetError("UDEV not initialized");
298
}
299
300
// See if there is a udev library already loaded
301
if (SDL_UDEV_load_syms()) {
302
return true;
303
}
304
305
#ifdef SDL_UDEV_DYNAMIC
306
// Check for the build environment's libudev first
307
if (!_this->udev_handle) {
308
_this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
309
if (_this->udev_handle) {
310
result = SDL_UDEV_load_syms();
311
if (!result) {
312
SDL_UDEV_UnloadLibrary();
313
}
314
}
315
}
316
#endif
317
318
if (!_this->udev_handle) {
319
for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
320
_this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
321
if (_this->udev_handle) {
322
result = SDL_UDEV_load_syms();
323
if (!result) {
324
SDL_UDEV_UnloadLibrary();
325
} else {
326
break;
327
}
328
}
329
}
330
331
if (!_this->udev_handle) {
332
result = false;
333
// Don't call SDL_SetError(): SDL_LoadObject already did.
334
}
335
}
336
337
return result;
338
}
339
340
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
341
{
342
const char *value;
343
char text[4096];
344
char *word;
345
int i;
346
unsigned long v;
347
348
SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));
349
value = _this->syms.udev_device_get_sysattr_value(pdev, attr);
350
if (!value) {
351
return;
352
}
353
354
SDL_strlcpy(text, value, sizeof(text));
355
i = 0;
356
while ((word = SDL_strrchr(text, ' ')) != NULL) {
357
v = SDL_strtoul(word + 1, NULL, 16);
358
if (i < bitmask_len) {
359
bitmask[i] = v;
360
}
361
++i;
362
*word = '\0';
363
}
364
v = SDL_strtoul(text, NULL, 16);
365
if (i < bitmask_len) {
366
bitmask[i] = v;
367
}
368
}
369
370
static int guess_device_class(struct udev_device *dev)
371
{
372
struct udev_device *pdev;
373
unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
374
unsigned long bitmask_ev[NBITS(EV_MAX)];
375
unsigned long bitmask_abs[NBITS(ABS_MAX)];
376
unsigned long bitmask_key[NBITS(KEY_MAX)];
377
unsigned long bitmask_rel[NBITS(REL_MAX)];
378
379
/* walk up the parental chain until we find the real input device; the
380
* argument is very likely a subdevice of this, like eventN */
381
pdev = dev;
382
while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
383
pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
384
}
385
if (!pdev) {
386
return 0;
387
}
388
389
get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));
390
get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
391
get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
392
get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
393
get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
394
395
return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],
396
&bitmask_ev[0],
397
&bitmask_abs[0],
398
&bitmask_key[0],
399
&bitmask_rel[0]);
400
}
401
402
static int device_class(struct udev_device *dev)
403
{
404
const char *subsystem;
405
const char *val = NULL;
406
int devclass = 0;
407
408
subsystem = _this->syms.udev_device_get_subsystem(dev);
409
if (!subsystem) {
410
return 0;
411
}
412
413
if (SDL_strcmp(subsystem, "sound") == 0) {
414
devclass = SDL_UDEV_DEVICE_SOUND;
415
} else if (SDL_strcmp(subsystem, "video4linux") == 0) {
416
val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
417
if (val && SDL_strcasestr(val, "capture")) {
418
devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
419
}
420
} else if (SDL_strcmp(subsystem, "input") == 0) {
421
// udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c
422
423
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
424
if (val && SDL_strcmp(val, "1") == 0) {
425
devclass |= SDL_UDEV_DEVICE_JOYSTICK;
426
}
427
428
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
429
if (val && SDL_strcmp(val, "1") == 0) {
430
devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;
431
}
432
433
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
434
if (val && SDL_strcmp(val, "1") == 0) {
435
devclass |= SDL_UDEV_DEVICE_MOUSE;
436
}
437
438
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
439
if (val && SDL_strcmp(val, "1") == 0) {
440
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
441
}
442
443
/* The undocumented rule is:
444
- All devices with keys get ID_INPUT_KEY
445
- From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
446
447
Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
448
*/
449
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
450
if (val && SDL_strcmp(val, "1") == 0) {
451
devclass |= SDL_UDEV_DEVICE_HAS_KEYS;
452
}
453
454
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
455
if (val && SDL_strcmp(val, "1") == 0) {
456
devclass |= SDL_UDEV_DEVICE_KEYBOARD;
457
}
458
459
if (devclass == 0) {
460
// Fall back to old style input classes
461
val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");
462
if (val) {
463
if (SDL_strcmp(val, "joystick") == 0) {
464
devclass = SDL_UDEV_DEVICE_JOYSTICK;
465
} else if (SDL_strcmp(val, "mouse") == 0) {
466
devclass = SDL_UDEV_DEVICE_MOUSE;
467
} else if (SDL_strcmp(val, "kbd") == 0) {
468
devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;
469
}
470
} else {
471
// We could be linked with libudev on a system that doesn't have udev running
472
devclass = guess_device_class(dev);
473
}
474
}
475
}
476
477
return devclass;
478
}
479
480
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
481
{
482
int devclass = 0;
483
const char *path;
484
SDL_UDEV_CallbackList *item;
485
486
path = _this->syms.udev_device_get_devnode(dev);
487
if (!path) {
488
return;
489
}
490
491
if (type == SDL_UDEV_DEVICEADDED) {
492
devclass = device_class(dev);
493
if (!devclass) {
494
return;
495
}
496
} else {
497
// The device has been removed, the class isn't available
498
}
499
500
// Process callbacks
501
for (item = _this->first; item; item = item->next) {
502
item->callback(type, devclass, path);
503
}
504
}
505
506
void SDL_UDEV_Poll(void)
507
{
508
struct udev_device *dev = NULL;
509
const char *action = NULL;
510
511
if (!_this) {
512
return;
513
}
514
515
while (SDL_UDEV_hotplug_update_available()) {
516
dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
517
if (!dev) {
518
break;
519
}
520
action = _this->syms.udev_device_get_action(dev);
521
522
if (action) {
523
if (SDL_strcmp(action, "add") == 0) {
524
device_event(SDL_UDEV_DEVICEADDED, dev);
525
} else if (SDL_strcmp(action, "remove") == 0) {
526
device_event(SDL_UDEV_DEVICEREMOVED, dev);
527
}
528
}
529
530
_this->syms.udev_device_unref(dev);
531
}
532
}
533
534
bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
535
{
536
SDL_UDEV_CallbackList *item;
537
item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));
538
if (!item) {
539
return false;
540
}
541
542
item->callback = cb;
543
544
if (!_this->last) {
545
_this->first = _this->last = item;
546
} else {
547
_this->last->next = item;
548
_this->last = item;
549
}
550
551
return true;
552
}
553
554
void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
555
{
556
SDL_UDEV_CallbackList *item;
557
SDL_UDEV_CallbackList *prev = NULL;
558
559
if (!_this) {
560
return;
561
}
562
563
for (item = _this->first; item; item = item->next) {
564
// found it, remove it.
565
if (item->callback == cb) {
566
if (prev) {
567
prev->next = item->next;
568
} else {
569
SDL_assert(_this->first == item);
570
_this->first = item->next;
571
}
572
if (item == _this->last) {
573
_this->last = prev;
574
}
575
SDL_free(item);
576
return;
577
}
578
prev = item;
579
}
580
}
581
582
const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)
583
{
584
if (!SDL_UDEV_Init()) {
585
SDL_SetError("Could not initialize UDEV");
586
return NULL;
587
}
588
589
return &_this->syms;
590
}
591
592
void SDL_UDEV_ReleaseUdevSyms(void)
593
{
594
SDL_UDEV_Quit();
595
}
596
597
#endif // SDL_USE_LIBUDEV
598
599