Repository navigation
Release 0.24.0: native cursor, DWM centring, and fixes - #383
Merged
Merged
Conversation
…ard; live High resolution cursor (#370) * feat(cursor): native cursor for High resolution cursor - DWM-drawn pointer, DWM centring (#369) With High resolution cursor on (smooth sampling), transform sessions now use the pointer Windows Magnifier uses instead of the sprite: - A cursor lens (hidden WC_MAGNIFIER window, MS_SHOWMAGNIFIEDCURSOR toggled only while zoomed) makes DWM draw the real pointer into the magnified frame: sharp, above every band (emoji panel, thumbnails, menus, UAC), latched with the view. No sprite, no blanker, no cursor swaps, no shell-panel freeze. - SetFullscreenMagnifierOffsetsDWMUpdated(TRUE) lets DWM re-centre the view on every cursor update where the view is a pure function of the pointer; Wind keeps the pan for caret, focus, keys, edge mode, Inspect, locked games and reachable MPO walls. Same-level writes and warm pulses stop while DWM centres. - The lens is built at idle after launch and kept warm with its style off: measured free for games (Independent Flip, 0 spikes with a pointer-toggling app), where the composed pointer at 1x costs 19 spikes of 20-42 ms. Avoids the 200-260 ms public write Magnification.dll would block on. Off keeps today's sprite path. txNativeCursor=0 is the kill switch. Version 0.24.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * feat(cursor): native cursor at both sampling modes; fix the smoothing setter lookup (#369) - The native cursor no longer needs High resolution cursor: DWM samples the pointer like the content, so nearest gives the same pixelated, shimmer-free look the sprite had, now as the real pointer above every panel with DWM centring. High resolution cursor only picks the filter. Measured at nearest: pointer on one screen point per frame at every speed, drawn over the emoji panel, 0 late frames at zoom-in and zoom-out, Independent Flip with 0 spikes at 1x. - MagSetFullscreenUseBitmapSmoothing was resolved by ordinal 1, which does not exist (the export ordinals start at 100), so Wind never set the filter and showed whatever state another process left in DWM. Resolved by name now; from a smooth DWM state Wind at nearest now really is nearest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): native pointer visible through zoom-in; zoom-shake experiment (#369) - win32k hands the cursor mode to DWM only on the next pointer update, so after switching the lens style on, zoom-in nudges the pointer a pixel and back. Before, the small hardware pointer stayed for the whole zoom-in while the hand was still (no composed pointer in any ramp frame). - A scale-changing MagSetInputTransform publish stops DWM drawing the composed pointer until the next cursor event: native sessions hold the publish while the level ramps (as Windows Magnifier does) and nudge after a scale-changing publish. Pure rules, tested. - txDwmCentre=0 kill switch for DWM centring. - Experiment txRampNearest (off, MPO-off only): nearest while the level moves, smooth at rest. The zoom shake is DWM's xBR smooth scaler re-shaping edges per scale step: tip jitter 8-10 px p95 at smooth vs max 4 px with the experiment, no pop at the switch back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * revert(cursor): drop the nearest-while-zooming experiment; smooth is Lanczos (#369) The switch from pixel to smooth at rest is plainly visible (user test), so the experiment goes. Docs: DWM's smooth path is Lanczos on a normal system (disassembly), not xBR; the shake is measured and its suspected cause noted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): one centre during zoom - nudge after level writes while DWM centres (#369) Wind's level writes centre exactly on the pointer, DWM centres on its learned hotspot offset; at ~5x the view sat 5-10 px off during a zoom and snapped back when it stopped. A pixel-and-back cursor event after each level write lets DWM re-centre by its own rule in the same frame. Field-verified: the shift is gone and pans stay steady. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * feat(cursor): MPO nearest guard - invisible colour effect keeps planes off while zoomed (#369) Nearest sampling on an MPO boot is the NVIDIA 16-bit TDR combo because flip-model surfaces stay plane candidates under the zoom. A colour transform on the scaled desktop visual forces DWM's external layer (as smooth's resample property does), and nothing under it becomes a plane. While zoomed at nearest on an MPO boot Wind now applies a 0.998 RGB effect (invisible). mpoNearestGuard=1 lets nearest run on MPO boots (default off until verified on one); mpoGuardTest=1 forces the effect on an MPO-off boot to check its look. Pan walls stay armed (fail-closed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): keep writing the view while DWM centres - taskbar hover follows the zoom (#369) DWM's own centring moves only DWM's copy of the view; win32k's copy changes only on a client write and pointer-framework hit-testing (taskbar, XAML) maps points with it. With only level writes it froze and taskbar hover landed on the neighbouring icon, worse with zoom (field video). Like Windows Magnifier, every changed tick is written now, each followed by a pixel-and-back cursor event so DWM re-centres by its own rule in the same frame. DWM's centring was measured to match Wind's formula to under 1 px at every screen edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * feat(cursor): smooth-zoom ladder - zoom through levels DWM rounds cleanly (#369) DWM's smooth path rounds its scratch image's size and origin to whole pixels every frame; during a continuous zoom that shakes the image (measured 11 px p95, jumps to 42 px at 10-25x). Two closed terms predict the shift per level; with smooth sampling the applied level now snaps to the nearest level predicting under 1 px (never backwards in a ramp, held once settled). Standalone: jitter 0.7-0.8 px p95, jumps 1-2 px, as steady as nearest. txSmoothLadder=0 turns it off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): no pointer nudge during a click; MPO guard A/B and wall-lift switches (#369) - The pixel-and-back nudge could land between button-down and button-up and some clicks did not register (field report). No nudge while the left, right or middle button is held or within 250 ms of it (side buttons stay exempt: they are the zoom keys). - mpoGuard=0 turns the guard effect off for A/B. Measured on an MPO boot at nearest: without the guard a full-screen flip app stays on a hardware plane while zoomed (Hardware Composed: Independent Flip); with it the app goes Composed: Flip at zoom-in and keeps presenting. - mpoGuardLiftWall=1 (default 0) drops the pan walls and the write clamp while the guard is on, for the far-edge proof; with it DWM centring also stays on above ~9.3x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): DWM centring yields only near an armed MPO wall (#369) It switched off whenever a wall was reachable at the level (above ~9.3x on 3840 wide), and the switch made the view jump and the pan wobble anywhere on screen at high zoom (field video). Now it yields only when the view is within 64 source px of the wall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): smooth-zoom ladder no longer reads as an endless ramp (#369) The ladder holds a level slightly off the requested one at rest, and 'ramping' compared the two, so the native session held its input-transform publish forever: hover dead zones with smooth sampling. Ramping is judged on the level before the snap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): smooth-zoom ladder keeps the middle of the screen still, not just the pointer (#369) The rounding error grows with screen position, so levels clean at the pointer still moved content elsewhere (field: a horizontal line shook; predicted p95 3.2 px in the middle half, 6.4 px at the edges). Levels are now required clean (1 px, growing slowly above 12x) at the pointer and across the middle half of the screen: predicted p95 1.0 / 2.0 px, clean levels at most 1.1 % apart up to 12x, 3.1 % at 12-20x, 4.7 % above. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): no ghost or walls in plane-free sessions; zoom ladder never jumps (#369) - Smooth sampling and the MPO guard both keep flip apps off hardware planes while zoomed (measured on an MPO boot: Hardware Composed: Independent Flip -> Composed: Flip at zoom-in). With mpoGuardLiftWall those sessions now skip the MPO ghost too, whose hide cost 5-7 ms at every zoom-out (16-21 ms landing stalls), and drop the walls for smooth as well as for the guard. - The smooth-zoom ladder considers only levels within 0.5 % of the request (clean over the middle half first, then at the pointer, else the request), and keeps the level on screen when the zoom stops. Snapping up to 5 % away made zooms jump forwards and back and shift on key-up (field). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): zoom ladder search window grows with zoom (#369) A flat 0.5 % window found no clean level at 20-31x, so the shake came back there (field; predicted 15 px p95). The pointer pass now searches 0.5 % up to 12x, growing to 1.2 % at 24x (predicted 0 px at the pointer, under 3 px over the middle half at 20-31x); the hold-on-release uses the same window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): zoom ladder never settles after the key is released (#369) During the ease-out after release the ladder held a level and hopped to the next, which read as the zoom settling into place (field). Snaps are now capped at the zoom's own motion this frame: fast zooms keep their clean levels, a decelerating zoom glides out exactly and stops where it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): the zoom ease-out stops before a DWM rounding step (#369) After the zoom key was released the decelerating zoom either hopped between clean levels or, when not snapped, crossed one of DWM's whole-pixel rounding steps, which jumped the image (the view and cursor shifted once the zoom settled; gone with txSmoothLadder=0, so not DWM centring). The ease-out now follows the request only within the current rounding cell and stops before leaving it. Held keys, buttons and explicit targets (wheel, quick zoom) keep the ladder as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): smooth-zoom release stops at the level on screen (#369) With smooth sampling and the ladder, every slow ease-out crosses DWM rounding steps (an image jump of about the level in px each); snapping hopped and holding froze then caught up (field). On the release edge of the zoom keys/buttons the controller is set to the level on screen and its glide stopped, so the zoom ends exactly where it was let go. Wheel and quick-zoom targets are untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * feat(cursor): truncated ease-out keeps the user's zoom inertia with smoothing (#369) The dead stop on release removed the user's ease-out. Now the ease-out runs, snapped to clean levels like a held zoom, until it moves less per frame than clean levels are apart (0.6 x the snap window); then the zoom stops on the level on screen. Only the barely-moving tail, where every earlier attempt hopped, shifted or froze, is cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * fix(cursor): hover follows the zoom ease-out (#369) The input-transform publish was held for the whole ramp, and with a long ease-out (300 ms) hover only landed once the glide ended (field video: tab hover). It is now held only while a zoom key/button drives the ramp. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * feat(settings): High resolution cursor applies live, no MPO staging or restart (#369) Smooth sampling and the MPO guard both keep apps off hardware planes while zoomed (measured on an MPO boot), so either look is safe with MPO on. The toggle no longer writes the MPO registry value or prompts for a restart; mpoNearestGuard and mpoGuardLiftWall default to 1 (no walls, write clamp or ghost in plane-free sessions), and EffectiveSamplingMode runs the ini value as is with the guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * docs(cursor): state what the plane-free MPO lift has and has not been tested on (#369) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * docs(cursor): MPO guard field-tested in the far corner above 10x (#369) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…376) The cut-off ran before the zoom controller ticked, when level() still equalled last frame's level, so 'still moving' was never true and every ease-out ran its slow tail, where DWM's half-pixel origin rounding flipped the image back and forth (measured: pointer jumps of 17-33 px at the end of a zoom with a 150-300 ms ease-out). It now runs right after the tick. Also: a request equal to the level on screen is never re-snapped. Measured after: end-of-zoom jumps 1.4-5 px at ease 0/150/300, 2-6x and up to ~20x, the same as during the zoom. Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Writers replace magnifier.ini atomically, but around each MoveFileEx the name briefly refuses opens (measured: ~1% of reads during a burst of replaces failed with ERROR_ACCESS_DENIED). LoadConfig treated that as a missing file and wrote the defaults over the user's ini; read-modify-write callers got "" from ReadTextFile. Field: a tray Night light drag mid-zoom unbound the zoom keys (stuck zoom) and set onboarded=0 (setup at the next start). - ReadTextFileOk / WriteTextFileAtomic retry sharing and access errors until a deadline (250 ms; the tick thread reads with 20 ms), and reads share delete. - LoadConfig creates the defaults only for a missing file; TryLoadConfig reports failure. - The hot-reload keeps the running settings on an unreadable ini and re-checks next poll. - ReadLiveIni guards the read-modify-write writers (settings app, tray, profile seed). Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#380) lockApps (and the lock tells) put DOOM: The Dark Ages menus on the locked path: the view pans from raw mickeys and the weld re-parks the real pointer once per tick. The sprite hid that; the native cursor is the real pointer, drawn by DWM wherever the hand moved it between ticks, so it wandered around the centre and snapped back (4.7x: 22 px spread slow, 74 px medium, jumps to 118 px; free with DWM centring: 0 px). Games show the pointer in menus and hide it for mouselook, so in a native-cursor session the lock applies only while GetCursorInfo reports the pointer hidden (LockApplies). Every gate reads the tick's result (t.lockEff). The sprite path keeps the old rule. Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… (#382) * fix(cursor): no view write without its nudge during a held click (#381) While DWM centres, every pan write is followed by a pixel-and-back nudge so DWM re-centres in the same frame. The nudge is skipped while a mouse button is held (it broke clicks), but the write still went out, so during a drag-select or a held click the view alternated between Wind's centre and DWM's: the shake. Hold pan-only writes for the click instead; level changes and forced writes still go out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu * docs(cursor): no write without its nudge (#381) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The numeric version parts were still 0.23.3 while the string said 0.24.0; release.yml and the installer read the numbers, so main would have refreshed the existing 0.23.3 release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu
This was referenced Oct 8, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings develop to main and cuts 0.24.0.
Each change was deployed and field-tested on the dev PC; the combined build has been the installed build since 2026-10-08. Merged with rebase so main and develop stay one line.
🤖 Generated with Claude Code
https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu