taisei/src/gamepad.c
2017-10-25 02:02:39 +03:00

621 lines
15 KiB
C

/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2017, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2017, Andrei Alexeyev <akari@alienslab.net>.
*/
#include "gamepad.h"
#include "config.h"
#include "events.h"
#include "global.h"
static struct {
bool initialized;
int current_devnum;
SDL_GameController *device;
SDL_JoystickID instance;
signed char *axis;
struct {
int *id_map;
size_t count;
} devices;
} gamepad;
static bool gamepad_event_handler(SDL_Event *event, void *arg);
static int gamepad_load_mappings(const char *vpath, int warn_noexist) {
char *repr = vfs_repr(vpath, true);
char *errstr = NULL;
const char *const_errstr = NULL;
SDL_RWops *mappings = vfs_open(vpath, VFS_MODE_READ | VFS_MODE_SEEKABLE);
int num_loaded = -1;
LogLevel loglvl = LOG_WARN;
// Yay for gotos!
if(!mappings) {
if(!warn_noexist) {
VFSInfo vinfo = vfs_query(vpath);
if(!vinfo.error && !vinfo.exists && !vinfo.is_dir) {
loglvl = LOG_INFO;
const_errstr = errstr = strfmt("Custom mappings file '%s' does not exist (this is ok)", repr);
goto cleanup;
}
}
const_errstr = vfs_get_error();
goto cleanup;
}
if((num_loaded = SDL_GameControllerAddMappingsFromRW(mappings, true)) < 0) {
const_errstr = SDL_GetError();
}
cleanup:
if(const_errstr) {
log_custom(loglvl, "Couldn't load mappings: %s", const_errstr);
} else if(num_loaded >= 0) {
log_info("Loaded %i mappings from '%s'", num_loaded, repr);
}
free(repr);
free(errstr);
return num_loaded;
}
static void gamepad_load_all_mappings(void) {
gamepad_load_mappings("res/gamecontrollerdb.txt", true);
gamepad_load_mappings("storage/gamecontrollerdb.txt", false);
}
static const char* gamepad_devicename_unmapped(int idx) {
const char *name = SDL_GameControllerNameForIndex(idx);
if(!strcasecmp(name, "Xinput Controller")) {
// HACK: let's try to get a more descriptive name...
name = SDL_JoystickNameForIndex(idx);
}
return name;
}
const char* gamepad_devicename(int num) {
if(num < 0 || num >= gamepad.devices.count) {
return NULL;
}
return gamepad_devicename_unmapped(gamepad.devices.id_map[num]);
}
static void 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(!cnt) {
log_info("No joysticks attached");
return;
}
int *idmap_ptr = gamepad.devices.id_map = malloc(sizeof(int) * cnt);
for(int i = 0; i < cnt; ++i) {
SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(i);
char guid_str[33] = { 0 };
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
if(!*guid_str) {
log_warn("Failed to read GUID of joystick at index %i: %s", i, SDL_GetError());
continue;
}
if(!SDL_IsGameController(i)) {
log_warn("Joystick at index %i (name: \"%s\"; guid: %s) is not recognized as a game controller by SDL. "
"Most likely it just doesn't have a mapping. See https://git.io/vdvdV for solutions",
i, SDL_JoystickNameForIndex(i), guid_str);
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);
log_info("Found device '%s' (#%i): %s", guid_str, num, gamepad_devicename_unmapped(i));
}
if(!gamepad.devices.count) {
log_info("No usable devices");
return;
}
}
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(!strcasecmp(guid_str, guid_out) || !strcasecmp(guid_str, "default")) {
*out_localdevnum = i;
return gamepad.devices.id_map[i];
}
}
*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;
}
if(SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
log_warn("SDL_InitSubSystem() failed: %s", SDL_GetError());
return;
}
gamepad_load_all_mappings();
gamepad_update_device_list();
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.axis = malloc(SDL_CONTROLLER_AXIS_MAX);
log_info("Using device '%s' (#%i): %s", guid, dev, gamepad_devicename(dev));
SDL_GameControllerEventState(SDL_ENABLE);
config_set_str(CONFIG_GAMEPAD_DEVICE, guid);
events_register_handler(&(EventHandler){
.proc = gamepad_event_handler,
.priority = EPRIO_TRANSLATION,
});
gamepad.initialized = true;
}
void gamepad_shutdown(void) {
if(!gamepad.initialized) {
return;
}
log_info("Disabled the gamepad subsystem");
if(gamepad.device) {
SDL_GameControllerClose(gamepad.device);
}
free(gamepad.axis);
free(gamepad.devices.id_map);
SDL_GameControllerEventState(SDL_IGNORE);
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
memset(&gamepad, 0, sizeof(gamepad));
events_unregister_handler(gamepad_event_handler);
}
void gamepad_restart(void) {
gamepad_shutdown();
gamepad_init();
}
int gamepad_axis2gamekey(SDL_GameControllerAxis id, int val) {
val *= sign(gamepad_axis_sens(id));
if(!val) {
return -1;
}
if(id == config_get_int(CONFIG_GAMEPAD_AXIS_LR)) {
return val == AXISVAL_LEFT ? KEY_LEFT : KEY_RIGHT;
}
if(id == config_get_int(CONFIG_GAMEPAD_AXIS_UD)) {
return val == AXISVAL_UP ? KEY_UP : KEY_DOWN;
}
return -1;
}
SDL_GameControllerAxis gamepad_gamekey2axis(KeyIndex key) {
switch(key) {
case KEY_UP: case KEY_DOWN: return config_get_int(CONFIG_GAMEPAD_AXIS_UD);
case KEY_LEFT: case KEY_RIGHT: return config_get_int(CONFIG_GAMEPAD_AXIS_LR);
default: return SDL_CONTROLLER_AXIS_INVALID;
}
}
int gamepad_gamekey2axisval(KeyIndex key) {
switch(key) {
case KEY_UP: return AXISVAL_UP;
case KEY_DOWN: return AXISVAL_DOWN;
case KEY_LEFT: return AXISVAL_LEFT;
case KEY_RIGHT: return AXISVAL_RIGHT;
default: return AXISVAL_NULL;
}
}
static int gamepad_axis2menuevt(SDL_GameControllerAxis id, int val) {
if(id == SDL_CONTROLLER_AXIS_LEFTX || id == SDL_CONTROLLER_AXIS_RIGHTX)
return val == AXISVAL_LEFT ? TE_MENU_CURSOR_LEFT : TE_MENU_CURSOR_RIGHT;
if(id == SDL_CONTROLLER_AXIS_LEFTY || id == SDL_CONTROLLER_AXIS_RIGHTY)
return val == AXISVAL_UP ? TE_MENU_CURSOR_UP : TE_MENU_CURSOR_DOWN;
return -1;
}
static int gamepad_axis2gameevt(SDL_GameControllerAxis id) {
if(id == config_get_int(CONFIG_GAMEPAD_AXIS_LR))
return TE_GAME_AXIS_LR;
if(id == config_get_int(CONFIG_GAMEPAD_AXIS_UD))
return TE_GAME_AXIS_UD;
return -1;
}
float gamepad_axis_sens(SDL_GameControllerAxis id) {
if(id == config_get_int(CONFIG_GAMEPAD_AXIS_LR))
return config_get_float(CONFIG_GAMEPAD_AXIS_LR_SENS);
if(id == config_get_int(CONFIG_GAMEPAD_AXIS_UD))
return config_get_float(CONFIG_GAMEPAD_AXIS_UD_SENS);
return 1.0;
}
static int gamepad_axis_process_value_deadzone(int raw) {
int val, vsign;
float deadzone = clamp(config_get_float(CONFIG_GAMEPAD_AXIS_DEADZONE), 0.01, 0.999);
int minval = clamp(deadzone, 0, 1) * GAMEPAD_AXIS_MAX;
val = raw;
vsign = sign(val);
val = abs(val);
if(val < minval) {
val = 0;
} else {
val = vsign * clamp((val - minval) / (1.0 - deadzone), 0, GAMEPAD_AXIS_MAX);
}
return val;
}
static int gamepad_axis_process_value(SDL_GameControllerAxis id, int raw) {
double sens = gamepad_axis_sens(id);
int sens_sign = sign(sens);
raw = gamepad_axis_process_value_deadzone(raw);
double x = raw / (double)GAMEPAD_AXIS_MAX;
int in_sign = sign(x);
x = pow(fabs(x), 1.0 / fabs(sens)) * in_sign * sens_sign;
x = x ? x : 0;
x = clamp(x * GAMEPAD_AXIS_MAX, GAMEPAD_AXIS_MIN, GAMEPAD_AXIS_MAX);
return (int)x;
}
int gamepad_get_player_axis_value(GamepadPlrAxis paxis) {
SDL_GameControllerAxis id;
if(!gamepad.initialized) {
return 0;
}
if(paxis == PLRAXIS_LR) {
id = config_get_int(CONFIG_GAMEPAD_AXIS_LR);
} else if(paxis == PLRAXIS_UD) {
id = config_get_int(CONFIG_GAMEPAD_AXIS_UD);
} else {
return INT_MAX;
}
return gamepad_axis_process_value(id, SDL_GameControllerGetAxis(gamepad.device, id));
}
void gamepad_axis(SDL_GameControllerAxis id, int raw) {
signed char *a = gamepad.axis;
signed char val = AXISVAL(gamepad_axis_process_value_deadzone(raw));
bool restricted = !config_get_int(CONFIG_GAMEPAD_AXIS_FREE);
if(val * a[id] < 0) {
// axis changed direction without passing the '0' state
// this can be bad for digital input simulation (aka 'restricted mode')
// so we insert a fake 0 event inbetween
gamepad_axis(id, 0);
}
events_emit(TE_GAMEPAD_AXIS, id, (void*)(intptr_t)raw, NULL);
if(!restricted) {
int evt = gamepad_axis2gameevt(id);
if(evt >= 0) {
events_emit(evt, gamepad_axis_process_value(id, raw), NULL, NULL);
}
}
if(val) { // simulate press
if(!a[id]) {
a[id] = val;
int key = gamepad_axis2gamekey(id, val);
if(key >= 0) {
if(restricted) {
events_emit(TE_GAME_KEY_DOWN, key, (void*)(intptr_t)INDEV_GAMEPAD, NULL);
}
int evt = gamepad_axis2menuevt(id, val);
if(evt >= 0) {
events_emit(evt, 0, (void*)(intptr_t)INDEV_GAMEPAD, NULL);
}
}
}
} else if(a[id]) { // simulate release
if(restricted) {
int key = gamepad_axis2gamekey(id, a[id]);
if(key >= 0) {
events_emit(TE_GAME_KEY_UP, key, (void*)(intptr_t)INDEV_GAMEPAD, NULL);
}
}
a[id] = AXISVAL_NULL;
}
}
void gamepad_button(SDL_GameControllerButton button, int state) {
int gpkey = config_gamepad_key_from_gamepad_button(button);
int key = config_key_from_gamepad_key(gpkey);
void *indev = (void*)(intptr_t)INDEV_GAMEPAD;
if(state == SDL_PRESSED) {
events_emit(TE_GAMEPAD_BUTTON_DOWN, button, indev, NULL);
switch(button) {
case SDL_CONTROLLER_BUTTON_START:
events_emit(TE_MENU_ACCEPT, 0, indev, NULL);
events_emit(TE_GAME_PAUSE, 0, indev, NULL);
break;
case SDL_CONTROLLER_BUTTON_BACK:
events_emit(TE_MENU_ABORT, 0, indev, NULL);
events_emit(TE_GAME_PAUSE, 0, indev, NULL);
break;
case SDL_CONTROLLER_BUTTON_DPAD_UP: events_emit(TE_MENU_CURSOR_UP, 0, indev, NULL); break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: events_emit(TE_MENU_CURSOR_DOWN, 0, indev, NULL); break;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: events_emit(TE_MENU_CURSOR_LEFT, 0, indev, NULL); break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: events_emit(TE_MENU_CURSOR_RIGHT, 0, indev, NULL); break;
case SDL_CONTROLLER_BUTTON_A:
events_emit(TE_MENU_ACCEPT, 0, indev, NULL);
break;
case SDL_CONTROLLER_BUTTON_B:
events_emit(TE_MENU_ABORT, 0, indev, NULL);
break;
default:
if(key >= 0) {
events_emit(TE_GAME_KEY_DOWN, key, indev, NULL);
} break;
}
} else {
events_emit(TE_GAMEPAD_BUTTON_UP, button, indev, NULL);
if(key >= 0) {
events_emit(TE_GAME_KEY_UP, key, indev, NULL);
}
}
}
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) {
gamepad_axis(event->caxis.axis, event->caxis.value);
}
return true;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
if(event->cbutton.which == gamepad.instance) {
gamepad_button(event->cbutton.button, event->cbutton.state);
}
return true;
}
return false;
}
int gamepad_devicecount(void) {
return gamepad.devices.count;
}
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) {
return SDL_GameControllerGetButton(gamepad.device, btn);
}
bool gamepad_gamekeypressed(KeyIndex key) {
if(!gamepad.initialized)
return false;
if(!config_get_int(CONFIG_GAMEPAD_AXIS_FREE)) {
SDL_GameControllerAxis axis = gamepad_gamekey2axis(key);
if(axis != SDL_CONTROLLER_AXIS_INVALID && gamepad.axis[axis] == gamepad_gamekey2axisval(key)) {
return true;
}
}
int gpkey = config_gamepad_key_from_key(key);
if(gpkey < 0) {
return false;
}
int cfgidx = GPKEYIDX_TO_CFGIDX(gpkey);
int button = config_get_int(cfgidx);
return gamepad_buttonpressed(button);
}
const char* gamepad_button_name(SDL_GameControllerButton btn) {
static const char *const map[] = {
"A",
"B",
"X",
"Y",
"Back",
"Guide",
"Start",
"Left Stick",
"Right Stick",
"Left Bumper",
"Right Bumper",
"Up",
"Down",
"Left",
"Right",
};
if(btn > SDL_CONTROLLER_BUTTON_INVALID && btn < SDL_CONTROLLER_BUTTON_MAX) {
return map[btn];
}
return "Unknown";
}
const char* gamepad_axis_name(SDL_GameControllerAxis axis) {
static const char *const map[] = {
"Left X",
"Left Y",
"Right X",
"Right Y",
"Left Trigger",
"Right Trigger",
};
if(axis > SDL_CONTROLLER_AXIS_INVALID && axis < SDL_CONTROLLER_AXIS_MAX) {
return map[axis];
}
return "Unknown";
}