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)); +}