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