cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_udev.c (16261B)


      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
     36static char* SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
     37
     38#define _THIS SDL_UDEV_PrivateData *_this
     39static _THIS = NULL;
     40
     41static SDL_bool SDL_UDEV_load_sym(const char *fn, void **addr);
     42static int SDL_UDEV_load_syms(void);
     43static SDL_bool SDL_UDEV_hotplug_update_available(void);
     44static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
     45
     46static SDL_bool
     47SDL_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
     58static int
     59SDL_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
     96static SDL_bool
     97SDL_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
    116int
    117SDL_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
    163void
    164SDL_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
    198void
    199SDL_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
    234void
    235SDL_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
    247int
    248SDL_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
    287static 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
    317static int
    318guess_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        } else if (test_bit(BTN_TRIGGER, bitmask_key) ||
    354                 test_bit(BTN_A, bitmask_key) ||
    355                 test_bit(BTN_1, bitmask_key) ||
    356                 test_bit(ABS_RX, bitmask_abs) ||
    357                 test_bit(ABS_RY, bitmask_abs) ||
    358                 test_bit(ABS_RZ, bitmask_abs) ||
    359                 test_bit(ABS_THROTTLE, bitmask_abs) ||
    360                 test_bit(ABS_RUDDER, bitmask_abs) ||
    361                 test_bit(ABS_WHEEL, bitmask_abs) ||
    362                 test_bit(ABS_GAS, bitmask_abs) ||
    363                 test_bit(ABS_BRAKE, bitmask_abs)) {
    364            devclass |= SDL_UDEV_DEVICE_JOYSTICK; /* ID_INPUT_JOYSTICK */
    365        }
    366    }
    367
    368    if (test_bit(EV_REL, bitmask_ev) &&
    369        test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
    370        test_bit(BTN_MOUSE, bitmask_key)) {
    371        devclass |= SDL_UDEV_DEVICE_MOUSE; /* ID_INPUT_MOUSE */
    372    }
    373
    374    /* the first 32 bits are ESC, numbers, and Q to D; if we have all of
    375     * those, consider it a full keyboard; do not test KEY_RESERVED, though */
    376    keyboard_mask = 0xFFFFFFFE;
    377    if ((bitmask_key[0] & keyboard_mask) == keyboard_mask)
    378        devclass |= SDL_UDEV_DEVICE_KEYBOARD; /* ID_INPUT_KEYBOARD */
    379
    380    return devclass;
    381}
    382
    383static void 
    384device_event(SDL_UDEV_deviceevent type, struct udev_device *dev) 
    385{
    386    const char *subsystem;
    387    const char *val = NULL;
    388    int devclass = 0;
    389    const char *path;
    390    SDL_UDEV_CallbackList *item;
    391    
    392    path = _this->udev_device_get_devnode(dev);
    393    if (path == NULL) {
    394        return;
    395    }
    396    
    397    subsystem = _this->udev_device_get_subsystem(dev);
    398    if (SDL_strcmp(subsystem, "sound") == 0) {
    399        devclass = SDL_UDEV_DEVICE_SOUND;
    400    } else if (SDL_strcmp(subsystem, "input") == 0) {
    401        /* udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c */
    402        
    403        val = _this->udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
    404        if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
    405            devclass |= SDL_UDEV_DEVICE_JOYSTICK;
    406        }
    407        
    408        val = _this->udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
    409        if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
    410            devclass |= SDL_UDEV_DEVICE_MOUSE;
    411        }
    412
    413        /* The undocumented rule is:
    414           - All devices with keys get ID_INPUT_KEY
    415           - From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
    416           
    417           Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
    418        */
    419        val = _this->udev_device_get_property_value(dev, "ID_INPUT_KEY");
    420        if (val != NULL && SDL_strcmp(val, "1") == 0 ) {
    421            devclass |= SDL_UDEV_DEVICE_KEYBOARD;
    422        }
    423
    424        if (devclass == 0) {
    425            /* Fall back to old style input classes */
    426            val = _this->udev_device_get_property_value(dev, "ID_CLASS");
    427            if (val != NULL) {
    428                if (SDL_strcmp(val, "joystick") == 0) {
    429                    devclass = SDL_UDEV_DEVICE_JOYSTICK;
    430                } else if (SDL_strcmp(val, "mouse") == 0) {
    431                    devclass = SDL_UDEV_DEVICE_MOUSE;
    432                } else if (SDL_strcmp(val, "kbd") == 0) {
    433                    devclass = SDL_UDEV_DEVICE_KEYBOARD;
    434                } else {
    435                    return;
    436                }
    437            } else {
    438                /* We could be linked with libudev on a system that doesn't have udev running */
    439                devclass = guess_device_class(dev);
    440            }
    441        }
    442    } else {
    443        return;
    444    }
    445    
    446    /* Process callbacks */
    447    for (item = _this->first; item != NULL; item = item->next) {
    448        item->callback(type, devclass, path);
    449    }
    450}
    451
    452void 
    453SDL_UDEV_Poll(void)
    454{
    455    struct udev_device *dev = NULL;
    456    const char *action = NULL;
    457
    458    if (_this == NULL) {
    459        return;
    460    }
    461
    462    while (SDL_UDEV_hotplug_update_available()) {
    463        dev = _this->udev_monitor_receive_device(_this->udev_mon);
    464        if (dev == NULL) {
    465            break;
    466        }
    467        action = _this->udev_device_get_action(dev);
    468
    469        if (SDL_strcmp(action, "add") == 0) {
    470            device_event(SDL_UDEV_DEVICEADDED, dev);
    471        } else if (SDL_strcmp(action, "remove") == 0) {
    472            device_event(SDL_UDEV_DEVICEREMOVED, dev);
    473        }
    474        
    475        _this->udev_device_unref(dev);
    476    }
    477}
    478
    479int 
    480SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
    481{
    482    SDL_UDEV_CallbackList *item;
    483    item = (SDL_UDEV_CallbackList *) SDL_calloc(1, sizeof (SDL_UDEV_CallbackList));
    484    if (item == NULL) {
    485        return SDL_OutOfMemory();
    486    }
    487    
    488    item->callback = cb;
    489
    490    if (_this->last == NULL) {
    491        _this->first = _this->last = item;
    492    } else {
    493        _this->last->next = item;
    494        _this->last = item;
    495    }
    496    
    497    return 1;
    498}
    499
    500void 
    501SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
    502{
    503    SDL_UDEV_CallbackList *item;
    504    SDL_UDEV_CallbackList *prev = NULL;
    505
    506    for (item = _this->first; item != NULL; item = item->next) {
    507        /* found it, remove it. */
    508        if (item->callback == cb) {
    509            if (prev != NULL) {
    510                prev->next = item->next;
    511            } else {
    512                SDL_assert(_this->first == item);
    513                _this->first = item->next;
    514            }
    515            if (item == _this->last) {
    516                _this->last = prev;
    517            }
    518            SDL_free(item);
    519            return;
    520        }
    521        prev = item;
    522    }
    523    
    524}
    525
    526
    527#endif /* SDL_USE_LIBUDEV */