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:
parent
57a138c1d1
commit
644757a65a
8 changed files with 230 additions and 72 deletions
|
@ -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) \
|
||||
|
|
157
src/gamepad.c
157
src/gamepad.c
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -30,6 +30,7 @@ typedef enum BindingType {
|
|||
BT_Scale,
|
||||
BT_GamepadKeyBinding,
|
||||
BT_GamepadAxisBinding,
|
||||
BT_GamepadDevice,
|
||||
} BindingType;
|
||||
|
||||
typedef struct OptionBinding {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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] = '.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue