Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/architecture/07-cursor.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions src/game_cursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
12 changes: 8 additions & 4 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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),
Expand Down
4 changes: 2 additions & 2 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
8 changes: 8 additions & 0 deletions tests/test_game_cursor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Loading