From 304441c71997ee61e618146f4d4302c6d2b952ad Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 18:21:13 +0100 Subject: GUI: Insert-only close, freeze mouse look at the source Feedback pass: - Insert now toggles the GUI; the old text tree moved to F1. - Remove the title-bar X. Insert is the only close path; Esc only cancels bind capture. - Discard accumulated mouse deltas in CInput::CreateMove before the game can apply them (CInput::ClearStates), so the camera no longer moves for a frame and snaps back. Also skip ExtraMouseSample and zero relative mouse state while the GUI is open. - Strip attack/use/impulse/weapon selection from the command while open; keep jump, duck and walk. Skip combat command edits (spread seed) too. - Widen System page buttons so "Reload config" and "Reset defaults" fit. - Update CONTEXT/ADR for the Insert-only close behavior. --- src/cinput.cpp | 38 ++++++++++++++++++++++++++++++- src/gui.cpp | 71 ++++++++++++++++++++++++++++++++++++++++++---------------- src/hl2h.cpp | 3 ++- src/hl2h.h | 3 ++- src/menu.cpp | 9 ++++---- 5 files changed, 98 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/cinput.cpp b/src/cinput.cpp index a75cc4f..ad7face 100644 --- a/src/cinput.cpp +++ b/src/cinput.cpp @@ -85,12 +85,42 @@ void fix_verified_crc(int seq, uint32_t crc) { using InputCreateMoveFn = void (*)(void *, int, float, bool); static InputCreateMoveFn oInputCreateMove = nullptr; +using InputExtraMouseFn = void (*)(void *, float, bool); +static InputExtraMouseFn oInputExtraMouseSample = nullptr; + +// ExtraMouseSample runs AdjustAngles at render-frame rate, between movement +// commands. While the GUI is open it must not touch the view; movement still +// comes through CreateMove. +static void hkdInputExtraMouseSample(void *self, float frametime, bool active) { + if (gui_is_open()) + return; + if (oInputExtraMouseSample) + oInputExtraMouseSample(self, frametime, active); +} + +void input_clear_mouse_states() { + if (!g_input) + return; + // CInput::ClearStates (IInput slot 20): zeros both the software mouse + // accumulator and the engine raw-input accumulator. + using Fn = void (*)(void *); + vfunc((void *)g_input, 20)((void *)g_input); +} static void hkdInputCreateMove(void *self, int seq, float frametime, bool active) { + // Mouse deltas are accumulated between ticks. With the custom GUI open we + // must discard them before CInput::CreateMove applies mouse look, otherwise + // the view moves for one frame and snaps back when do_move freezes it. + if (gui_is_open()) + input_clear_mouse_states(); if (oInputCreateMove) oInputCreateMove(self, seq, frametime, active); + // No combat features may modify the command while the GUI is open. + if (gui_is_open()) + return; + CUserCmd *cmd = get_usercmd(seq); if (!cmd) return; @@ -125,8 +155,14 @@ static bool install_input_hook() { memcpy(nvt, vt, N * sizeof(void *)); oInputCreateMove = (InputCreateMoveFn)vt[3]; nvt[3] = (void *)&hkdInputCreateMove; + if (N > 4) { + oInputExtraMouseSample = (InputExtraMouseFn)vt[4]; + nvt[4] = (void *)&hkdInputExtraMouseSample; + } VTABLE((void *)g_input) = nvt; - logf("CInput CreateMove hooked: slot 3 orig=%p", (void *)oInputCreateMove); + logf("CInput CreateMove hooked: slot 3 orig=%p; ExtraMouseSample slot 4 " + "orig=%p", + (void *)oInputCreateMove, (void *)oInputExtraMouseSample); return true; } diff --git a/src/gui.cpp b/src/gui.cpp index 923cd4d..eb9062e 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,4 +1,4 @@ -// gui.cpp — custom immediate-mode GUI shell (temporary F2 preview) +// gui.cpp — custom immediate-mode GUI shell (temporary Insert preview) // // This is the first vertical slice: the visual shell, categories, cards, // dummy controls, cursor handling and input freeze. It does not read or write @@ -575,6 +575,7 @@ static void close_gui() { g_dragging = false; g_scroll_drag = false; g_slider_drag = -1; + input_clear_mouse_states(); if (g_sdl_ready) { if (sd_set_relative) sd_set_relative(g_prev_relative ? 1 : 0); @@ -611,6 +612,7 @@ static void open_gui() { if (sd_show_cursor) sd_show_cursor(1); } + input_clear_mouse_states(); g_open = true; g_capture_card = -1; g_wheel.exchange(0); @@ -659,7 +661,7 @@ static void handle_capture(const unsigned char *keys) { for (int k = 0; k < 512; k++) { if (!keys[k] || g_prev_keys[k]) continue; - if (k == 73 || k == 59) // Insert reserved; F2 closes the preview + if (k == 73) // Insert is reserved for opening/closing the GUI continue; g_cards[g_capture_card].bind = k; g_capture_card = -1; @@ -674,12 +676,7 @@ static void handle_mouse_click() { // title bar: drag or close Rect title{g_win_x, g_win_y, g_win_w, S(38.0f)}; - Rect close{g_win_x + g_win_w - S(38.0f), g_win_y, S(38.0f), S(38.0f)}; if (title.has(mx, my)) { - if (close.has(mx, my)) { - close_gui(); - return; - } g_dragging = true; g_drag_dx = mx - g_win_x; g_drag_dy = my - g_win_y; @@ -787,7 +784,7 @@ static void handle_mouse_click() { return; } if (ct.kind == CK_BUTTON) { - Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), + Rect b{cr.x + cr.w - S(176.0f), by + S(3.0f), S(160.0f), S(26.0f)}; if (b.has(mx, my)) { logf("gui: dummy action %s", ct.label ? ct.label : "?"); @@ -858,8 +855,8 @@ void gui_handle_input() { if (nk > 512) nk = 512; update_mouse(); - bool f2 = (nk > 59) && keys[59]; - bool f2_edge = f2 && !g_prev_keys[59]; + bool toggle_key = (nk > 73) && keys[73]; + bool toggle_edge = toggle_key && !g_prev_keys[73]; bool esc = (nk > 41) && keys[41]; bool esc_edge = esc && !g_prev_keys[41]; bool left = (nk > 80) && keys[80]; @@ -867,7 +864,7 @@ void gui_handle_input() { bool left_edge = left && !g_prev_keys[80]; bool right_edge = right && !g_prev_keys[79]; - if (f2_edge) { + if (toggle_edge) { if (g_open) close_gui(); else @@ -887,6 +884,11 @@ void gui_handle_input() { sd_set_relative(0); if (sd_show_cursor) sd_show_cursor(1); + if (g_iface.engine) { + QAngle f = g_frozen; + using SetViewFn = void (*)(void *, QAngle *); + vfunc(g_iface.engine, 20)(g_iface.engine, &f); + } handle_capture(keys); @@ -1063,7 +1065,7 @@ static void draw_card(const Rect &clip, int index, int y) { text_in(clip, cr.x + S(16.0f), by + S(1.0f), ct.label, g_font_body, g_body_h, cr.w - S(24.0f), C_DIM); } else if (ct.kind == CK_BUTTON) { - Rect b{cr.x + cr.w - S(130.0f), by + S(3.0f), S(114.0f), S(26.0f)}; + Rect b{cr.x + cr.w - S(176.0f), by + S(3.0f), S(160.0f), S(26.0f)}; fill(b, Col{45, 50, 60, 255}, &clip); outline(b, C_BORDER, &clip); text_in(clip, b.x + S(10.0f), b.y + S(4.0f), ct.label, g_font_body, @@ -1126,12 +1128,6 @@ void gui_draw() { g_title_h, win.w - S(80.0f), C_TEXT); text_in(win, win.x + S(110.0f), win.y + S(10.0f), "GUI shell preview", g_font_body, g_body_h, win.w - S(180.0f), C_DIM); - Rect close{win.x + win.w - S(38.0f), win.y, S(38.0f), S(38.0f)}; - fill(close, Col{45, 48, 56, 255}); - line(close.x + S(13.0f), close.y + S(12.0f), close.x + S(25.0f), - close.y + S(25.0f), C_DIM); - line(close.x + S(25.0f), close.y + S(12.0f), close.x + S(13.0f), - close.y + S(25.0f), C_DIM); Rect side{win.x, win.y + S(38.0f), S(190.0f), win.h - S(38.0f) - S(30.0f)}; @@ -1185,7 +1181,7 @@ void gui_draw() { "config: ~/.config/hl2hake/config", g_font_body, g_body_h, foot.w - S(220.0f), C_DIM); text_in(foot, foot.x + foot.w - S(200.0f), foot.y + S(6.0f), - g_capture_card >= 0 ? "press a key / Esc" : "F2 close | Insert text", + g_capture_card >= 0 ? "press a key / Esc" : "INS close | F1 text menu", g_font_body, g_body_h, S(190.0f), C_DIM); draw_tooltip(); @@ -1281,3 +1277,40 @@ SDL_WarpMouseGlobal(int x, int y) { if (real_warp_global) real_warp_global(x, y); } + +static unsigned int (*real_get_rel_mouse)(int *, int *) = nullptr; +static int (*real_poll_event)(void *) = nullptr; + +extern "C" __attribute__((visibility("default"))) unsigned int +SDL_GetRelativeMouseState(int *x, int *y) { + if (!real_get_rel_mouse) + real_get_rel_mouse = + (unsigned int (*)(int *, int *))dlsym(RTLD_NEXT, + "SDL_GetRelativeMouseState"); + unsigned int mask = real_get_rel_mouse ? real_get_rel_mouse(x, y) : 0; + if (g_open) { + if (x) *x = 0; + if (y) *y = 0; + } + return mask; +} + +extern "C" __attribute__((visibility("default"))) int +SDL_PollEvent(void *event) { + if (!real_poll_event) + real_poll_event = (int (*)(void *))dlsym(RTLD_NEXT, "SDL_PollEvent"); + if (!real_poll_event) + return 0; + for (;;) { + int r = real_poll_event(event); + if (!r) + return 0; + if (!g_open || !event) + return r; + unsigned int type = *(unsigned int *)event; + if (type != 0x400) // SDL_MOUSEMOTION + return r; + // Drop motion while the GUI is open: the game must not accumulate look + // deltas. The GUI uses absolute SDL_GetMouseState polling. + } +} diff --git a/src/hl2h.cpp b/src/hl2h.cpp index a0e6b53..1460577 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -561,7 +561,8 @@ static void do_move(int seq, float frametime) { cmd->buttons = 0; } else { // Movement is allowed while the GUI is open, but not look/attack/use. - cmd->buttons &= (IN_JUMP | IN_DUCK); + // Keep locomotion buttons (jump, duck, walk) only. + cmd->buttons &= (IN_JUMP | IN_DUCK | IN_SPEED); } cmd->impulse = 0; cmd->weaponselect = 0; diff --git a/src/hl2h.h b/src/hl2h.h index 8a81439..d5a9dc3 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -129,7 +129,7 @@ struct CUserCmd { // engine constants // --------------------------------------------------------------------------- -enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5, IN_ATTACK2 = 1 << 11 }; +enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5, IN_ATTACK2 = 1 << 11, IN_SPEED = 1 << 17 }; enum { FL_ONGROUND = 1 << 0, FL_DUCKING = 1 << 1, FL_INWATER = 1 << 9 }; enum { MOVETYPE_WALK = 2, MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 }; @@ -180,6 +180,7 @@ enum EspOption { // --------------------------------------------------------------------------- void note_modified(const CUserCmd *cmd, int flags, const float *vel); CUserCmd *get_usercmd(int seq); +void input_clear_mouse_states(); void fix_verified_crc(int seq, uint32_t crc); uint32_t usercmd_checksum(const CUserCmd *c); bool find_input_global(); diff --git a/src/menu.cpp b/src/menu.cpp index 56fe6fa..a0cf323 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -467,7 +467,8 @@ static void config_load() { // --------------------------------------------------------------------------- // SDL scancodes for navigation keys (matches the game's scancode set) -static constexpr int SCAN_INSERT = 73; +static constexpr int SCAN_INSERT = 73; // reserved for the GUI +static constexpr int SCAN_TEXT_MENU = 58; // F1 preview key for the old tree static constexpr int SCAN_UP = 82; // these are SDL scancodes static constexpr int SCAN_DOWN = 81; static constexpr int SCAN_LEFT = 80; @@ -637,7 +638,7 @@ static void menu_handle_input() { // SDL state can be sampled several times per rendered frame (both ISurface // hooks call outer_draw). Use a physical-key latch instead of the one-poll // edge so one press cannot open and close the menu in the same frame. - bool insert_down = key_held(SCAN_INSERT); + bool insert_down = key_held(SCAN_TEXT_MENU); bool insert_edge = insert_down && !g_insert_latch; if (insert_down) g_insert_latch = true; @@ -662,8 +663,8 @@ static void menu_handle_input() { for (int i = 0; i < MOUSE_BASE + 5; i++) { if (!g_key_down[i]) continue; - if (i == SCAN_INSERT || i == SCAN_UP || i == SCAN_DOWN || - i == SCAN_LEFT || i == SCAN_RIGHT) { + if (i == SCAN_INSERT || i == SCAN_TEXT_MENU || i == SCAN_UP || + i == SCAN_DOWN || i == SCAN_LEFT || i == SCAN_RIGHT) { g_bind_mode = false; return; // cancel bind select } -- cgit v1.2.3