Mac and Linux SDL2 binary snapshots
Edward Rudd
2020-05-02 03f8528315fa46c95991a34f3325d7b33ae5538c
source/src/joystick/linux/SDL_sysjoystick.c
@@ -1,6 +1,6 @@
/*
  Simple DirectMedia Layer
  Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
  Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
@@ -34,11 +34,13 @@
#include <limits.h>             /* For the definition of PATH_MAX */
#include <sys/ioctl.h>
#include <unistd.h>
#include <dirent.h>
#include <linux/joystick.h>
#include "SDL_assert.h"
#include "SDL_joystick.h"
#include "SDL_endian.h"
#include "SDL_timer.h"
#include "../../events/SDL_events_c.h"
#include "../SDL_sysjoystick.h"
#include "../SDL_joystick_c.h"
@@ -56,9 +58,7 @@
static int MaybeAddDevice(const char *path);
#if SDL_USE_LIBUDEV
static int MaybeRemoveDevice(const char *path);
static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);
#endif /* SDL_USE_LIBUDEV */
/* A linked list of available joysticks */
typedef struct SDL_joylist_item
@@ -79,110 +79,53 @@
static SDL_joylist_item *SDL_joylist_tail = NULL;
static int numjoysticks = 0;
#if !SDL_USE_LIBUDEV
static Uint32 last_joy_detect_time;
static time_t last_input_dir_mtime;
#endif
#define test_bit(nr, addr) \
    (((1UL << ((nr) % (sizeof(long) * 8))) & ((addr)[(nr) / (sizeof(long) * 8)])) != 0)
#define NBITS(x) ((((x)-1)/(sizeof(long) * 8))+1)
static int
PrefixMatch(const char *a, const char *b)
{
    int matchlen = 0;
    while (*a && *b) {
        if (*a++ == *b++) {
            ++matchlen;
        } else {
            break;
        }
    }
    return matchlen;
}
static void
FixupDeviceInfoForMapping(int fd, struct input_id *inpid)
{
    if (inpid->vendor == 0x045e && inpid->product == 0x0b05 && inpid->version == 0x0903) {
        /* This is a Microsoft Xbox One Elite Series 2 controller */
        unsigned long keybit[NBITS(KEY_MAX)] = { 0 };
        /* The first version of the firmware duplicated all the inputs */
        if ((ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybit)), keybit) >= 0) &&
            test_bit(0x2c0, keybit)) {
            /* Change the version to 0x0902, so we can map it differently */
            inpid->version = 0x0902;
        }
    }
}
static int
IsJoystick(int fd, char *namebuf, const size_t namebuflen, SDL_JoystickGUID *guid)
{
    /* This list is taken from:
       https://raw.githubusercontent.com/denilsonsa/udev-joystick-blacklist/master/generate_rules.py
     */
    static Uint32 joystick_blacklist[] = {
        /* Microsoft Microsoft Wireless Optical Desktop® 2.10 */
        /* Microsoft Wireless Desktop - Comfort Edition */
        MAKE_VIDPID(0x045e, 0x009d),
        /* Microsoft Microsoft® Digital Media Pro Keyboard */
        /* Microsoft Corp. Digital Media Pro Keyboard */
        MAKE_VIDPID(0x045e, 0x00b0),
        /* Microsoft Microsoft® Digital Media Keyboard */
        /* Microsoft Corp. Digital Media Keyboard 1.0A */
        MAKE_VIDPID(0x045e, 0x00b4),
        /* Microsoft Microsoft® Digital Media Keyboard 3000 */
        MAKE_VIDPID(0x045e, 0x0730),
        /* Microsoft Microsoft® 2.4GHz Transceiver v6.0 */
        /* Microsoft Microsoft® 2.4GHz Transceiver v8.0 */
        /* Microsoft Corp. Nano Transceiver v1.0 for Bluetooth */
        /* Microsoft Wireless Mobile Mouse 1000 */
        /* Microsoft Wireless Desktop 3000 */
        MAKE_VIDPID(0x045e, 0x0745),
        /* Microsoft® SideWinder(TM) 2.4GHz Transceiver */
        MAKE_VIDPID(0x045e, 0x0748),
        /* Microsoft Corp. Wired Keyboard 600 */
        MAKE_VIDPID(0x045e, 0x0750),
        /* Microsoft Corp. Sidewinder X4 keyboard */
        MAKE_VIDPID(0x045e, 0x0768),
        /* Microsoft Corp. Arc Touch Mouse Transceiver */
        MAKE_VIDPID(0x045e, 0x0773),
        /* Microsoft® 2.4GHz Transceiver v9.0 */
        /* Microsoft® Nano Transceiver v2.1 */
        /* Microsoft Sculpt Ergonomic Keyboard (5KV-00001) */
        MAKE_VIDPID(0x045e, 0x07a5),
        /* Microsoft® Nano Transceiver v1.0 */
        /* Microsoft Wireless Keyboard 800 */
        MAKE_VIDPID(0x045e, 0x07b2),
        /* Microsoft® Nano Transceiver v2.0 */
        MAKE_VIDPID(0x045e, 0x0800),
        /* List of Wacom devices at: http://linuxwacom.sourceforge.net/wiki/index.php/Device_IDs */
        MAKE_VIDPID(0x056a, 0x0010),  /* Wacom ET-0405 Graphire */
        MAKE_VIDPID(0x056a, 0x0011),  /* Wacom ET-0405A Graphire2 (4x5) */
        MAKE_VIDPID(0x056a, 0x0012),  /* Wacom ET-0507A Graphire2 (5x7) */
        MAKE_VIDPID(0x056a, 0x0013),  /* Wacom CTE-430 Graphire3 (4x5) */
        MAKE_VIDPID(0x056a, 0x0014),  /* Wacom CTE-630 Graphire3 (6x8) */
        MAKE_VIDPID(0x056a, 0x0015),  /* Wacom CTE-440 Graphire4 (4x5) */
        MAKE_VIDPID(0x056a, 0x0016),  /* Wacom CTE-640 Graphire4 (6x8) */
        MAKE_VIDPID(0x056a, 0x0017),  /* Wacom CTE-450 Bamboo Fun (4x5) */
        MAKE_VIDPID(0x056a, 0x0016),  /* Wacom CTE-640 Graphire 4 6x8 */
        MAKE_VIDPID(0x056a, 0x0017),  /* Wacom CTE-450 Bamboo Fun 4x5 */
        MAKE_VIDPID(0x056a, 0x0018),  /* Wacom CTE-650 Bamboo Fun 6x8 */
        MAKE_VIDPID(0x056a, 0x0019),  /* Wacom CTE-631 Bamboo One */
        MAKE_VIDPID(0x056a, 0x00d1),  /* Wacom Bamboo Pen and Touch CTH-460 */
        MAKE_VIDPID(0x09da, 0x054f),  /* A4 Tech Co., G7 750 mouse */
        MAKE_VIDPID(0x09da, 0x3043),  /* A4 Tech Co., Ltd Bloody R8A Gaming Mouse */
        MAKE_VIDPID(0x09da, 0x31b5),  /* A4 Tech Co., Ltd Bloody TL80 Terminator Laser Gaming Mouse */
        MAKE_VIDPID(0x09da, 0x3997),  /* A4 Tech Co., Ltd Bloody RT7 Terminator Wireless */
        MAKE_VIDPID(0x09da, 0x3f8b),  /* A4 Tech Co., Ltd Bloody V8 mouse */
        MAKE_VIDPID(0x09da, 0x51f4),  /* Modecom MC-5006 Keyboard */
        MAKE_VIDPID(0x09da, 0x5589),  /* A4 Tech Co., Ltd Terminator TL9 Laser Gaming Mouse */
        MAKE_VIDPID(0x09da, 0x7b22),  /* A4 Tech Co., Ltd Bloody V5 */
        MAKE_VIDPID(0x09da, 0x7f2d),  /* A4 Tech Co., Ltd Bloody R3 mouse */
        MAKE_VIDPID(0x09da, 0x8090),  /* A4 Tech Co., Ltd X-718BK Oscar Optical Gaming Mouse */
        MAKE_VIDPID(0x09da, 0x9066),  /* A4 Tech Co., Sharkoon Fireglider Optical */
        MAKE_VIDPID(0x09da, 0x9090),  /* A4 Tech Co., Ltd XL-730K / XL-750BK / XL-755BK Laser Mouse */
        MAKE_VIDPID(0x09da, 0x90c0),  /* A4 Tech Co., Ltd X7 G800V keyboard */
        MAKE_VIDPID(0x09da, 0xf012),  /* A4 Tech Co., Ltd Bloody V7 mouse */
        MAKE_VIDPID(0x09da, 0xf32a),  /* A4 Tech Co., Ltd Bloody B540 keyboard */
        MAKE_VIDPID(0x09da, 0xf613),  /* A4 Tech Co., Ltd Bloody V2 mouse */
        MAKE_VIDPID(0x09da, 0xf624),  /* A4 Tech Co., Ltd Bloody B120 Keyboard */
        MAKE_VIDPID(0x1d57, 0xad03),  /* [T3] 2.4GHz and IR Air Mouse Remote Control */
        MAKE_VIDPID(0x1e7d, 0x2e4a),  /* Roccat Tyon Mouse */
        MAKE_VIDPID(0x20a0, 0x422d),  /* Winkeyless.kr Keyboards */
        MAKE_VIDPID(0x2516, 0x001f),  /* Cooler Master Storm Mizar Mouse */
        MAKE_VIDPID(0x2516, 0x0028),  /* Cooler Master Storm Alcor Mouse */
    };
    struct input_id inpid;
    int i;
    Uint32 id;
    Uint16 *guid16 = (Uint16 *)guid->data;
    const char *name;
    const char *spot;
#if !SDL_USE_LIBUDEV
    /* When udev is enabled we only get joystick devices here, so there's no need to test them */
@@ -202,28 +145,36 @@
    }
#endif
    if (ioctl(fd, EVIOCGNAME(namebuflen), namebuf) < 0) {
        return 0;
    }
    if (ioctl(fd, EVIOCGID, &inpid) < 0) {
        return 0;
    }
    name = SDL_GetCustomJoystickName(inpid.vendor, inpid.product);
    if (name) {
        SDL_strlcpy(namebuf, name, namebuflen);
    } else {
        if (ioctl(fd, EVIOCGNAME(namebuflen), namebuf) < 0) {
            return 0;
        }
        /* Remove duplicate manufacturer in the name */
        for (spot = namebuf + 1; *spot; ++spot) {
            int matchlen = PrefixMatch(namebuf, spot);
            if (matchlen > 0 && spot[matchlen - 1] == ' ') {
                SDL_memmove(namebuf, spot, SDL_strlen(spot)+1);
                break;
            }
        }
    }
#ifdef SDL_JOYSTICK_HIDAPI
    if (HIDAPI_IsDevicePresent(inpid.vendor, inpid.product, inpid.version)) {
    if (HIDAPI_IsDevicePresent(inpid.vendor, inpid.product, inpid.version, namebuf)) {
        /* The HIDAPI driver is taking care of this device */
        return 0;
    }
#endif
    /* Check the joystick blacklist */
    id = MAKE_VIDPID(inpid.vendor, inpid.product);
    for (i = 0; i < SDL_arraysize(joystick_blacklist); ++i) {
        if (id == joystick_blacklist[i]) {
            return 0;
        }
    }
    FixupDeviceInfoForMapping(fd, &inpid);
#ifdef DEBUG_JOYSTICK
    printf("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d\n", namebuf, inpid.bustype, inpid.vendor, inpid.product, inpid.version);
@@ -279,8 +230,6 @@
}
#endif /* SDL_USE_LIBUDEV */
/* !!! FIXME: I would love to dump this code and use libudev instead. */
static int
MaybeAddDevice(const char *path)
{
@@ -356,7 +305,6 @@
}
#if SDL_USE_LIBUDEV
/* !!! FIXME: I would love to dump this code and use libudev instead. */
static int
MaybeRemoveDevice(const char *path)
{
@@ -401,45 +349,46 @@
}
#endif
#if ! SDL_USE_LIBUDEV
static int
JoystickInitWithoutUdev(void)
static void
HandlePendingRemovals(void)
{
    int i;
    char path[PATH_MAX];
    SDL_joylist_item *prev = NULL;
    SDL_joylist_item *item = SDL_joylist;
    /* !!! FIXME: only finds sticks if they're called /dev/input/event[0..31] */
    /* !!! FIXME:  we could at least readdir() through /dev/input...? */
    /* !!! FIXME:  (or delete this and rely on libudev?) */
    for (i = 0; i < 32; i++) {
        SDL_snprintf(path, SDL_arraysize(path), "/dev/input/event%d", i);
        MaybeAddDevice(path);
    while (item != NULL) {
        if (item->hwdata && item->hwdata->gone) {
            item->hwdata->item = NULL;
            if (prev != NULL) {
                prev->next = item->next;
            } else {
                SDL_assert(SDL_joylist == item);
                SDL_joylist = item->next;
            }
            if (item == SDL_joylist_tail) {
                SDL_joylist_tail = prev;
            }
            /* Need to decrement the joystick count before we post the event */
            --numjoysticks;
            SDL_PrivateJoystickRemoved(item->device_instance);
            SDL_free(item->path);
            SDL_free(item->name);
            SDL_free(item);
            if (prev != NULL) {
                item = prev->next;
            } else {
                item = SDL_joylist;
            }
        } else {
            prev = item;
            item = item->next;
        }
    }
    return 0;
}
#endif
#if SDL_USE_LIBUDEV
static int
JoystickInitWithUdev(void)
{
    if (SDL_UDEV_Init() < 0) {
        return SDL_SetError("Could not initialize UDEV");
    }
    /* Set up the udev callback */
    if (SDL_UDEV_AddCallback(joystick_udev_callback) < 0) {
        SDL_UDEV_Quit();
        return SDL_SetError("Could not set up joystick <-> udev callback");
    }
    /* Force a scan to build the initial device list */
    SDL_UDEV_Scan();
    return 0;
}
#endif
static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickGUID guid, int *device_instance)
{
@@ -512,6 +461,49 @@
    }
}
static void
LINUX_JoystickDetect(void)
{
#if SDL_USE_LIBUDEV
    SDL_UDEV_Poll();
#else
    const Uint32 SDL_JOY_DETECT_INTERVAL_MS = 3000;  /* Update every 3 seconds */
    Uint32 now = SDL_GetTicks();
    if (!last_joy_detect_time || SDL_TICKS_PASSED(now, last_joy_detect_time + SDL_JOY_DETECT_INTERVAL_MS)) {
        struct stat sb;
        /* Opening input devices can generate synchronous device I/O, so avoid it if we can */
        if (stat("/dev/input", &sb) == 0 && sb.st_mtime != last_input_dir_mtime) {
            DIR *folder;
            struct dirent *dent;
            folder = opendir("/dev/input");
            if (folder) {
                while ((dent = readdir(folder))) {
                    int len = SDL_strlen(dent->d_name);
                    if (len > 5 && SDL_strncmp(dent->d_name, "event", 5) == 0) {
                        char path[PATH_MAX];
                        SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", dent->d_name);
                        MaybeAddDevice(path);
                    }
                }
                closedir(folder);
            }
            last_input_dir_mtime = sb.st_mtime;
        }
        last_joy_detect_time = now;
    }
#endif
    HandlePendingRemovals();
    SDL_UpdateSteamControllers();
}
static int
LINUX_JoystickInit(void)
{
@@ -535,26 +527,34 @@
                             SteamControllerDisconnectedCallback);
#if SDL_USE_LIBUDEV
    return JoystickInitWithUdev();
#else
    return JoystickInitWithoutUdev();
    if (SDL_UDEV_Init() < 0) {
        return SDL_SetError("Could not initialize UDEV");
    }
    /* Set up the udev callback */
    if (SDL_UDEV_AddCallback(joystick_udev_callback) < 0) {
        SDL_UDEV_Quit();
        return SDL_SetError("Could not set up joystick <-> udev callback");
    }
    /* Force a scan to build the initial device list */
    SDL_UDEV_Scan();
#else
    /* Force immediate joystick detection */
    last_joy_detect_time = 0;
    last_input_dir_mtime = 0;
    /* Report all devices currently present */
    LINUX_JoystickDetect();
#endif
    return 0;
}
static int
LINUX_JoystickGetCount(void)
{
    return numjoysticks;
}
static void
LINUX_JoystickDetect(void)
{
#if SDL_USE_LIBUDEV
    SDL_UDEV_Poll();
#endif
    SDL_UpdateSteamControllers();
}
static SDL_joylist_item *
@@ -586,6 +586,11 @@
LINUX_JoystickGetDevicePlayerIndex(int device_index)
{
    return -1;
}
static void
LINUX_JoystickSetDevicePlayerIndex(int device_index, int player_index)
{
}
static SDL_JoystickGUID
@@ -711,17 +716,18 @@
        for (i = ABS_HAT0X; i <= ABS_HAT3Y; i += 2) {
            if (test_bit(i, absbit) || test_bit(i + 1, absbit)) {
                struct input_absinfo absinfo;
                int hat_index = (i - ABS_HAT0X) / 2;
                if (ioctl(fd, EVIOCGABS(i), &absinfo) < 0) {
                    continue;
                }
#ifdef DEBUG_INPUT_EVENTS
                printf("Joystick has hat %d\n", (i - ABS_HAT0X) / 2);
                printf("Joystick has hat %d\n", hat_index);
                printf("Values = { %d, %d, %d, %d, %d }\n",
                       absinfo.value, absinfo.minimum, absinfo.maximum,
                       absinfo.fuzz, absinfo.flat);
#endif /* DEBUG_INPUT_EVENTS */
                ++joystick->nhats;
                joystick->hwdata->hats_indices[hat_index] = joystick->nhats++;
            }
        }
        if (test_bit(REL_X, relbit) || test_bit(REL_Y, relbit)) {
@@ -776,6 +782,7 @@
    joystick->hwdata->guid = item->guid;
    joystick->hwdata->effect.id = -1;
    joystick->hwdata->m_bSteamController = item->m_bSteamController;
    SDL_memset(joystick->hwdata->abs_map, 0xFF, sizeof(joystick->hwdata->abs_map));
    if (item->m_bSteamController) {
        joystick->hwdata->fd = -1;
@@ -816,7 +823,7 @@
}
static int
LINUX_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
LINUX_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
{
    struct input_event event;
@@ -824,7 +831,7 @@
        struct ff_effect *effect = &joystick->hwdata->effect;
        effect->type = FF_RUMBLE;
        effect->replay.length = SDL_min(duration_ms, 32767);
        effect->replay.length = SDL_MAX_RUMBLE_DURATION_MS;
        effect->u.rumble.strong_magnitude = low_frequency_rumble;
        effect->u.rumble.weak_magnitude = high_frequency_rumble;
    } else if (joystick->hwdata->ff_sine) {
@@ -833,7 +840,7 @@
        struct ff_effect *effect = &joystick->hwdata->effect;
        effect->type = FF_PERIODIC;
        effect->replay.length = SDL_min(duration_ms, 32767);
        effect->replay.length = SDL_MAX_RUMBLE_DURATION_MS;
        effect->u.periodic.waveform = FF_SINE;
        effect->u.periodic.magnitude = magnitude;
    } else {
@@ -841,7 +848,11 @@
    }
    if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) {
        return SDL_SetError("Couldn't update rumble effect: %s", strerror(errno));
        /* The kernel may have lost this effect, try to allocate a new one */
        joystick->hwdata->effect.id = -1;
        if (ioctl(joystick->hwdata->fd, EVIOCSFF, &joystick->hwdata->effect) < 0) {
            return SDL_SetError("Couldn't update rumble effect: %s", strerror(errno));
        }
    }
    event.type = EV_FF;
@@ -874,8 +885,7 @@
    if (value != the_hat->axis[axis]) {
        the_hat->axis[axis] = value;
        SDL_PrivateJoystickHat(stick, hat,
                               position_map[the_hat->
                                            axis[1]][the_hat->axis[0]]);
                               position_map[the_hat->axis[1]][the_hat->axis[0]]);
    }
}
@@ -919,36 +929,26 @@
PollAllValues(SDL_Joystick * joystick)
{
    struct input_absinfo absinfo;
    int a, b = 0;
    int i;
    /* Poll all axis */
    for (a = ABS_X; b < ABS_MAX; a++) {
        switch (a) {
        case ABS_HAT0X:
        case ABS_HAT0Y:
        case ABS_HAT1X:
        case ABS_HAT1Y:
        case ABS_HAT2X:
        case ABS_HAT2Y:
        case ABS_HAT3X:
        case ABS_HAT3Y:
            /* ingore hats */
            break;
        default:
            if (joystick->hwdata->abs_correct[b].used) {
                if (ioctl(joystick->hwdata->fd, EVIOCGABS(a), &absinfo) >= 0) {
                    absinfo.value = AxisCorrect(joystick, b, absinfo.value);
    for (i = ABS_X; i < ABS_MAX; i++) {
        if (i == ABS_HAT0X) {
            i = ABS_HAT3Y;
            continue;
        }
        if (joystick->hwdata->abs_correct[i].used) {
            if (ioctl(joystick->hwdata->fd, EVIOCGABS(i), &absinfo) >= 0) {
                absinfo.value = AxisCorrect(joystick, i, absinfo.value);
#ifdef DEBUG_INPUT_EVENTS
                    printf("Joystick : Re-read Axis %d (%d) val= %d\n",
                        joystick->hwdata->abs_map[b], a, absinfo.value);
                printf("Joystick : Re-read Axis %d (%d) val= %d\n",
                    joystick->hwdata->abs_map[i], i, absinfo.value);
#endif
                    SDL_PrivateJoystickAxis(joystick,
                            joystick->hwdata->abs_map[b],
                            absinfo.value);
                }
                SDL_PrivateJoystickAxis(joystick,
                        joystick->hwdata->abs_map[i],
                        absinfo.value);
            }
            b++;
        }
    }
}
@@ -986,14 +986,16 @@
                case ABS_HAT3X:
                case ABS_HAT3Y:
                    code -= ABS_HAT0X;
                    HandleHat(joystick, code / 2, code % 2, events[i].value);
                    HandleHat(joystick, joystick->hwdata->hats_indices[code / 2], code % 2, events[i].value);
                    break;
                default:
                    events[i].value =
                        AxisCorrect(joystick, code, events[i].value);
                    SDL_PrivateJoystickAxis(joystick,
                                            joystick->hwdata->abs_map[code],
                                            events[i].value);
                    if (joystick->hwdata->abs_map[code] != 0xFF) {
                        events[i].value =
                            AxisCorrect(joystick, code, events[i].value);
                        SDL_PrivateJoystickAxis(joystick,
                                                joystick->hwdata->abs_map[code],
                                                events[i].value);
                    }
                    break;
                }
                break;
@@ -1023,6 +1025,11 @@
                break;
            }
        }
    }
    if (errno == ENODEV) {
        /* We have to wait until the JoystickDetect callback to remove this */
        joystick->hwdata->gone = SDL_TRUE;
    }
}
@@ -1107,6 +1114,7 @@
    LINUX_JoystickDetect,
    LINUX_JoystickGetDeviceName,
    LINUX_JoystickGetDevicePlayerIndex,
    LINUX_JoystickSetDevicePlayerIndex,
    LINUX_JoystickGetDeviceGUID,
    LINUX_JoystickGetDeviceInstanceID,
    LINUX_JoystickOpen,