From ed3d9b770b32eff3bbc2a9267af28bf74a234e91 Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:51:30 +0200 Subject: [PATCH] fix(cursor): game-cursor follow only while the hidden pointer moves (#443) Review of #444/#446: - The game-drawn cursor mode switched on for any covering window that hides the pointer (a video player's idle hide, hiding while typing), handing the view to Wind and moving it by DWM's pixel. It now switches on only while the hidden pointer moves (GameCursorStep). - Warm pulses follow who writes the view (!dc.freeCursor), so they run with a game-drawn cursor and in the hold after Inspect. - The pointer history is cleared at session start. - The pan_glide.h include comment had moved onto game_cursor.h. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu --- docs/architecture/07-cursor.md | 4 ++++ src/game_cursor.h | 9 +++++++++ src/main.cpp | 12 ++++++++---- src/version.h | 4 ++-- tests/test_game_cursor.cpp | 8 ++++++++ 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/architecture/07-cursor.md b/docs/architecture/07-cursor.md index f283408..5d0a061 100644 --- a/docs/architecture/07-cursor.md +++ b/docs/architecture/07-cursor.md @@ -141,6 +141,10 @@ slightly during zoom ramps, nearest keeps it pixelated and steady. Pure rules: per-tick `PointerHistory`: rms 15-23 px at 1000 px/s, 6 px at 400 (29 before). Wind's own write without the delay measured the same as DWM centring, so the gain is the delay. The rest is the game's frame timing. A mouselook game that recentres the pointer is locked and unaffected. + It switches on only while the hidden pointer moves (`GameCursorStep`): a video player hiding an + idle pointer, or typing, would otherwise hand the view to Wind and move it by DWM's pixel (#445). + Warm pulses follow who writes the view (`!dc.freeCursor`), so they run here and in the hold after + Inspect. - **Pan glide (#430, `panGlideMaxPx`, 0 = off).** A soft stop: when a mouse movement stops, the POINTER eases on at the hand's speed and slows to rest within `panGlideMaxPx` SCREEN px at any zoom (`src/pan_glide.h`). One setting since #434: the ease's time constant follows the diff --git a/src/game_cursor.h b/src/game_cursor.h index 279db03..9c0adf2 100644 --- a/src/game_cursor.h +++ b/src/game_cursor.h @@ -17,6 +17,15 @@ inline bool GameDrawsCursor(bool freeCursor, bool fsCover, bool pointerShowing, return freeCursor && fsCover && !pointerShowing && !hiddenByWind; } +// On only while the hidden pointer MOVES (review of #444): a fullscreen video player hiding an idle +// pointer, or Windows hiding it while typing, also clears CURSOR_SHOWING over a covering window, and +// the hand-over to Wind's own write moves the view by DWM's pixel (#445). A pointer that is hidden and +// still never switches; a game menu hides it while the hand moves it, which also hides the hand-over. +// Off as soon as the pointer shows or the window stops qualifying. +inline bool GameCursorStep(bool wasOn, bool drawsCursor, bool handMoved) { + return drawsCursor && (wasOn || handMoved); +} + // The delay in ms: cfg < 0 means one display frame at hz, 0 turns the delay off (DWM centring as for // any other window), anything else is used as given (capped at 100 ms). inline double GameCursorLagMs(int cfgMs, double hz) { diff --git a/src/main.cpp b/src/main.cpp index 3ea12ce..36976e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,8 +46,8 @@ #include "cursor_decode.h" // edge mode measures the cursor body #include "focus_track.h" // tracking: caret/focus watcher thread #include "tray_host.h" // WindTray.exe owns the icon and menu (#291) -#include "pan_glide.h" -#include "game_cursor.h" // momentum after a flick (#430) +#include "pan_glide.h" // momentum after a flick (#430) +#include "game_cursor.h" // the view follows a game-drawn cursor late (#443) #include "gain_learner.h" // learned pointer ballistics: locked pan at TRUE desktop speed #include "tray_ipc.h" // the status block shared with WindTray.exe #include "pointer_binds.h" // kWindInjectTag: tag our own injected clicks (#285) @@ -1446,6 +1446,7 @@ static void RunTick(TickState& t) { // detached view survived the 1x gap and the next zoom-in opened on the previous // session's caret. Every session starts with the mouse in charge. wind::ResetViewOwnerForSession(t.viewOwner); + t.ptrHist.clear(); // no samples from the previous session in the first lagged read (#443) t.postInspectHold = false; // a new session starts with DWM centring (#445) t.viewDetached = false; t.viewVx = 0; t.viewVy = 0; @@ -1912,7 +1913,8 @@ static void RunTick(TickState& t) { CURSORINFO ci{}; ci.cbSize = sizeof(ci); const bool showing = !GetCursorInfo(&ci) || (ci.flags & CURSOR_SHOWING) != 0; const bool byWind = t.cursorHidden || t.cursorHiddenByUs || t.cfg.cursorVisibility == "never"; - gameCursor = wind::GameDrawsCursor(freeCursor, fsCover, showing, byWind); + gameCursor = wind::GameCursorStep(t.gameCursor, wind::GameDrawsCursor(freeCursor, fsCover, showing, byWind), + curDx != 0 || curDy != 0); } if (gameCursor != t.gameCursor) { wind::Log(wind::LogLevel::Info, "lock", "game-drawn cursor %s (lag %.1f ms) lvl=%.2f", @@ -2343,7 +2345,9 @@ static void RunTick(TickState& t) { dc.wallNeeded = wind::NearWall(wallNeeded, r.srcLeft, r.srcTop, lvl, kMaxSafeTxMagnitude, 64.0); dc.quiesce = quiesceHold; ex.dwmCentre = wind::WantDwmCentring(dc); - ex.warmAllowed = !freeCursor; + // Warm pulses wherever Wind, not DWM centring, writes the view: a game-drawn cursor and + // the hold after Inspect write it from a free pointer too (review of #444/#446). + ex.warmAllowed = !dc.freeCursor; } // Our tray menu is open (in WindTray.exe, flagged through the shared block): the pointer // belongs to the USER (they are aiming at menu items), diff --git a/src/version.h b/src/version.h index f8a9f21..69c8b6a 100644 --- a/src/version.h +++ b/src/version.h @@ -4,7 +4,7 @@ #define WIND_VER_MAJOR 0 #define WIND_VER_MINOR 29 -#define WIND_VER_PATCH 2 +#define WIND_VER_PATCH 3 // String form for logs/snapshot/UI. Keep in sync with the numeric parts above. -#define WIND_VERSION_STR "0.29.2" +#define WIND_VERSION_STR "0.29.3" diff --git a/tests/test_game_cursor.cpp b/tests/test_game_cursor.cpp index 20cd9b5..8bb25e7 100644 --- a/tests/test_game_cursor.cpp +++ b/tests/test_game_cursor.cpp @@ -43,3 +43,11 @@ TEST_CASE("the pointer history wraps and keeps the newest samples") { CHECK(h.at(newest - 7.0 * 1.5, x, y)); CHECK(x == doctest::Approx(PointerHistory::kN + 9 - 1.5)); CHECK(h.at(0.0, x, y)); CHECK(x == doctest::Approx(10)); // the oldest kept sample } + +TEST_CASE("game cursor turns on only while the hidden pointer moves, off when it shows") { + CHECK_FALSE(GameCursorStep(false, true, false)); // hidden and still: video player, typing + CHECK(GameCursorStep(false, true, true)); // hidden and moving: a game menu + CHECK(GameCursorStep(true, true, false)); // stays on when the hand pauses + CHECK_FALSE(GameCursorStep(true, false, false)); // pointer shown again + CHECK_FALSE(GameCursorStep(true, false, true)); +}