From 5ae0dd4b5223a2002950b831caeb652a44340517 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Sat, 24 Jul 2021 17:44:35 -0400 Subject: [PATCH] joystick: Split out Linux opening code for reuse by querying code. This prevents an assertion whem LINUX_JoystickGetGamepadMapping tried to open the stick temporarily and messed with global state by doing so. Now the global state is only set in LINUX_JoystickOpen, but the common code is shared by both interfaces. Fixes #4198. --- src/joystick/linux/SDL_sysjoystick.c | 97 ++++++++++++++++++---------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index f0c2d5ce5..3e267e38f 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -965,6 +965,47 @@ ConfigJoystick(SDL_Joystick *joystick, int fd) } +/* This is used to do the heavy lifting for LINUX_JoystickOpen and + also LINUX_JoystickGetGamepadMapping, so we can query the hardware + without adding an opened SDL_Joystick object to the system. + This expects `joystick->hwdata` to be allocated and will not free it + on error. Returns -1 on error, 0 on success. */ +static int +PrepareJoystickHwdata(SDL_Joystick *joystick, SDL_joylist_item *item) +{ + joystick->hwdata->item = item; + 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; + SDL_GetSteamControllerInputs(&joystick->nbuttons, + &joystick->naxes, + &joystick->nhats); + } else { + const int fd = open(item->path, O_RDWR, 0); + if (fd < 0) { + return SDL_SetError("Unable to open %s", item->path); + } + + joystick->hwdata->fd = fd; + joystick->hwdata->fname = SDL_strdup(item->path); + if (joystick->hwdata->fname == NULL) { + close(fd); + return SDL_OutOfMemory(); + } + + /* Set the joystick to non-blocking read mode */ + fcntl(fd, F_SETFL, O_NONBLOCK); + + /* Get the number of buttons and axes on the joystick */ + ConfigJoystick(joystick, fd); + } +} + + /* Function to open a joystick for use. The joystick to open is specified by the device index. This should fill the nbuttons and naxes fields of the joystick structure. @@ -985,39 +1026,11 @@ LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) if (joystick->hwdata == NULL) { return SDL_OutOfMemory(); } - joystick->hwdata->item = item; - 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; - SDL_GetSteamControllerInputs(&joystick->nbuttons, - &joystick->naxes, - &joystick->nhats); - } else { - int fd = open(item->path, O_RDWR, 0); - if (fd < 0) { - SDL_free(joystick->hwdata); - joystick->hwdata = NULL; - return SDL_SetError("Unable to open %s", item->path); - } - - joystick->hwdata->fd = fd; - joystick->hwdata->fname = SDL_strdup(item->path); - if (joystick->hwdata->fname == NULL) { - SDL_free(joystick->hwdata); - joystick->hwdata = NULL; - close(fd); - return SDL_OutOfMemory(); - } - - /* Set the joystick to non-blocking read mode */ - fcntl(fd, F_SETFL, O_NONBLOCK); - - /* Get the number of buttons and axes on the joystick */ - ConfigJoystick(joystick, fd); + if (PrepareJoystickHwdata(joystick, item) == -1) { + SDL_free(joystick->hwdata); + joystick->hwdata = NULL; + return -1; /* SDL_SetError will already have been called */ } SDL_assert(item->hwdata == NULL); @@ -1026,7 +1039,7 @@ LINUX_JoystickOpen(SDL_Joystick *joystick, int device_index) /* mark joystick as fresh and ready */ joystick->hwdata->fresh = SDL_TRUE; - return (0); + return 0; } static int @@ -1417,18 +1430,32 @@ LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) return SDL_TRUE; } + /* We temporarily open the device to check how it's configured. Make + a fake SDL_Joystick object to do so. */ joystick = (SDL_Joystick *) SDL_calloc(sizeof(*joystick), 1); if (joystick == NULL) { SDL_OutOfMemory(); return SDL_FALSE; } - /* We temporarily open the device to check how it's configured. */ - if (LINUX_JoystickOpen(joystick, device_index) < 0) { + joystick->hwdata = (struct joystick_hwdata *) + SDL_calloc(1, sizeof(*joystick->hwdata)); + if (joystick->hwdata == NULL) { SDL_free(joystick); + SDL_OutOfMemory(); return SDL_FALSE; } + if (PrepareJoystickHwdata(joystick, item) == -1) { + SDL_free(joystick->hwdata); + SDL_free(joystick); + return SDL_FALSE; /* SDL_SetError will already have been called */ + } + + /* don't assign `item->hwdata` so it's not in any global state. */ + + /* it is now safe to call LINUX_JoystickClose on this fake joystick. */ + if (!joystick->hwdata->has_key[BTN_GAMEPAD]) { /* Not a gamepad according to the specs. */ LINUX_JoystickClose(joystick);