Skip to content

feat(windows): DPI awareness, with the Scale setting as on macOS - #333

Merged
joske merged 5 commits into
masterfrom
feat/windows-dpi
Nov 4, 2026
Merged

joske merged 5 commits into
masterfrom
feat/windows-dpi

Conversation

@joske

@joske joske commented Nov 4, 2026

Copy link
Copy Markdown
Owner

On Windows, yserver is now DPI-aware: on a display scaled to 125%, 150%, 200% and so on, X windows are no longer stretched blurrily by Windows. They're sized and sharpened according to the Scale setting, as on macOS.

  • The scale model is macOS's. A Windows logical pixel plays the role of a point, and the monitor's DPI/96 the role of the backing scale. --scale and the Scale setting mean the same as on macOS:

    • auto rounds the display scale to a whole factor: 1 at 100% and 125%, 2 at 150%, 175% and 200%;
    • 1 is one X pixel per screen pixel;
    • 2 is one X pixel per 2×2 screen pixels.

    The copy factor is always a whole number, so text stays sharp and pointer positions stay exact. At 125% and 175% that means X apps come out somewhat smaller or larger than native ones.

  • Changing the scaling while yserver runs, or moving to a monitor with another scale, resizes and redraws both rooted and rootless windows (WM_DPICHANGED). The cursor image is scaled to match.

  • Physical size: xrandr reports the monitor's real size in millimetres, from its EDID, or from the DPI when there's no EDID. The core screen stays at 96 DPI, as on macOS.

  • Settings: the Scale row is back in the Settings dialog, which is now laid out for the DPI of the monitor it's on. The DPI awareness comes from a manifest embedded in the exe, which also gives the dialog the current Windows controls.

  • Rootless uses one factor for all monitors, taken from the primary one, as on macOS.

Verification:

  • Unit tests for the mapping at 100, 125, 150 and 200% with each scale choice, with the arithmetic written out, and for the EDID size parser, checked against edid-decode.
  • A lavapipe test for the new 2× copy in the shared rootless code path. On macOS that path still copies at 1×, unchanged.
  • In a Windows 11 VM, at 100%, 150% and 200%, rooted and rootless, with each scale choice:
    • a click on a mousepad menu opens it;
    • the pointer position in X matches;
    • xrandr shows the expected sizes;
    • changing the scaling live resizes everything correctly.
  • Not tested: two monitors with different scaling, and a real EDID (the VM has neither).

🤖 Generated with Claude Code

joske and others added 5 commits November 4, 2026 19:30
PixelScale maps a window system whose coordinates are device pixels
(Windows, per-monitor DPI aware) onto the macOS --scale model: a logical
pixel is the point, DPI/96 the backing scale, with copy_factor and
x_per_point unchanged. size_mm reads a monitor's physical size from its
EDID base block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RootlessHost::copy_scale (1 by default, so macOS is unchanged) shows each
backing pixel as NxN swapchain pixels with a NEAREST blit, for a host
whose surfaces are in device pixels. The copy region is the rooted
output's copy_region.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yserver.exe's manifest makes it per-monitor DPI aware (v2) and pulls in
common controls v6; without it the process asks at startup. Window sizes,
pointer positions, warps, cursor images, min/max sizes and resize
increments go through the copy factor of the macOS scale model, at the
window's DPI. WM_DPICHANGED takes the suggested rect and posts the new
size, factor and millimetres; rootless keeps one global factor and the
client size across a DPI change (WM_GETDPISCALEDSIZE). The output's
millimetres come from the monitor's EDID, else from its DPI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Scale row is back with macOS's three choices in screen pixels; as on
macOS it applies at restart. The dialog measures and places its controls
at its own DPI and is built again when that changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@joske
joske merged commit f97f6ac into master Nov 4, 2026
9 of 11 checks 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.

1 participant