mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-12 18:31:05 +00:00
The branch forked 79 commits before main and 98 before dev. Resolutions: 1. Aspect ratio: dev's implementation stays (camera patch at page +56 on 1.4.3, tools/ultrawide_layout.py layouts per size in romfs-patches-<w>x<h>, the aspect = fit|stretch setting). The PR's second implementation is dropped: the aspect sets of patches-v121.txt and patches-v143.txt (the 1.4.3 one patched the same viewport sites as dev's), tools/aspect_layouts.py, the resolution.c shapes that limited game resolutions to 16:9, 16:10 and 21:9, and the picture_fit setting. Its letterbox code (gfx_fit_rect) is kept and reads TOTK_ASPECT. 2. Code dev changed where the PR moved it: the stick dead zone, the test gamepad and the paddles go to input.c; the DPI awareness, keep-awake and IME detach of gfx_window.c go to gfx_window_win.c; device fault reports and GPU breadcrumbs to gfx_present.c. gfx_window_dpi gets an SDL version. 3. dev's Win32 calls in shared code go through platform.h: the shader cache pack reader (PlatFile), the romfs overlay checks, the audio pacing waits, the motion, ASTC and device fault locks. plat_cpu_usable and plat_commit_info get Linux versions; plat_cpu_count counts the machine's processors on both. dpi_probe.c becomes dpi_probe_win.c. 4. assume_focus defaults to 1 as the PR has it (a load interrupted by a focus loss can stop for good) and stays live, with dev's Game tab row. 5. The frame limits 40 and 45 join dev's list. The BC 3D storage fallback for Vega only runs with TOTK_BLOCK_VIEWS_3D=1, since dev makes 3D BC images without block views by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
368 lines
17 KiB
C
368 lines
17 KiB
C
// 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 <ctype.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#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}
|
|
|
|
// 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 = 26, 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"};
|
|
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:<name> or key:<name>; stick directions 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
}
|