Skip to content

feat(cursor): native cursor, DWM centring, smooth-zoom ladder, MPO guard; live High resolution cursor - #370

Merged
Maxaubert merged 22 commits into
developfrom
feat/369-native-cursor
Oct 7, 2026
Merged

Maxaubert merged 22 commits into
developfrom
feat/369-native-cursor

Conversation

@Maxaubert

@Maxaubert Maxaubert commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Closes #369.

What

  • Native cursor (both sampling modes; txNativeCursor=0 = old sprite). A hidden WC_MAGNIFIER cursor lens, built at idle and kept warm, with MS_SHOWMAGNIFIEDCURSOR on only while zoomed: DWM draws the real pointer into the magnified frame, above every band (emoji panel, thumbnails, Start, menus, UAC, Snipping Tool). No sprite, blanker, cursor swaps or shell-panel freeze in normal use. Pointer nudges (a pixel and back) make win32k hand the cursor mode to DWM; none during a click.
  • DWM centring (SetFullscreenMagnifierOffsetsDWMUpdated): the pointer stays on one screen point per frame at any speed. Wind keeps writing every change (win32k's copy feeds taskbar/XAML hover) and nudges after each write so DWM re-centres by its own rule. txDwmCentre=0 kill switch.
  • Smoothing setter bug fixed: MagSetFullscreenUseBitmapSmoothing was resolved by a non-existent ordinal, so Wind never set the filter. Resolved by name.
  • Smooth-zoom ladder (txSmoothLadder): DWM rounds its smoothing scratch image to whole pixels every frame (the zoom shake). Levels are snapped (within 0.5 %, up to 1.2 % at high zoom) to ones predicting under ~1 px over the pointer and the middle half of the screen; held at rest; the release ease-out runs until it moves less than the clean-level spacing, then stops on the level on screen; hover follows the ease-out.
  • MPO guard (mpoNearestGuard, default 1): zoomed at nearest on an MPO boot, an invisible 0.998 colour effect keeps flip apps off hardware planes (no 16-bit overflow). Plane-free sessions (smooth or guard; mpoGuardLiftWall, default 1) drop the pan walls, write clamp and MPO ghost (the ghost cost 16-21 ms zoom-out stalls).
  • High resolution cursor applies live: no MPO registry staging, no restart prompt.

Measured on this PC (4K 144 Hz, RTX 5090, signed UIAccess build)

Before After
Pointer per frame while panning tick-paced drift (18-96 px at 3x) / sprite one fixed screen point
Cursor over emoji panel / thumbnails sprite under the panel drawn on top
Smooth zoom shake at the pointer (to 31x) 8-26 px jumps <=2 px (measurement floor)
Smooth zoom shake away from the pointer (to 31x) 6-9 px p95 2-4 px p95
Taskbar hover while zoomed (with DWM centring) neighbouring icon correct
Flip app at 1x with Wind idle Independent Flip Independent Flip, 0 spikes
Full-screen flip app zoomed at nearest, MPO on Hardware plane (TDR combo) Composed (guard)

Field-tested by the owner: native cursor, centring, panels, taskbar hover, clicks, ease-out, shake, MPO boot with the guard in the far-right/bottom corner above 10x (no driver reset), live High resolution toggle.

Gates

build.bat check, build.bat test (592/592), Playwright UI (173/173), CI.

Known limits / follow-ups

  • DWM relearns its pointer offset only on a cursor shape change; a learn taken mid-motion leaves a few px of static offset (Windows Magnifier has the same). Protection planned as a follow-up.
  • A DWM restart drops the cursor lens until Wind restarts.
  • The sprite path (txNativeCursor=0) stays as a fallback; removal is a follow-up.
  • A ~13-15 ms Wind-loop hitch at zoom-out to 1x remains (pre-existing, outside timed spans); to be measured.

🤖 Generated with Claude Code

https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu

Maxaubert and others added 2 commits October 7, 2026 12:46
…inter, 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
… 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
@Maxaubert Maxaubert changed the title feat(cursor): native cursor for High resolution cursor (DWM-drawn pointer, DWM centring) feat(cursor): native cursor (DWM-drawn pointer, DWM centring) + smoothing setter fix Oct 7, 2026
Maxaubert and others added 20 commits October 7, 2026 15:00
…iment (#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
…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
…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
…s 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
…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
…anly (#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
…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
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
)

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
… 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
…ver 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
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
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
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
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
…moothing (#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
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
…r 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
… tested on (#369)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zPmivSAQeivGUdBjTsaMu
@Maxaubert Maxaubert changed the title feat(cursor): native cursor (DWM-drawn pointer, DWM centring) + smoothing setter fix feat(cursor): native cursor, DWM centring, smooth-zoom ladder, MPO guard; live High resolution cursor Oct 7, 2026
@Maxaubert
Maxaubert changed the base branch from main to develop October 7, 2026 18:33
@Maxaubert
Maxaubert merged commit cf6c48c into develop Oct 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native cursor for the high resolution mode: DWM-drawn pointer with DWM centring

1 participant