sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_udev.c (15746B)


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