From 5b26d4e0ca71b8c82c9d0b23c81d1def1fd43a08 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 9 Dec 2023 20:31:28 -0800 Subject: [PATCH] Sort Steam virtual gamepads by Steam controller slot This makes sure that games launched by Steam see the first controller first, and the controllers in the game match up with the controllers in the Steam UI. Fixes https://github.com/libsdl-org/SDL/issues/8672 (cherry picked from commit 17723381da9bc3eadb129beaeaed2164c64b5155) --- src/joystick/linux/SDL_sysjoystick.c | 115 +++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 14 deletions(-) diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 9e8a181aa..64c78059a 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -828,6 +828,104 @@ static int sort_entries(const void *_a, const void *_b) return numA - numB; } +typedef struct +{ + char *path; + int slot; +} VirtualGamepadEntry; + +static int SDLCALL sort_virtual_gamepads(const void *_a, const void *_b) +{ + const VirtualGamepadEntry *a = (const VirtualGamepadEntry *)_a; + const VirtualGamepadEntry *b = (const VirtualGamepadEntry *)_b; + return a->slot - b->slot; +} + +static SDL_bool get_virtual_gamepad_slot(const char *name, int *slot) +{ + const char *digits = SDL_strstr(name, "pad "); + if (digits) { + digits += 4; + if (SDL_isdigit(*digits)) { + *slot = SDL_atoi(digits); + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static void LINUX_ScanSteamVirtualGamepads(void) +{ + int i, count; + int fd; + struct dirent **entries = NULL; + char path[PATH_MAX]; + char name[128]; + struct input_id inpid; + int num_virtual_gamepads = 0; + int virtual_gamepad_slot; + VirtualGamepadEntry *virtual_gamepads = NULL; + + count = scandir("/dev/input", &entries, filter_entries, NULL); + for (i = 0; i < count; ++i) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); + + fd = open(path, O_RDONLY | O_CLOEXEC, 0); + if (fd >= 0) { + if (ioctl(fd, EVIOCGID, &inpid) == 0 && + inpid.vendor == USB_VENDOR_VALVE && + inpid.product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD && + ioctl(fd, EVIOCGNAME(sizeof(name)), name) > 0 && + get_virtual_gamepad_slot(name, &virtual_gamepad_slot)) { + VirtualGamepadEntry *new_virtual_gamepads = (VirtualGamepadEntry *)SDL_realloc(virtual_gamepads, (num_virtual_gamepads + 1) * sizeof(*virtual_gamepads)); + if (new_virtual_gamepads) { + VirtualGamepadEntry *entry = &new_virtual_gamepads[num_virtual_gamepads]; + entry->path = SDL_strdup(path); + entry->slot = virtual_gamepad_slot; + if (entry->path) { + virtual_gamepads = new_virtual_gamepads; + ++num_virtual_gamepads; + } else { + SDL_free(entry->path); + SDL_free(new_virtual_gamepads); + } + } + } + close(fd); + } + free(entries[i]); /* This should NOT be SDL_free() */ + } + free(entries); /* This should NOT be SDL_free() */ + + if (num_virtual_gamepads > 1) { + SDL_qsort(virtual_gamepads, num_virtual_gamepads, sizeof(*virtual_gamepads), sort_virtual_gamepads); + } + for (i = 0; i < num_virtual_gamepads; ++i) { + MaybeAddDevice(virtual_gamepads[i].path); + SDL_free(virtual_gamepads[i].path); + } + SDL_free(virtual_gamepads); +} + +static void LINUX_ScanInputDevices(void) +{ + int i, count; + struct dirent **entries = NULL; + char path[PATH_MAX]; + + count = scandir("/dev/input", &entries, filter_entries, NULL); + if (count > 1) { + SDL_qsort(entries, count, sizeof(*entries), sort_entries); + } + for (i = 0; i < count; ++i) { + (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); + MaybeAddDevice(path); + + free(entries[i]); /* This should NOT be SDL_free() */ + } + free(entries); /* This should NOT be SDL_free() */ +} + static void LINUX_FallbackJoystickDetect(void) { const Uint32 SDL_JOY_DETECT_INTERVAL_MS = 3000; /* Update every 3 seconds */ @@ -838,21 +936,10 @@ static void LINUX_FallbackJoystickDetect(void) /* 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) { - int i, count; - struct dirent **entries = NULL; - char path[PATH_MAX]; + /* Look for Steam virtual gamepads first, and sort by Steam controller slot */ + LINUX_ScanSteamVirtualGamepads(); - count = scandir("/dev/input", &entries, filter_entries, NULL); - if (count > 1) { - qsort(entries, count, sizeof(*entries), sort_entries); - } - for (i = 0; i < count; ++i) { - (void)SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", entries[i]->d_name); - MaybeAddDevice(path); - - free(entries[i]); /* This should NOT be SDL_free() */ - } - free(entries); /* This should NOT be SDL_free() */ + LINUX_ScanInputDevices(); last_input_dir_mtime = sb.st_mtime; }