diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index 9e8a181aa..0ab56ce95 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -157,6 +157,7 @@ typedef struct SDL_joylist_item char *name; /* "SideWinder 3D Pro" or whatever */ SDL_JoystickGUID guid; dev_t devnum; + int steam_virtual_gamepad_slot; struct joystick_hwdata *hwdata; struct SDL_joylist_item *next; @@ -220,6 +221,19 @@ static SDL_bool IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, } #endif /* SDL_JOYSTICK_HIDAPI */ +static SDL_bool GetVirtualGamepadSlot(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 int GuessDeviceClass(int fd) { unsigned long evbit[NBITS(EV_MAX)] = { 0 }; @@ -255,7 +269,7 @@ static int GuessIsSensor(int fd) return 0; } -static int IsJoystick(const char *path, int fd, char **name_return, SDL_JoystickGUID *guid) +static int IsJoystick(const char *path, int fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_JoystickGUID *guid) { struct input_id inpid; char *name; @@ -308,6 +322,8 @@ static int IsJoystick(const char *path, int fd, char **name_return, SDL_Joystick return 0; } *name_return = name; + *vendor_return = inpid.vendor; + *product_return = inpid.product; return 1; } @@ -385,6 +401,7 @@ static void MaybeAddDevice(const char *path) struct stat sb; int fd = -1; char *name = NULL; + Uint16 vendor, product; SDL_JoystickGUID guid; SDL_joylist_item *item; SDL_sensorlist_item *item_sensor; @@ -420,7 +437,7 @@ static void MaybeAddDevice(const char *path) SDL_Log("Checking %s\n", path); #endif - if (IsJoystick(path, fd, &name, &guid)) { + if (IsJoystick(path, fd, &name, &vendor, &product, &guid)) { #ifdef DEBUG_INPUT_EVENTS SDL_Log("found joystick: %s\n", path); #endif @@ -432,10 +449,16 @@ static void MaybeAddDevice(const char *path) } item->devnum = sb.st_rdev; + item->steam_virtual_gamepad_slot = -1; item->path = SDL_strdup(path); item->name = name; item->guid = guid; + if (vendor == USB_VENDOR_VALVE && + product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) { + GetVirtualGamepadSlot(item->name, &item->steam_virtual_gamepad_slot); + } + if ((!item->path) || (!item->name)) { FreeJoylistItem(item); goto done; @@ -828,6 +851,91 @@ 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 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 && + GetVirtualGamepadSlot(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 +946,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; } @@ -1017,7 +1114,7 @@ static const char *LINUX_JoystickGetDevicePath(int device_index) static int LINUX_JoystickGetDevicePlayerIndex(int device_index) { - return -1; + return JoystickByDevIndex(device_index)->steam_virtual_gamepad_slot; } static void LINUX_JoystickSetDevicePlayerIndex(int device_index, int player_index)