From b3a0128be66938349329d7fda66376bc4d202739 Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev <0x416b617269@gmail.com> Date: Fri, 19 Apr 2019 14:28:32 +0300 Subject: [PATCH] gamepad: support hotplug, any-device option --- src/config.h | 2 +- src/gamepad.c | 265 +++++++++++++++++++++++++++++---------------- src/gamepad.h | 8 +- src/menu/options.c | 137 +++++++++++++---------- 4 files changed, 261 insertions(+), 151 deletions(-) diff --git a/src/config.h b/src/config.h index 328e3f0a..9eca0258 100644 --- a/src/config.h +++ b/src/config.h @@ -117,7 +117,7 @@ CONFIGDEF_FLOAT (SCORETEXT_ALPHA, "scoretext_alpha", 1) \ KEYDEFS \ CONFIGDEF_INT (GAMEPAD_ENABLED, "gamepad_enabled", 1) \ - CONFIGDEF_STRING (GAMEPAD_DEVICE, "gamepad_device", "default") \ + CONFIGDEF_STRING (GAMEPAD_DEVICE, "gamepad_device", "any") \ CONFIGDEF_INT (GAMEPAD_AXIS_UD, "gamepad_axis_ud", 1) \ CONFIGDEF_INT (GAMEPAD_AXIS_LR, "gamepad_axis_lr", 0) \ CONFIGDEF_INT (GAMEPAD_AXIS_FREE, "gamepad_axis_free", 1) \ diff --git a/src/gamepad.c b/src/gamepad.c index b58d9e84..23238319 100644 --- a/src/gamepad.c +++ b/src/gamepad.c @@ -25,21 +25,25 @@ typedef struct GamepadButtonState { hrtime_t repeat_time; } GamepadButtonState; -static struct { - bool initialized; - int current_devnum; - SDL_GameController *device; - SDL_JoystickID instance; +typedef struct GamepadDevice { + SDL_GameController *controller; + SDL_JoystickID joy_instance; + int sdl_id; +} GamepadDevice; +static struct { GamepadAxisState *axes; GamepadButtonState *buttons; - - struct { - int *id_map; - size_t count; - } devices; + GamepadDevice *devices; + size_t num_devices; + size_t num_devices_allocated; + int active_dev_num; + bool initialized; + bool update_needed; } gamepad; +#define DEVNUM(dev) (int)(ptrdiff_t)((dev) - gamepad.devices) + static bool gamepad_event_handler(SDL_Event *event, void *arg); static int gamepad_load_mappings(const char *vpath, int warn_noexist) { @@ -51,8 +55,6 @@ static int gamepad_load_mappings(const char *vpath, int warn_noexist) { int num_loaded = -1; LogLevel loglvl = LOG_WARN; - // Yay for gotos! - if(!mappings) { if(!warn_noexist) { VFSInfo vinfo = vfs_query(vpath); @@ -92,36 +94,70 @@ static void gamepad_load_all_mappings(void) { static const char* gamepad_device_name_unmapped(int idx) { const char *name = SDL_GameControllerNameForIndex(idx); + if(name == NULL) { + return "Unknown device"; + } + if(!strcasecmp(name, "Xinput Controller")) { // HACK: let's try to get a more descriptive name... + const char *prev_name = name; name = SDL_JoystickNameForIndex(idx); + + if(name == NULL) { + name = prev_name; + } } return name; } -const char* gamepad_device_name(int num) { - if(num < 0 || num >= gamepad.devices.count) { +static inline GamepadDevice* gamepad_get_device(int num) { + if(num < 0 || num >= gamepad.num_devices) { return NULL; } - return gamepad_device_name_unmapped(gamepad.devices.id_map[num]); + return gamepad.devices + num; } +const char* gamepad_device_name(int num) { + GamepadDevice *dev = gamepad_get_device(num); + + if(dev) { + return gamepad_device_name_unmapped(dev->sdl_id); + } + + return NULL; +} static bool gamepad_update_device_list(void) { int cnt = SDL_NumJoysticks(); log_info("Updating gamepad devices list"); - free(gamepad.devices.id_map); - memset(&gamepad.devices, 0, sizeof(gamepad.devices)); + if(gamepad.num_devices > 0) { + assume(gamepad.devices != NULL); + + for(uint i = 0; i < gamepad.num_devices; ++i) { + SDL_GameControllerClose(gamepad.devices[i].controller); + } + } + + gamepad.num_devices = 0; if(!cnt) { + free(gamepad.devices); + gamepad.devices = NULL; + gamepad.num_devices_allocated = 0; log_info("No joysticks attached"); return false; } - int *idmap_ptr = gamepad.devices.id_map = malloc(sizeof(int) * cnt); + if(gamepad.num_devices_allocated != cnt) { + free(gamepad.devices); + gamepad.devices = calloc(cnt, sizeof(GamepadDevice)); + gamepad.num_devices_allocated = cnt; + } + + GamepadDevice *dev = gamepad.devices; for(int i = 0; i < cnt; ++i) { SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(i); @@ -140,15 +176,28 @@ static bool gamepad_update_device_list(void) { continue; } - *idmap_ptr = i; - int num = (int)(uintmax_t)(idmap_ptr - gamepad.devices.id_map); - ++idmap_ptr; - gamepad.devices.count = (uintptr_t)(idmap_ptr - gamepad.devices.id_map); + dev->sdl_id = i; + dev->controller = SDL_GameControllerOpen(i); - log_info("Found device '%s' (#%i): %s", guid_str, num, gamepad_device_name_unmapped(i)); + if(dev->controller == NULL) { + log_sdl_error(LOG_WARN, "SDL_GameControllerOpen"); + continue; + } + + dev->joy_instance = SDL_JoystickGetDeviceInstanceID(i); + + if(dev->joy_instance < 0) { + log_sdl_error(LOG_WARN, "SDL_JoystickGetDeviceInstanceID"); + continue; + } + + log_info("Found device '%s' (#%i): %s", guid_str, DEVNUM(dev), gamepad_device_name_unmapped(i)); + + ++gamepad.num_devices; + ++dev; } - if(!gamepad.devices.count) { + if(!gamepad.num_devices) { log_info("No usable devices"); return false; } @@ -156,46 +205,86 @@ static bool gamepad_update_device_list(void) { return true; } -static int gamepad_find_device_by_guid(const char *guid_str, char *guid_out, size_t guid_out_sz, int *out_localdevnum) { - for(int i = 0; i < gamepad.devices.count; ++i) { +static GamepadDevice* gamepad_find_device_by_guid(const char *guid_str, char *guid_out, size_t guid_out_sz, int *out_localdevnum) { + if(gamepad.num_devices < 1) { + *out_localdevnum = GAMEPAD_DEVNUM_INVALID; + return NULL; + } + + if(!strcasecmp(guid_str, "any")) { + *out_localdevnum = GAMEPAD_DEVNUM_ANY; + strlcpy(guid_out, "any", guid_out_sz); + return NULL; + } + + for(int i = 0; i < gamepad.num_devices; ++i) { *guid_out = 0; - SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(gamepad.devices.id_map[i]); + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(gamepad.devices[i].sdl_id); SDL_JoystickGetGUIDString(guid, guid_out, guid_out_sz); if(!strcasecmp(guid_str, guid_out) || !strcasecmp(guid_str, "default")) { *out_localdevnum = i; - return gamepad.devices.id_map[i]; + return gamepad.devices + i; } } - *out_localdevnum = -1; - return -1; + *out_localdevnum = GAMEPAD_DEVNUM_INVALID; + return NULL; } -static int gamepad_find_device(char *guid_out, size_t guid_out_sz, int *out_localdevnum) { - int dev; +static GamepadDevice* gamepad_find_device(char *guid_out, size_t guid_out_sz, int *out_localdevnum) { + GamepadDevice *dev; const char *want_guid = config_get_str(CONFIG_GAMEPAD_DEVICE); dev = gamepad_find_device_by_guid(want_guid, guid_out, guid_out_sz, out_localdevnum); - if(dev >= 0) { + if(dev || *out_localdevnum == GAMEPAD_DEVNUM_ANY) { return dev; } if(strcasecmp(want_guid, "default")) { - log_warn("Requested device '%s' is not available, trying first usable", want_guid); - dev = gamepad_find_device_by_guid("default", guid_out, guid_out_sz, out_localdevnum); - - if(dev >= 0) { - return dev; - } + log_warn("Requested device '%s' is not available", want_guid); + return gamepad_find_device_by_guid("any", guid_out, guid_out_sz, out_localdevnum); } - *out_localdevnum = -1; + *out_localdevnum = GAMEPAD_DEVNUM_INVALID; strcpy(guid_out, want_guid); - return -1; + return NULL; +} + +int gamepad_get_active_device(void) { + if(gamepad.initialized) { + return gamepad.active_dev_num; + } + + return GAMEPAD_DEVNUM_INVALID; +} + +bool gamepad_update_devices(void) { + gamepad.update_needed = false; + + if(!gamepad_update_device_list()) { + return false; + } + + char guid[33]; + GamepadDevice *dev = gamepad_find_device(guid, sizeof(guid), &gamepad.active_dev_num); + + if(gamepad.active_dev_num == GAMEPAD_DEVNUM_ANY) { + log_info("Using all available devices (%zu)", gamepad.num_devices); + return true; + } + + if(dev == NULL) { + log_info("No devices available"); + return false; + } + + log_info("Using device '%s' (#%i): %s", guid, gamepad.active_dev_num, gamepad_device_name(gamepad.active_dev_num)); + config_set_str(CONFIG_GAMEPAD_DEVICE, guid); + return true; } void gamepad_init(void) { @@ -209,44 +298,17 @@ void gamepad_init(void) { } gamepad.initialized = true; - gamepad_load_all_mappings(); - - if(!gamepad_update_device_list()) { - gamepad_shutdown(); - return; - } - - char guid[33]; - int dev = gamepad_find_device(guid, sizeof(guid), &gamepad.current_devnum); - - if(dev < 0) { - log_warn("Device '%s' is not available", guid); - gamepad_shutdown(); - return; - } - - gamepad.device = SDL_GameControllerOpen(dev); - - if(!gamepad.device) { - log_warn("Failed to open device '%s' (#%i): %s", guid, dev, SDL_GetError()); - gamepad_shutdown(); - return; - } - - SDL_Joystick *joy = SDL_GameControllerGetJoystick(gamepad.device); - gamepad.instance = SDL_JoystickInstanceID(joy); gamepad.axes = calloc(GAMEPAD_AXIS_MAX, sizeof(GamepadAxisState)); gamepad.buttons = calloc(GAMEPAD_BUTTON_MAX + GAMEPAD_EMULATED_BUTTON_MAX, sizeof(GamepadButtonState)); - - log_info("Using device '%s' (#%i): %s", guid, dev, gamepad_device_name(dev)); - SDL_GameControllerEventState(SDL_ENABLE); - - config_set_str(CONFIG_GAMEPAD_DEVICE, guid); + gamepad.active_dev_num = GAMEPAD_DEVNUM_INVALID; + gamepad_load_all_mappings(); events_register_handler(&(EventHandler){ .proc = gamepad_event_handler, .priority = EPRIO_TRANSLATION, }); + + SDL_GameControllerEventState(SDL_ENABLE); } void gamepad_shutdown(void) { @@ -256,13 +318,16 @@ void gamepad_shutdown(void) { log_info("Disabled the gamepad subsystem"); - if(gamepad.device) { - SDL_GameControllerClose(gamepad.device); - } - free(gamepad.axes); free(gamepad.buttons); - free(gamepad.devices.id_map); + + for(uint i = 0; i < gamepad.num_devices; ++i) { + if(gamepad.devices[i].controller) { + SDL_GameControllerClose(gamepad.devices[i].controller); + } + } + + free(gamepad.devices); SDL_GameControllerEventState(SDL_IGNORE); SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER); @@ -613,12 +678,15 @@ static void gamepad_handle_button_repeat(GamepadButton btn, hrtime_t time) { } } +#define INSTANCE_IS_VALID(inst) \ + (gamepad.active_dev_num == GAMEPAD_DEVNUM_ANY || (gamepad.active_dev_num >= 0 && (inst) == gamepad.devices[gamepad.active_dev_num].joy_instance)) + static bool gamepad_event_handler(SDL_Event *event, void *arg) { assert(gamepad.initialized); switch(event->type) { case SDL_CONTROLLERAXISMOTION: - if(event->caxis.which == gamepad.instance) { + if(INSTANCE_IS_VALID(event->caxis.which)) { GamepadAxis axis = gamepad_axis_from_sdl_axis(event->caxis.axis); if(axis != GAMEPAD_AXIS_INVALID) { @@ -629,7 +697,7 @@ static bool gamepad_event_handler(SDL_Event *event, void *arg) { case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: - if(event->cbutton.which == gamepad.instance) { + if(INSTANCE_IS_VALID(event->cbutton.which)) { GamepadButton btn = gamepad_button_from_sdl_button(event->cbutton.button); if(btn != GAMEPAD_BUTTON_INVALID) { @@ -637,9 +705,19 @@ static bool gamepad_event_handler(SDL_Event *event, void *arg) { } } return true; + + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + case SDL_CONTROLLERDEVICEREMAPPED: + gamepad.update_needed = true; + return true; } if(event->type == MAKE_TAISEI_EVENT(TE_FRAME)) { + if(gamepad.update_needed) { + gamepad_update_devices(); + } + hrtime_t time = time_get(); for(GamepadButton btn = 0; btn < GAMEPAD_BUTTON_MAX; ++btn) { @@ -655,20 +733,29 @@ static bool gamepad_event_handler(SDL_Event *event, void *arg) { } int gamepad_device_count(void) { - return gamepad.devices.count; + return gamepad.num_devices; } void gamepad_device_guid(int num, char *guid_str, size_t guid_str_sz) { - if(num < 0 || num >= gamepad.devices.count) { + if(num == GAMEPAD_DEVNUM_ANY) { + strlcpy(guid_str, "any", guid_str_sz); return; } - SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(gamepad.devices.id_map[num]); - SDL_JoystickGetGUIDString(guid, guid_str, guid_str_sz); + GamepadDevice *dev = gamepad_get_device(num); + + if(dev) { + SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(dev->sdl_id); + SDL_JoystickGetGUIDString(guid, guid_str, guid_str_sz); + } } int gamepad_device_num_from_guid(const char *guid_str) { - for(int i = 0; i < gamepad.devices.count; ++i) { + if(strcasecmp(guid_str, "any")) { + return GAMEPAD_DEVNUM_ANY; + } + + for(uint i = 0; i < gamepad.num_devices; ++i) { char guid[33] = {0}; gamepad_device_guid(i, guid, sizeof(guid)); @@ -677,15 +764,7 @@ int gamepad_device_num_from_guid(const char *guid_str) { } } - return -1; -} - -int gamepad_current_device_num(void) { - if(gamepad.initialized) { - return gamepad.current_devnum; - } - - return -1; + return GAMEPAD_DEVNUM_INVALID; } bool gamepad_button_pressed(GamepadButton btn) { diff --git a/src/gamepad.h b/src/gamepad.h index 103f4911..bd2e4a2c 100644 --- a/src/gamepad.h +++ b/src/gamepad.h @@ -84,6 +84,11 @@ typedef enum GamepadAxis { GAMEPAD_AXIS_MAX } GamepadAxis; +enum { + GAMEPAD_DEVNUM_INVALID = -1, + GAMEPAD_DEVNUM_ANY = -2, +}; + void gamepad_init(void); void gamepad_shutdown(void); void gamepad_restart(void); @@ -93,7 +98,8 @@ int gamepad_device_count(void); const char* gamepad_device_name(int); void gamepad_device_guid(int num, char *guid_str, size_t guid_str_sz); int gamepad_device_num_from_guid(const char *guid_str); -int gamepad_current_device_num(void); +int gamepad_get_active_device(void); +bool gamepad_update_devices(void); bool gamepad_button_pressed(GamepadButton btn); bool gamepad_game_key_pressed(KeyIndex key); diff --git a/src/menu/options.c b/src/menu/options.c index 24254a2a..3df5ea93 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -39,7 +39,6 @@ typedef struct OptionBinding { char *strvalue; }; bool displaysingle; - int valcount; int valrange_min; int valrange_max; float scale_min; @@ -72,9 +71,13 @@ static OptionBinding* bind_new(void) { static void bind_free(OptionBinding *bind) { int i; - if(bind->values) { - for(i = 0; i < bind->valcount; ++i) + if(bind->type == BT_StrValue) { + free(bind->strvalue); + } else if(bind->values) { + assert(bind->valrange_min == 0); + for(i = 0; i <= bind->valrange_max; ++i) { free(*(bind->values+i)); + } free(bind->values); } } @@ -128,15 +131,32 @@ static OptionBinding* bind_gpaxisbinding(int cfgentry) { } static int bind_gpdev_get(OptionBinding *b) { - return gamepad_device_num_from_guid(config_get_str(b->configentry)); + const char *guid = config_get_str(b->configentry); + int val = gamepad_device_num_from_guid(guid); + + if(val == GAMEPAD_DEVNUM_ANY) { + val = -1; + } + + return val; } static int bind_gpdev_set(OptionBinding *b, int v) { char guid[33] = {0}; + + if(v == -1) { + v = GAMEPAD_DEVNUM_ANY; + } + gamepad_device_guid(v, guid, sizeof(guid)); if(*guid) { config_set_str(b->configentry, guid); + + if(v == GAMEPAD_DEVNUM_ANY) { + v = -1; + } + b->selected = v; } @@ -153,7 +173,7 @@ static OptionBinding* bind_gpdevice(int cfgentry) { bind->getter = bind_gpdev_get; bind->setter = bind_gpdev_set; - bind->valrange_min = 0; + bind->valrange_min = -1; bind->valrange_max = 0; // updated later bind->selected = gamepad_device_num_from_guid(config_get_str(bind->configentry)); @@ -173,7 +193,8 @@ static OptionBinding* bind_stroption(ConfigIndex cfgentry) { // BT_Resolution: super-special binding type for the resolution setting static void bind_resolution_update(OptionBinding *bind) { - bind->valcount = video.mcount; + bind->valrange_min = 0; + bind->valrange_max = video.mcount - 1; for(int i = 0; i < video.mcount; ++i) { VideoMode *m = video.modes + i; @@ -217,14 +238,25 @@ static OptionBinding* bind_getinputblocking(MenuData *m) { // Adds a value to a BT_IntValue type binding static int bind_addvalue(OptionBinding *b, char *val) { - b->values = realloc(b->values, ++b->valcount * sizeof(char*)); - b->values[b->valcount-1] = malloc(strlen(val) + 1); - strcpy(b->values[b->valcount-1], val); - return b->valcount-1; + assert(b->valrange_min == 0); + + if(b->values == NULL) { + b->values = malloc(sizeof(char*)); + b->valrange_min = 0; + b->valrange_max = 0; + } else { + assert(b->valrange_min == 0); + ++b->valrange_max; + } + + b->values = realloc(b->values, (1 + b->valrange_max) * sizeof(char*)); + b->values[b->valrange_max] = strdup(val); + return b->valrange_max; } attr_unused static void bind_setvaluerange(OptionBinding *b, int vmin, int vmax) { + assert(b->values == NULL); b->valrange_min = vmin; b->valrange_max = vmax; } @@ -240,14 +272,7 @@ static int bind_setvalue(OptionBinding *b, int v) { // Called to get the selected value of a BT_IntValue type binding by index static int bind_getvalue(OptionBinding *b) { if(b->getter) { - if(b->selected >= b->valcount && b->valcount) { - b->selected = 0; - } else { - b->selected = b->getter(b); - - if(b->selected >= b->valcount && b->valcount) - b->selected = 0; - } + b->selected = b->getter(b); } return b->selected; @@ -257,11 +282,8 @@ static int bind_getvalue(OptionBinding *b) { static int bind_setnext(OptionBinding *b) { int s = b->selected + 1; - if(b->valrange_max) { - if(s > b->valrange_max) - s = b->valrange_min; - } else if(s >= b->valcount) { - s = 0; + if(s > b->valrange_max) { + s = b->valrange_min; } return bind_setvalue(b, s); @@ -271,11 +293,8 @@ static int bind_setnext(OptionBinding *b) { static int bind_setprev(OptionBinding *b) { int s = b->selected - 1; - if(b->valrange_max) { - if(s < b->valrange_min) - s = b->valrange_max; - } else if(s < 0) { - s = b->valcount - 1; + if(s < b->valrange_min) { + s = b->valrange_max; } return bind_setvalue(b, s); @@ -627,7 +646,7 @@ static void destroy_options_menu_gamepad(MenuData *m) { OptionsMenuContext *ctx = m->context; if(config_get_int(CONFIG_GAMEPAD_ENABLED) && strcasecmp(config_get_str(CONFIG_GAMEPAD_DEVICE), ctx->data)) { - gamepad_restart(); + gamepad_update_devices(); } destroy_options_menu(m); @@ -924,16 +943,7 @@ static void draw_options_menu(MenuData *menu) { case BT_IntValue: { int val = bind_getvalue(bind); - if(bind->valrange_max) { - char tmp[16]; // who'd use a 16-digit number here anyway? - snprintf(tmp, 16, "%d", bind_getvalue(bind)); - - text_draw(tmp, &(TextParams) { - .pos = { origin, 20*i }, - .align = ALIGN_RIGHT, - .color = &clr, - }); - } else if(bind->configentry == CONFIG_PRACTICE_POWER) { + if(bind->configentry == CONFIG_PRACTICE_POWER) { Font *fnt_int = get_font("standard"); Font *fnt_fract = get_font("small"); @@ -948,18 +958,29 @@ static void draw_options_menu(MenuData *menu) { &clr, false ); - } else for(j = bind->displaysingle? val : bind->valcount-1; (j+1) && (!bind->displaysingle || j == val); --j) { - if(j != bind->valcount-1 && !bind->displaysingle) { - origin -= text_width(get_font("standard"), bind->values[j+1], 0) + 5; - } + } else if(bind->values) { + for(j = bind->displaysingle? val : bind->valrange_max; (j+1) && (!bind->displaysingle || j == val); --j) { + if(j != bind->valrange_max && !bind->displaysingle) { + origin -= text_width(get_font("standard"), bind->values[j+1], 0) + 5; + } - if(val == j) { - clr = *RGBA_MUL_ALPHA(0.9, 0.6, 0.2, alpha); - } else { - clr = *RGBA_MUL_ALPHA(0.5, 0.5, 0.5, 0.7 * alpha); - } + if(val == j) { + clr = *RGBA_MUL_ALPHA(0.9, 0.6, 0.2, alpha); + } else { + clr = *RGBA_MUL_ALPHA(0.5, 0.5, 0.5, 0.7 * alpha); + } - text_draw(bind->values[j], &(TextParams) { + text_draw(bind->values[j], &(TextParams) { + .pos = { origin, 20*i }, + .align = ALIGN_RIGHT, + .color = &clr, + }); + } + } else { + char tmp[16]; // who'd use a 16-digit number here anyway? + snprintf(tmp, 16, "%d", bind_getvalue(bind)); + + text_draw(tmp, &(TextParams) { .pos = { origin, 20*i }, .align = ALIGN_RIGHT, .color = &clr, @@ -1005,20 +1026,24 @@ static void draw_options_menu(MenuData *menu) { // XXX: I'm not exactly a huge fan of fixing up state in drawing code, but it seems the way to go for now... bind->valrange_max = gamepad_device_count() - 1; - if(bind->selected < 0 || bind->selected > bind->valrange_max) { - bind->selected = gamepad_current_device_num(); + if(bind->selected < -1 || bind->selected > bind->valrange_max) { + bind->selected = gamepad_get_active_device(); - if(bind->selected < 0) { - bind->selected = 0; + if(bind->selected < -1) { + bind->selected = -1; } } char *txt; char buf[64]; - if(bind->valrange_max > 0) { - snprintf(buf, sizeof(buf), "#%i: %s", bind->selected + 1, gamepad_device_name(bind->selected)); - txt = buf; + if(bind->valrange_max >= 0) { + if(bind->selected < 0) { + txt = "All devices"; + } else { + snprintf(buf, sizeof(buf), "#%i: %s", bind->selected + 1, gamepad_device_name(bind->selected)); + txt = buf; + } } else { txt = "No devices available"; }