From 3fae02c7d342981c9910db7715305bc57b261a8b Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 18:09:19 +0100 Subject: GUI: allow movement while open; freeze look and block bind capture Feedback pass on the preview shell: - Freeze the rendered view explicitly in CreateMove and again at FRAME_RENDER_START; CInput was still letting mouse look move the camera. - Allow normal WASD/jump/duck movement while the GUI is open; strip attack/use/impulse/weapon selection so GUI clicks do not fire. - While bind capture is active, block all game input. - Esc no longer closes the GUI; it only cancels bind capture. F2 or the title-bar X close the preview. - Interpose SDL_ShowCursor / SDL_SetRelativeMouseMode / warp / window grab so the game cannot re-capture or hide the native cursor while the GUI is open. --- CONTEXT.md | 16 +++--- docs/adr/0001-custom-isurface-gui.md | 5 +- src/gui.cpp | 96 +++++++++++++++++++++++++++++++++--- src/hl2h.cpp | 38 ++++++++++---- src/hl2h.h | 1 + 5 files changed, 133 insertions(+), 23 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 1c43b1f..53c662e 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -148,20 +148,24 @@ Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604. - **Category**: sidebar group. Movement, Combat, Visuals, Weapons, System. - **Card**: per-feature panel. Header always shows Active + Bind; body is a collapsible set of settings. -- **Freeze**: while the GUI is open, the usercmd movement/buttons/impulses/view - are held and combat features are skipped; the game keeps simulating and - ESP/FOV circle/speed graph keep drawing. No invulnerability. +- **Freeze**: while the GUI is open, look/attack/use/impulse/weapon selection + are blocked and combat features are skipped, but normal WASD/jump/duck + movement is allowed. Bind capture blocks all game input until it ends. The + game keeps simulating and ESP/FOV circle/speed graph keep drawing. No + invulnerability. - **Native cursor**: on open, leave SDL relative-mouse mode and show the OS cursor; restore on close. Absolute mouse coordinates are used. -- **Bind capture**: click Bind, press a key/button; Esc or clicking elsewhere - cancels; Clear sets None; Insert is reserved for the GUI. +- **Bind capture**: click Bind, press a key/button; Esc cancels capture but + does not close the GUI; clicking elsewhere cancels; Clear sets None; Insert + is reserved for the GUI. - **UI scale**: `clamp(min(screen_w/1920, screen_h/1080), 0.75, 1.5)`; base window 960x620, body/title fonts 15/20 px at scale 1. - Every feature gets Active + Bind; bind None means always-on. - System page: config path/build info, Reload config, and a two-click Reset that restores feature settings/binds but keeps GUI layout. - Window: fixed-size, draggable by title bar, clamped to screen, position, - category and expanded cards persisted under `[gui]`. + category and expanded cards persisted under `[gui]`. Esc does not close the + GUI; the toggle key or title-bar X does. ## Bones / ESP diff --git a/docs/adr/0001-custom-isurface-gui.md b/docs/adr/0001-custom-isurface-gui.md index 9da3c0c..9595ca8 100644 --- a/docs/adr/0001-custom-isurface-gui.md +++ b/docs/adr/0001-custom-isurface-gui.md @@ -38,8 +38,9 @@ path. - Left sidebar categories (Movement, Combat, Visuals, Weapons, System) and a content pane of feature cards with collapsible settings. - Native OS cursor by releasing SDL relative-mouse mode while the GUI is open. -- While open, freeze usercmd input/view and skip combat features; keep the - game simulating and keep ESP/FOV circle/speed graph drawing. +- While open, freeze look/attack/use and skip combat features, but allow + normal movement; bind capture blocks all game input. Keep the game + simulating and keep ESP/FOV circle/speed graph drawing. - Replace the text menu with the GUI. No permanent fallback menu; git history retains the old implementation. diff --git a/src/gui.cpp b/src/gui.cpp index 74dd537..923cd4d 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -879,12 +879,8 @@ void gui_handle_input() { return; } - if (esc_edge) { - if (g_capture_card >= 0) - g_capture_card = -1; - else - close_gui(); - } + if (esc_edge && g_capture_card >= 0) + g_capture_card = -1; // Keep the native cursor free even if the game re-grabs it. if (sd_set_relative) @@ -1196,4 +1192,92 @@ void gui_draw() { } bool gui_is_open() { return g_open; } +bool gui_bind_capture_active() { return g_open && g_capture_card >= 0; } QAngle gui_frozen_view() { return g_frozen; } + +// --------------------------------------------------------------------------- +// SDL cursor interposition +// --------------------------------------------------------------------------- +// The game re-asserts relative mouse mode / hides the cursor every frame. While +// the GUI is open, force the opposite so the native cursor stays usable. + +static int (*real_show_cursor)(int) = nullptr; +static int (*real_set_relative)(int) = nullptr; +static void (*real_warp_mouse)(void *, int, int) = nullptr; +static void (*real_set_grab)(void *, int) = nullptr; + +extern "C" __attribute__((visibility("default"))) int SDL_ShowCursor(int toggle) { + if (!real_show_cursor) + real_show_cursor = (int (*)(int))dlsym(RTLD_NEXT, "SDL_ShowCursor"); + if (g_open && toggle != -1) { + if (real_show_cursor) + real_show_cursor(1); + return 1; + } + return real_show_cursor ? real_show_cursor(toggle) : 0; +} + +extern "C" __attribute__((visibility("default"))) int +SDL_SetRelativeMouseMode(int enabled) { + if (!real_set_relative) + real_set_relative = + (int (*)(int))dlsym(RTLD_NEXT, "SDL_SetRelativeMouseMode"); + if (g_open) { + if (real_set_relative) + real_set_relative(0); + return 0; + } + return real_set_relative ? real_set_relative(enabled) : 0; +} + +extern "C" __attribute__((visibility("default"))) void +SDL_WarpMouseInWindow(void *window, int x, int y) { + if (!real_warp_mouse) + real_warp_mouse = + (void (*)(void *, int, int))dlsym(RTLD_NEXT, "SDL_WarpMouseInWindow"); + if (g_open) + return; + if (real_warp_mouse) + real_warp_mouse(window, x, y); +} + +extern "C" __attribute__((visibility("default"))) void +SDL_SetWindowGrab(void *window, int grabbed) { + if (!real_set_grab) + real_set_grab = + (void (*)(void *, int))dlsym(RTLD_NEXT, "SDL_SetWindowGrab"); + if (g_open) { + if (real_set_grab) + real_set_grab(window, 0); + return; + } + if (real_set_grab) + real_set_grab(window, grabbed); +} + +static void (*real_set_mouse_grab)(void *, int) = nullptr; +static void (*real_warp_global)(int, int) = nullptr; + +extern "C" __attribute__((visibility("default"))) void +SDL_SetWindowMouseGrab(void *window, int grabbed) { + if (!real_set_mouse_grab) + real_set_mouse_grab = + (void (*)(void *, int))dlsym(RTLD_NEXT, "SDL_SetWindowMouseGrab"); + if (g_open) { + if (real_set_mouse_grab) + real_set_mouse_grab(window, 0); + return; + } + if (real_set_mouse_grab) + real_set_mouse_grab(window, grabbed); +} + +extern "C" __attribute__((visibility("default"))) void +SDL_WarpMouseGlobal(int x, int y) { + if (!real_warp_global) + real_warp_global = (void (*)(int, int))dlsym(RTLD_NEXT, "SDL_WarpMouseGlobal"); + if (g_open) + return; + if (real_warp_global) + real_warp_global(x, y); +} diff --git a/src/hl2h.cpp b/src/hl2h.cpp index c0c27af..a0e6b53 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -546,10 +546,23 @@ static void do_move(int seq, float frametime) { cmd->viewangles[0] = f.x; cmd->viewangles[1] = f.y; cmd->viewangles[2] = f.z; - cmd->forwardmove = 0.0f; - cmd->sidemove = 0.0f; - cmd->upmove = 0.0f; - cmd->buttons = 0; + // Freeze the rendered view explicitly: CInput may not push the modified + // command view back to the engine if the client mode returns false. + if (g_iface.engine) { + QAngle set = f; + using SetViewFn = void (*)(void *, QAngle *); + vfunc(g_iface.engine, 20)(g_iface.engine, &set); + } + if (gui_bind_capture_active()) { + // Bind capture must not move/act in the game while keys are sampled. + cmd->forwardmove = 0.0f; + cmd->sidemove = 0.0f; + cmd->upmove = 0.0f; + cmd->buttons = 0; + } else { + // Movement is allowed while the GUI is open, but not look/attack/use. + cmd->buttons &= (IN_JUMP | IN_DUCK); + } cmd->impulse = 0; cmd->weaponselect = 0; cmd->weaponsubtype = 0; @@ -670,12 +683,19 @@ void set_silent_restore(const QAngle &angles) { static void hkdFrameStageNotify(void *self, int stage) { if (oFrameStageNotify) oFrameStageNotify(self, stage); - if (stage == 5 && g_silent_restore_pending) { - g_silent_restore_pending = false; - if (g_iface.engine) { - QAngle a = g_silent_restore_angles; + if (stage == 5) { + if (g_silent_restore_pending) { + g_silent_restore_pending = false; + if (g_iface.engine) { + QAngle a = g_silent_restore_angles; + using SetViewFn = void (*)(void *, QAngle *); + vfunc(g_iface.engine, 20)(g_iface.engine, &a); + } + } + if (gui_is_open() && g_iface.engine) { + QAngle f = gui_frozen_view(); using SetViewFn = void (*)(void *, QAngle *); - vfunc(g_iface.engine, 20)(g_iface.engine, &a); + vfunc(g_iface.engine, 20)(g_iface.engine, &f); } } } diff --git a/src/hl2h.h b/src/hl2h.h index 11b9014..8a81439 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -234,6 +234,7 @@ void menu_force_close(); void gui_handle_input(); void gui_draw(); bool gui_is_open(); +bool gui_bind_capture_active(); QAngle gui_frozen_view(); // ISurface pointer (defined in menu.cpp, used for drawing tests) struct ISurface; -- cgit v1.2.3