src/core/linux/SDL_udev.c
author Sam Lantinga <slouken@libsdl.org>
Thu, 04 Jun 2020 12:30:25 -0700
changeset 13898 aa9d7c43a982
parent 13422 fd6a12de91c7
permissions -rw-r--r--
Fixed exception if getManifestEnvironmentVariables() is called without a current SDL activity
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2020 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_assert.h"
    35 #include "SDL_loadso.h"
    36 #include "SDL_timer.h"
    37 #include "SDL_hints.h"
    38 #include "../unix/SDL_poll.h"
    39 
    40 static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
    41 
    42 #define _THIS SDL_UDEV_PrivateData *_this
    43 static _THIS = NULL;
    44 
    45 static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr);
    46 static int SDL_UDEV_load_syms(void);
    47 static SDL_bool SDL_UDEV_hotplug_update_available(void);
    48 static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
    49 
    50 static SDL_bool
    51 SDL_UDEV_load_sym(const char *fn, void **addr)
    52 {
    53     *addr = SDL_LoadFunction(_this->udev_handle, fn);
    54     if (*addr == NULL) {
    55         /* Don't call SDL_SetError(): SDL_LoadFunction already did. */
    56         return SDL_FALSE;
    57     }
    58 
    59     return SDL_TRUE;
    60 }
    61 
    62 static int
    63 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)) return -1
    68 
    69     SDL_UDEV_SYM(udev_device_get_action);
    70     SDL_UDEV_SYM(udev_device_get_devnode);
    71     SDL_UDEV_SYM(udev_device_get_subsystem);
    72     SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
    73     SDL_UDEV_SYM(udev_device_get_property_value);
    74     SDL_UDEV_SYM(udev_device_get_sysattr_value);
    75     SDL_UDEV_SYM(udev_device_new_from_syspath);
    76     SDL_UDEV_SYM(udev_device_unref);
    77     SDL_UDEV_SYM(udev_enumerate_add_match_property);
    78     SDL_UDEV_SYM(udev_enumerate_add_match_subsystem);
    79     SDL_UDEV_SYM(udev_enumerate_get_list_entry);
    80     SDL_UDEV_SYM(udev_enumerate_new);
    81     SDL_UDEV_SYM(udev_enumerate_scan_devices);
    82     SDL_UDEV_SYM(udev_enumerate_unref);
    83     SDL_UDEV_SYM(udev_list_entry_get_name);
    84     SDL_UDEV_SYM(udev_list_entry_get_next);
    85     SDL_UDEV_SYM(udev_monitor_enable_receiving);
    86     SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype);
    87     SDL_UDEV_SYM(udev_monitor_get_fd);
    88     SDL_UDEV_SYM(udev_monitor_new_from_netlink);
    89     SDL_UDEV_SYM(udev_monitor_receive_device);
    90     SDL_UDEV_SYM(udev_monitor_unref);
    91     SDL_UDEV_SYM(udev_new);
    92     SDL_UDEV_SYM(udev_unref);
    93     SDL_UDEV_SYM(udev_device_new_from_devnum);
    94     SDL_UDEV_SYM(udev_device_get_devnum);
    95     #undef SDL_UDEV_SYM
    96 
    97     return 0;
    98 }
    99 
   100 static SDL_bool
   101 SDL_UDEV_hotplug_update_available(void)
   102 {
   103     if (_this->udev_mon != NULL) {
   104         const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon);
   105         if (SDL_IOReady(fd, SDL_FALSE, 0)) {
   106             return SDL_TRUE;
   107         }
   108     }
   109     return SDL_FALSE;
   110 }
   111 
   112 
   113 int
   114 SDL_UDEV_Init(void)
   115 {
   116     int retval = 0;
   117     
   118     if (_this == NULL) {
   119         _this = (SDL_UDEV_PrivateData *) SDL_calloc(1, sizeof(*_this));
   120         if(_this == NULL) {
   121             return SDL_OutOfMemory();
   122         }
   123         
   124         retval = SDL_UDEV_LoadLibrary();
   125         if (retval < 0) {
   126             SDL_UDEV_Quit();
   127             return retval;
   128         }
   129         
   130         /* Set up udev monitoring 
   131          * Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
   132          */
   133         
   134         _this->udev = _this->syms.udev_new();
   135         if (_this->udev == NULL) {
   136             SDL_UDEV_Quit();
   137             return SDL_SetError("udev_new() failed");
   138         }
   139 
   140         _this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev");
   141         if (_this->udev_mon == NULL) {
   142             SDL_UDEV_Quit();
   143             return SDL_SetError("udev_monitor_new_from_netlink() failed");
   144         }
   145         
   146         _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
   147         _this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
   148         _this->syms.udev_monitor_enable_receiving(_this->udev_mon);
   149         
   150         /* Do an initial scan of existing devices */
   151         SDL_UDEV_Scan();
   152 
   153     }
   154     
   155     _this->ref_count += 1;
   156     
   157     return retval;
   158 }
   159 
   160 void
   161 SDL_UDEV_Quit(void)
   162 {
   163     SDL_UDEV_CallbackList *item;
   164     
   165     if (_this == NULL) {
   166         return;
   167     }
   168     
   169     _this->ref_count -= 1;
   170     
   171     if (_this->ref_count < 1) {
   172         
   173         if (_this->udev_mon != NULL) {
   174             _this->syms.udev_monitor_unref(_this->udev_mon);
   175             _this->udev_mon = NULL;
   176         }
   177         if (_this->udev != NULL) {
   178             _this->syms.udev_unref(_this->udev);
   179             _this->udev = NULL;
   180         }
   181         
   182         /* Remove existing devices */
   183         while (_this->first != NULL) {
   184             item = _this->first;
   185             _this->first = _this->first->next;
   186             SDL_free(item);
   187         }
   188         
   189         SDL_UDEV_UnloadLibrary();
   190         SDL_free(_this);
   191         _this = NULL;
   192     }
   193 }
   194 
   195 void
   196 SDL_UDEV_Scan(void)
   197 {
   198     struct udev_enumerate *enumerate = NULL;
   199     struct udev_list_entry *devs = NULL;
   200     struct udev_list_entry *item = NULL;  
   201     
   202     if (_this == NULL) {
   203         return;
   204     }
   205    
   206     enumerate = _this->syms.udev_enumerate_new(_this->udev);
   207     if (enumerate == NULL) {
   208         SDL_UDEV_Quit();
   209         SDL_SetError("udev_enumerate_new() failed");
   210         return;
   211     }
   212     
   213     _this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
   214     _this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
   215     
   216     _this->syms.udev_enumerate_scan_devices(enumerate);
   217     devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
   218     for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {
   219         const char *path = _this->syms.udev_list_entry_get_name(item);
   220         struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
   221         if (dev != NULL) {
   222             device_event(SDL_UDEV_DEVICEADDED, dev);
   223             _this->syms.udev_device_unref(dev);
   224         }
   225     }
   226 
   227     _this->syms.udev_enumerate_unref(enumerate);
   228 }
   229 
   230 
   231 void
   232 SDL_UDEV_UnloadLibrary(void)
   233 {
   234     if (_this == NULL) {
   235         return;
   236     }
   237     
   238     if (_this->udev_handle != NULL) {
   239         SDL_UnloadObject(_this->udev_handle);
   240         _this->udev_handle = NULL;
   241     }
   242 }
   243 
   244 int
   245 SDL_UDEV_LoadLibrary(void)
   246 {
   247     int retval = 0, i;
   248     
   249     if (_this == NULL) {
   250         return SDL_SetError("UDEV not initialized");
   251     }
   252  
   253     /* See if there is a udev library already loaded */
   254     if (SDL_UDEV_load_syms() == 0) {
   255         return 0;
   256     }
   257 
   258 #ifdef SDL_UDEV_DYNAMIC
   259     /* Check for the build environment's libudev first */
   260     if (_this->udev_handle == NULL) {
   261         _this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
   262         if (_this->udev_handle != NULL) {
   263             retval = SDL_UDEV_load_syms();
   264             if (retval < 0) {
   265                 SDL_UDEV_UnloadLibrary();
   266             }
   267         }
   268     }
   269 #endif
   270 
   271     if (_this->udev_handle == NULL) {
   272         for( i = 0 ; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
   273             _this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
   274             if (_this->udev_handle != NULL) {
   275                 retval = SDL_UDEV_load_syms();
   276                 if (retval < 0) {
   277                     SDL_UDEV_UnloadLibrary();
   278                 }
   279                 else {
   280                     break;
   281                 }
   282             }
   283         }
   284         
   285         if (_this->udev_handle == NULL) {
   286             retval = -1;
   287             /* Don't call SDL_SetError(): SDL_LoadObject already did. */
   288         }
   289     }
   290 
   291     return retval;
   292 }
   293 
   294 #define BITS_PER_LONG           (sizeof(unsigned long) * 8)
   295 #define NBITS(x)                ((((x)-1)/BITS_PER_LONG)+1)
   296 #define OFF(x)                  ((x)%BITS_PER_LONG)
   297 #define LONG(x)                 ((x)/BITS_PER_LONG)
   298 #define test_bit(bit, array)    ((array[LONG(bit)] >> OFF(bit)) & 1)
   299 
   300 static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
   301 {
   302     const char *value;
   303     char text[4096];
   304     char *word;
   305     int i;
   306     unsigned long v;
   307 
   308     SDL_memset(bitmask, 0, bitmask_len*sizeof(*bitmask));
   309     value = _this->syms.udev_device_get_sysattr_value(pdev, attr);
   310     if (!value) {
   311         return;
   312     }
   313 
   314     SDL_strlcpy(text, value, sizeof(text));
   315     i = 0;
   316     while ((word = SDL_strrchr(text, ' ')) != NULL) {
   317         v = SDL_strtoul(word+1, NULL, 16);
   318         if (i < bitmask_len) {
   319             bitmask[i] = v;
   320         }
   321         ++i;
   322         *word = '\0';
   323     }
   324     v = SDL_strtoul(text, NULL, 16);
   325     if (i < bitmask_len) {
   326         bitmask[i] = v;
   327     }
   328 }
   329 
   330 static int
   331 guess_device_class(struct udev_device *dev)
   332 {
   333     int devclass = 0;
   334     struct udev_device *pdev;
   335     unsigned long bitmask_ev[NBITS(EV_MAX)];
   336     unsigned long bitmask_abs[NBITS(ABS_MAX)];
   337     unsigned long bitmask_key[NBITS(KEY_MAX)];
   338     unsigned long bitmask_rel[NBITS(REL_MAX)];
   339     unsigned long keyboard_mask;
   340 
   341     /* walk up the parental chain until we find the real input device; the
   342      * argument is very likely a subdevice of this, like eventN */
   343     pdev = dev;
   344     while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
   345         pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
   346     }
   347     if (!pdev) {
   348         return 0;
   349     }
   350 
   351     get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
   352     get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
   353     get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
   354     get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
   355 
   356     if (test_bit(EV_ABS, bitmask_ev) &&
   357         test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs)) {
   358         if (test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key)) {
   359             ; /* ID_INPUT_TABLET */
   360         } else if (test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key)) {
   361             ; /* ID_INPUT_TOUCHPAD */
   362         } else if (test_bit(BTN_MOUSE, bitmask_key)) {
   363             devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
   364         } else if (test_bit(BTN_TOUCH, bitmask_key)) {
   365             /* TODO: better determining between touchscreen and multitouch touchpad,
   366                see https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-input_id.c */
   367             devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; /* ID_INPUT_TOUCHSCREEN */
   368         }
   369 
   370         if (test_bit(BTN_TRIGGER, bitmask_key) ||
   371             test_bit(BTN_A, bitmask_key) ||
   372             test_bit(BTN_1, bitmask_key) ||
   373             test_bit(ABS_RX, bitmask_abs) ||
   374             test_bit(ABS_RY, bitmask_abs) ||
   375             test_bit(ABS_RZ, bitmask_abs) ||
   376             test_bit(ABS_THROTTLE, bitmask_abs) ||
   377             test_bit(ABS_RUDDER, bitmask_abs) ||
   378             test_bit(ABS_WHEEL, bitmask_abs) ||
   379             test_bit(ABS_GAS, bitmask_abs) ||
   380             test_bit(ABS_BRAKE, bitmask_abs)) {
   381             devclass |= SDL_UDEV_DEVICE_JOYSTICK; /* ID_INPUT_JOYSTICK */
   382         }
   383     }
   384 
   385     if (test_bit(EV_REL, bitmask_ev) &&
   386         test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
   387         test_bit(BTN_MOUSE, bitmask_key)) {
   388         devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
   389     }
   390 
   391     /* the first 32 bits are ESC, numbers, and Q to D; if we have any of
   392      * those, consider it a keyboard device; do not test KEY_RESERVED, though */
   393     keyboard_mask = 0xFFFFFFFE;
   394     if ((bitmask_key[0] & keyboard_mask) != 0)
   395         devclass |= SDL_UDEV_DEVICE_KEYBOARD; /* ID_INPUT_KEYBOARD */
   396 
   397     return devclass;
   398 }
   399 
   400 static void 
   401 device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) 
   402 {
   403     const char *subsystem;
   404     const char *val = NULL;
   405     int devclass = 0;
   406     const char *path;
   407     SDL_UDEV_CallbackList *item;
   408     
   409     path = _this->syms.udev_device_get_devnode(dev);
   410     if (path == NULL) {
   411         return;
   412     }
   413     
   414     subsystem = _this->syms.udev_device_get_subsystem(dev);
   415     if (SDL_strcmp(subsystem, "sound") == 0) {
   416         devclass = SDL_UDEV_DEVICE_SOUND;
   417     } else if (SDL_strcmp(subsystem, "input") == 0) {
   418         /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
   419         
   420         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
   421         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
   422             devclass |= SDL_UDEV_DEVICE_JOYSTICK;
   423         }
   424 
   425         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
   426         if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE) &&
   427             val != NULL && SDL_strcmp(val, "1") == 0 ) {
   428             devclass |= SDL_UDEV_DEVICE_JOYSTICK;
   429 	}
   430         
   431         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
   432         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
   433             devclass |= SDL_UDEV_DEVICE_MOUSE;
   434         }
   435         
   436         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
   437         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
   438             devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
   439         }
   440 
   441         /* The undocumented rule is:
   442            - All devices with keys get ID_INPUT_KEY
   443            - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
   444            
   445            Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
   446         */
   447         val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
   448         if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
   449             devclass |= SDL_UDEV_DEVICE_KEYBOARD;
   450         }
   451 
   452         if (devclass == 0) {
   453             /* Fall back to old style input classes */
   454             val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");
   455             if (val != NULL) {
   456                 if (SDL_strcmp(val, "joystick") == 0) {
   457                     devclass = SDL_UDEV_DEVICE_JOYSTICK;
   458                 } else if (SDL_strcmp(val, "mouse") == 0) {
   459                     devclass = SDL_UDEV_DEVICE_MOUSE;
   460                 } else if (SDL_strcmp(val, "kbd") == 0) {
   461                     devclass = SDL_UDEV_DEVICE_KEYBOARD;
   462                 } else {
   463                     return;
   464                 }
   465             } else {
   466                 /* We could be linked with libudev on a system that doesn't have udev running */
   467                 devclass = guess_device_class(dev);
   468             }
   469         }
   470     } else {
   471         return;
   472     }
   473     
   474     /* Process callbacks */
   475     for (item = _this->first; item != NULL; item = item->next) {
   476         item->callback(type, devclass, path);
   477     }
   478 }
   479 
   480 void 
   481 SDL_UDEV_Poll(void)
   482 {
   483     struct udev_device *dev = NULL;
   484     const char *action = NULL;
   485 
   486     if (_this == NULL) {
   487         return;
   488     }
   489 
   490     while (SDL_UDEV_hotplug_update_available()) {
   491         dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
   492         if (dev == NULL) {
   493             break;
   494         }
   495         action = _this->syms.udev_device_get_action(dev);
   496 
   497         if (SDL_strcmp(action, "add") == 0) {
   498             /* Wait for the device to finish initialization */
   499             SDL_Delay(100);
   500 
   501             device_event(SDL_UDEV_DEVICEADDED, dev);
   502         } else if (SDL_strcmp(action, "remove") == 0) {
   503             device_event(SDL_UDEV_DEVICEREMOVED, dev);
   504         }
   505         
   506         _this->syms.udev_device_unref(dev);
   507     }
   508 }
   509 
   510 int 
   511 SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
   512 {
   513     SDL_UDEV_CallbackList *item;
   514     item = (SDL_UDEV_CallbackList *) SDL_calloc(1, sizeof (SDL_UDEV_CallbackList));
   515     if (item == NULL) {
   516         return SDL_OutOfMemory();
   517     }
   518     
   519     item->callback = cb;
   520 
   521     if (_this->last == NULL) {
   522         _this->first = _this->last = item;
   523     } else {
   524         _this->last->next = item;
   525         _this->last = item;
   526     }
   527     
   528     return 1;
   529 }
   530 
   531 void 
   532 SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
   533 {
   534     SDL_UDEV_CallbackList *item;
   535     SDL_UDEV_CallbackList *prev = NULL;
   536 
   537     for (item = _this->first; item != NULL; item = item->next) {
   538         /* found it, remove it. */
   539         if (item->callback == cb) {
   540             if (prev != NULL) {
   541                 prev->next = item->next;
   542             } else {
   543                 SDL_assert(_this->first == item);
   544                 _this->first = item->next;
   545             }
   546             if (item == _this->last) {
   547                 _this->last = prev;
   548             }
   549             SDL_free(item);
   550             return;
   551         }
   552         prev = item;
   553     }
   554     
   555 }
   556 
   557 const SDL_UDEV_Symbols *
   558 SDL_UDEV_GetUdevSyms(void)
   559 {
   560     if (SDL_UDEV_Init() < 0) {
   561         SDL_SetError("Could not initialize UDEV");
   562         return NULL;
   563     }
   564 
   565     return &_this->syms;
   566 }
   567 
   568 void
   569 SDL_UDEV_ReleaseUdevSyms(void)
   570 {
   571     SDL_UDEV_Quit();
   572 }
   573 
   574 #endif /* SDL_USE_LIBUDEV */
   575 
   576 /* vi: set ts=4 sw=4 expandtab: */