// Binding tables (keyboard/mouse and gamepad), their totk.ini overrides, the pure mapping functions and the Buttons // tab's edits (input_map.h). No Win32 or SDL here. #include #include #include #include #include "cfg.h" #include "host.h" #include "input_map.h" #define STICK_MAX 32767 #define DIAGONAL 23170 // STICK_MAX / sqrt(2) #define MOUSE_FULL_CPS 3000.0 #define DEFAULT_KEYS \ {KEY_W, B_SL_UP}, {KEY_A, B_SL_LEFT}, {KEY_S, B_SL_DOWN}, {KEY_D, B_SL_RIGHT}, /* move */ \ {KEY_UP, B_SR_UP}, {KEY_LEFT, B_SR_LEFT}, {KEY_DOWN, B_SR_DOWN}, {KEY_RIGHT, B_SR_RIGHT}, /* camera, second source */ \ {KEY_MOUSE_L, B_Y}, {KEY_MOUSE_R, B_ZL}, {KEY_Z, B_ZL}, {KEY_F, B_ZR}, {KEY_X, B_ZR}, \ {KEY_SPACE, B_X}, {KEY_LSHIFT, B_B}, {KEY_E, B_A}, {KEY_Q, B_L}, {KEY_R, B_R}, \ {KEY_LCTRL, B_STICK_L}, {KEY_C, B_STICK_R}, \ {KEY_1, B_LEFT}, {KEY_2, B_UP}, {KEY_3, B_RIGHT}, {KEY_4, B_DOWN}, \ {KEY_TAB, B_PLUS}, {KEY_M, B_MINUS}, {KEY_G, B_SHAKE} // By position: south is Switch B, east A, west Y, north X (Nintendo layout); the Xbox layout swaps A with B and X with Y // (south A, as the Xbox labels; cross A on a PlayStation pad). The triggers are ZL and ZR. #define DEFAULT_PADS(south, east, west, north) \ {PAD_SOUTH, south}, {PAD_EAST, east}, {PAD_WEST, west}, {PAD_NORTH, north}, {PAD_LSTICK, B_STICK_L}, \ {PAD_RSTICK, B_STICK_R}, {PAD_L, B_L}, {PAD_R, B_R}, {PAD_START, B_PLUS}, {PAD_BACK, B_MINUS}, \ {PAD_DPAD_LEFT, B_LEFT}, {PAD_DPAD_UP, B_UP}, {PAD_DPAD_RIGHT, B_RIGHT}, {PAD_DPAD_DOWN, B_DOWN}, {PAD_ZL, B_ZL}, \ {PAD_ZR, B_ZR} #define NINTENDO_PADS DEFAULT_PADS(B_B, B_A, B_Y, B_X) #define XBOX_PADS DEFAULT_PADS(B_A, B_B, B_X, B_Y) enum { DEFAULT_KEY_COUNT = 27, DEFAULT_PAD_COUNT = 16 }; _Static_assert(sizeof (Binding[]){DEFAULT_KEYS} / sizeof (Binding) == DEFAULT_KEY_COUNT, "DEFAULT_KEY_COUNT"); static const InputMap defaults = {{DEFAULT_KEYS}, DEFAULT_KEY_COUNT, {{NINTENDO_PADS}, {XBOX_PADS}}, {DEFAULT_PAD_COUNT, DEFAULT_PAD_COUNT}}; InputMap input_map = {{DEFAULT_KEYS}, DEFAULT_KEY_COUNT, {{NINTENDO_PADS}, {XBOX_PADS}}, {DEFAULT_PAD_COUNT, DEFAULT_PAD_COUNT}}; static const char *const action_names[ACTION_COUNT] = { "A", "B", "X", "Y", "LCLICK", "RCLICK", "L", "R", "ZL", "ZR", "PLUS", "MINUS", "LEFT", "UP", "RIGHT", "DOWN", "LSTICK_LEFT", "LSTICK_UP", "LSTICK_RIGHT", "LSTICK_DOWN", "RSTICK_LEFT", "RSTICK_UP", "RSTICK_RIGHT", "RSTICK_DOWN", "SHAKE"}; static const char *const pad_names[PAD_COUNT] = { NULL, "south", "east", "west", "north", "lstick", "rstick", "l", "r", "start", "back", "dpad_left", "dpad_up", "dpad_right", "dpad_down", "zl", "zr", "lpaddle1", "rpaddle1", "lpaddle2", "rpaddle2"}; static const char *const key_names[KEY_COUNT] = { // each group starts at its id; the ids of a group are consecutive [KEY_A] = "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", [KEY_0] = "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", [KEY_SPACE] = "space", "tab", "lshift", "lctrl", "up", "down", "left", "right", [KEY_MOUSE_L] = "mouse_l", "mouse_r", "mouse_m", "mouse_x1", "mouse_x2", [KEY_ENTER] = "enter", "backspace", "rshift", "rctrl", "lalt", "capslock", "insert", "delete", "home", "end", "pageup", "pagedown", [KEY_F2] = "f2", "f3", "f4", "f5", "f6", "f7", "f9", "f12", [KEY_NUM0] = "num0", "num1", "num2", "num3", "num4", "num5", "num6", "num7", "num8", "num9", [KEY_NUM_ADD] = "num_add", "num_sub", "num_mul", "num_div", "num_decimal", [KEY_MINUS] = "minus", "equals", "lbracket", "rbracket", "semicolon", "apostrophe", "comma", "period", "slash", "backslash", "grave"}; const char *input_action_name(int action) { return action >= 0 && action < ACTION_COUNT ? action_names[action] : NULL; } const char *input_key_name(int key) { return key > KEY_NONE && key < KEY_COUNT ? key_names[key] : NULL; } const char *input_pad_name(int pad) { return pad > PAD_NONE && pad < PAD_COUNT ? pad_names[pad] : NULL; } void input_map_reset(void) { input_map = defaults; } static int key_id(const char *name) { for (int k = KEY_A; k < KEY_COUNT; k++) if (key_names[k] && !strcasecmp(key_names[k], name)) return k; return KEY_NONE; } static int pad_id(const char *name) { for (int p = PAD_SOUTH; p < PAD_COUNT; p++) if (!strcasecmp(pad_names[p], name)) return p; return PAD_NONE; } static char *trim(char *s) { while (*s == ' ' || *s == '\t') s++; char *e = s + strlen(s); while (e > s && (e[-1] == ' ' || e[-1] == '\t' || e[-1] == '\r')) *--e = 0; return s; } int input_map_keys_of(const InputMap *m, int action, uint8_t *out, int max) { int n = 0; for (int i = 0; i < m->nkeys; i++) if (m->keys[i].action == action && n < max) out[n++] = m->keys[i].key; return n; } int input_map_pads_of(const InputMap *m, int action, int layout, uint8_t *out, int max) { int n = 0; for (int i = 0; i < m->npads[layout]; i++) if (m->pads[layout][i].action == action && n < max) out[n++] = m->pads[layout][i].pad; return n; } // action's keys become list[0..n): its old ones go, the new ones are appended (the table keeps its order otherwise). static void keys_put(InputMap *m, int action, const uint8_t *list, int n) { int w = 0; for (int i = 0; i < m->nkeys; i++) if (m->keys[i].action != action) m->keys[w++] = m->keys[i]; m->nkeys = w; for (int i = 0; i < n && m->nkeys < INPUT_BINDING_MAX; i++) m->keys[m->nkeys++] = (Binding){list[i], (uint8_t)action}; } static void pads_put(InputMap *m, int layout, int action, const uint8_t *list, int n) { PadBinding *t = m->pads[layout]; int w = 0; for (int i = 0; i < m->npads[layout]; i++) if (t[i].action != action) t[w++] = t[i]; m->npads[layout] = w; for (int i = 0; i < n && m->npads[layout] < INPUT_PAD_BINDING_MAX; i++) t[m->npads[layout]++] = (PadBinding){list[i], (uint8_t)action}; } int input_map_bind(const char *button, const char *list) { int action = -1; for (int a = 0; a < ACTION_COUNT; a++) if (!strcasecmp(action_names[a], button)) action = a; if (action < 0) { rt_log("input: bind: unknown button '%s'", button); return 0; } uint8_t keys[INPUT_BINDING_MAX], pads[INPUT_PAD_BINDING_MAX]; int nk = 0, np = 0, none = 0, key_def = 0, pad_def = 0, bad = 0; char buf[256]; snprintf(buf, sizeof buf, "%s", list); for (char *save = NULL, *tok = strtok_r(buf, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) { tok = trim(tok); if (!strcasecmp(tok, "none")) { none = 1; continue; } int ok = 0; if (!strncasecmp(tok, "pad:", 4) && action < B_SL_LEFT) { const char *name = trim(tok + 4); int p = pad_id(name); if (!strcasecmp(name, "default")) pad_def = ok = 1; else if (p && np < INPUT_PAD_BINDING_MAX) pads[np++] = (uint8_t)p, ok = 1; } else if (!strncasecmp(tok, "key:", 4)) { const char *name = trim(tok + 4); int k = key_id(name); if (!strcasecmp(name, "default")) key_def = ok = 1; else if (k && nk < INPUT_BINDING_MAX) keys[nk++] = (uint8_t)k, ok = 1; } if (!ok) { bad++; rt_log("input: bind %s: ignoring '%s' (expected pad: or key:; stick directions and SHAKE take keys only)", button, tok); } } if (!nk && !np && !none && !key_def && !pad_def) { if (bad) rt_log("input: bind %s: no valid entry, keeping the defaults", button); return 0; } uint8_t list_k[INPUT_BINDING_MAX]; int n = key_def ? input_map_keys_of(&defaults, action, list_k, INPUT_BINDING_MAX) : 0; for (int i = 0; i < nk && n < INPUT_BINDING_MAX; i++) list_k[n++] = keys[i]; keys_put(&input_map, action, list_k, n); for (int l = 0; l < PAD_LAYOUT_COUNT; l++) { // a bind line names physical buttons: the same in every layout uint8_t list_p[INPUT_PAD_BINDING_MAX]; int c = pad_def ? input_map_pads_of(&defaults, action, l, list_p, INPUT_PAD_BINDING_MAX) : 0; for (int i = 0; i < np && c < INPUT_PAD_BINDING_MAX; i++) list_p[c++] = pads[i]; pads_put(&input_map, l, action, list_p, c); } return 1; } void input_map_configure(void) { input_map_reset(); for (int a = 0; a < ACTION_COUNT; a++) { char var[32]; snprintf(var, sizeof var, "TOTK_BIND_%s", action_names[a]); const char *v = cfg_get(var); if (v && *v && input_map_bind(action_names[a], v)) rt_log("input: %s = %s", var + 5, v); } } int input_map_set_key(InputMap *m, int action, int slot, int key) { uint8_t list[INPUT_BINDING_MAX]; int n = input_map_keys_of(m, action, list, INPUT_BINDING_MAX), lost = -1; if (key != KEY_NONE) { for (int i = 0; i < m->nkeys; i++) if (m->keys[i].key == key && m->keys[i].action != action) lost = m->keys[i].action; if (lost >= 0) { uint8_t other[INPUT_BINDING_MAX]; int c = 0; for (int i = 0; i < m->nkeys; i++) if (m->keys[i].action == lost && m->keys[i].key != key) other[c++] = m->keys[i].key; keys_put(m, lost, other, c); } int w = 0; // the key once in this action's list too for (int i = 0; i < n; i++) if (list[i] != key) list[w++] = list[i]; n = w; if (slot < n) list[slot] = (uint8_t)key; else if (n < INPUT_BINDING_MAX) list[n++] = (uint8_t)key; } else if (slot < n) { memmove(list + slot, list + slot + 1, (size_t)(n - slot - 1)); n--; } keys_put(m, action, list, n); return lost; } static int same_set(const uint8_t *a, int na, const uint8_t *b, int nb); // Every action has its default gamepad buttons in layout. static int pads_at_defaults(const InputMap *m, int layout) { for (int a = 0; a < B_SL_LEFT; a++) { uint8_t p[INPUT_PAD_BINDING_MAX], dp[INPUT_PAD_BINDING_MAX]; int np = input_map_pads_of(m, a, layout, p, INPUT_PAD_BINDING_MAX); if (!same_set(p, np, dp, input_map_pads_of(&defaults, a, layout, dp, INPUT_PAD_BINDING_MAX))) return 0; } return 1; } int input_map_set_pad(InputMap *m, int action, int layout, int pad) { uint8_t mine[INPUT_PAD_BINDING_MAX]; int n = input_map_pads_of(m, action, layout, mine, INPUT_PAD_BINDING_MAX), other = -1; if (pad != PAD_NONE) for (int i = 0; i < m->npads[layout]; i++) if (m->pads[layout][i].pad == pad && m->pads[layout][i].action != action) other = m->pads[layout][i].action; if (other >= 0) { // the other action gives up pad and takes this action's old button uint8_t list[INPUT_PAD_BINDING_MAX]; int c = 0; for (int i = 0; i < m->npads[layout]; i++) if (m->pads[layout][i].action == other && m->pads[layout][i].pad != pad) list[c++] = m->pads[layout][i].pad; if (n && mine[0] != pad && c < INPUT_PAD_BINDING_MAX) list[c++] = mine[0]; pads_put(m, layout, other, list, c); } uint8_t p = (uint8_t)pad; pads_put(m, layout, action, &p, pad != PAD_NONE); if (pads_at_defaults(m, layout)) { // put back as it was: the layout setting moves the face buttons again memcpy(m->pads, defaults.pads, sizeof m->pads); memcpy(m->npads, defaults.npads, sizeof m->npads); return other; } int pin[6] = {B_A, B_B, B_X, B_Y, action, other}, npin = other >= 0 ? 6 : 5; for (int i = 0; i < npin; i++) { uint8_t list[INPUT_PAD_BINDING_MAX]; int c = input_map_pads_of(m, pin[i], layout, list, INPUT_PAD_BINDING_MAX); for (int l = 0; l < PAD_LAYOUT_COUNT; l++) if (l != layout) pads_put(m, l, pin[i], list, c); } return other; } // The same bindings in any order (the menu appends; a line lists them its own way). static int same_set(const uint8_t *a, int na, const uint8_t *b, int nb) { if (na != nb) return 0; for (int i = 0; i < na; i++) { int ca = 0, cb = 0; for (int j = 0; j < na; j++) ca += a[j] == a[i], cb += b[j] == a[i]; if (ca != cb) return 0; } return 1; } void input_map_line(const InputMap *m, int action, char *out, size_t n) { uint8_t k[INPUT_BINDING_MAX], dk[INPUT_BINDING_MAX]; int nk = input_map_keys_of(m, action, k, INPUT_BINDING_MAX); int key_def = same_set(k, nk, dk, input_map_keys_of(&defaults, action, dk, INPUT_BINDING_MAX)); int pad_def = 1; for (int l = 0; l < PAD_LAYOUT_COUNT; l++) { uint8_t p[INPUT_PAD_BINDING_MAX], dp[INPUT_PAD_BINDING_MAX]; int np = input_map_pads_of(m, action, l, p, INPUT_PAD_BINDING_MAX); pad_def &= same_set(p, np, dp, input_map_pads_of(&defaults, action, l, dp, INPUT_PAD_BINDING_MAX)); } out[0] = 0; if (key_def && pad_def) return; size_t len = 0; #define PUT(...) (len += (size_t)snprintf(out + len, len < n ? n - len : 0, __VA_ARGS__)) if (key_def) PUT("key:default"); else for (int i = 0; i < nk; i++) PUT("%skey:%s", len ? ", " : "", key_names[k[i]]); if (pad_def && action < B_SL_LEFT) PUT("%spad:default", len ? ", " : ""); else if (!pad_def) { uint8_t p[INPUT_PAD_BINDING_MAX]; int np = input_map_pads_of(m, action, PAD_LAYOUT_NINTENDO, p, INPUT_PAD_BINDING_MAX); for (int i = 0; i < np; i++) PUT("%spad:%s", len ? ", " : "", pad_names[p[i]]); } if (!len) PUT("none"); #undef PUT } int input_pad_layout_pick(const char *setting, int east_labelled_a) { if (setting && (!strcasecmp(setting, "nintendo") || !strcasecmp(setting, "xbox_prompts"))) return PAD_LAYOUT_NINTENDO; if (setting && !strcasecmp(setting, "xbox")) return PAD_LAYOUT_XBOX; return east_labelled_a ? PAD_LAYOUT_NINTENDO : PAD_LAYOUT_XBOX; } int input_prompts_pick(const char *prompts, const char *pad_layout) { if (prompts && *prompts) return !strcasecmp(prompts, "xbox") ? PROMPTS_XBOX : !strcasecmp(prompts, "pc") ? PROMPTS_PC : PROMPTS_NINTENDO; return pad_layout && !strcasecmp(pad_layout, "xbox_prompts") ? PROMPTS_XBOX : PROMPTS_NINTENDO; } const char *input_prompts_folder(int prompts, const char *pad_layout) { if (prompts == PROMPTS_PC) return "romfs-patches-pc"; if (prompts != PROMPTS_XBOX) return NULL; // The folders are read at start, before a pad is open: auto counts as Xbox, as for a pad without Nintendo labels. // A Nintendo-labelled pad under auto plays by label too (its A is on the button labelled A), so it matches as well. return input_pad_layout_pick(pad_layout, 0) == PAD_LAYOUT_NINTENDO ? "romfs-patches-xbox" : "romfs-patches-xbox-label"; } uint64_t input_map_pad_buttons(const uint8_t down[PAD_COUNT], int layout) { const PadBinding *t = input_map.pads[layout]; uint64_t b = 0; for (int i = 0; i < input_map.npads[layout]; i++) if (down[t[i].pad]) b |= 1ull << t[i].action; return b; } uint64_t input_map_buttons(const uint8_t down[KEY_COUNT]) { uint64_t b = 0; for (int i = 0; i < input_map.nkeys; i++) if (input_map.keys[i].action < B_SL_LEFT && down[input_map.keys[i].key]) b |= 1ull << input_map.keys[i].action; return b; } int input_map_held(const uint8_t down[KEY_COUNT], int action) { for (int i = 0; i < input_map.nkeys; i++) if (input_map.keys[i].action == action && down[input_map.keys[i].key]) return 1; return 0; } void input_map_stick(const uint8_t down[KEY_COUNT], int stick, int32_t out[2]) { int first = stick ? B_SR_LEFT : B_SL_LEFT, dir[4] = {0, 0, 0, 0}; // left, up, right, down for (int i = 0; i < input_map.nkeys; i++) { int a = input_map.keys[i].action - first; if (a >= 0 && a < 4 && down[input_map.keys[i].key]) dir[a] = 1; } int dx = dir[2] - dir[0], dy = dir[1] - dir[3]; int32_t m = dx && dy ? DIAGONAL : STICK_MAX; out[0] = dx * m; out[1] = dy * m; } void input_mouse_stick(int32_t dx, int32_t dy, double dt_s, float sens, int32_t out[2]) { out[0] = out[1] = 0; if (!(dt_s > 0) || !(sens > 0)) return; double k = STICK_MAX * (double)sens / (MOUSE_FULL_CPS * dt_s); double x = dx * k, y = -dy * k, len = sqrt(x * x + y * y); if (len > STICK_MAX) { x *= STICK_MAX / len; y *= STICK_MAX / len; } out[0] = (int32_t)lrint(x); out[1] = (int32_t)lrint(y); } void input_pad_stick(int32_t x, int32_t y, float dead, int32_t out[2]) { double fx = x / (double)STICK_MAX, fy = y / (double)STICK_MAX, m = sqrt(fx * fx + fy * fy); out[0] = out[1] = 0; if (m <= dead) return; double k = ((m > 1 ? 1 : m) - dead) / (1 - dead) / m; // past the edge (a square gate's corner) the length is 1 out[0] = (int32_t)lrint(fmax(-1, fmin(1, fx * k)) * STICK_MAX); out[1] = (int32_t)lrint(fmax(-1, fmin(1, fy * k)) * STICK_MAX); } float input_pad_deadzone(int stick) { const char *own = stick ? "TOTK_RSTICK_DEADZONE" : "TOTK_LSTICK_DEADZONE", *set = cfg_get(own); double v = cfg_double(set && *set ? own : "TOTK_STICK_DEADZONE"); return (float)(v < 0 ? 0 : v > 50 ? 50 : v) / 100.f; } double input_stick_percent(int32_t x, int32_t y) { return sqrt((double)x * x + (double)y * y) * 100.0 / STICK_MAX; } // Minus and Plus held together open or close the menu. *held: latched from the poll where both go down until neither is // down (the caller keeps it). Every poll of the latch reports neither to the game, so releasing one button first does not // leak the other; Minus alone before Plus reaches the game unchanged. With the menu open the game sees a neutral pad. // Returns 1 in the poll where both go down: the caller toggles the menu once. static int chord(int *held, uint64_t *buttons, uint64_t combo) { uint64_t raw = *buttons & combo; int edge = 0; if (*held) { *held = raw != 0; } else if (raw == combo) { *held = 1; edge = 1; } if (*held) *buttons &= ~combo; return edge; } int input_menu_gate(int *held, int menu_open, uint64_t *buttons, int32_t sticks[4]) { int edge = chord(held, buttons, 1ull << B_MINUS | 1ull << B_PLUS); if (menu_open) { *buttons = 0; sticks[0] = sticks[1] = sticks[2] = sticks[3] = 0; } return edge; } // Both stick clicks held together step the performance overlay as F10 does (a gamepad has no F10: the Steam Deck in // Game Mode). The same latch as the menu's above; the game sees neither click while it holds. int input_overlay_gate(int *held, uint64_t *buttons) { return chord(held, buttons, 1ull << B_STICK_L | 1ull << B_STICK_R); }