src/core/linux/SDL_udev.c
author Sam Lantinga <slouken@libsdl.org>
Mon, 25 May 2015 16:22:09 -0700
changeset 9616 eb2a8d9cfb74
parent 9454 bdc0c1dda0a4
child 9619 b94b6d0bff0f
permissions -rw-r--r--
Wait for devices to finish initializing when inserted, before using them. Fixes hotplug issue with XBox 360 game controller.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2014 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 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 */