gamepad settings improvements

GUIDs are now used to identify devices. this allows reliable persistent
configuration on multi-gamepad systems.

changed the way the device listing in the options menu works. it's
inactive when the gamepad system is disabled, and dynamically updated
when it's enabled. as a result, the "bare" init state is now finally
gone. this should work with minimal or no changes when the hotplugging
events are properly handled.

the "enable gamepad support" toggle is now effective immediately. the
gamepad system is no longer restarted every time the user leaves the
gamepad menu, unless they have changed the device.
This commit is contained in:
Andrei "Akari" Alexeyev 2017-08-28 14:51:05 +03:00
parent 57a138c1d1
commit 644757a65a
No known key found for this signature in database
GPG key ID: 048C3D2A5648B785
8 changed files with 230 additions and 72 deletions

View file

@ -84,7 +84,7 @@
CONFIGDEF_FLOAT (BG_QUALITY, "bg_quality", 1.0) \
KEYDEFS \
CONFIGDEF_INT (GAMEPAD_ENABLED, "gamepad_enabled", 0) \
CONFIGDEF_INT (GAMEPAD_DEVICE, "gamepad_device", 0) \
CONFIGDEF_STRING (GAMEPAD_DEVICE, "gamepad_device", "default") \
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) \

View file

@ -12,7 +12,8 @@
#include "global.h"
static struct {
int initialized;
bool initialized;
int current_devnum;
SDL_GameController *device;
SDL_JoystickID instance;
signed char *axis;
@ -42,14 +43,88 @@ static void gamepad_update_device_list(void) {
}
*idmap_ptr = i;
log_info("Gamepad device #%"PRIuMAX": %s", (uintmax_t)(idmap_ptr - gamepad.devices.id_map), SDL_GameControllerNameForIndex(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);
char guid[33] = {0};
gamepad_deviceguid(num, guid, sizeof(guid));
log_info("Gamepad device '%s' (#%i): %s", guid, num, SDL_GameControllerNameForIndex(i));
}
}
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) {
*guid_out = 0;
SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(gamepad.devices.id_map[i]);
SDL_JoystickGetGUIDString(guid, guid_out, guid_out_sz);
if(!*guid_out) {
log_warn("Failed to read GUID of device %i: %s", gamepad.devices.id_map[i], SDL_GetError());
continue;
}
if(!strcasecmp(guid_str, guid_out) || !strcasecmp(guid_str, "default")) {
*out_localdevnum = i;
return gamepad.devices.id_map[i];
}
}
gamepad.devices.count = (uintptr_t)(idmap_ptr - gamepad.devices.id_map);
*out_localdevnum = -1;
return -1;
}
static int gamepad_find_device(char *guid_out, size_t guid_out_sz, int *out_localdevnum) {
int 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) {
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;
}
}
*out_localdevnum = -1;
strcpy(guid_out, want_guid);
return -1;
}
static void gamepad_cfg_enabled_callback(ConfigIndex idx, ConfigValue v) {
config_set_int(idx, v.i);
if(v.i) {
gamepad_init();
} else {
gamepad_shutdown();
}
}
static void gamepad_setup_cfg_callbacks(void) {
static bool done = false;
if(done) {
return;
}
done = true;
config_set_callback(CONFIG_GAMEPAD_ENABLED, gamepad_cfg_enabled_callback);
}
void gamepad_init(void) {
gamepad_setup_cfg_callbacks();
if(!config_get_int(CONFIG_GAMEPAD_ENABLED) || gamepad.initialized) {
return;
}
@ -61,16 +136,19 @@ void gamepad_init(void) {
gamepad_update_device_list();
int dev = config_get_int(CONFIG_GAMEPAD_DEVICE);
if(dev < 0 || dev >= gamepad.devices.count) {
log_warn("Device #%i is not available", dev);
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 %i: %s", dev, SDL_GetError());
log_warn("Failed to open device '%s' (#%i): %s", guid, dev, SDL_GetError());
gamepad_shutdown();
return;
}
@ -79,10 +157,11 @@ void gamepad_init(void) {
gamepad.instance = SDL_JoystickInstanceID(joy);
gamepad.axis = malloc(max(SDL_JoystickNumAxes(joy), 1));
log_info("Using device #%i: %s", dev, gamepad_devicename(dev));
log_info("Using device '%s' (#%i): %s", guid, dev, gamepad_devicename(dev));
SDL_GameControllerEventState(SDL_ENABLE);
gamepad.initialized = 1;
config_set_str(CONFIG_GAMEPAD_DEVICE, guid);
gamepad.initialized = true;
}
void gamepad_shutdown(void) {
@ -90,9 +169,7 @@ void gamepad_shutdown(void) {
return;
}
if(gamepad.initialized != 2) {
log_info("Disabled the gamepad subsystem");
}
log_info("Disabled the gamepad subsystem");
if(gamepad.device) {
SDL_GameControllerClose(gamepad.device);
@ -311,12 +388,42 @@ int gamepad_devicecount(void) {
return gamepad.devices.count;
}
const char* gamepad_devicename(int id) {
if(id < 0 || id >= gamepad.devices.count) {
const char* gamepad_devicename(int num) {
if(num < 0 || num >= gamepad.devices.count) {
return NULL;
}
return SDL_GameControllerNameForIndex(gamepad.devices.id_map[id]);
return SDL_GameControllerNameForIndex(gamepad.devices.id_map[num]);
}
const void gamepad_deviceguid(int num, char *guid_str, size_t guid_str_sz) {
if(num < 0 || num >= gamepad.devices.count) {
return;
}
SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(gamepad.devices.id_map[num]);
SDL_JoystickGetGUIDString(guid, guid_str, guid_str_sz);
}
int gamepad_numfromguid(const char *guid_str) {
for(int i = 0; i < gamepad.devices.count; ++i) {
char guid[33] = {0};
gamepad_deviceguid(i, guid, sizeof(guid));
if(!strcasecmp(guid_str, guid)) {
return i;
}
}
return -1;
}
int gamepad_currentdevice(void) {
if(gamepad.initialized) {
return gamepad.current_devnum;
}
return -1;
}
bool gamepad_buttonpressed(SDL_GameControllerButton btn) {
@ -339,26 +446,6 @@ bool gamepad_gamekeypressed(KeyIndex key) {
return gamepad_buttonpressed(button);
}
void gamepad_init_bare(void) {
if(gamepad.initialized) {
return;
}
if(SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
log_warn("SDL_InitSubSystem() failed: %s", SDL_GetError());
return;
}
gamepad_update_device_list();
gamepad.initialized = 2;
}
void gamepad_shutdown_bare(void) {
if(gamepad.initialized == 2) {
gamepad_shutdown();
}
}
const char* gamepad_button_name(SDL_GameControllerButton btn) {
static const char *const map[] = {
"A",

View file

@ -17,11 +17,15 @@
void gamepad_init(void);
void gamepad_shutdown(void);
void gamepad_restart(void);
int gamepad_devicecount(void);
float gamepad_axis_sens(int);
const char* gamepad_devicename(int);
void gamepad_event(SDL_Event*, EventHandler, EventFlags, void*);
int gamepad_devicecount(void);
const char* gamepad_devicename(int);
const void gamepad_deviceguid(int num, char *guid_str, size_t guid_str_sz);
int gamepad_numfromguid(const char *guid_str);
int gamepad_currentdevice(void);
bool gamepad_buttonpressed(int btn);
bool gamepad_gamekeypressed(KeyIndex key);

View file

@ -85,6 +85,40 @@ OptionBinding* bind_gpaxisbinding(int cfgentry) {
return bind;
}
static int bind_gpdev_get(OptionBinding *b) {
return gamepad_numfromguid(config_get_str(b->configentry));
}
static int bind_gpdev_set(OptionBinding *b, int v) {
char guid[33] = {0};
gamepad_deviceguid(v, guid, sizeof(guid));
if(*guid) {
config_set_str(b->configentry, guid);
b->selected = v;
}
return b->selected;
}
// BT_GamepadDevice: dynamic device list
OptionBinding* bind_gpdevice(int cfgentry) {
OptionBinding *bind = bind_new();
bind->configentry = cfgentry;
bind->type = BT_GamepadDevice;
bind->getter = bind_gpdev_get;
bind->setter = bind_gpdev_set;
bind->valrange_min = 0;
bind->valrange_max = 0; // updated later
bind->selected = gamepad_numfromguid(config_get_str(bind->configentry));
return bind;
}
// BT_StrValue: with a half-assed "textbox"
OptionBinding* bind_stroption(ConfigIndex cfgentry) {
OptionBinding *bind = bind_new();
@ -180,13 +214,14 @@ int bind_getvalue(OptionBinding *b) {
// Selects the next to current value of a BT_IntValue type binding
int bind_setnext(OptionBinding *b) {
int s = b->selected +1;
int s = b->selected + 1;
if(b->valrange_max) {
if(s > b->valrange_max)
s = b->valrange_min;
} else if(s >= b->valcount)
} else if(s >= b->valcount) {
s = 0;
}
return bind_setvalue(b, s);
}
@ -198,8 +233,9 @@ int bind_setprev(OptionBinding *b) {
if(b->valrange_max) {
if(s < b->valrange_min)
s = b->valrange_max;
} else if(s < 0)
} else if(s < 0) {
s = b->valcount - 1;
}
return bind_setvalue(b, s);
}
@ -381,10 +417,14 @@ void bind_setvaluerange_fancy(OptionBinding *b, int ma) {
}
}
bool gamepad_sens_depencence(void) {
static bool gamepad_sens_depencence(void) {
return config_get_int(CONFIG_GAMEPAD_AXIS_FREE);
}
static bool gamepad_enabled_depencence(void) {
return config_get_int(CONFIG_GAMEPAD_ENABLED);
}
void options_sub_gamepad_controls(MenuData *parent, void *arg) {
MenuData menu, *m;
m = &menu;
@ -432,7 +472,6 @@ void options_sub_gamepad_controls(MenuData *parent, void *arg) {
menu_loop(m);
parent->frames = 0;
gamepad_restart();
}
void options_sub_gamepad(MenuData *parent, void *arg) {
@ -447,26 +486,12 @@ void options_sub_gamepad(MenuData *parent, void *arg) {
); bind_onoff(b);
add_menu_entry(m, "Device", do_nothing,
b = bind_option(CONFIG_GAMEPAD_DEVICE, bind_common_intget, bind_common_intset)
); b->displaysingle = true;
b = bind_gpdevice(CONFIG_GAMEPAD_DEVICE)
); bind_setdependence(b, gamepad_enabled_depencence);
add_menu_separator(m);
add_menu_entry(m, "Customize controls…", options_sub_gamepad_controls, NULL);
gamepad_init_bare();
int cnt = gamepad_devicecount();
int i; for(i = 0; i < cnt; ++i) {
char buf[50];
snprintf(buf, sizeof(buf), "#%i: %s", i+1, gamepad_devicename(i));
bind_addvalue(b, buf);
}
if(!i) {
bind_addvalue(b, "No devices available");
b->selected = 0;
}
gamepad_shutdown_bare();
add_menu_separator(m);
add_menu_entry(m, "X axis", do_nothing,
@ -499,9 +524,16 @@ void options_sub_gamepad(MenuData *parent, void *arg) {
add_menu_separator(m);
add_menu_entry(m, "Back", menu_commonaction_close, NULL);
char *gpdev = strdup(config_get_str(CONFIG_GAMEPAD_DEVICE));
menu_loop(m);
parent->frames = 0;
gamepad_restart();
if(config_get_int(CONFIG_GAMEPAD_ENABLED) && strcasecmp(config_get_str(CONFIG_GAMEPAD_DEVICE), gpdev)) {
gamepad_restart();
}
free(gpdev);
}
void options_sub_controls(MenuData *parent, void *arg) {
@ -722,6 +754,36 @@ void draw_options_menu(MenuData *menu) {
break;
}
case BT_GamepadDevice: {
if(bind_isactive(bind)) {
// 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_devicecount() - 1;
if(bind->selected < 0 || bind->selected > bind->valrange_max) {
bind->selected = gamepad_currentdevice();
if(bind->selected < 0) {
bind->selected = 0;
}
}
char *txt;
char buf[64];
if(bind->valrange_max > 0) {
snprintf(buf, sizeof(buf), "#%i: %s", bind->selected + 1, gamepad_devicename(bind->selected));
shorten_text_up_to_width(buf, (SCREEN_W - 220) / 2, _fonts.standard);
txt = buf;
} else {
txt = "No devices available";
}
draw_text(AL_Right, origin, 20*i, txt, _fonts.standard);
}
break;
}
case BT_GamepadKeyBinding:
case BT_GamepadAxisBinding: {
bool is_axis = (bind->type == BT_GamepadAxisBinding);
@ -946,6 +1008,7 @@ static void options_input_event(EventType type, int state, void *arg) {
play_ui_sound("generic_shot");
if(bind) {
switch(bind->type) {
case BT_GamepadDevice:
case BT_IntValue:
case BT_Resolution:
bind_setprev(bind);
@ -965,6 +1028,7 @@ static void options_input_event(EventType type, int state, void *arg) {
play_ui_sound("generic_shot");
if(bind) {
switch(bind->type) {
case BT_GamepadDevice:
case BT_IntValue:
case BT_Resolution:
bind_setnext(bind);

View file

@ -30,6 +30,7 @@ typedef enum BindingType {
BT_Scale,
BT_GamepadKeyBinding,
BT_GamepadAxisBinding,
BT_GamepadDevice,
} BindingType;
typedef struct OptionBinding {

View file

@ -82,17 +82,6 @@ static void replayview_freearg(void *a) {
free(ctx);
}
static void shorten(char *s, float width) {
while(stringwidth(s, _fonts.standard) > width) {
s[strlen(s) - 1] = 0;
int l = strlen(s);
for(int i = 0; i < 3; ++i) {
s[l - i - 1] = '.';
}
}
}
static void replayview_draw_stagemenu(MenuData *m) {
// this context is shared with the parent menu
ReplayviewContext *ctx = m->context;
@ -196,7 +185,8 @@ static void replayview_drawitem(void *n, int item, int cnt) {
break;
}
shorten(tmp, csize);
shorten_text_up_to_width(tmp, csize, _fonts.standard);
switch(a) {
case AL_Center: o += csize * 0.5 - stringwidth(tmp, _fonts.standard) * 0.5; break;
case AL_Right: o += csize - stringwidth(tmp, _fonts.standard); break;

View file

@ -224,3 +224,14 @@ int charwidth(char c, TTF_Font *font) {
s[1] = 0;
return stringwidth(s, font);
}
void shorten_text_up_to_width(char *s, float width, TTF_Font *font) {
while(stringwidth(s, font) > width) {
s[strlen(s) - 1] = 0;
int l = strlen(s);
for(int i = 0; i < 3; ++i) {
s[l - i - 1] = '.';
}
}
}

View file

@ -45,6 +45,7 @@ void draw_text_prerendered(Alignment align, float x, float y, SDL_Surface *surf)
int stringwidth(char *s, TTF_Font *font);
int stringheight(char *s, TTF_Font *font);
int charwidth(char c, TTF_Font *font);
void shorten_text_up_to_width(char *s, float width, TTF_Font *font);
void init_fonts(void);
void uninit_fonts(void);