Mercurial > repos > blastem
view bindings.c @ 2316:523ab225815b
Allow dpad to increment/decrement property fields
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Sun, 02 Apr 2023 23:21:04 -0700 |
parents | 59fd8aa352e2 |
children | e836cf11783b |
line wrap: on
line source
#include <string.h> #include <stdlib.h> #include "render.h" #include "system.h" #include "io.h" #include "blastem.h" #include "saves.h" #include "util.h" #include "genesis.h" #include "sms.h" #include "menu.h" #include "bindings.h" #include "controller_info.h" #ifndef DISABLE_NUKLEAR #include "nuklear_ui/blastem_nuklear.h" #endif enum { BIND_NONE, BIND_UI, BIND_GAMEPAD, BIND_MOUSE }; typedef enum { UI_DEBUG_MODE_INC, UI_ENTER_DEBUGGER, UI_SAVE_STATE, UI_LOAD_STATE, UI_SET_SPEED, UI_NEXT_SPEED, UI_PREV_SPEED, UI_RELEASE_MOUSE, UI_TOGGLE_KEYBOARD_CAPTURE, UI_TOGGLE_FULLSCREEN, UI_SOFT_RESET, UI_RELOAD, UI_SMS_PAUSE, UI_SCREENSHOT, UI_RECORD_VIDEO, UI_VGM_LOG, UI_MENU, UI_EXIT, UI_PLANE_DEBUG, UI_VRAM_DEBUG, UI_CRAM_DEBUG, UI_COMPOSITE_DEBUG, UI_OSCILLOSCOPE_DEBUG, UI_CD_GRAPHICS_DEBUG } ui_action; typedef struct { uint8_t bind_type; uint8_t subtype_a; uint8_t subtype_b; } keybinding; typedef struct { keybinding bindings[4]; uint8_t state; } joydpad; typedef struct { keybinding positive; keybinding negative; int16_t value; } joyaxis; typedef struct { keybinding *buttons; joydpad *dpads; joyaxis *axes; uint32_t num_buttons; //number of entries in the buttons array, not necessarily the number of buttons on the device uint32_t num_dpads; //number of entries in the dpads array, not necessarily the number of dpads on the device uint32_t num_axes; //number of entries in the axes array, not necessarily the number of dpads on the device } joystick; typedef struct { keybinding buttons[MAX_MOUSE_BUTTONS]; keybinding motion; } mousebinding; #define DEFAULT_JOYBUTTON_ALLOC 12 static keybinding *bindings[0x10000]; static joystick joysticks[MAX_JOYSTICKS]; static mousebinding mice[MAX_MICE]; const uint8_t dpadbits[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT}; static void do_bind(keybinding *binding, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) { binding->bind_type = bind_type; binding->subtype_a = subtype_a; binding->subtype_b = subtype_b; } void bind_key(int keycode, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) { int bucket = keycode >> 15 & 0xFFFF; if (!bindings[bucket]) { bindings[bucket] = malloc(sizeof(keybinding) * 0x8000); memset(bindings[bucket], 0, sizeof(keybinding) * 0x8000); } int idx = keycode & 0x7FFF; do_bind(bindings[bucket] + idx, bind_type, subtype_a, subtype_b); } void bind_button(int joystick, int button, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) { if (joystick >= MAX_JOYSTICKS) { return; } if (!joysticks[joystick].buttons) { joysticks[joystick].num_buttons = button < DEFAULT_JOYBUTTON_ALLOC ? DEFAULT_JOYBUTTON_ALLOC : button + 1; joysticks[joystick].buttons = calloc(joysticks[joystick].num_buttons, sizeof(keybinding)); } else if (joysticks[joystick].num_buttons <= button) { uint32_t old_capacity = joysticks[joystick].num_buttons; joysticks[joystick].num_buttons *= 2; joysticks[joystick].buttons = realloc(joysticks[joystick].buttons, sizeof(keybinding) * joysticks[joystick].num_buttons); memset(joysticks[joystick].buttons + old_capacity, 0, joysticks[joystick].num_buttons - old_capacity); } do_bind(joysticks[joystick].buttons + button, bind_type, subtype_a, subtype_b); } void bind_dpad(int joystick, int dpad, int direction, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) { if (joystick >= MAX_JOYSTICKS) { return; } if (!joysticks[joystick].dpads) { //multiple D-pads/hats are not common, so don't allocate any extra space joysticks[joystick].dpads = calloc(dpad+1, sizeof(joydpad)); joysticks[joystick].num_dpads = dpad+1; } else if (joysticks[joystick].num_dpads <= dpad) { uint32_t old_capacity = joysticks[joystick].num_dpads; joysticks[joystick].num_dpads *= 2; joysticks[joystick].dpads = realloc(joysticks[joystick].dpads, sizeof(joydpad) * joysticks[joystick].num_dpads); memset(joysticks[joystick].dpads + old_capacity, 0, (joysticks[joystick].num_dpads - old_capacity) * sizeof(joydpad)); } for (int i = 0; i < 4; i ++) { if (dpadbits[i] & direction) { do_bind(joysticks[joystick].dpads[dpad].bindings + i, bind_type, subtype_a, subtype_b); break; } } } void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) { if (joystick >= MAX_JOYSTICKS) { return; } if (!joysticks[joystick].axes) { //typical gamepad has 4 axes joysticks[joystick].num_axes = axis+1 > 4 ? axis+1 : 4; joysticks[joystick].axes = calloc(joysticks[joystick].num_axes, sizeof(joyaxis)); } else if (joysticks[joystick].num_axes <= axis) { uint32_t old_capacity = joysticks[joystick].num_axes; joysticks[joystick].num_axes *= 2; joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes); memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis)); } if (positive) { do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b); } else { do_bind(&joysticks[joystick].axes[axis].negative, bind_type, subtype_a, subtype_b); } } void reset_joystick_bindings(int joystick) { if (joystick >= MAX_JOYSTICKS) { return; } if (joysticks[joystick].buttons) { for (int i = 0; i < joysticks[joystick].num_buttons; i++) { joysticks[joystick].buttons[i].bind_type = BIND_NONE; } } if (joysticks[joystick].dpads) { for (int i = 0; i < joysticks[joystick].num_dpads; i++) { for (int dir = 0; dir < 4; dir++) { joysticks[joystick].dpads[i].bindings[dir].bind_type = BIND_NONE; } } } if (joysticks[joystick].axes) { for (int i = 0; i < joysticks[joystick].num_axes; i++) { joysticks[joystick].axes[i].positive.bind_type = BIND_NONE; joysticks[joystick].axes[i].negative.bind_type = BIND_NONE; } } } static uint8_t content_binds_enabled = 1; void set_content_binding_state(uint8_t enabled) { content_binds_enabled = enabled; } void handle_binding_down(keybinding * binding) { if (!current_system || !content_binds_enabled) { return; } if (binding->bind_type == BIND_GAMEPAD && current_system && current_system->gamepad_down) { current_system->gamepad_down(current_system, binding->subtype_a, binding->subtype_b); } else if (binding->bind_type == BIND_MOUSE && current_system && current_system->mouse_down) { current_system->mouse_down(current_system, binding->subtype_a, binding->subtype_b); } } static uint8_t keyboard_captured; void handle_keydown(int keycode, uint8_t scancode) { int bucket = keycode >> 15 & 0xFFFF; int idx = keycode & 0x7FFF; keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL; if (binding && (!keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) { handle_binding_down(binding); } else if (keyboard_captured && current_system && current_system->keyboard_down) { current_system->keyboard_down(current_system, scancode); } } void handle_joydown(int joystick, int button) { if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) { return; } keybinding * binding = joysticks[joystick].buttons + button; handle_binding_down(binding); } static uint8_t mouse_mode = MOUSE_NONE; static uint8_t mouse_captured; void handle_mousedown(int mouse, int button) { if (mouse_mode == MOUSE_CAPTURE && !mouse_captured) { mouse_captured = 1; render_relative_mouse(1); return; } if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) { return; } keybinding * binding = mice[mouse].buttons + button - 1; handle_binding_down(binding); } static int current_speed = 0; static int num_speeds = 1; static uint32_t * speeds = NULL; static uint8_t mouse_captured; #ifdef _WIN32 #define localtime_r(a,b) localtime(a) #endif char *get_content_config_path(char *config_path, char *config_template, char *default_name) { char *base = tern_find_path(config, config_path, TVAL_PTR).ptrval; if (!base) { base = "$HOME"; } const system_media *media = current_media(); tern_node *vars = tern_insert_ptr(NULL, "HOME", get_home_dir()); vars = tern_insert_ptr(vars, "EXEDIR", get_exe_dir()); vars = tern_insert_ptr(vars, "USERDATA", (char *)get_userdata_dir()); vars = tern_insert_ptr(vars, "ROMNAME", media->name); vars = tern_insert_ptr(vars, "ROMDIR", media->dir); base = replace_vars(base, vars, 1); tern_free(vars); ensure_dir_exists(base); time_t now = time(NULL); struct tm local_store; char fname_part[256]; char *template = tern_find_path(config, config_template, TVAL_PTR).ptrval; if (template) { vars = tern_insert_ptr(NULL, "ROMNAME", media->name); template = replace_vars(template, vars, 0); } else { template = strdup(default_name); } strftime(fname_part, sizeof(fname_part), template, localtime_r(&now, &local_store)); char const *parts[] = {base, PATH_SEP, fname_part}; char *path = alloc_concat_m(3, parts); free(base); free(template); return path; } void handle_binding_up(keybinding * binding) { uint8_t allow_content_binds = content_binds_enabled && current_system; switch(binding->bind_type) { case BIND_GAMEPAD: if (allow_content_binds && current_system->gamepad_up) { current_system->gamepad_up(current_system, binding->subtype_a, binding->subtype_b); } break; case BIND_MOUSE: if (allow_content_binds && current_system->mouse_up) { current_system->mouse_up(current_system, binding->subtype_a, binding->subtype_b); } break; case BIND_UI: switch (binding->subtype_a) { case UI_DEBUG_MODE_INC: if (allow_content_binds) { current_system->inc_debug_mode(current_system); } break; case UI_ENTER_DEBUGGER: if (allow_content_binds) { current_system->enter_debugger = 1; } break; case UI_SAVE_STATE: if (allow_content_binds) { current_system->save_state = QUICK_SAVE_SLOT+1; } break; case UI_LOAD_STATE: if (allow_content_binds) { current_system->load_state(current_system, QUICK_SAVE_SLOT); } break; case UI_NEXT_SPEED: if (allow_content_binds) { current_speed++; if (current_speed >= num_speeds) { current_speed = 0; } printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); current_system->set_speed_percent(current_system, speeds[current_speed]); } break; case UI_PREV_SPEED: if (allow_content_binds) { current_speed--; if (current_speed < 0) { current_speed = num_speeds - 1; } printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); current_system->set_speed_percent(current_system, speeds[current_speed]); } break; case UI_SET_SPEED: if (allow_content_binds) { if (binding->subtype_b < num_speeds) { current_speed = binding->subtype_b; printf("Setting speed to %d: %d\n", current_speed, speeds[current_speed]); current_system->set_speed_percent(current_system, speeds[current_speed]); } else { printf("Setting speed to %d\n", speeds[current_speed]); current_system->set_speed_percent(current_system, speeds[current_speed]); } } break; case UI_RELEASE_MOUSE: if (mouse_captured) { mouse_captured = 0; render_relative_mouse(0); } break; case UI_TOGGLE_KEYBOARD_CAPTURE: if (allow_content_binds && current_system->has_keyboard) { keyboard_captured = !keyboard_captured; } break; case UI_TOGGLE_FULLSCREEN: render_toggle_fullscreen(); break; case UI_SOFT_RESET: if (allow_content_binds) { current_system->soft_reset(current_system); } break; case UI_RELOAD: if (allow_content_binds) { system_media *lock = current_media()->chain; if (lock) { const char* parts[] = {lock->dir, PATH_SEP, lock->name, ".", lock->extension}; char const **start = parts[0] ? parts : parts + 2; int num_parts = parts[0] ? 5 : 3; if (!parts[4]) { num_parts -= 2; } char *path = alloc_concat_m(num_parts, start); lockon_media(path); free(path); } else { reload_media(); } } break; case UI_SMS_PAUSE: if (allow_content_binds && current_system->gamepad_down) { current_system->gamepad_down(current_system, GAMEPAD_MAIN_UNIT, MAIN_UNIT_PAUSE); } break; case UI_SCREENSHOT: if (allow_content_binds) { char *path = get_content_config_path("ui\0screenshot_path\0", "ui\0screenshot_template\0", "blastem_%c.ppm"); render_save_screenshot(path); } break; case UI_RECORD_VIDEO: if (allow_content_binds) { if (render_saving_video()) { render_end_video(); render_end_audio(); } else { char *path = get_content_config_path("ui\0video_path\0", "ui\0video_template\0", "blastem_%c.apng"); render_save_video(path); path = get_content_config_path("ui\0audio_path\0", "ui\0audio_template\0", "blastem_%c.wav"); render_save_audio(path); } } break; case UI_VGM_LOG: if (allow_content_binds && current_system->start_vgm_log) { if (current_system->vgm_logging) { current_system->stop_vgm_log(current_system); } else { char *path = get_content_config_path("ui\0vgm_path\0", "ui\0vgm_template\0", "blastem_%c.vgm"); current_system->start_vgm_log(current_system, path); free(path); } } break; case UI_MENU: #ifndef DISABLE_NUKLEAR if (is_nuklear_active()) { show_pause_menu(); } else { #endif system_request_exit(current_system, 1); if (current_system->type == SYSTEM_GENESIS) { genesis_context *gen = (genesis_context *)current_system; if (gen->extra) { //TODO: More robust mechanism for detecting menu menu_context *menu = gen->extra; menu->external_game_load = 1; } } #ifndef DISABLE_NUKLEAR } #endif break; case UI_EXIT: exit(0); case UI_PLANE_DEBUG: case UI_VRAM_DEBUG: case UI_CRAM_DEBUG: case UI_COMPOSITE_DEBUG: case UI_OSCILLOSCOPE_DEBUG: case UI_CD_GRAPHICS_DEBUG: if (allow_content_binds && current_system->toggle_debug_view) { current_system->toggle_debug_view(current_system, binding->subtype_a - UI_PLANE_DEBUG + DEBUG_PLANE); /* vdp_context *vdp = NULL; if (current_system->type == SYSTEM_GENESIS || current_system->type == SYSTEM_SEGACD) { genesis_context *gen = (genesis_context *)current_system; vdp = gen->vdp; } else if (current_system->type == SYSTEM_SMS) { sms_context *sms = (sms_context *)current_system; vdp = sms->vdp; } if (vdp) { uint8_t debug_type; switch(binding->subtype_a) { case UI_PLANE_DEBUG: debug_type = VDP_DEBUG_PLANE; break; case UI_VRAM_DEBUG: debug_type = VDP_DEBUG_VRAM; break; case UI_CRAM_DEBUG: debug_type = VDP_DEBUG_CRAM; break; case UI_COMPOSITE_DEBUG: debug_type = VDP_DEBUG_COMPOSITE; break; default: return; } vdp_toggle_debug_view(vdp, debug_type); }*/ break; } } break; } } void handle_keyup(int keycode, uint8_t scancode) { int bucket = keycode >> 15 & 0xFFFF; int idx = keycode & 0x7FFF; keybinding * binding = bindings[bucket] ? bindings[bucket] + idx : NULL; if (binding && (!keyboard_captured || (binding->bind_type == BIND_UI && binding->subtype_a == UI_TOGGLE_KEYBOARD_CAPTURE))) { handle_binding_up(binding); } else if (keyboard_captured && current_system && current_system->keyboard_up) { current_system->keyboard_up(current_system, scancode); } } void handle_joyup(int joystick, int button) { if (joystick >= MAX_JOYSTICKS || button >= joysticks[joystick].num_buttons) { return; } keybinding * binding = joysticks[joystick].buttons + button; handle_binding_up(binding); } void handle_joy_dpad(int joystick, int dpadnum, uint8_t value) { if (joystick >= MAX_JOYSTICKS || dpadnum >= joysticks[joystick].num_dpads) { return; } joydpad * dpad = joysticks[joystick].dpads + dpadnum; uint8_t newdown = (value ^ dpad->state) & value; uint8_t newup = ((~value) ^ (~dpad->state)) & (~value); dpad->state = value; for (int i = 0; i < 4; i++) { if (newdown & dpadbits[i]) { handle_binding_down(dpad->bindings + i); } else if(newup & dpadbits[i]) { handle_binding_up(dpad->bindings + i); } } } #define JOY_AXIS_THRESHOLD 2000 void handle_joy_axis(int joystick, int axis, int16_t value) { if (joystick >= MAX_JOYSTICKS || axis >= joysticks[joystick].num_axes) { return; } joyaxis *jaxis = joysticks[joystick].axes + axis; int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD; int new_active = abs(value) > JOY_AXIS_THRESHOLD; int old_pos = jaxis->value > 0; int new_pos = value > 0; jaxis->value = value; if (old_active && (!new_active || old_pos != new_pos)) { //previously activated direction is no longer active handle_binding_up(old_pos ? &jaxis->positive : &jaxis->negative); } if (new_active && (!old_active || old_pos != new_pos)) { //previously unactivated direction is now active handle_binding_down(new_pos ? &jaxis->positive : &jaxis->negative); } } void handle_mouse_moved(int mouse, uint16_t x, uint16_t y, int16_t deltax, int16_t deltay) { if (mouse >= MAX_MICE || !current_system) { return; } if (mice[mouse].motion.bind_type == BIND_MOUSE && mice[mouse].motion.subtype_b == PSEUDO_BUTTON_MOTION) { uint8_t target_mouse = mice[mouse].motion.subtype_a; switch(mouse_mode) { case MOUSE_NONE: break; case MOUSE_ABSOLUTE: { if (current_system->mouse_motion_absolute) { float scale_x = (render_emulated_width() * 2.0f) / ((float)render_width()); float scale_y = (render_emulated_height() * 2.0f) / ((float)render_height()); int32_t adj_x = x * scale_x + 2 * render_overscan_left() - 2 * BORDER_LEFT; int32_t adj_y = y * scale_y + 2 * render_overscan_top() - 4; current_system->mouse_motion_absolute(current_system, target_mouse, adj_x, adj_y); } break; } case MOUSE_RELATIVE: { if (current_system->mouse_motion_relative) { current_system->mouse_motion_relative(current_system, target_mouse, deltax, deltay); } break; } case MOUSE_CAPTURE: { if (mouse_captured && current_system->mouse_motion_relative) { current_system->mouse_motion_relative(current_system, target_mouse, deltax, deltay); } break; } } } else { handle_binding_up(&mice[mouse].motion); } } void handle_mouseup(int mouse, int button) { if (mouse >= MAX_MICE || button > MAX_MOUSE_BUTTONS || button <= 0) { return; } keybinding * binding = mice[mouse].buttons + button - 1; handle_binding_up(binding); } void bindings_release_capture(void) { if (mouse_mode == MOUSE_RELATIVE || (mouse_mode == MOUSE_CAPTURE && mouse_captured)) { render_relative_mouse(0); } keyboard_captured = 0; } void bindings_reacquire_capture(void) { if (mouse_mode == MOUSE_RELATIVE || (mouse_mode == MOUSE_CAPTURE && mouse_captured)) { render_relative_mouse(1); } } int parse_binding_target(int device_num, const char * target, tern_node * padbuttons, tern_node *mousebuttons, uint8_t * subtype_a, uint8_t * subtype_b) { const int gpadslen = strlen("gamepads."); const int mouselen = strlen("mouse."); if (startswith(target, "gamepads.")) { int padnum = target[gpadslen] == 'n' ? device_num + 1 : target[gpadslen] - '0'; if (padnum >= 1 && padnum <= 8) { int button = tern_find_int(padbuttons, target + gpadslen + 1, 0); if (button) { *subtype_a = padnum; *subtype_b = button; return BIND_GAMEPAD; } else { if (target[gpadslen+1]) { warning("Gamepad mapping string '%s' refers to an invalid button '%s'\n", target, target + gpadslen + 1); } else { warning("Gamepad mapping string '%s' has no button component\n", target); } } } else { warning("Gamepad mapping string '%s' refers to an invalid gamepad number %c\n", target, target[gpadslen]); } } else if(startswith(target, "mouse.")) { int mousenum = target[mouselen] == 'n' ? device_num + 1 : target[mouselen] - '0'; if (mousenum >= 1 && mousenum <= 8) { int button = tern_find_int(mousebuttons, target + mouselen + 1, 0); if (button) { *subtype_a = mousenum; *subtype_b = button; return BIND_MOUSE; } else { if (target[mouselen+1]) { warning("Mouse mapping string '%s' refers to an invalid button '%s'\n", target, target + mouselen + 1); } else { warning("Mouse mapping string '%s' has no button component\n", target); } } } else { warning("Gamepad mapping string '%s' refers to an invalid mouse number %c\n", target, target[mouselen]); } } else if(startswith(target, "ui.")) { if (!strcmp(target + 3, "vdp_debug_mode")) { *subtype_a = UI_DEBUG_MODE_INC; } else if(!strcmp(target + 3, "vdp_debug_pal")) { //legacy binding, ignore return 0; } else if(!strcmp(target + 3, "enter_debugger")) { *subtype_a = UI_ENTER_DEBUGGER; } else if(!strcmp(target + 3, "save_state")) { *subtype_a = UI_SAVE_STATE; } else if(!strcmp(target + 3, "load_state")) { *subtype_a = UI_LOAD_STATE; } else if(startswith(target + 3, "set_speed.")) { *subtype_a = UI_SET_SPEED; *subtype_b = atoi(target + 3 + strlen("set_speed.")); } else if(!strcmp(target + 3, "next_speed")) { *subtype_a = UI_NEXT_SPEED; } else if(!strcmp(target + 3, "prev_speed")) { *subtype_a = UI_PREV_SPEED; } else if(!strcmp(target + 3, "release_mouse")) { *subtype_a = UI_RELEASE_MOUSE; } else if(!strcmp(target + 3, "toggle_keyboard_captured")) { *subtype_a = UI_TOGGLE_KEYBOARD_CAPTURE; } else if (!strcmp(target + 3, "toggle_fullscreen")) { *subtype_a = UI_TOGGLE_FULLSCREEN; } else if (!strcmp(target + 3, "soft_reset")) { *subtype_a = UI_SOFT_RESET; } else if (!strcmp(target + 3, "reload")) { *subtype_a = UI_RELOAD; } else if (!strcmp(target + 3, "sms_pause")) { *subtype_a = UI_SMS_PAUSE; } else if (!strcmp(target + 3, "screenshot")) { *subtype_a = UI_SCREENSHOT; } else if (!strcmp(target + 3, "record_video")) { *subtype_a = UI_RECORD_VIDEO; } else if (!strcmp(target + 3, "vgm_log")) { *subtype_a = UI_VGM_LOG; } else if(!strcmp(target + 3, "menu")) { *subtype_a = UI_MENU; } else if(!strcmp(target + 3, "exit")) { *subtype_a = UI_EXIT; } else if (!strcmp(target + 3, "plane_debug")) { *subtype_a = UI_PLANE_DEBUG; } else if (!strcmp(target + 3, "vram_debug")) { *subtype_a = UI_VRAM_DEBUG; } else if (!strcmp(target + 3, "cram_debug")) { *subtype_a = UI_CRAM_DEBUG; } else if (!strcmp(target + 3, "compositing_debug")) { *subtype_a = UI_COMPOSITE_DEBUG; } else if (!strcmp(target + 3, "oscilloscope")) { *subtype_a = UI_OSCILLOSCOPE_DEBUG; } else if (!strcmp(target + 3, "cd_graphics_debug")) { *subtype_a = UI_CD_GRAPHICS_DEBUG; } else { warning("Unreconized UI binding type %s\n", target); return 0; } return BIND_UI; } else { warning("Unrecognized binding type %s\n", target); } return 0; } void process_keys(tern_node * cur, tern_node * special, tern_node * padbuttons, tern_node *mousebuttons, char * prefix) { char * curstr = NULL; int len; if (!cur) { return; } char onec[2]; if (prefix) { len = strlen(prefix); curstr = malloc(len + 2); memcpy(curstr, prefix, len); } else { curstr = onec; len = 0; } curstr[len] = cur->el; curstr[len+1] = 0; if (cur->el) { process_keys(cur->straight.next, special, padbuttons, mousebuttons, curstr); } else { int keycode = tern_find_int(special, curstr, 0); if (!keycode) { keycode = curstr[0]; if (curstr[1] != 0) { warning("%s is not recognized as a key identifier, truncating to %c\n", curstr, curstr[0]); } } char * target = cur->straight.value.ptrval; uint8_t subtype_a = 0, subtype_b = 0; int bindtype = parse_binding_target(0, target, padbuttons, mousebuttons, &subtype_a, &subtype_b); bind_key(keycode, bindtype, subtype_a, subtype_b); } process_keys(cur->left, special, padbuttons, mousebuttons, prefix); process_keys(cur->right, special, padbuttons, mousebuttons, prefix); if (curstr && len) { free(curstr); } } void process_speeds(tern_node * cur, char * prefix) { char * curstr = NULL; int len; if (!cur) { return; } char onec[2]; if (prefix) { len = strlen(prefix); curstr = malloc(len + 2); memcpy(curstr, prefix, len); } else { curstr = onec; len = 0; } curstr[len] = cur->el; curstr[len+1] = 0; if (cur->el) { process_speeds(cur->straight.next, curstr); } else { char *end; long speed_index = strtol(curstr, &end, 10); if (speed_index < 0 || end == curstr || *end) { warning("%s is not a valid speed index", curstr); } else { if (speed_index >= num_speeds) { speeds = realloc(speeds, sizeof(uint32_t) * (speed_index+1)); for(; num_speeds < speed_index + 1; num_speeds++) { speeds[num_speeds] = 0; } } speeds[speed_index] = atoi(cur->straight.value.ptrval); if (speeds[speed_index] < 1) { warning("%s is not a valid speed percentage, setting speed %d to 100", cur->straight.value.ptrval, speed_index); speeds[speed_index] = 100; } } } process_speeds(cur->left, prefix); process_speeds(cur->right, prefix); if (curstr && len) { free(curstr); } } typedef struct { tern_node *padbuttons; tern_node *mousebuttons; int mouseidx; } pmb_state; void process_mouse_button(char *buttonstr, tern_val value, uint8_t valtype, void *data) { pmb_state *state = data; int buttonnum = atoi(buttonstr); if (buttonnum < 1 || buttonnum > MAX_MOUSE_BUTTONS) { warning("Mouse button %s is out of the supported range of 1-8\n", buttonstr); return; } if (valtype != TVAL_PTR) { warning("Mouse button %s is not a scalar value!\n", buttonstr); return; } buttonnum--; uint8_t subtype_a = 0, subtype_b = 0; int bindtype = parse_binding_target(state->mouseidx, value.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b); mice[state->mouseidx].buttons[buttonnum].bind_type = bindtype; mice[state->mouseidx].buttons[buttonnum].subtype_a = subtype_a; mice[state->mouseidx].buttons[buttonnum].subtype_b = subtype_b; } void process_mouse(char *mousenum, tern_val value, uint8_t valtype, void *data) { tern_node **buttonmaps = data; if (valtype != TVAL_NODE) { warning("Binding for mouse %s is a scalar!\n", mousenum); return; } tern_node *mousedef = value.ptrval; tern_node *padbuttons = buttonmaps[0]; tern_node *mousebuttons = buttonmaps[1]; int mouseidx = atoi(mousenum); if (mouseidx < 0 || mouseidx >= MAX_MICE) { warning("Mouse numbers must be between 0 and %d, but %d is not\n", MAX_MICE, mouseidx); return; } char *motion = tern_find_ptr(mousedef, "motion"); if (motion) { uint8_t subtype_a = 0, subtype_b = 0; int bindtype = parse_binding_target(mouseidx, motion, padbuttons, mousebuttons, &subtype_a, &subtype_b); mice[mouseidx].motion.bind_type = bindtype; mice[mouseidx].motion.subtype_a = subtype_a; mice[mouseidx].motion.subtype_b = subtype_b; } tern_node *buttons = tern_find_path(mousedef, "buttons\0\0", TVAL_NODE).ptrval; if (buttons) { pmb_state state = {padbuttons, mousebuttons, mouseidx}; tern_foreach(buttons, process_mouse_button, &state); } } typedef struct { int padnum; tern_node *padbuttons; tern_node *mousebuttons; } pad_button_state; static long map_warning_pad = -1; void process_pad_button(char *key, tern_val val, uint8_t valtype, void *data) { pad_button_state *state = data; int hostpadnum = state->padnum; if (valtype != TVAL_PTR) { warning("Pad button %s has a non-scalar value\n", key); return; } uint8_t subtype_a = 0, subtype_b = 0; int bindtype = parse_binding_target(hostpadnum, val.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b); char *end; long hostbutton = strtol(key, &end, 10); if (*end) { //key is not a valid base 10 integer hostbutton = render_translate_input_name(hostpadnum, key, 0); if (hostbutton < 0) { if (hostbutton == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); } else if (hostbutton == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); map_warning_pad = hostpadnum; } return; } if (hostbutton & RENDER_DPAD_BIT) { bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b); return; } else if (hostbutton & RENDER_AXIS_BIT) { bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, bindtype, subtype_a, subtype_b); return; } } bind_button(hostpadnum, hostbutton, bindtype, subtype_a, subtype_b); } void process_pad_axis(char *key, tern_val val, uint8_t valtype, void *data) { key = strdup(key); pad_button_state *state = data; int hostpadnum = state->padnum; if (valtype != TVAL_PTR) { warning("Mapping for axis %s has a non-scalar value", key); return; } uint8_t subtype_a = 0, subtype_b = 0; int bindtype = parse_binding_target(hostpadnum, val.ptrval, state->padbuttons, state->mousebuttons, &subtype_a, &subtype_b); char *modifier = strchr(key, '.'); int positive = 1; if (modifier) { *modifier = 0; modifier++; if (!strcmp("negative", modifier)) { positive = 0; } else if(strcmp("positive", modifier)) { warning("Invalid axis modifier %s for axis %s on pad %d\n", modifier, key, hostpadnum); } } char *end; long axis = strtol(key, &end, 10); if (*end) { //key is not a valid base 10 integer axis = render_translate_input_name(hostpadnum, key, 1); if (axis < 0) { if (axis == RENDER_INVALID_NAME) { warning("%s is not a valid gamepad input name\n", key); } else if (axis == RENDER_NOT_MAPPED && hostpadnum != map_warning_pad) { debug_message("No SDL 2 mapping exists for input %s on gamepad %d\n", key, hostpadnum); map_warning_pad = hostpadnum; } goto done; } if (axis & RENDER_DPAD_BIT) { bind_dpad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), bindtype, subtype_a, subtype_b); goto done; } else if (axis & RENDER_AXIS_BIT) { axis = render_axis_part(axis); } else { bind_button(hostpadnum, axis, bindtype, subtype_a, subtype_b); goto done; } } bind_axis(hostpadnum, axis, positive, bindtype, subtype_a, subtype_b); done: free(key); return; } static tern_node *get_pad_buttons() { static tern_node *padbuttons; if (!padbuttons) { padbuttons = tern_insert_int(NULL, ".up", DPAD_UP); padbuttons = tern_insert_int(padbuttons, ".down", DPAD_DOWN); padbuttons = tern_insert_int(padbuttons, ".left", DPAD_LEFT); padbuttons = tern_insert_int(padbuttons, ".right", DPAD_RIGHT); padbuttons = tern_insert_int(padbuttons, ".a", BUTTON_A); padbuttons = tern_insert_int(padbuttons, ".b", BUTTON_B); padbuttons = tern_insert_int(padbuttons, ".c", BUTTON_C); padbuttons = tern_insert_int(padbuttons, ".x", BUTTON_X); padbuttons = tern_insert_int(padbuttons, ".y", BUTTON_Y); padbuttons = tern_insert_int(padbuttons, ".z", BUTTON_Z); padbuttons = tern_insert_int(padbuttons, ".start", BUTTON_START); padbuttons = tern_insert_int(padbuttons, ".mode", BUTTON_MODE); } return padbuttons; } static tern_node *get_mouse_buttons() { static tern_node *mousebuttons; if (!mousebuttons) { mousebuttons = tern_insert_int(NULL, ".left", MOUSE_LEFT); mousebuttons = tern_insert_int(mousebuttons, ".middle", MOUSE_MIDDLE); mousebuttons = tern_insert_int(mousebuttons, ".right", MOUSE_RIGHT); mousebuttons = tern_insert_int(mousebuttons, ".start", MOUSE_START); mousebuttons = tern_insert_int(mousebuttons, ".motion", PSEUDO_BUTTON_MOTION); } return mousebuttons; } uint8_t bind_up(const char *target) { keybinding bind; bind.bind_type = parse_binding_target(0, target, get_pad_buttons(), get_mouse_buttons(), &bind.subtype_a, &bind.subtype_b); if (!bind.bind_type) { return 0; } handle_binding_up(&bind); return 1; } uint8_t bind_down(const char *target) { keybinding bind; bind.bind_type = parse_binding_target(0, target, get_pad_buttons(), get_mouse_buttons(), &bind.subtype_a, &bind.subtype_b); if (!bind.bind_type) { return 0; } handle_binding_down(&bind); return 1; } tern_node *get_binding_node_for_pad(int padnum) { if (padnum > MAX_JOYSTICKS) { return NULL; } tern_node * pads = tern_find_path(config, "bindings\0pads\0", TVAL_NODE).ptrval; if (!pads) { return NULL; } char numstr[11]; sprintf(numstr, "%d", padnum); tern_node * pad = tern_find_node(pads, numstr); if (!pad) { char *type_id = render_joystick_type_id(padnum); pad = tern_find_node(pads, type_id); free(type_id); } if (!pad) { controller_info info = get_controller_info(padnum); char *key = make_controller_type_key(&info); pad = tern_find_node(pads, key); free(key); } if (!pad) { pad = tern_find_node(pads, "default"); } return pad; } void handle_joy_added(int joystick) { tern_node *pad = get_binding_node_for_pad(joystick); if (!pad) { return; } tern_node * dpad_node = tern_find_node(pad, "dpads"); if (dpad_node) { for (int dpad = 0; dpad < 10; dpad++) { char numstr[2] = {dpad + '0', 0}; tern_node * pad_dpad = tern_find_node(dpad_node, numstr); char * dirs[] = {"up", "down", "left", "right"}; char *render_dirs[] = {"dpup", "dpdown", "dpleft", "dpright"}; int dirnums[] = {RENDER_DPAD_UP, RENDER_DPAD_DOWN, RENDER_DPAD_LEFT, RENDER_DPAD_RIGHT}; for (int dir = 0; dir < sizeof(dirs)/sizeof(dirs[0]); dir++) { char * target = tern_find_ptr(pad_dpad, dirs[dir]); if (target) { uint8_t subtype_a = 0, subtype_b = 0; int bindtype = parse_binding_target(joystick, target, get_pad_buttons(), get_mouse_buttons(), &subtype_a, &subtype_b); int32_t hostbutton = dpad >0 ? -1 : render_translate_input_name(joystick, render_dirs[dir], 0); if (hostbutton < 0) { //assume this is a raw dpad mapping bind_dpad(joystick, dpad, dirnums[dir], bindtype, subtype_a, subtype_b); } else if (hostbutton & RENDER_DPAD_BIT) { bind_dpad(joystick, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b); } else if (hostbutton & RENDER_AXIS_BIT) { //SDL2 knows internally whether this should be a positive or negative binding, but doesn't expose that externally //for now I'll just assume that any controller with axes for a d-pad has these mapped the "sane" way bind_axis(joystick, render_axis_part(hostbutton), dir == 1 || dir == 3 ? 1 : 0, bindtype, subtype_a, subtype_b); } else { bind_button(joystick, hostbutton, bindtype, subtype_a, subtype_b); } } } } } tern_node *button_node = tern_find_node(pad, "buttons"); if (button_node) { pad_button_state state = { .padnum = joystick, .padbuttons = get_pad_buttons(), .mousebuttons = get_mouse_buttons() }; tern_foreach(button_node, process_pad_button, &state); } tern_node *axes_node = tern_find_node(pad, "axes"); if (axes_node) { pad_button_state state = { .padnum = joystick, .padbuttons = get_pad_buttons(), .mousebuttons = get_mouse_buttons() }; tern_foreach(axes_node, process_pad_axis, &state); } } //only handles keyboards and mice as gamepads are handled on hotplug events void set_bindings(void) { tern_node * special = tern_insert_int(NULL, "up", RENDERKEY_UP); special = tern_insert_int(special, "down", RENDERKEY_DOWN); special = tern_insert_int(special, "left", RENDERKEY_LEFT); special = tern_insert_int(special, "right", RENDERKEY_RIGHT); special = tern_insert_int(special, "enter", '\r'); special = tern_insert_int(special, "space", ' '); special = tern_insert_int(special, "tab", '\t'); special = tern_insert_int(special, "backspace", '\b'); special = tern_insert_int(special, "esc", RENDERKEY_ESC); special = tern_insert_int(special, "delete", RENDERKEY_DEL); special = tern_insert_int(special, "lshift", RENDERKEY_LSHIFT); special = tern_insert_int(special, "rshift", RENDERKEY_RSHIFT); special = tern_insert_int(special, "lctrl", RENDERKEY_LCTRL); special = tern_insert_int(special, "rctrl", RENDERKEY_RCTRL); special = tern_insert_int(special, "lalt", RENDERKEY_LALT); special = tern_insert_int(special, "ralt", RENDERKEY_RALT); special = tern_insert_int(special, "home", RENDERKEY_HOME); special = tern_insert_int(special, "end", RENDERKEY_END); special = tern_insert_int(special, "pageup", RENDERKEY_PAGEUP); special = tern_insert_int(special, "pagedown", RENDERKEY_PAGEDOWN); special = tern_insert_int(special, "f1", RENDERKEY_F1); special = tern_insert_int(special, "f2", RENDERKEY_F2); special = tern_insert_int(special, "f3", RENDERKEY_F3); special = tern_insert_int(special, "f4", RENDERKEY_F4); special = tern_insert_int(special, "f5", RENDERKEY_F5); special = tern_insert_int(special, "f6", RENDERKEY_F6); special = tern_insert_int(special, "f7", RENDERKEY_F7); special = tern_insert_int(special, "f8", RENDERKEY_F8); special = tern_insert_int(special, "f9", RENDERKEY_F9); special = tern_insert_int(special, "f10", RENDERKEY_F10); special = tern_insert_int(special, "f11", RENDERKEY_F11); special = tern_insert_int(special, "f12", RENDERKEY_F12); special = tern_insert_int(special, "select", RENDERKEY_SELECT); special = tern_insert_int(special, "play", RENDERKEY_PLAY); special = tern_insert_int(special, "search", RENDERKEY_SEARCH); special = tern_insert_int(special, "back", RENDERKEY_BACK); special = tern_insert_int(special, "np0", RENDERKEY_NP0); special = tern_insert_int(special, "np1", RENDERKEY_NP1); special = tern_insert_int(special, "np2", RENDERKEY_NP2); special = tern_insert_int(special, "np3", RENDERKEY_NP3); special = tern_insert_int(special, "np4", RENDERKEY_NP4); special = tern_insert_int(special, "np5", RENDERKEY_NP5); special = tern_insert_int(special, "np6", RENDERKEY_NP6); special = tern_insert_int(special, "np7", RENDERKEY_NP7); special = tern_insert_int(special, "np8", RENDERKEY_NP8); special = tern_insert_int(special, "np9", RENDERKEY_NP9); special = tern_insert_int(special, "np/", RENDERKEY_NP_DIV); special = tern_insert_int(special, "np*", RENDERKEY_NP_MUL); special = tern_insert_int(special, "np-", RENDERKEY_NP_MIN); special = tern_insert_int(special, "np+", RENDERKEY_NP_PLUS); special = tern_insert_int(special, "npenter", RENDERKEY_NP_ENTER); special = tern_insert_int(special, "np.", RENDERKEY_NP_STOP); tern_node *padbuttons = get_pad_buttons(); tern_node *mousebuttons = get_mouse_buttons(); tern_node * keys = tern_find_path(config, "bindings\0keys\0", TVAL_NODE).ptrval; process_keys(keys, special, padbuttons, mousebuttons, NULL); tern_free(special); memset(mice, 0, sizeof(mice)); tern_node * mice = tern_find_path(config, "bindings\0mice\0", TVAL_NODE).ptrval; if (mice) { tern_node *buttonmaps[2] = {padbuttons, mousebuttons}; tern_foreach(mice, process_mouse, buttonmaps); } tern_node * speed_nodes = tern_find_path(config, "clocks\0speeds\0", TVAL_NODE).ptrval; free(speeds); speeds = malloc(sizeof(uint32_t)); speeds[0] = 100; num_speeds = 1; process_speeds(speed_nodes, NULL); for (int i = 0; i < num_speeds; i++) { if (!speeds[i]) { warning("Speed index %d was not set to a valid percentage!", i); speeds[i] = 100; } } } void update_pad_bindings(void) { for (int i = 0; i < MAX_JOYSTICKS; i++) { if (joysticks[i].num_buttons || joysticks[i].num_axes || joysticks[i].num_dpads) { reset_joystick_bindings(i); handle_joy_added(i); } } } void bindings_set_mouse_mode(uint8_t mode) { mouse_mode = mode; if (mode == MOUSE_RELATIVE) { render_relative_mouse(1); } }