diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index f35c3782..cd38b9b9 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -2103,6 +2103,10 @@ GameOptions load_game_options(const fs::path& path) { // ---- UserSettings (settings.toml) — launcher-written override layer ---- +static bool valid_user_audio_freq(int n) { + return n == 32040 || n == 32000 || n == 44100 || n == 48000; +} + UserSettings load_user_settings(const fs::path& path) { UserSettings s; std::error_code ec; @@ -2245,6 +2249,13 @@ UserSettings load_user_settings(const fs::path& path) { } if (doc.contains("audio")) { const toml::value& a = toml::find(doc, "audio"); + if (a.contains("frequency")) try_get([&]{ + const auto n = toml::find(a, "frequency"); + if (valid_user_audio_freq((int)n)) { + s.audio_freq = (int)n; + s.has_audio_freq = true; + } + }); if (a.contains("spu_hq")) try_get([&]{ s.spu_hq = toml::find(a, "spu_hq"); s.has_spu_hq = true; }); @@ -2487,6 +2498,8 @@ bool save_user_settings(const fs::path& path, const UserSettings& s) { if (s.has_rewind_interval) f << "rewind_interval = " << s.rewind_interval << "\n"; f << "\n[audio]\n"; + if (s.has_audio_freq) + f << "frequency = " << s.audio_freq << "\n"; if (s.has_spu_hq) f << "spu_hq = " << (s.spu_hq ? "true" : "false") << "\n"; if (s.has_hotkey_pad_rewind || s.has_hotkey_pad_save_state_menu) { diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 011eaf4d..1829e024 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -1115,6 +1115,7 @@ struct UserSettings { bool has_hotkey_pad_save_state_menu = false; int hotkey_pad_save_state_menu = 2040; /* select+r1 */ // [audio] bool has_spu_hq = false; bool spu_hq = false; + bool has_audio_freq = false; int audio_freq = 44100; // [bios] / [disc] / [memcard] bool has_bios_path = false; std::filesystem::path bios_path; bool has_disc_path = false; std::filesystem::path disc_path; diff --git a/recompiler/tests/video_enhancement_settings_test.cpp b/recompiler/tests/video_enhancement_settings_test.cpp index 3f51f9d8..d099a203 100644 --- a/recompiler/tests/video_enhancement_settings_test.cpp +++ b/recompiler/tests/video_enhancement_settings_test.cpp @@ -122,13 +122,17 @@ static void test_user_settings_read() { fs::path p = write_temp("psxrecomp_pgxp_settings.toml", "[video]\n" "geometry_correction = true\n" - "perspective_texturing = false\n"); + "perspective_texturing = false\n" + "[audio]\n" + "frequency = 48000\n"); auto us = PSXRecompV4::load_user_settings(p); check(!us.parse_error, "settings.toml parses"); check(us.has_geometry_correction && us.geometry_correction, "settings.toml geometry_correction = true read"); check(us.has_perspective_texturing && !us.perspective_texturing, "settings.toml perspective_texturing = false read (explicit off)"); + check(us.has_audio_freq && us.audio_freq == 48000, + "settings.toml audio frequency read"); fs::remove(p); } @@ -150,6 +154,7 @@ static void test_user_settings_round_trip() { PSXRecompV4::UserSettings out; out.geometry_correction = true; out.has_geometry_correction = true; out.perspective_texturing = true; out.has_perspective_texturing = true; + out.audio_freq = 48000; out.has_audio_freq = true; fs::path p = fs::temp_directory_path() / "psxrecomp_pgxp_roundtrip.toml"; check(PSXRecompV4::save_user_settings(p, out), "save_user_settings writes"); @@ -160,6 +165,8 @@ static void test_user_settings_round_trip() { "geometry_correction survives a save/load round trip"); check(back.has_perspective_texturing && back.perspective_texturing, "perspective_texturing survives a save/load round trip"); + check(back.has_audio_freq && back.audio_freq == 48000, + "audio frequency survives a save/load round trip"); fs::remove(p); } diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index b6692c46..45934989 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -52,6 +52,17 @@ if(BUILD_TESTING) PSX_OVERLAY_DLL_BUILD=1) add_test(NAME psx_icache_fastpath_test COMMAND psx_icache_fastpath_test) + add_executable(host_keymap_test + tests/test_host_keymap.c + src/host_keymap.c) + target_include_directories(host_keymap_test PRIVATE + include ${PSX_SDL_INCLUDE_DIRS}) + target_compile_definitions(host_keymap_test PRIVATE + SDL_MAIN_HANDLED + $<$:PSX_SDL3=1>) + target_link_libraries(host_keymap_test PRIVATE ${PSX_SDL_LIBRARIES}) + add_test(NAME host_keymap_test COMMAND host_keymap_test) + # Registered since before this change, but it did NOT LINK on origin/master: # sio.c had grown psx_read_word / psx_get_cycle_count / i_mask / # g_debug_last_store_pc references that this test's stub block never gained, diff --git a/runtime/include/host_keymap.h b/runtime/include/host_keymap.h index 24d78314..7ad5476e 100644 --- a/runtime/include/host_keymap.h +++ b/runtime/include/host_keymap.h @@ -10,13 +10,15 @@ extern "C" { /* * Host hotkeys from recomp-ui config.ini [KeyMap] (SDL key names + Ctrl+/Alt+/ - * Shift+ prefixes). VolumeUp / VolumeDown are honored by the runtime; missing - * lines keep the historic keypad +/- defaults. + * Shift+ prefixes). Missing lines keep the historic runtime defaults. */ typedef enum HostKeymapAction { - HOST_KEYMAP_VOLUME_UP = 0, + HOST_KEYMAP_FULLSCREEN = 0, + HOST_KEYMAP_TURBO, + HOST_KEYMAP_VOLUME_UP, HOST_KEYMAP_VOLUME_DOWN, + HOST_KEYMAP_DISPLAY_PERF, HOST_KEYMAP_REWIND, /* default F8 */ HOST_KEYMAP_SAVE_STATE_MENU, /* default F7 */ HOST_KEYMAP_ACTION_COUNT @@ -28,6 +30,9 @@ void host_keymap_load(const char *config_ini_path); /* 1 if (keycode, mod) matches a binding for `action`. */ int host_keymap_match(HostKeymapAction action, int keycode, int mod); +/* 1 if any configured bind for `action` is currently held. */ +int host_keymap_down(HostKeymapAction action, const uint8_t *keys, int mod); + /* Format the primary bind for `action` into out (e.g. "F8", "Ctrl+R", * "GRAVE"). Punctuation key names are tokenized for the rewind overlay font. * Returns out, or "F8" / "" on empty action. Always NUL-terminates. */ diff --git a/runtime/include/host_osd.h b/runtime/include/host_osd.h index fefab1dd..e4e6f307 100644 --- a/runtime/include/host_osd.h +++ b/runtime/include/host_osd.h @@ -19,6 +19,9 @@ extern "C" { /* Top-left text toast. duration_ms <= 0 uses 2000. */ void host_osd_push(const char *msg, int duration_ms); +/* Persistent top-left status text. Passing NULL/empty clears it. */ +void host_osd_set_status(const char *msg); + /* Right-side vertical volume bar (percent clamped 0..100). */ void host_osd_show_volume(int percent, int duration_ms); diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 5d13b6a9..8a49dda9 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -441,18 +441,34 @@ endif() # Local rewind: full rbengine when netplay is on; otherwise compile snap_ring.c # only (no recomp-net / sched / hash_confirm). Never both — duplicate symbols. +# Defaults ON because local rewind is useful for every PSX title. If a title +# deliberately opts out with -DPSX_REWIND=OFF, recomp-ui hides the Rewind +# controls instead of exposing dead hotkeys. +option(PSX_REWIND "Build and expose local rewind support" ON) set(PSXRECOMP_HAS_RBENGINE_SNAP FALSE) set(PSXRECOMP_RBENGINE_SNAP_INCLUDE "") -if(PSXRECOMP_HAS_RBENGINE) - set(PSXRECOMP_HAS_RBENGINE_SNAP TRUE) -elseif(RECOMP_RBENGINE_ROOT - AND EXISTS "${RECOMP_RBENGINE_ROOT}/src/snap/rbe_snap_ring.c" - AND EXISTS "${RECOMP_RBENGINE_ROOT}/include/retcomm_rbengine/snap_ring.h") - list(APPEND PSXRECOMP_RUNTIME_SOURCES - ${RECOMP_RBENGINE_ROOT}/src/snap/rbe_snap_ring.c) - set(PSXRECOMP_HAS_RBENGINE_SNAP TRUE) - set(PSXRECOMP_RBENGINE_SNAP_INCLUDE "${RECOMP_RBENGINE_ROOT}/include") - message(STATUS "psxrecomp: rewind snap_ring (${RECOMP_RBENGINE_ROOT})") +if(PSX_REWIND) + if(PSXRECOMP_HAS_RBENGINE) + set(PSXRECOMP_HAS_RBENGINE_SNAP TRUE) + elseif(RECOMP_RBENGINE_ROOT + AND EXISTS "${RECOMP_RBENGINE_ROOT}/src/snap/rbe_snap_ring.c" + AND EXISTS "${RECOMP_RBENGINE_ROOT}/include/retcomm_rbengine/snap_ring.h") + list(APPEND PSXRECOMP_RUNTIME_SOURCES + ${RECOMP_RBENGINE_ROOT}/src/snap/rbe_snap_ring.c) + set(PSXRECOMP_HAS_RBENGINE_SNAP TRUE) + set(PSXRECOMP_RBENGINE_SNAP_INCLUDE "${RECOMP_RBENGINE_ROOT}/include") + message(STATUS "psxrecomp: rewind snap_ring (${RECOMP_RBENGINE_ROOT})") + endif() + if(NOT PSXRECOMP_HAS_RBENGINE_SNAP) + message(FATAL_ERROR + "psxrecomp: PSX_REWIND=ON exposes the Rewind launcher controls " + "but no retcomm-rbengine snap-ring backend was found.\n" + " git submodule update --init lib/retcomm-rbengine\n" + " or -DRECOMP_RBENGINE_ROOT=/path/to/retcomm-rbengine\n" + " or configure with -DPSX_REWIND=OFF to hide Rewind.") + endif() +else() + message(STATUS "psxrecomp: local rewind disabled (PSX_REWIND=OFF)") endif() # Lobby WebSocket client helpers are vendored under runtime/src/lobby_ws/ @@ -1358,6 +1374,8 @@ function(psxrecomp_add_runtime_target target) list(APPEND _psx_recomp_ui_args BRAND "${PSXRT_LAUNCHER_BRAND}") endif() recomp_target_launcher_ui(${target} ${_psx_recomp_ui_args}) + target_compile_definitions(${target} PRIVATE + RECOMP_UI_PSX_HAS_REWIND=$) endif() if(WIN32 OR MINGW) diff --git a/runtime/src/host_keymap.c b/runtime/src/host_keymap.c index ff28ea69..da2bbfbf 100644 --- a/runtime/src/host_keymap.c +++ b/runtime/src/host_keymap.c @@ -18,6 +18,7 @@ typedef struct HostKeyBind { int keycode; + int scancode; int mods; /* KMOD_CTRL | KMOD_ALT | KMOD_SHIFT subset */ } HostKeyBind; @@ -65,32 +66,44 @@ static void clear_all(void) { memset(s_actions, 0, sizeof(s_actions)); } -static void add_bind(HostKeymapAction action, int keycode, int mods) { +static void add_bind(HostKeymapAction action, int keycode, int scancode, int mods) { HostKeyAction *a; if (action < 0 || action >= HOST_KEYMAP_ACTION_COUNT) return; if (keycode == 0 || keycode == SDLK_UNKNOWN) return; a = &s_actions[action]; if (a->count >= HOST_KEYMAP_MAX_BINDS) return; a->binds[a->count].keycode = keycode; + a->binds[a->count].scancode = scancode; a->binds[a->count].mods = mods; a->count++; } static void apply_defaults(void) { + if (s_actions[HOST_KEYMAP_FULLSCREEN].count == 0) { + 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) + add_bind(HOST_KEYMAP_TURBO, (int)SDLK_TAB, (int)SDL_SCANCODE_TAB, 0); if (s_actions[HOST_KEYMAP_VOLUME_UP].count == 0) - add_bind(HOST_KEYMAP_VOLUME_UP, (int)SDLK_KP_PLUS, 0); + 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) - add_bind(HOST_KEYMAP_VOLUME_DOWN, (int)SDLK_KP_MINUS, 0); + 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) + 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) - add_bind(HOST_KEYMAP_REWIND, (int)SDLK_F8, 0); + add_bind(HOST_KEYMAP_REWIND, (int)SDLK_F8, (int)SDL_SCANCODE_F8, 0); +#endif if (s_actions[HOST_KEYMAP_SAVE_STATE_MENU].count == 0) - add_bind(HOST_KEYMAP_SAVE_STATE_MENU, (int)SDLK_F7, 0); + add_bind(HOST_KEYMAP_SAVE_STATE_MENU, (int)SDLK_F7, (int)SDL_SCANCODE_F7, 0); } /* Parse one "Ctrl+Alt+PageUp" token into key+mods. */ static void parse_one_token(HostKeymapAction action, char *tok) { int mods = 0; SDL_Keycode key; + SDL_Scancode sc; trim_inplace(tok); if (!tok[0] || ieq(tok, "(unbound)") || ieq(tok, "None")) return; for (;;) { @@ -114,7 +127,8 @@ static void parse_one_token(HostKeymapAction action, char *tok) { fprintf(stderr, "host_keymap: unknown key '%s'\n", tok); return; } - add_bind(action, (int)key, mods); + sc = SDL_GetScancodeFromName(tok); + add_bind(action, (int)key, (int)sc, mods); } static void parse_value(HostKeymapAction action, const char *value) { @@ -133,8 +147,11 @@ static void parse_value(HostKeymapAction action, const char *value) { } static HostKeymapAction action_for_key(const char *name) { + if (ieq(name, "Fullscreen")) return HOST_KEYMAP_FULLSCREEN; + if (ieq(name, "Turbo")) return HOST_KEYMAP_TURBO; if (ieq(name, "VolumeUp")) return HOST_KEYMAP_VOLUME_UP; if (ieq(name, "VolumeDown")) return HOST_KEYMAP_VOLUME_DOWN; + if (ieq(name, "DisplayPerf")) return HOST_KEYMAP_DISPLAY_PERF; if (ieq(name, "Rewind")) return HOST_KEYMAP_REWIND; if (ieq(name, "SaveStateMenu")) return HOST_KEYMAP_SAVE_STATE_MENU; return HOST_KEYMAP_ACTION_COUNT; @@ -201,6 +218,21 @@ int host_keymap_match(HostKeymapAction action, int keycode, int mod) { return 0; } +int host_keymap_down(HostKeymapAction action, const uint8_t *keys, int mod) { + const HostKeyAction *a; + const int relevant = (int)(KMOD_CTRL | KMOD_ALT | KMOD_SHIFT); + int i; + if (!keys || action < 0 || action >= HOST_KEYMAP_ACTION_COUNT) return 0; + a = &s_actions[action]; + for (i = 0; i < a->count; i++) { + const int sc = a->binds[i].scancode; + if (sc <= 0 || sc >= SDL_NUM_SCANCODES) continue; + if (!keys[sc]) continue; + if ((mod & relevant) == a->binds[i].mods) return 1; + } + return 0; +} + /* Rewind overlay FONT8 only draws ASCII 32..90 (space..Z); lowercase is * uppercased by the drawer. Map punctuation / odd SDL single-glyph names to * short tokens so binds like backtick don't render as "?". */ @@ -257,9 +289,12 @@ const char *host_keymap_label(HostKeymapAction action, char *out, size_t cap) { if (action < 0 || action >= HOST_KEYMAP_ACTION_COUNT) return out; a = &s_actions[action]; if (a->count <= 0) { +#if defined(PSX_HAS_RBENGINE_SNAP) if (action == HOST_KEYMAP_REWIND) snprintf(out, cap, "F8"); - else if (action == HOST_KEYMAP_SAVE_STATE_MENU) + else +#endif + if (action == HOST_KEYMAP_SAVE_STATE_MENU) snprintf(out, cap, "F7"); return out; } diff --git a/runtime/src/host_osd.c b/runtime/src/host_osd.c index 63d27505..62c387d0 100644 --- a/runtime/src/host_osd.c +++ b/runtime/src/host_osd.c @@ -130,6 +130,10 @@ static Uint32 s_expire_ms; static int s_active; static int s_img_dirty = 1; +static char s_status_msg[OSD_MAX_CHARS]; +static int s_status_active; +static int s_status_dirty = 1; + static int s_volume = 100; /* host master 0..100 */ static int s_vol_pct = 100; /* last shown bar fill */ static Uint32 s_vol_expire_ms; @@ -197,8 +201,8 @@ static int vol_visible(void) { return 1; } -static void rasterize_text(void) { - int n = (int)strlen(s_msg); +static void rasterize_text(const char *msg) { + int n = (int)strlen(msg); if (n > OSD_MAX_CHARS) n = OSD_MAX_CHARS; s_img_w = (OSD_PAD_X * 2 + n * OSD_GLYPH_W) * OSD_SCALE; s_img_h = (OSD_PAD_Y * 2 + OSD_GLYPH_H) * OSD_SCALE; @@ -211,7 +215,7 @@ static void rasterize_text(void) { s_img[i] = 0xFF202020u; for (int ci = 0; ci < n; ci++) { - unsigned char ch = (unsigned char)s_msg[ci]; + unsigned char ch = (unsigned char)msg[ci]; if (ch < 32 || ch > 126) ch = '?'; const uint8_t *g = FONT8X8[ch - 32]; for (int row = 0; row < OSD_GLYPH_H; row++) { @@ -338,6 +342,25 @@ void host_osd_push(const char *msg, int duration_ms) { #endif } +void host_osd_set_status(const char *msg) { +#if !HOST_OSD_VISUAL + (void)msg; + return; +#else + if (!msg || !msg[0]) { + if (s_status_active) s_needs_clear = 1; + s_status_msg[0] = '\0'; + s_status_active = 0; + s_status_dirty = 1; + return; + } + snprintf(s_status_msg, sizeof(s_status_msg), "%s", msg); + s_status_active = 1; + s_status_dirty = 1; + s_needs_clear = 0; +#endif +} + void host_osd_show_volume(int percent, int duration_ms) { #if !HOST_OSD_VISUAL (void)percent; @@ -375,7 +398,7 @@ int host_osd_needs_present(void) { #if !HOST_OSD_VISUAL return 0; #else - if (msg_visible() || vol_visible()) return 1; + if (msg_visible() || s_status_active || vol_visible()) return 1; return s_needs_clear; #endif } @@ -387,13 +410,21 @@ int host_osd_image(const uint32_t **pixels, int *w, int *h) { if (h) *h = 0; return 0; #else - if (!msg_visible()) { + const int show_msg = msg_visible(); + if (!show_msg && !s_status_active) { if (pixels) *pixels = NULL; if (w) *w = 0; if (h) *h = 0; return 0; } - if (s_img_dirty) rasterize_text(); + if (show_msg) { + if (s_img_dirty) rasterize_text(s_msg); + } else { + if (s_status_dirty || s_img_dirty) { + rasterize_text(s_status_msg); + s_status_dirty = 0; + } + } if (pixels) *pixels = s_img; if (w) *w = s_img_w; if (h) *h = s_img_h; @@ -426,7 +457,7 @@ void host_osd_present_done(void) { #if !HOST_OSD_VISUAL return; #else - if (!s_active && !s_vol_active) s_needs_clear = 0; + if (!s_active && !s_status_active && !s_vol_active) s_needs_clear = 0; #endif } diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index c2904bb1..e5d50bde 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -465,6 +465,7 @@ static int post_load_probe_env_on(void) { static Uint64 s_fps_last_time = 0; static uint64_t s_fps_last_frame = 0; static std::string s_fps_base_title; +static int s_fps_telemetry_enabled = -1; /* -1 = unread env */ static FramePacer s_frame_pacer = { 0 }; static int s_turbo_present_skip = 0; static int s_fmv_skip_present_skip = 0; @@ -517,6 +518,27 @@ static void present_session_reset(void) { smooth_60_reset(); } +static int fps_telemetry_enabled(void) { + if (s_fps_telemetry_enabled < 0) { + const char *e = std::getenv("PSX_FPS_TELEMETRY"); + s_fps_telemetry_enabled = (e && e[0] && e[0] != '0') ? 1 : 0; + } + return s_fps_telemetry_enabled; +} + +static void fps_telemetry_toggle(void) { + const int enabled = fps_telemetry_enabled() ? 0 : 1; + s_fps_telemetry_enabled = enabled; + s_fps_last_time = 0; + s_fps_last_frame = 0; + if (!enabled && sdl_window && !s_fps_base_title.empty()) + SDL_SetWindowTitle(sdl_window, s_fps_base_title.c_str()); + if (!enabled) + host_osd_set_status(NULL); + s_fps_base_title.clear(); + host_osd_push(enabled ? "FPS readout on" : "FPS readout off", 1500); +} + static void post_load_probe_stall_pc_note(uint32_t pc) { if (!pc) return; for (int i = 0; i < s_stall_pc_used; i++) { @@ -1065,6 +1087,7 @@ static int g_fullscreen = 0; /* tri-state: 0 windowed, 1 borderle static int g_video_screen = 0; /* 0=raw,1=crt,2=composite,3=trinitron */ static int g_video_win_w = 1280; /* window width (height follows aspect) */ static bool g_audio_spu_hq = false; /* SPU float-shadow (env overrides) */ +static int g_audio_freq = 44100; /* host device request */ static int g_auto_skip_fmv = 0; /* skip FMVs the instant they're detected */ static int g_rewind_depth = 50; /* local rewind snap count (50/100/150/200) */ static int g_rewind_interval = 15; /* frames between snaps (1/4/8/12/15) */ @@ -5635,14 +5658,14 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) { * than presents so turbo and skipped-frame modes still report game speed. * Skip during netplay post-load barrier — admit is stalled and the window * is not updating, so a climbing FPS line is misleading. */ - if (!psx_netplay_in_load_barrier()) { + if (fps_telemetry_enabled() && !psx_netplay_in_load_barrier()) { extern uint64_t s_frame_count; const Uint64 now = SDL_GetPerformanceCounter(); const Uint64 frequency = SDL_GetPerformanceFrequency(); if (!s_fps_last_time) { s_fps_last_time = now; s_fps_last_frame = s_frame_count; - if (sdl_window) { + if (sdl_window && s_fps_base_title.empty()) { const char *title = SDL_GetWindowTitle(sdl_window); if (title) s_fps_base_title = title; } @@ -5656,6 +5679,11 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) { s_fps_base_title.c_str(), fps, speed); SDL_SetWindowTitle(sdl_window, title); } + if (!g_headless) { + char osd[64]; + snprintf(osd, sizeof(osd), "FPS %.0f %.2fx", fps, speed); + host_osd_set_status(osd); + } if (netplay_timing_on() && s_np_timing_frames > 0) { const double invf = 1000.0 / (double)frequency; const double admit_ms = @@ -5773,6 +5801,11 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) { debug_force_cd_reinsert(); host_osd_push("CD reinsert", 1500); } + else if (!key_repeat && + host_keymap_match(HOST_KEYMAP_DISPLAY_PERF, (int)key, + (int)mod)) { + fps_telemetry_toggle(); + } /* Host volume: config.ini [KeyMap] VolumeUp/VolumeDown * (defaults: keypad +/-). 5% steps; shows right-side bar. */ else if (host_keymap_match(HOST_KEYMAP_VOLUME_UP, (int)key, @@ -5790,8 +5823,9 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) { * set in both SDL_WINDOW_FULLSCREEN and * SDL_WINDOW_FULLSCREEN_DESKTOP, so testing just that bit * detects "currently fullscreen, either mode". */ - else if ((key == SDLK_RETURN && (mod & KMOD_ALT)) || - (key == SDLK_f && (mod & (KMOD_GUI | KMOD_CTRL)))) { + else if (!key_repeat && + host_keymap_match(HOST_KEYMAP_FULLSCREEN, (int)key, + (int)mod)) { Uint32 is_fs = SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_FULLSCREEN; if (is_fs) { @@ -6004,7 +6038,7 @@ static NetplayVblankEpilogue sdl_vblank_present_body(void) { const Uint8* keys = SDL_GetKeyboardState(NULL); static int turbo_skip = 0; const int TURBO_PRESENT_EVERY = 30; - if (keys[SDL_SCANCODE_TAB]) { + if (host_keymap_down(HOST_KEYMAP_TURBO, keys, (int)SDL_GetModState())) { turbo_skip = (turbo_skip + 1) % TURBO_PRESENT_EVERY; if (turbo_skip != 0) { ep.skip_pace = 1; @@ -10657,6 +10691,7 @@ int main(int argc, char** argv) { g_video_aspect_num = us.aspect_num; g_video_aspect_den = us.aspect_den; } + if (us.has_audio_freq) g_audio_freq = us.audio_freq; if (us.has_spu_hq) g_audio_spu_hq = us.spu_hq; if (us.has_rewind_depth) g_rewind_depth = us.rewind_depth; if (us.has_rewind_interval) g_rewind_interval = us.rewind_interval; @@ -11073,6 +11108,7 @@ int main(int argc, char** argv) { seed.has_frame_interpolation_fps = true; seed.aspect_num = g_video_aspect_num; seed.aspect_den = g_video_aspect_den; seed.has_aspect_ratio = true; + seed.audio_freq = g_audio_freq; seed.has_audio_freq = true; seed.spu_hq = g_audio_spu_hq; seed.has_spu_hq = true; seed.rewind_depth = g_rewind_depth; seed.has_rewind_depth = true; seed.rewind_interval = g_rewind_interval; seed.has_rewind_interval = true; @@ -11178,7 +11214,7 @@ int main(int argc, char** argv) { ls.widescreen = (seed.aspect_num == 16 && seed.aspect_den == 9) ? 1 : 0; ls.widescreen_hud = ls.widescreen; ls.enable_audio = 1; - ls.audio_freq = 44100; + ls.audio_freq = seed.audio_freq; ls.volume = host_volume_get(); { const int n = std::min(PSX_MAX_PLAYERS, RECOMP_LAUNCHER_MAX_PLAYERS); @@ -11466,6 +11502,7 @@ int main(int argc, char** argv) { seed.screen_kind = ls.screen_kind; seed.has_screen_kind = true; seed.frame_interpolation = ls.frame_interp != 0; seed.has_frame_interpolation = true; seed.frame_interpolation_fps = ls.frame_interp_fps; seed.has_frame_interpolation_fps = true; + seed.audio_freq = ls.audio_freq; seed.has_audio_freq = true; seed.spu_hq = ls.spu_hq != 0; seed.has_spu_hq = true; seed.rewind_depth = ls.rewind_depth > 0 ? ls.rewind_depth : 50; seed.has_rewind_depth = true; @@ -11677,6 +11714,7 @@ int main(int argc, char** argv) { g_frame_interpolation_fps = seed.frame_interpolation_fps; g_video_aspect_num = seed.aspect_num; g_video_aspect_den = seed.aspect_den; + g_audio_freq = seed.audio_freq; g_audio_spu_hq = seed.spu_hq; g_rewind_depth = seed.has_rewind_depth && seed.rewind_depth > 0 ? seed.rewind_depth : 50; @@ -12250,7 +12288,7 @@ session_reboot: if (SDL_InitSubSystem(SDL_INIT_AUDIO) == 0) { PsxSdlAudioSpec want = {}; PsxSdlAudioSpec have = {}; - want.freq = 44100; + want.freq = g_audio_freq; want.format = AUDIO_S16SYS; want.channels = 2; want.samples = 1024; @@ -13008,7 +13046,7 @@ soft_return_lobby: (g_video_aspect_num == 16 && g_video_aspect_den == 9) ? 1 : 0; ls.widescreen_hud = ls.widescreen; ls.enable_audio = 1; - ls.audio_freq = 44100; + ls.audio_freq = g_audio_freq; ls.volume = host_volume_get(); ls.window_width = g_video_win_w; ls.renderer = g_video_renderer; @@ -13279,6 +13317,8 @@ soft_return_lobby: us.has_frame_interpolation = true; us.frame_interpolation_fps = ls.frame_interp_fps; us.has_frame_interpolation_fps = true; + us.audio_freq = ls.audio_freq; + us.has_audio_freq = true; us.spu_hq = ls.spu_hq != 0; us.has_spu_hq = true; us.rewind_depth = ls.rewind_depth > 0 ? ls.rewind_depth : 50; @@ -13337,6 +13377,7 @@ soft_return_lobby: g_fullscreen = ls.fullscreen != 0; g_frame_interpolation = ls.frame_interp ? 1 : 0; g_frame_interpolation_fps = ls.frame_interp_fps; + g_audio_freq = ls.audio_freq; g_audio_spu_hq = ls.spu_hq != 0; if (ls.rewind_depth > 0) { g_rewind_depth = ls.rewind_depth; diff --git a/runtime/tests/test_host_keymap.c b/runtime/tests/test_host_keymap.c new file mode 100644 index 00000000..25ddb8f5 --- /dev/null +++ b/runtime/tests/test_host_keymap.c @@ -0,0 +1,84 @@ +#include "host_keymap.h" +#include "psx_sdl.h" + +#include +#include + +static int failures = 0; + +static void check(int cond, const char *what) { + if (!cond) { + fprintf(stderr, "FAIL: %s\n", what); + failures++; + } +} + +static int mod_ctrl(void) { return (int)KMOD_CTRL; } +static int mod_alt(void) { return (int)KMOD_ALT; } + +int main(int argc, char **argv) { + uint8_t keys[SDL_NUM_SCANCODES]; + const char *cfg = "host_keymap_test.ini"; + FILE *f; + (void)argc; + (void)argv; + + memset(keys, 0, sizeof(keys)); + host_keymap_load(NULL); + check(host_keymap_match(HOST_KEYMAP_FULLSCREEN, (int)SDLK_RETURN, mod_alt()), + "default fullscreen includes Alt+Return"); + check(host_keymap_match(HOST_KEYMAP_FULLSCREEN, (int)SDLK_f, mod_ctrl()), + "default fullscreen includes Ctrl+F"); + keys[SDL_SCANCODE_TAB] = 1; + check(host_keymap_down(HOST_KEYMAP_TURBO, keys, 0), + "default turbo is held by Tab"); + keys[SDL_SCANCODE_TAB] = 0; + check(host_keymap_match(HOST_KEYMAP_VOLUME_UP, (int)SDLK_KP_PLUS, 0), + "default volume up is keypad plus"); + check(host_keymap_match(HOST_KEYMAP_VOLUME_DOWN, (int)SDLK_KP_MINUS, 0), + "default volume down is keypad minus"); + check(host_keymap_match(HOST_KEYMAP_DISPLAY_PERF, (int)SDLK_f, 0), + "default display perf is F"); + + f = fopen(cfg, "wb"); + check(f != NULL, "create temporary config.ini"); + if (!f) return 1; + fputs("[KeyMap]\n" + "Fullscreen = F11\n" + "Turbo = Q\n" + "VolumeUp = Up\n" + "VolumeDown = Down\n" + "DisplayPerf = F10\n", + f); + fclose(f); + + host_keymap_load(cfg); + check(!host_keymap_match(HOST_KEYMAP_FULLSCREEN, (int)SDLK_f, mod_ctrl()), + "fullscreen rebind disables Ctrl+F fallback"); + check(host_keymap_match(HOST_KEYMAP_FULLSCREEN, (int)SDLK_F11, 0), + "fullscreen rebind uses F11"); + memset(keys, 0, sizeof(keys)); + keys[SDL_SCANCODE_TAB] = 1; + check(!host_keymap_down(HOST_KEYMAP_TURBO, keys, 0), + "turbo rebind disables Tab fallback"); + keys[SDL_SCANCODE_TAB] = 0; + keys[SDL_SCANCODE_Q] = 1; + check(host_keymap_down(HOST_KEYMAP_TURBO, keys, 0), + "turbo rebind uses Q"); + check(host_keymap_match(HOST_KEYMAP_VOLUME_UP, (int)SDLK_UP, 0), + "volume up rebind uses Up"); + check(host_keymap_match(HOST_KEYMAP_VOLUME_DOWN, (int)SDLK_DOWN, 0), + "volume down rebind uses Down"); + check(!host_keymap_match(HOST_KEYMAP_DISPLAY_PERF, (int)SDLK_f, 0), + "display perf rebind disables F fallback"); + check(host_keymap_match(HOST_KEYMAP_DISPLAY_PERF, (int)SDLK_F10, 0), + "display perf rebind uses F10"); + + remove(cfg); + if (failures) { + fprintf(stderr, "host_keymap_test: %d failure(s)\n", failures); + return 1; + } + printf("PASS: host keymap defaults and rebinds\n"); + return 0; +}