SDL  2.0
SDL_udev.c
Go to the documentation of this file.
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
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
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
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
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
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
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
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
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 
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
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  ; /* ID_INPUT_TOUCHSCREEN */
353  }
354 
355  if (test_bit(BTN_TRIGGER, bitmask_key) ||
356  test_bit(BTN_A, bitmask_key) ||
357  test_bit(BTN_1, bitmask_key) ||
358  test_bit(ABS_RX, bitmask_abs) ||
359  test_bit(ABS_RY, bitmask_abs) ||
360  test_bit(ABS_RZ, bitmask_abs) ||
361  test_bit(ABS_THROTTLE, bitmask_abs) ||
362  test_bit(ABS_RUDDER, bitmask_abs) ||
363  test_bit(ABS_WHEEL, bitmask_abs) ||
364  test_bit(ABS_GAS, bitmask_abs) ||
365  test_bit(ABS_BRAKE, bitmask_abs)) {
366  devclass |= SDL_UDEV_DEVICE_JOYSTICK; /* ID_INPUT_JOYSTICK */
367  }
368  }
369 
370  if (test_bit(EV_REL, bitmask_ev) &&
371  test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
372  test_bit(BTN_MOUSE, bitmask_key)) {
373  devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
374  }
375 
376  /* the first 32 bits are ESC, numbers, and Q to D; if we have any of
377  * those, consider it a keyboard device; do not test KEY_RESERVED, though */
378  keyboard_mask = 0xFFFFFFFE;
379  if ((bitmask_key[0] & keyboard_mask) != 0)
380  devclass |= SDL_UDEV_DEVICE_KEYBOARD; /* ID_INPUT_KEYBOARD */
381 
382  return devclass;
383 }
384 
385 static void
386 device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
387 {
388  const char *subsystem;
389  const char *val = NULL;
390  int devclass = 0;
391  const char *path;
392  SDL_UDEV_CallbackList *item;
393 
394  path = _this->udev_device_get_devnode(dev);
395  if (path == NULL) {
396  return;
397  }
398 
399  subsystem = _this->udev_device_get_subsystem(dev);
400  if (SDL_strcmp(subsystem, "sound") == 0) {
401  devclass = SDL_UDEV_DEVICE_SOUND;
402  } else if (SDL_strcmp(subsystem, "input") == 0) {
403  /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
404 
405  val = _this->udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
406  if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
407  devclass |= SDL_UDEV_DEVICE_JOYSTICK;
408  }
409 
410  val = _this->udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
411  if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
412  devclass |= SDL_UDEV_DEVICE_MOUSE;
413  }
414 
415  /* The undocumented rule is:
416  - All devices with keys get ID_INPUT_KEY
417  - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
418 
419  Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
420  */
421  val = _this->udev_device_get_property_value(dev, "ID_INPUT_KEY");
422  if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
423  devclass |= SDL_UDEV_DEVICE_KEYBOARD;
424  }
425 
426  if (devclass == 0) {
427  /* Fall back to old style input classes */
428  val = _this->udev_device_get_property_value(dev, "ID_CLASS");
429  if (val != NULL) {
430  if (SDL_strcmp(val, "joystick") == 0) {
431  devclass = SDL_UDEV_DEVICE_JOYSTICK;
432  } else if (SDL_strcmp(val, "mouse") == 0) {
433  devclass = SDL_UDEV_DEVICE_MOUSE;
434  } else if (SDL_strcmp(val, "kbd") == 0) {
435  devclass = SDL_UDEV_DEVICE_KEYBOARD;
436  } else {
437  return;
438  }
439  } else {
440  /* We could be linked with libudev on a system that doesn't have udev running */
441  devclass = guess_device_class(dev);
442  }
443  }
444  } else {
445  return;
446  }
447 
448  /* Process callbacks */
449  for (item = _this->first; item != NULL; item = item->next) {
450  item->callback(type, devclass, path);
451  }
452 }
453 
454 void
455 SDL_UDEV_Poll(void)
456 {
457  struct udev_device *dev = NULL;
458  const char *action = NULL;
459 
460  if (_this == NULL) {
461  return;
462  }
463 
464  while (SDL_UDEV_hotplug_update_available()) {
465  dev = _this->udev_monitor_receive_device(_this->udev_mon);
466  if (dev == NULL) {
467  break;
468  }
469  action = _this->udev_device_get_action(dev);
470 
471  if (SDL_strcmp(action, "add") == 0) {
472  /* Wait for the device to finish initialization */
473  SDL_Delay(100);
474 
475  device_event(SDL_UDEV_DEVICEADDED, dev);
476  } else if (SDL_strcmp(action, "remove") == 0) {
477  device_event(SDL_UDEV_DEVICEREMOVED, dev);
478  }
479 
480  _this->udev_device_unref(dev);
481  }
482 }
483 
484 int
485 SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
486 {
487  SDL_UDEV_CallbackList *item;
488  item = (SDL_UDEV_CallbackList *) SDL_calloc(1, sizeof (SDL_UDEV_CallbackList));
489  if (item == NULL) {
490  return SDL_OutOfMemory();
491  }
492 
493  item->callback = cb;
494 
495  if (_this->last == NULL) {
496  _this->first = _this->last = item;
497  } else {
498  _this->last->next = item;
499  _this->last = item;
500  }
501 
502  return 1;
503 }
504 
505 void
506 SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
507 {
508  SDL_UDEV_CallbackList *item;
509  SDL_UDEV_CallbackList *prev = NULL;
510 
511  for (item = _this->first; item != NULL; item = item->next) {
512  /* found it, remove it. */
513  if (item->callback == cb) {
514  if (prev != NULL) {
515  prev->next = item->next;
516  } else {
517  SDL_assert(_this->first == item);
518  _this->first = item->next;
519  }
520  if (item == _this->last) {
521  _this->last = prev;
522  }
523  SDL_free(item);
524  return;
525  }
526  prev = item;
527  }
528 
529 }
530 
531 
532 #endif /* SDL_USE_LIBUDEV */
#define SDL_strlcpy
#define SDL_LoadObject
#define SDL_UnloadObject
static SDL_VideoDevice * _this
Definition: SDL_video.c:114
SDL_bool
Definition: SDL_stdinc.h:126
const GLdouble * v
Definition: SDL_opengl.h:2057
SDL_bool retval
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1564
void * SDL_calloc(size_t nmemb, size_t size)
GLuint GLfloat * val
GLsizei const GLfloat * value
#define _THIS
void SDL_free(void *mem)
GLenum const void * addr
#define SDL_Delay
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:42
#define SDL_assert(condition)
Definition: SDL_assert.h:167
#define NULL
Definition: begin_code.h:143
#define SDL_OutOfMemory()
Definition: SDL_error.h:52
#define SDL_SetError
static char text[MAX_TEXT_LENGTH]
Definition: testime.c:34
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:93
GLsizei const GLchar *const * path
#define SDL_strcmp
void * SDL_LoadFunction(void *handle, const char *name)
#define SDL_strtoul
#define SDL_strrchr
#define SDL_memset