b6978178b1
Introduces wrappers around memory allocation functions in `memory.h` that should be used instead of the standard C ones. These never return NULL and, with the exception of `mem_realloc()`, zero-initialize the allocated memory like `calloc()` does. All allocations made with the memory.h API must be deallocated with `mem_free()`. Although standard `free()` will work on some platforms, it's not portable (currently it won't work on Windows). Likewise, `mem_free()` must not be used to free foreign allocations. The standard C allocation functions are now diagnosed as deprecated. They are, however, available with the `libc_` prefix in case interfacing with foreign APIs is required. So far they are only used to implement `memory.h`. Perhaps the most important change is the introduction of the `ALLOC()`, `ALLOC_ARRAY()`, and `ALLOC_FLEX()` macros. They take a type as a parameter, and allocate enough memory with the correct alignment for that type. That includes overaligned types as well. In most circumstances you should prefer to use these macros. See the `memory.h` header for some usage examples.
992 lines
25 KiB
C
992 lines
25 KiB
C
/*
|
|
* This software is licensed under the terms of the MIT License.
|
|
* See COPYING for further information.
|
|
* ---
|
|
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
|
|
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
|
|
*/
|
|
|
|
#include "taisei.h"
|
|
|
|
#include "gamepad.h"
|
|
#include "config.h"
|
|
#include "events.h"
|
|
#include "global.h"
|
|
#include "hirestime.h"
|
|
|
|
typedef struct GamepadAxisState {
|
|
int16_t raw;
|
|
int16_t analog;
|
|
int8_t digital;
|
|
} GamepadAxisState;
|
|
|
|
typedef struct GamepadButtonState {
|
|
bool held;
|
|
hrtime_t repeat_time;
|
|
} GamepadButtonState;
|
|
|
|
typedef struct GamepadDevice {
|
|
SDL_GameController *controller;
|
|
SDL_JoystickID joy_instance;
|
|
int sdl_id;
|
|
} GamepadDevice;
|
|
|
|
static struct {
|
|
GamepadAxisState *axes;
|
|
GamepadButtonState *buttons;
|
|
DYNAMIC_ARRAY(GamepadDevice) devices;
|
|
int active_dev_num;
|
|
bool initialized;
|
|
bool update_needed;
|
|
} gamepad;
|
|
|
|
#define DEVNUM(dev) dynarray_indexof(&gamepad.devices, (dev))
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
mem_free(repr);
|
|
mem_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_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;
|
|
}
|
|
|
|
static inline GamepadDevice* gamepad_get_device(int num) {
|
|
if(num < 0 || num >= gamepad.devices.num_elements) {
|
|
return NULL;
|
|
}
|
|
|
|
return dynarray_get_ptr(&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");
|
|
|
|
dynarray_foreach_elem(&gamepad.devices, auto dev, {
|
|
SDL_GameControllerClose(dev->controller);
|
|
});
|
|
|
|
gamepad.devices.num_elements = 0;
|
|
|
|
if(!cnt) {
|
|
dynarray_free_data(&gamepad.devices);
|
|
log_info("No joysticks attached");
|
|
return false;
|
|
}
|
|
|
|
dynarray_ensure_capacity(&gamepad.devices, 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;
|
|
}
|
|
|
|
auto dev = dynarray_append(&gamepad.devices);
|
|
dev->sdl_id = i;
|
|
dev->controller = SDL_GameControllerOpen(i);
|
|
|
|
if(dev->controller == NULL) {
|
|
log_sdl_error(LOG_WARN, "SDL_GameControllerOpen");
|
|
--gamepad.devices.num_elements;
|
|
continue;
|
|
}
|
|
|
|
dev->joy_instance = SDL_JoystickGetDeviceInstanceID(i);
|
|
|
|
if(dev->joy_instance < 0) {
|
|
log_sdl_error(LOG_WARN, "SDL_JoystickGetDeviceInstanceID");
|
|
--gamepad.devices.num_elements;
|
|
continue;
|
|
}
|
|
|
|
log_info("Found device '%s' (#%i): %s", guid_str, DEVNUM(dev), gamepad_device_name_unmapped(i));
|
|
}
|
|
|
|
if(!gamepad.devices.num_elements) {
|
|
log_info("No usable devices");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static GamepadDevice* gamepad_find_device_by_guid(const char *guid_str, char *guid_out, size_t guid_out_sz, int *out_localdevnum) {
|
|
if(gamepad.devices.num_elements < 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;
|
|
}
|
|
|
|
dynarray_foreach(&gamepad.devices, int i, auto dev, {
|
|
*guid_out = 0;
|
|
|
|
SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(dev->sdl_id);
|
|
SDL_JoystickGetGUIDString(guid, guid_out, guid_out_sz);
|
|
|
|
if(!strcasecmp(guid_str, guid_out) || !strcasecmp(guid_str, "default")) {
|
|
*out_localdevnum = i;
|
|
return dev;
|
|
}
|
|
});
|
|
|
|
*out_localdevnum = GAMEPAD_DEVNUM_INVALID;
|
|
return NULL;
|
|
}
|
|
|
|
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 || *out_localdevnum == GAMEPAD_DEVNUM_ANY) {
|
|
return dev;
|
|
}
|
|
|
|
if(strcasecmp(want_guid, "default")) {
|
|
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 = GAMEPAD_DEVNUM_INVALID;
|
|
strcpy(guid_out, want_guid);
|
|
|
|
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 (%u)", gamepad.devices.num_elements);
|
|
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;
|
|
}
|
|
|
|
static inline void set_events_state(int state) {
|
|
SDL_JoystickEventState(state);
|
|
SDL_GameControllerEventState(state);
|
|
}
|
|
|
|
void gamepad_init(void) {
|
|
set_events_state(SDL_IGNORE);
|
|
|
|
if(!config_get_int(CONFIG_GAMEPAD_ENABLED) || gamepad.initialized) {
|
|
return;
|
|
}
|
|
|
|
if(SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) < 0) {
|
|
log_sdl_error(LOG_ERROR, "SDL_InitSubSystem");
|
|
return;
|
|
}
|
|
|
|
gamepad.initialized = true;
|
|
gamepad.update_needed = true;
|
|
gamepad.axes = ALLOC_ARRAY(GAMEPAD_AXIS_MAX, typeof(*gamepad.axes));
|
|
gamepad.buttons = ALLOC_ARRAY(GAMEPAD_BUTTON_MAX + GAMEPAD_EMULATED_BUTTON_MAX, typeof(*gamepad.buttons));
|
|
gamepad.active_dev_num = GAMEPAD_DEVNUM_INVALID;
|
|
gamepad_load_all_mappings();
|
|
|
|
events_register_handler(&(EventHandler){
|
|
.proc = gamepad_event_handler,
|
|
.priority = EPRIO_TRANSLATION,
|
|
});
|
|
|
|
set_events_state(SDL_ENABLE);
|
|
}
|
|
|
|
void gamepad_shutdown(void) {
|
|
if(!gamepad.initialized) {
|
|
return;
|
|
}
|
|
|
|
log_info("Disabled the gamepad subsystem");
|
|
|
|
mem_free(gamepad.axes);
|
|
mem_free(gamepad.buttons);
|
|
|
|
dynarray_foreach_elem(&gamepad.devices, auto dev, {
|
|
if(dev->controller) {
|
|
SDL_GameControllerClose(dev->controller);
|
|
}
|
|
});
|
|
|
|
dynarray_free_data(&gamepad.devices);
|
|
|
|
set_events_state(SDL_IGNORE);
|
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
|
|
memset(&gamepad, 0, sizeof(gamepad));
|
|
events_unregister_handler(gamepad_event_handler);
|
|
}
|
|
|
|
static float gamepad_axis_sens(GamepadAxis 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_axis2gameevt(GamepadAxis 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;
|
|
}
|
|
|
|
static int get_axis_abs_limit(int val) {
|
|
if(val < 0) {
|
|
return -GAMEPAD_AXIS_MIN_VALUE;
|
|
}
|
|
|
|
return GAMEPAD_AXIS_MAX_VALUE;
|
|
}
|
|
|
|
static int gamepad_axis_process_value_deadzone(int raw) {
|
|
int val, vsign;
|
|
int limit = get_axis_abs_limit(raw);
|
|
float deadzone = clamp(config_get_float(CONFIG_GAMEPAD_AXIS_DEADZONE), 0.01, 0.999);
|
|
int minval = clamp(deadzone, 0, 1) * limit;
|
|
|
|
val = raw;
|
|
vsign = sign(val);
|
|
val = abs(val);
|
|
|
|
if(val < minval) {
|
|
val = 0;
|
|
} else {
|
|
val = vsign * clamp((val - minval) / (1.0 - deadzone), 0, limit);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int gamepad_axis_process_value(GamepadAxis id, int raw) {
|
|
double sens = gamepad_axis_sens(id);
|
|
int sens_sign = sign(sens);
|
|
|
|
raw = gamepad_axis_process_value_deadzone(raw);
|
|
|
|
int limit = get_axis_abs_limit(sens_sign * raw);
|
|
double x = raw / (double)limit;
|
|
int in_sign = sign(x);
|
|
|
|
x = pow(fabs(x), 1.0 / fabs(sens)) * in_sign * sens_sign;
|
|
x = x ? x : 0;
|
|
x = clamp(x * limit, GAMEPAD_AXIS_MIN_VALUE, GAMEPAD_AXIS_MAX_VALUE);
|
|
|
|
return (int)x;
|
|
}
|
|
|
|
int gamepad_axis_value(GamepadAxis id) {
|
|
assert(id > GAMEPAD_AXIS_INVALID);
|
|
assert(id < GAMEPAD_AXIS_MAX);
|
|
|
|
return gamepad.axes[id].analog;
|
|
}
|
|
|
|
int gamepad_player_axis_value(GamepadPlrAxis paxis) {
|
|
GamepadAxis 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_value(id);
|
|
}
|
|
|
|
static void gamepad_button(GamepadButton button, int state, bool is_repeat);
|
|
static void gamepad_axis(GamepadAxis id, int raw);
|
|
|
|
double gamepad_normalize_axis_value(int val) {
|
|
if(val < 0) {
|
|
return -val / (double)GAMEPAD_AXIS_MIN_VALUE;
|
|
} else if(val > 0) {
|
|
return val / (double)GAMEPAD_AXIS_MAX_VALUE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int gamepad_denormalize_axis_value(double val) {
|
|
if(val < 0) {
|
|
return -val * GAMEPAD_AXIS_MIN_VALUE;
|
|
} else if(val > 0) {
|
|
return val * GAMEPAD_AXIS_MAX_VALUE;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void gamepad_update_game_axis(GamepadAxis axis, int oldval) {
|
|
if(oldval != gamepad.axes[axis].analog) {
|
|
int evt = gamepad_axis2gameevt(axis);
|
|
|
|
if(evt >= 0) {
|
|
events_emit(evt, gamepad.axes[axis].analog, NULL, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gamepad_restrict_player_axis_vals(GamepadAxis new_axis, int new_val) {
|
|
typedef enum {
|
|
UP = (1 << 0),
|
|
DOWN = (1 << 1),
|
|
RIGHT = (1 << 3),
|
|
LEFT = (1 << 2),
|
|
} MoveDir;
|
|
|
|
GamepadAxis axis_x = config_get_int(CONFIG_GAMEPAD_AXIS_LR);
|
|
GamepadAxis axis_y = config_get_int(CONFIG_GAMEPAD_AXIS_UD);
|
|
|
|
int old_x = gamepad.axes[axis_x].analog;
|
|
int old_y = gamepad.axes[axis_y].analog;
|
|
|
|
gamepad.axes[new_axis].analog = new_val;
|
|
|
|
assert(axis_x > GAMEPAD_AXIS_INVALID && axis_x < GAMEPAD_AXIS_MAX);
|
|
assert(axis_y > GAMEPAD_AXIS_INVALID && axis_y < GAMEPAD_AXIS_MAX);
|
|
|
|
double x = gamepad_normalize_axis_value(gamepad_player_axis_value(PLRAXIS_LR));
|
|
double y = gamepad_normalize_axis_value(gamepad_player_axis_value(PLRAXIS_UD));
|
|
|
|
MoveDir move = 0;
|
|
|
|
if(x || y) {
|
|
int d = (int)rint(atan2(-y, x) / (M_PI/4));
|
|
|
|
switch(d) {
|
|
case 0: move = 0 | RIGHT; break;
|
|
case -1: move = UP | RIGHT; break;
|
|
case -2: move = UP | 0; break;
|
|
case -3: move = UP | LEFT; break;
|
|
case -4: case 4: move = 0 | LEFT; break;
|
|
case 3: move = DOWN | LEFT; break;
|
|
case 2: move = DOWN | 0; break;
|
|
case 1: move = DOWN | RIGHT; break;
|
|
}
|
|
}
|
|
|
|
gamepad.axes[axis_x].analog = (move & LEFT) ? GAMEPAD_AXIS_MIN_VALUE :
|
|
(move & RIGHT) ? GAMEPAD_AXIS_MAX_VALUE : 0;
|
|
|
|
gamepad.axes[axis_y].analog = (move & DOWN) ? GAMEPAD_AXIS_MIN_VALUE :
|
|
(move & UP) ? GAMEPAD_AXIS_MAX_VALUE : 0;
|
|
|
|
gamepad_update_game_axis(axis_x, old_x);
|
|
gamepad_update_game_axis(axis_y, old_y);
|
|
}
|
|
|
|
static void gamepad_axis(GamepadAxis id, int raw) {
|
|
int8_t digital = AXISVAL(gamepad_axis_process_value_deadzone(raw));
|
|
int16_t analog = gamepad_axis_process_value(id, raw);
|
|
|
|
if(digital * gamepad.axes[id].digital < 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);
|
|
|
|
int old_analog = gamepad.axes[id].analog;
|
|
gamepad.axes[id].raw = raw;
|
|
|
|
if(config_get_int(CONFIG_GAMEPAD_AXIS_FREE)) {
|
|
gamepad.axes[id].analog = analog;
|
|
gamepad_update_game_axis(id, old_analog);
|
|
} else if(gamepad_axis2gameevt(id) >= 0) {
|
|
gamepad_restrict_player_axis_vals(id, analog);
|
|
}
|
|
|
|
if(digital != AXISVAL_NULL) { // simulate press
|
|
if(!gamepad.axes[id].digital) {
|
|
gamepad.axes[id].digital = digital;
|
|
|
|
GamepadButton btn = gamepad_button_from_axis(id, digital);
|
|
|
|
if(btn != GAMEPAD_BUTTON_INVALID) {
|
|
gamepad_button(btn, SDL_PRESSED, false);
|
|
}
|
|
}
|
|
} else if(gamepad.axes[id].digital != AXISVAL_NULL) { // simulate release
|
|
GamepadButton btn = gamepad_button_from_axis(id, gamepad.axes[id].digital);
|
|
gamepad.axes[id].digital = AXISVAL_NULL;
|
|
|
|
if(btn != GAMEPAD_BUTTON_INVALID) {
|
|
gamepad_button(btn, SDL_RELEASED, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
static GamepadButtonState* gamepad_button_state(GamepadButton button) {
|
|
if(button > GAMEPAD_BUTTON_INVALID && button < GAMEPAD_BUTTON_MAX) {
|
|
return gamepad.buttons + button;
|
|
}
|
|
|
|
if(button & GAMEPAD_BUTTON_EMULATED) {
|
|
GamepadEmulatedButton ebutton = button & ~GAMEPAD_BUTTON_EMULATED;
|
|
|
|
if(ebutton > GAMEPAD_EMULATED_BUTTON_INVALID && ebutton < GAMEPAD_EMULATED_BUTTON_MAX) {
|
|
return gamepad.buttons + GAMEPAD_BUTTON_MAX + ebutton;
|
|
}
|
|
}
|
|
|
|
log_warn("Unknown button id %i ignored", button);
|
|
return NULL;
|
|
}
|
|
|
|
static const char* gamepad_button_name_internal(GamepadButton btn);
|
|
|
|
static struct {
|
|
GamepadButton button;
|
|
KeyIndex game_key;
|
|
} gamepad_default_button_mappings[] = {
|
|
{ GAMEPAD_BUTTON_BACK, KEY_SKIP },
|
|
};
|
|
|
|
#define NUM_DEFAULT_BUTTON_MAPPINGS (sizeof(gamepad_default_button_mappings)/sizeof(gamepad_default_button_mappings[0]))
|
|
|
|
static int gamepad_game_key_for_button(GamepadButton button) {
|
|
int gpkey = config_gamepad_key_from_gamepad_button(button);
|
|
int key = config_key_from_gamepad_key(gpkey);
|
|
|
|
if(key >= 0) {
|
|
return key;
|
|
}
|
|
|
|
for(int i = 0; i < NUM_DEFAULT_BUTTON_MAPPINGS; ++i) {
|
|
if(gamepad_default_button_mappings[i].button == button) {
|
|
return gamepad_default_button_mappings[i].game_key;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static inline hrtime_t gamepad_repeat_interval(ConfigIndex opt) {
|
|
return HRTIME_RESOLUTION * (double)config_get_float(opt);
|
|
}
|
|
|
|
static void gamepad_button(GamepadButton button, int state, bool is_repeat) {
|
|
int key = gamepad_game_key_for_button(button);
|
|
void *indev = (void*)(intptr_t)INDEV_GAMEPAD;
|
|
GamepadButtonState *btnstate = gamepad_button_state(button);
|
|
|
|
if(UNLIKELY(btnstate == NULL)) {
|
|
return;
|
|
}
|
|
|
|
if(state == SDL_PRESSED) {
|
|
if(is_repeat) {
|
|
btnstate->repeat_time = time_get() + gamepad_repeat_interval(CONFIG_GAMEPAD_BTNREPEAT_INTERVAL);
|
|
} else {
|
|
btnstate->repeat_time = time_get() + gamepad_repeat_interval(CONFIG_GAMEPAD_BTNREPEAT_DELAY);
|
|
}
|
|
|
|
btnstate->held = true;
|
|
|
|
if(gamepad_button_name_internal(button) != NULL) {
|
|
events_emit(TE_GAMEPAD_BUTTON_DOWN, button, indev, NULL);
|
|
}
|
|
|
|
if(!is_repeat || transition.state == TRANS_IDLE) {
|
|
static struct eventmap_s {
|
|
GamepadButton button;
|
|
TaiseiEvent events[3];
|
|
} eventmap[] = {
|
|
{ GAMEPAD_BUTTON_START, { TE_MENU_ACCEPT, TE_GAME_PAUSE, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_BACK, { TE_MENU_ABORT, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_DPAD_UP, { TE_MENU_CURSOR_UP, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_DPAD_DOWN, { TE_MENU_CURSOR_DOWN, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_DPAD_LEFT, { TE_MENU_CURSOR_LEFT, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_DPAD_RIGHT, { TE_MENU_CURSOR_RIGHT, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_ANALOG_STICK_UP, { TE_MENU_CURSOR_UP, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_ANALOG_STICK_DOWN, { TE_MENU_CURSOR_DOWN, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_ANALOG_STICK_LEFT, { TE_MENU_CURSOR_LEFT, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_ANALOG_STICK_RIGHT, { TE_MENU_CURSOR_RIGHT, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_A, { TE_MENU_ACCEPT, TE_INVALID } },
|
|
{ GAMEPAD_BUTTON_B, { TE_MENU_ABORT, TE_INVALID } },
|
|
};
|
|
|
|
for(int i = 0; i < sizeof(eventmap)/sizeof(eventmap[0]); ++i) {
|
|
struct eventmap_s *e = eventmap + i;
|
|
|
|
if(e->button == button) {
|
|
for(int j = 0; j < sizeof(e->events)/sizeof(e->events[0]) && e->events[j] > TE_INVALID; ++j) {
|
|
if(e->events[j] != TE_MENU_ACCEPT || !is_repeat) {
|
|
events_emit(e->events[j], 0, indev, NULL);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(key >= 0 && !is_repeat) {
|
|
events_emit(TE_GAME_KEY_DOWN, key, indev, NULL);
|
|
}
|
|
} else {
|
|
btnstate->held = false;
|
|
events_emit(TE_GAMEPAD_BUTTON_UP, button, indev, NULL);
|
|
|
|
if(key >= 0) {
|
|
events_emit(TE_GAME_KEY_UP, key, indev, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void gamepad_handle_button_repeat(GamepadButton btn, hrtime_t time) {
|
|
GamepadButtonState *state = gamepad_button_state(btn);
|
|
|
|
if(UNLIKELY(state == NULL)) {
|
|
return;
|
|
}
|
|
|
|
if(state->held && time >= state->repeat_time) {
|
|
gamepad_button(btn, SDL_PRESSED, true);
|
|
}
|
|
}
|
|
|
|
#define INSTANCE_IS_VALID(inst) \
|
|
( \
|
|
gamepad.active_dev_num == GAMEPAD_DEVNUM_ANY || \
|
|
( \
|
|
gamepad.active_dev_num >= 0 && \
|
|
(inst) == dynarray_get(&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(INSTANCE_IS_VALID(event->caxis.which)) {
|
|
GamepadAxis axis = gamepad_axis_from_sdl_axis(event->caxis.axis);
|
|
|
|
if(axis != GAMEPAD_AXIS_INVALID) {
|
|
gamepad_axis(axis, event->caxis.value);
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
if(INSTANCE_IS_VALID(event->cbutton.which)) {
|
|
GamepadButton btn = gamepad_button_from_sdl_button(event->cbutton.button);
|
|
|
|
if(btn != GAMEPAD_BUTTON_INVALID) {
|
|
gamepad_button(btn, event->cbutton.state, false);
|
|
}
|
|
}
|
|
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) {
|
|
gamepad_handle_button_repeat(btn, time);
|
|
}
|
|
|
|
for(GamepadEmulatedButton btn = 0; btn < GAMEPAD_EMULATED_BUTTON_MAX; ++btn) {
|
|
gamepad_handle_button_repeat(btn | GAMEPAD_BUTTON_EMULATED, time);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int gamepad_device_count(void) {
|
|
return gamepad.devices.num_elements;
|
|
}
|
|
|
|
void gamepad_device_guid(int num, char *guid_str, size_t guid_str_sz) {
|
|
if(num == GAMEPAD_DEVNUM_ANY) {
|
|
strlcpy(guid_str, "any", guid_str_sz);
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
if(strcasecmp(guid_str, "any")) {
|
|
return GAMEPAD_DEVNUM_ANY;
|
|
}
|
|
|
|
dynarray_foreach_idx(&gamepad.devices, int i, {
|
|
char guid[33] = {0};
|
|
gamepad_device_guid(i, guid, sizeof(guid));
|
|
|
|
if(!strcasecmp(guid_str, guid)) {
|
|
return i;
|
|
}
|
|
});
|
|
|
|
return GAMEPAD_DEVNUM_INVALID;
|
|
}
|
|
|
|
bool gamepad_button_pressed(GamepadButton btn) {
|
|
if(btn <= GAMEPAD_BUTTON_INVALID) {
|
|
return false;
|
|
}
|
|
|
|
if(btn < GAMEPAD_BUTTON_MAX) {
|
|
return gamepad.buttons[btn].held;
|
|
}
|
|
|
|
if(btn & GAMEPAD_BUTTON_EMULATED) {
|
|
GamepadEmulatedButton ebtn = btn & ~GAMEPAD_BUTTON_EMULATED;
|
|
|
|
if(ebtn > GAMEPAD_EMULATED_BUTTON_INVALID && ebtn < GAMEPAD_EMULATED_BUTTON_MAX) {
|
|
return gamepad.buttons[GAMEPAD_BUTTON_MAX + ebtn].held;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool gamepad_game_key_pressed(KeyIndex key) {
|
|
if(!gamepad.initialized) {
|
|
return false;
|
|
}
|
|
|
|
bool pressed;
|
|
int gpkey = config_gamepad_key_from_key(key);
|
|
|
|
if(gpkey < 0) {
|
|
pressed = false;
|
|
} else {
|
|
int cfgidx = GPKEYIDX_TO_CFGIDX(gpkey);
|
|
int button = config_get_int(cfgidx);
|
|
pressed = gamepad_button_pressed(button);
|
|
}
|
|
|
|
if(!pressed) {
|
|
for(int i = 0; i < NUM_DEFAULT_BUTTON_MAPPINGS; ++i) {
|
|
if(gamepad_default_button_mappings[i].game_key == key) {
|
|
pressed = gamepad_button_pressed(gamepad_default_button_mappings[i].button);
|
|
|
|
if(pressed) {
|
|
return pressed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return pressed;
|
|
}
|
|
|
|
static const char *const gamepad_button_names[GAMEPAD_BUTTON_MAX] = {
|
|
[GAMEPAD_BUTTON_A] = "A",
|
|
[GAMEPAD_BUTTON_B] = "B",
|
|
[GAMEPAD_BUTTON_X] = "X",
|
|
[GAMEPAD_BUTTON_Y] = "Y",
|
|
[GAMEPAD_BUTTON_BACK] = "Back",
|
|
[GAMEPAD_BUTTON_GUIDE] = "Guide",
|
|
[GAMEPAD_BUTTON_START] = "Start",
|
|
[GAMEPAD_BUTTON_STICK_LEFT] = "Left Stick",
|
|
[GAMEPAD_BUTTON_STICK_RIGHT] = "Right Stick",
|
|
[GAMEPAD_BUTTON_SHOULDER_LEFT] = "Left Bumper",
|
|
[GAMEPAD_BUTTON_SHOULDER_RIGHT] = "Right Bumper",
|
|
[GAMEPAD_BUTTON_DPAD_UP] = "Up",
|
|
[GAMEPAD_BUTTON_DPAD_DOWN] = "Down",
|
|
[GAMEPAD_BUTTON_DPAD_LEFT] = "Left",
|
|
[GAMEPAD_BUTTON_DPAD_RIGHT] = "Right",
|
|
[GAMEPAD_BUTTON_MISC1] = "Misc",
|
|
[GAMEPAD_BUTTON_P1] = "P1",
|
|
[GAMEPAD_BUTTON_P2] = "P2",
|
|
[GAMEPAD_BUTTON_P3] = "P3",
|
|
[GAMEPAD_BUTTON_P4] = "P4",
|
|
[GAMEPAD_BUTTON_TOUCHPAD] = "Touchpad",
|
|
};
|
|
|
|
static const char *const gamepad_emulated_button_names[GAMEPAD_EMULATED_BUTTON_MAX] = {
|
|
[GAMEPAD_EMULATED_BUTTON_TRIGGER_LEFT] = "Left Trigger",
|
|
[GAMEPAD_EMULATED_BUTTON_TRIGGER_RIGHT] = "Right Trigger",
|
|
};
|
|
|
|
static const char *const gamepad_axis_names[GAMEPAD_AXIS_MAX] = {
|
|
[GAMEPAD_AXIS_LEFT_X] = "Left X",
|
|
[GAMEPAD_AXIS_LEFT_Y] = "Left Y",
|
|
[GAMEPAD_AXIS_RIGHT_X] = "Right X",
|
|
[GAMEPAD_AXIS_RIGHT_Y] = "Right Y",
|
|
[GAMEPAD_AXIS_TRIGGER_LEFT] = "Left Trigger",
|
|
[GAMEPAD_AXIS_TRIGGER_RIGHT] = "Right Trigger",
|
|
};
|
|
|
|
static const char* gamepad_button_name_internal(GamepadButton btn) {
|
|
if(btn > GAMEPAD_BUTTON_INVALID && btn < GAMEPAD_BUTTON_MAX) {
|
|
return gamepad_button_names[btn];
|
|
}
|
|
|
|
if(btn & GAMEPAD_BUTTON_EMULATED) {
|
|
return gamepad_emulated_button_names[btn & ~GAMEPAD_BUTTON_EMULATED];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char* gamepad_button_name(GamepadButton btn) {
|
|
const char *name = gamepad_button_name_internal(btn);
|
|
|
|
if(name == NULL) {
|
|
return "Unknown";
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
const char* gamepad_axis_name(GamepadAxis axis) {
|
|
if(axis > GAMEPAD_AXIS_INVALID && axis < GAMEPAD_AXIS_MAX) {
|
|
return gamepad_axis_names[axis];
|
|
}
|
|
|
|
return "Unknown";
|
|
}
|
|
|
|
GamepadButton gamepad_button_from_name(const char *name) {
|
|
for(int i = 0; i < GAMEPAD_BUTTON_MAX; ++i) {
|
|
if(!strcasecmp(gamepad_button_names[i], name)) {
|
|
return (GamepadButton)i;
|
|
}
|
|
}
|
|
|
|
for(int i = 0; i < GAMEPAD_EMULATED_BUTTON_MAX; ++i) {
|
|
if(gamepad_emulated_button_names[i] && !strcasecmp(gamepad_emulated_button_names[i], name)) {
|
|
return (GamepadButton)(i | GAMEPAD_BUTTON_EMULATED);
|
|
}
|
|
}
|
|
|
|
// for compatibility
|
|
return (GamepadButton)SDL_GameControllerGetButtonFromString(name);
|
|
}
|
|
|
|
GamepadAxis gamepad_axis_from_name(const char *name) {
|
|
for(int i = 0; i < GAMEPAD_AXIS_MAX; ++i) {
|
|
if(!strcasecmp(gamepad_axis_names[i], name)) {
|
|
return (GamepadAxis)i;
|
|
}
|
|
}
|
|
|
|
// for compatibility
|
|
return (GamepadAxis)SDL_GameControllerGetAxisFromString(name);
|
|
}
|
|
|
|
GamepadButton gamepad_button_from_sdl_button(SDL_GameControllerButton btn) {
|
|
if(btn <= SDL_CONTROLLER_BUTTON_INVALID || btn >= SDL_CONTROLLER_BUTTON_MAX) {
|
|
return GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
|
|
return (GamepadButton)btn;
|
|
}
|
|
|
|
GamepadButton gamepad_button_from_axis(GamepadAxis axis, GamepadAxisDigitalValue dval) {
|
|
switch(axis) {
|
|
case GAMEPAD_AXIS_TRIGGER_LEFT:
|
|
return GAMEPAD_BUTTON_TRIGGER_LEFT;
|
|
|
|
case GAMEPAD_AXIS_TRIGGER_RIGHT:
|
|
return GAMEPAD_BUTTON_TRIGGER_RIGHT;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(axis == GAMEPAD_AXIS_LEFT_Y || axis == GAMEPAD_AXIS_RIGHT_Y) {
|
|
switch(dval) {
|
|
case AXISVAL_UP:
|
|
return GAMEPAD_BUTTON_ANALOG_STICK_UP;
|
|
|
|
case AXISVAL_DOWN:
|
|
return GAMEPAD_BUTTON_ANALOG_STICK_DOWN;
|
|
|
|
default:
|
|
return GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
}
|
|
|
|
if(axis == GAMEPAD_AXIS_LEFT_X || axis == GAMEPAD_AXIS_RIGHT_X) {
|
|
switch(dval) {
|
|
case AXISVAL_LEFT:
|
|
return GAMEPAD_BUTTON_ANALOG_STICK_LEFT;
|
|
|
|
case AXISVAL_RIGHT:
|
|
return GAMEPAD_BUTTON_ANALOG_STICK_RIGHT;
|
|
|
|
default:
|
|
return GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
}
|
|
|
|
return GAMEPAD_BUTTON_INVALID;
|
|
}
|
|
|
|
SDL_GameControllerButton gamepad_button_to_sdl_button(GamepadButton btn) {
|
|
if(btn <= GAMEPAD_BUTTON_INVALID || btn >= GAMEPAD_BUTTON_MAX) {
|
|
return SDL_CONTROLLER_BUTTON_INVALID;
|
|
}
|
|
|
|
return (SDL_GameControllerButton)btn;
|
|
}
|
|
|
|
GamepadAxis gamepad_axis_from_sdl_axis(SDL_GameControllerAxis axis) {
|
|
if(axis <= SDL_CONTROLLER_AXIS_INVALID || axis >= SDL_CONTROLLER_AXIS_MAX) {
|
|
return GAMEPAD_AXIS_INVALID;
|
|
}
|
|
|
|
return (GamepadAxis)axis;
|
|
}
|
|
|
|
SDL_GameControllerAxis gamepad_axis_to_sdl_axis(GamepadAxis axis) {
|
|
if(axis <= GAMEPAD_AXIS_INVALID || axis >= GAMEPAD_AXIS_MAX) {
|
|
return SDL_CONTROLLER_AXIS_INVALID;
|
|
}
|
|
|
|
return (SDL_GameControllerAxis)axis;
|
|
}
|