From 1c78b0800759174abe1bac01ce5b9cd788a6a372 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 10 Nov 2021 20:02:25 -0800 Subject: [PATCH] Added support for /dev/input/js* on Linux Added the hint SDL_HINT_LINUX_JOYSTICK_CLASSIC to control whether /dev/input/js* or /dev/input/event* are used as joystick devices Added the hint SDL_HINT_JOYSTICK_DEVICE to allow the user to specify devices t hat will be opened in addition to the normal joystick detection Fixes https://github.com/libsdl-org/SDL/issues/1314 Fixes https://github.com/libsdl-org/SDL/issues/1727 Fixes https://github.com/libsdl-org/SDL/issues/1981 Closes https://github.com/libsdl-org/SDL/pull/4727 --- WhatsNew.txt | 4 + include/SDL_hints.h | 18 ++ src/joystick/linux/SDL_sysjoystick.c | 246 +++++++++++++++++++------ src/joystick/linux/SDL_sysjoystick_c.h | 5 + 4 files changed, 213 insertions(+), 60 deletions(-) diff --git a/WhatsNew.txt b/WhatsNew.txt index 4822970b3..e6076c552 100644 --- a/WhatsNew.txt +++ b/WhatsNew.txt @@ -5,6 +5,10 @@ This is a list of major changes in SDL's version history. 2.0.18: --------------------------------------------------------------------------- +Linux: +* Added the hint SDL_HINT_LINUX_JOYSTICK_CLASSIC to control whether /dev/input/js* or /dev/input/event* are used as joystick devices +* Added the hint SDL_HINT_JOYSTICK_DEVICE to allow the user to specify devices that will be opened in addition to the normal joystick detection + Android: * Added support for audio output and capture using AAudio on Android 8.1 and newer diff --git a/include/SDL_hints.h b/include/SDL_hints.h index db286a24c..d7782591b 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -816,6 +816,24 @@ extern "C" { */ #define SDL_HINT_KMSDRM_REQUIRE_DRM_MASTER "SDL_KMSDRM_REQUIRE_DRM_MASTER" + /** + * \brief A comma separated list of devices to open as joysticks + * + * This variable is currently only used by the Linux joystick driver. + */ +#define SDL_HINT_JOYSTICK_DEVICE "SDL_JOYSTICK_DEVICE" + + /** + * \brief A variable controlling whether to use the classic /dev/input/js* joystick interface or the newer /dev/input/event* joystick interface on Linux + * + * This variable can be set to the following values: + * "0" - Use /dev/input/event* + * "1" - Use /dev/input/js* + * + * By default the /dev/input/event* interfaces are used + */ +#define SDL_HINT_LINUX_JOYSTICK_CLASSIC "SDL_LINUX_JOYSTICK_CLASSIC" + /** * \brief A variable controlling whether joysticks on Linux adhere to their HID-defined deadzones or return unfiltered values. * diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index aef8e332e..52859d576 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -85,6 +85,7 @@ typedef enum static EnumerationMethod enumeration_method = ENUMERATION_UNSET; +static SDL_bool IsJoystickDeviceNode(const char *node); static int MaybeAddDevice(const char *path); static int MaybeRemoveDevice(const char *path); @@ -105,6 +106,7 @@ typedef struct SDL_joylist_item SDL_GamepadMapping *mapping; } SDL_joylist_item; +static SDL_bool SDL_classic_joysticks = SDL_TRUE; static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; @@ -183,17 +185,22 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid) char *name; char product_string[128]; - /* When udev is enabled we only get joystick devices here, so there's no need to test them */ - if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) { - return 0; - } + if (ioctl(fd, JSIOCGNAME(sizeof(product_string)), product_string) >= 0) { + inpid.vendor = 0; + inpid.product = 0; + } else { + /* When udev is enabled we only get joystick devices here, so there's no need to test them */ + if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) { + return 0; + } - if (ioctl(fd, EVIOCGID, &inpid) < 0) { - return 0; - } + if (ioctl(fd, EVIOCGID, &inpid) < 0) { + return 0; + } - if (ioctl(fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) { - return 0; + if (ioctl(fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) { + return 0; + } } name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string); @@ -213,7 +220,7 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid) FixupDeviceInfoForMapping(fd, &inpid); #ifdef DEBUG_JOYSTICK - printf("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d\n", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version); + SDL_Log("Joystick: %s, bustype = %d, vendor = 0x%.4x, product = 0x%.4x, version = %d\n", name, inpid.bustype, inpid.vendor, inpid.product, inpid.version); #endif SDL_memset(guid->data, 0, sizeof(guid->data)); @@ -254,6 +261,9 @@ static void joystick_udev_callback(SDL_UDEV_deviceevent udev_type, int udev_clas if (!(udev_class & SDL_UDEV_DEVICE_JOYSTICK)) { return; } + if (SDL_classic_joysticks && !IsJoystickDeviceNode(devpath)) { + return; + } MaybeAddDevice(devpath); break; @@ -308,7 +318,7 @@ MaybeAddDevice(const char *path) } #ifdef DEBUG_INPUT_EVENTS - printf("Checking %s\n", path); + SDL_Log("Checking %s\n", path); #endif isstick = IsJoystick(fd, &name, &guid); @@ -507,6 +517,20 @@ StrIsInteger(const char *string) return 1; } +static SDL_bool +IsJoystickDeviceNode(const char *node) +{ + const char *last_slash = SDL_strrchr(node, '/'); + if (last_slash) { + node = last_slash + 1; + } + if (SDL_classic_joysticks) { + return (StrHasPrefix(node, "js") && StrIsInteger(node + 2)); + } else { + return (StrHasPrefix(node, "event") && StrIsInteger(node + 5)); + } +} + static void LINUX_InotifyJoystickDetect(void) { @@ -519,6 +543,7 @@ LINUX_InotifyJoystickDetect(void) ssize_t bytes; size_t remain = 0; size_t len; + char path[PATH_MAX]; bytes = read(inotify_fd, &buf, sizeof (buf)); @@ -528,10 +553,7 @@ LINUX_InotifyJoystickDetect(void) while (remain > 0) { if (buf.event.len > 0) { - if (StrHasPrefix(buf.event.name, "event") && - StrIsInteger(buf.event.name + SDL_strlen ("event"))) { - char path[PATH_MAX]; - + if (IsJoystickDeviceNode(buf.event.name)) { SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name); if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) { @@ -560,7 +582,7 @@ LINUX_InotifyJoystickDetect(void) static int filter_entries(const struct dirent *entry) { - return (SDL_strlen(entry->d_name) > 5 && SDL_strncmp(entry->d_name, "event", 5) == 0); + return IsJoystickDeviceNode(entry->d_name); } static int sort_entries(const struct dirent **a, const struct dirent **b) @@ -627,7 +649,9 @@ LINUX_JoystickDetect(void) static int LINUX_JoystickInit(void) { - const char *devices = SDL_GetHint("SDL_JOYSTICK_DEVICE"); + const char *devices = SDL_GetHint(SDL_HINT_JOYSTICK_DEVICE); + + SDL_classic_joysticks = SDL_GetHintBoolean(SDL_HINT_LINUX_JOYSTICK_CLASSIC, SDL_FALSE); #if SDL_USE_LIBUDEV if (enumeration_method == ENUMERATION_UNSET) { @@ -824,6 +848,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; unsigned long relbit[NBITS(REL_MAX)] = { 0 }; unsigned long ffbit[NBITS(FF_MAX)] = { 0 }; + Uint8 key_pam_size, abs_pam_size; SDL_bool use_deadzones = SDL_GetHintBoolean(SDL_HINT_LINUX_JOYSTICK_DEADZONES, SDL_FALSE); /* See if this device uses the new unified event API */ @@ -835,7 +860,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) for (i = BTN_JOYSTICK; i < KEY_MAX; ++i) { if (test_bit(i, keybit)) { #ifdef DEBUG_INPUT_EVENTS - printf("Joystick has button: 0x%x\n", i); + SDL_Log("Joystick has button: 0x%x\n", i); #endif joystick->hwdata->key_map[i] = joystick->nbuttons; joystick->hwdata->has_key[i] = SDL_TRUE; @@ -845,7 +870,7 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) for (i = 0; i < BTN_JOYSTICK; ++i) { if (test_bit(i, keybit)) { #ifdef DEBUG_INPUT_EVENTS - printf("Joystick has button: 0x%x\n", i); + SDL_Log("Joystick has button: 0x%x\n", i); #endif joystick->hwdata->key_map[i] = joystick->nbuttons; joystick->hwdata->has_key[i] = SDL_TRUE; @@ -866,10 +891,10 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) continue; } #ifdef DEBUG_INPUT_EVENTS - printf("Joystick has absolute axis: 0x%.2x\n", i); - printf("Values = { %d, %d, %d, %d, %d }\n", - absinfo.value, absinfo.minimum, absinfo.maximum, - absinfo.fuzz, absinfo.flat); + SDL_Log("Joystick has absolute axis: 0x%.2x\n", i); + SDL_Log("Values = { %d, %d, %d, %d, %d }\n", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat); #endif /* DEBUG_INPUT_EVENTS */ joystick->hwdata->abs_map[i] = joystick->naxes; joystick->hwdata->has_abs[i] = SDL_TRUE; @@ -906,10 +931,10 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) continue; } #ifdef DEBUG_INPUT_EVENTS - 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); + SDL_Log("Joystick has hat %d\n", hat_index); + SDL_Log("Values = { %d, %d, %d, %d, %d }\n", + absinfo.value, absinfo.minimum, absinfo.maximum, + absinfo.fuzz, absinfo.flat); #endif /* DEBUG_INPUT_EVENTS */ joystick->hwdata->hats_indices[hat_index] = joystick->nhats++; joystick->hwdata->has_hat[hat_index] = SDL_TRUE; @@ -919,17 +944,73 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) ++joystick->nballs; } - /* Allocate data to keep track of these thingamajigs */ - if (joystick->nhats > 0) { - if (allocate_hatdata(joystick) < 0) { - joystick->nhats = 0; + } else if ((ioctl(fd, JSIOCGBUTTONS, &key_pam_size, sizeof(key_pam_size)) >= 0) && + (ioctl(fd, JSIOCGAXES, &abs_pam_size, sizeof(abs_pam_size)) >= 0)) { + size_t len; + + joystick->hwdata->classic = SDL_TRUE; + + len = (KEY_MAX - BTN_MISC + 1) * sizeof(*joystick->hwdata->key_pam); + joystick->hwdata->key_pam = (Uint16 *)SDL_calloc(1, len); + if (joystick->hwdata->key_pam) { + if (ioctl(fd, JSIOCGBTNMAP, joystick->hwdata->key_pam, len) < 0) { + SDL_free(joystick->hwdata->key_pam); + joystick->hwdata->key_pam = NULL; + key_pam_size = 0; } } - if (joystick->nballs > 0) { - if (allocate_balldata(joystick) < 0) { - joystick->nballs = 0; + for (i = 0; i < key_pam_size; ++i) { + Uint16 code = joystick->hwdata->key_pam[i]; +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has button: 0x%x\n", code); +#endif + joystick->hwdata->key_map[code] = joystick->nbuttons; + joystick->hwdata->has_key[code] = SDL_TRUE; + ++joystick->nbuttons; + } + + len = ABS_CNT * sizeof(*joystick->hwdata->abs_pam); + joystick->hwdata->abs_pam = (Uint8 *)SDL_calloc(1, len); + if (joystick->hwdata->abs_pam) { + if (ioctl(fd, JSIOCGAXMAP, joystick->hwdata->abs_pam, len) < 0) { + SDL_free(joystick->hwdata->abs_pam); + joystick->hwdata->abs_pam = NULL; + abs_pam_size = 0; } } + for (i = 0; i < abs_pam_size; ++i) { + Uint8 code = joystick->hwdata->abs_pam[i]; + + if (code >= ABS_HAT0X && code <= ABS_HAT3Y) { + int hat_index = (code - ABS_HAT0X) / 2; + if (!joystick->hwdata->has_hat[hat_index]) { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has hat %d\n", hat_index); +#endif + joystick->hwdata->hats_indices[hat_index] = joystick->nhats++; + joystick->hwdata->has_hat[hat_index] = SDL_TRUE; + } + } else { +#ifdef DEBUG_INPUT_EVENTS + SDL_Log("Joystick has absolute axis: 0x%.2x\n", code); +#endif + joystick->hwdata->abs_map[code] = joystick->naxes; + joystick->hwdata->has_abs[code] = SDL_TRUE; + ++joystick->naxes; + } + } + } + + /* Allocate data to keep track of these thingamajigs */ + if (joystick->nhats > 0) { + if (allocate_hatdata(joystick) < 0) { + joystick->nhats = 0; + } + } + if (joystick->nballs > 0) { + if (allocate_balldata(joystick) < 0) { + joystick->nballs = 0; + } } if (ioctl(fd, EVIOCGBIT(EV_FF, sizeof(ffbit)), ffbit) >= 0) { @@ -955,6 +1036,7 @@ PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item) joystick->hwdata->guid = item->guid; joystick->hwdata->effect.id = -1; joystick->hwdata->m_bSteamController = item->m_bSteamController; + SDL_memset(joystick->hwdata->key_map, 0xFF, sizeof(joystick->hwdata->key_map)); SDL_memset(joystick->hwdata->abs_map, 0xFF, sizeof(joystick->hwdata->abs_map)); if (item->m_bSteamController) { @@ -1098,7 +1180,7 @@ LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled) return SDL_Unsupported(); } -static SDL_INLINE void +static void HandleHat(SDL_Joystick *stick, Uint8 hat, int axis, int value) { struct hwdata_hat *the_hat; @@ -1123,14 +1205,14 @@ HandleHat(SDL_Joystick *stick, Uint8 hat, int axis, int value) } } -static SDL_INLINE void +static void HandleBall(SDL_Joystick *stick, Uint8 ball, int axis, int value) { stick->hwdata->balls[ball].axis[axis] += value; } -static SDL_INLINE int +static int AxisCorrect(SDL_Joystick *joystick, int which, int value) { struct axis_correct *correct; @@ -1164,7 +1246,7 @@ AxisCorrect(SDL_Joystick *joystick, int which, int value) return value; } -static SDL_INLINE void +static void PollAllValues(SDL_Joystick *joystick) { struct input_absinfo absinfo; @@ -1182,7 +1264,7 @@ PollAllValues(SDL_Joystick *joystick) absinfo.value = AxisCorrect(joystick, i, absinfo.value); #ifdef DEBUG_INPUT_EVENTS - printf("Joystick : Re-read Axis %d (%d) val= %d\n", + SDL_Log("Joystick : Re-read Axis %d (%d) val= %d\n", joystick->hwdata->abs_map[i], i, absinfo.value); #endif SDL_PrivateJoystickAxis(joystick, @@ -1212,7 +1294,7 @@ PollAllValues(SDL_Joystick *joystick) if (joystick->hwdata->has_key[i]) { const Uint8 value = test_bit(i, keyinfo) ? SDL_PRESSED : SDL_RELEASED; #ifdef DEBUG_INPUT_EVENTS - printf("Joystick : Re-read Button %d (%d) val= %d\n", + SDL_Log("Joystick : Re-read Button %d (%d) val= %d\n", joystick->hwdata->key_map[i], i, value); #endif SDL_PrivateJoystickButton(joystick, @@ -1224,12 +1306,11 @@ PollAllValues(SDL_Joystick *joystick) /* Joyballs are relative input, so there's no poll state. Events only! */ } -static SDL_INLINE void +static void HandleInputEvents(SDL_Joystick *joystick) { struct input_event events[32]; - int i, len; - int code; + int i, len, code; if (joystick->hwdata->fresh) { PollAllValues(joystick); @@ -1268,13 +1349,10 @@ HandleInputEvents(SDL_Joystick *joystick) HandleHat(joystick, joystick->hwdata->hats_indices[code / 2], code % 2, events[i].value); break; default: - 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); - } + events[i].value = AxisCorrect(joystick, code, events[i].value); + SDL_PrivateJoystickAxis(joystick, + joystick->hwdata->abs_map[code], + events[i].value); break; } break; @@ -1293,7 +1371,7 @@ HandleInputEvents(SDL_Joystick *joystick) switch (code) { case SYN_DROPPED : #ifdef DEBUG_INPUT_EVENTS - printf("Event SYN_DROPPED detected\n"); + SDL_Log("Event SYN_DROPPED detected\n"); #endif joystick->hwdata->recovering_from_dropped = SDL_TRUE; break; @@ -1318,6 +1396,48 @@ HandleInputEvents(SDL_Joystick *joystick) } } +static void +HandleClassicEvents(SDL_Joystick *joystick) +{ + struct js_event events[32]; + int i, len, code; + + joystick->hwdata->fresh = SDL_FALSE; + while ((len = read(joystick->hwdata->fd, events, (sizeof events))) > 0) { + len /= sizeof(events[0]); + for (i = 0; i < len; ++i) { + switch (events[i].type) { + case JS_EVENT_BUTTON: + code = joystick->hwdata->key_pam[events[i].number]; + SDL_PrivateJoystickButton(joystick, + joystick->hwdata->key_map[code], + events[i].value); + break; + case JS_EVENT_AXIS: + code = joystick->hwdata->abs_pam[events[i].number]; + switch (code) { + case ABS_HAT0X: + case ABS_HAT0Y: + case ABS_HAT1X: + case ABS_HAT1Y: + case ABS_HAT2X: + case ABS_HAT2Y: + case ABS_HAT3X: + case ABS_HAT3Y: + code -= ABS_HAT0X; + HandleHat(joystick, joystick->hwdata->hats_indices[code / 2], code % 2, events[i].value); + break; + default: + SDL_PrivateJoystickAxis(joystick, + joystick->hwdata->abs_map[code], + events[i].value); + break; + } + } + } + } +} + static void LINUX_JoystickUpdate(SDL_Joystick *joystick) { @@ -1328,7 +1448,11 @@ LINUX_JoystickUpdate(SDL_Joystick *joystick) return; } - HandleInputEvents(joystick); + if (joystick->hwdata->classic) { + HandleClassicEvents(joystick); + } else { + HandleInputEvents(joystick); + } /* Deliver ball motion updates */ for (i = 0; i < joystick->nballs; ++i) { @@ -1359,6 +1483,8 @@ LINUX_JoystickClose(SDL_Joystick *joystick) if (joystick->hwdata->item) { joystick->hwdata->item->hwdata = NULL; } + SDL_free(joystick->hwdata->key_pam); + SDL_free(joystick->hwdata->abs_pam); SDL_free(joystick->hwdata->hats); SDL_free(joystick->hwdata->balls); SDL_free(joystick->hwdata->fname); @@ -1525,20 +1651,20 @@ LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) out->lefttrigger.target = hat | 0x4; out->righttrigger.target = hat | 0x2; } else { - if (joystick->hwdata->has_key[BTN_TL2]) { - out->lefttrigger.kind = EMappingKind_Button; - out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2]; - } else if (joystick->hwdata->has_abs[ABS_Z]) { + if (joystick->hwdata->has_abs[ABS_Z]) { out->lefttrigger.kind = EMappingKind_Axis; out->lefttrigger.target = joystick->hwdata->abs_map[ABS_Z]; + } else if (joystick->hwdata->has_key[BTN_TL2]) { + out->lefttrigger.kind = EMappingKind_Button; + out->lefttrigger.target = joystick->hwdata->key_map[BTN_TL2]; } - if (joystick->hwdata->has_key[BTN_TR2]) { - out->righttrigger.kind = EMappingKind_Button; - out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2]; - } else if (joystick->hwdata->has_abs[ABS_RZ]) { + if (joystick->hwdata->has_abs[ABS_RZ]) { out->righttrigger.kind = EMappingKind_Axis; out->righttrigger.target = joystick->hwdata->abs_map[ABS_RZ]; + } else if (joystick->hwdata->has_key[BTN_TR2]) { + out->righttrigger.kind = EMappingKind_Button; + out->righttrigger.target = joystick->hwdata->key_map[BTN_TR2]; } } diff --git a/src/joystick/linux/SDL_sysjoystick_c.h b/src/joystick/linux/SDL_sysjoystick_c.h index 898f183dd..630e2f5c5 100644 --- a/src/joystick/linux/SDL_sysjoystick_c.h +++ b/src/joystick/linux/SDL_sysjoystick_c.h @@ -56,6 +56,11 @@ struct joystick_hwdata SDL_bool has_key[KEY_MAX]; SDL_bool has_abs[ABS_MAX]; + /* Support for the classic joystick interface */ + SDL_bool classic; + Uint16 *key_pam; + Uint8 *abs_pam; + struct axis_correct { SDL_bool use_deadzones;