Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,10 @@ acceleration is **opt-in**, per game, so the accurate path is never compromised:
speed until pressed again. Controller host shortcuts bound to a single
button or trigger are chords with Select — the launcher shows them as
`select + …`; only a two-button capture replaces the implicit Select.
Backspace while the launcher is listening clears a shortcut; in the files
that is `0` for a `[hotkeys]` pad value and `None` for a `[KeyMap]` key (a
present-but-empty or `None` line is an explicit unbind, a missing line
keeps the built-in default).
- **The "Fast Loading (host pacing)" and "CD Speed" mods** — automatic
acceleration during load waits, shipped with every title and **off by
default**, with `turbo_audio_sink` keeping the SPU timeline coherent through
Expand Down
34 changes: 23 additions & 11 deletions runtime/src/host_keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,19 @@ typedef struct HostKeyBind {
typedef struct HostKeyAction {
HostKeyBind binds[HOST_KEYMAP_MAX_BINDS];
int count;
/* 1 when [KeyMap] names this action with an empty / "None" / "(unbound)"
* value: the user cleared it, so apply_defaults must NOT put the built-in
* key back. A MISSING line still gets the default. */
int explicit_unbound;
} HostKeyAction;


static HostKeyAction s_actions[HOST_KEYMAP_ACTION_COUNT];

static int want_default(HostKeymapAction action) {
return s_actions[action].count == 0 && !s_actions[action].explicit_unbound;
}

static int ieq(const char *a, const char *b) {
if (!a || !b) return 0;
while (*a && *b) {
Expand Down Expand Up @@ -79,27 +88,27 @@ static void add_bind(HostKeymapAction action, int keycode, int scancode, int mod
}

static void apply_defaults(void) {
if (s_actions[HOST_KEYMAP_FULLSCREEN].count == 0) {
if (want_default(HOST_KEYMAP_FULLSCREEN)) {
add_bind(HOST_KEYMAP_FULLSCREEN, (int)SDLK_RETURN, (int)SDL_SCANCODE_RETURN, KMOD_ALT);
add_bind(HOST_KEYMAP_FULLSCREEN, (int)SDLK_f, (int)SDL_SCANCODE_F, KMOD_CTRL);
}
if (s_actions[HOST_KEYMAP_TURBO].count == 0)
if (want_default(HOST_KEYMAP_TURBO))
add_bind(HOST_KEYMAP_TURBO, (int)SDLK_TAB, (int)SDL_SCANCODE_TAB, 0);
if (s_actions[HOST_KEYMAP_VOLUME_UP].count == 0)
if (want_default(HOST_KEYMAP_VOLUME_UP))
add_bind(HOST_KEYMAP_VOLUME_UP, (int)SDLK_KP_PLUS, (int)SDL_SCANCODE_KP_PLUS, 0);
if (s_actions[HOST_KEYMAP_VOLUME_DOWN].count == 0)
if (want_default(HOST_KEYMAP_VOLUME_DOWN))
add_bind(HOST_KEYMAP_VOLUME_DOWN, (int)SDLK_KP_MINUS, (int)SDL_SCANCODE_KP_MINUS, 0);
if (s_actions[HOST_KEYMAP_DISPLAY_PERF].count == 0)
if (want_default(HOST_KEYMAP_DISPLAY_PERF))
add_bind(HOST_KEYMAP_DISPLAY_PERF, (int)SDLK_f, (int)SDL_SCANCODE_F, 0);
#if defined(PSX_HAS_RBENGINE_SNAP)
if (s_actions[HOST_KEYMAP_REWIND].count == 0)
if (want_default(HOST_KEYMAP_REWIND))
add_bind(HOST_KEYMAP_REWIND, (int)SDLK_F8, (int)SDL_SCANCODE_F8, 0);
#endif
if (s_actions[HOST_KEYMAP_SAVE_STATE_MENU].count == 0)
if (want_default(HOST_KEYMAP_SAVE_STATE_MENU))
add_bind(HOST_KEYMAP_SAVE_STATE_MENU, (int)SDLK_F7, (int)SDL_SCANCODE_F7, 0);
if (s_actions[HOST_KEYMAP_SCANLINES].count == 0)
if (want_default(HOST_KEYMAP_SCANLINES))
add_bind(HOST_KEYMAP_SCANLINES, (int)SDLK_F6, (int)SDL_SCANCODE_F6, 0);
if (s_actions[HOST_KEYMAP_TURBO_TOGGLE].count == 0)
if (want_default(HOST_KEYMAP_TURBO_TOGGLE))
add_bind(HOST_KEYMAP_TURBO_TOGGLE, (int)SDLK_F9, (int)SDL_SCANCODE_F9, 0);
}

Expand Down Expand Up @@ -202,10 +211,13 @@ void host_keymap_load(const char *config_ini_path) {
trim_inplace(val);
act = action_for_key(key);
if (act == HOST_KEYMAP_ACTION_COUNT) continue;
/* Explicit empty unbinds (no keypad fallback for that action until
* apply_defaults — empty means user cleared it; still fall back). */
/* Explicit empty / "None" unbinds: the user cleared it, so no
* default fallback for that action (explicit_unbound). */
s_actions[act].count = 0;
s_actions[act].explicit_unbound = 0;
parse_value(act, val);
if (s_actions[act].count == 0)
s_actions[act].explicit_unbound = 1;
}
fclose(f);
apply_defaults();
Expand Down
6 changes: 6 additions & 0 deletions runtime/tests/test_rewind_toggle_combo.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,11 @@
assert "HOST_KEYMAP_TURBO_TOGGLE," in KEYMAP_H
assert 'ieq(name, "TurboToggle")' in KEYMAP_C
assert "add_bind(HOST_KEYMAP_TURBO_TOGGLE, (int)SDLK_F9" in KEYMAP_C
# A present-but-empty / "None" [KeyMap] line is an explicit unbind: no
# built-in default is re-applied for that action (launcher Backspace-to-clear).
assert "int explicit_unbound;" in KEYMAP_C
assert "static int want_default(HostKeymapAction action)" in KEYMAP_C
assert "s_actions[HOST_KEYMAP_TURBO].count == 0" not in KEYMAP_C
assert "if (want_default(HOST_KEYMAP_TURBO))" in KEYMAP_C

print("host shortcut guard passed")