Visual parity between Windows displays of different pixel densities.
If you put a 27" 1440p monitor next to a 27" 1080p monitor, everything on the 1080p one is 33% bigger. Windows cannot fix this, because it has no display scale factor below 100%. Multi-Deck corrects what can actually be corrected — natively, without resampling anything.
Two displays of the same physical width but different resolutions have different pixel densities:
| Display | Resolution | Diagonal | Density |
|---|---|---|---|
| 27" 1440p | 2560 × 1440 | 27" | ≈ 108.9 PPI |
| 27" 1080p | 1920 × 1080 | 27" | ≈ 81.7 PPI |
To make a UI element look the same physical size on both, the 1080p display
needs a scale factor of 81.7 / 108.9 = 0.75 — exactly 75%, which is just
1080 / 1440.
Windows will not do that. This is not a UI limitation, it is enforced in the composition engine:
- Microsoft's OEM documentation:
"All display scale factors in this mode are constrained to be one of these
four values: 100%, 125%, 150%, 200%", and
LogPixelsis "limited to the range (100%-500%)". - Microsoft support, on this exact scenario: "Unfortunately, the system limit is only 100%, which is by design."
- Measured directly:
DisplayConfigGetDeviceInfowith the undocumentedGET_DPI_SCALErequest reportsminScaleRel = 0— no headroom below the recommended value. Writing a lower value throughSET_DPI_SCALEreturnsERROR_SUCCESSand is stored, butGetDpiForMonitorkeeps reporting 96 DPI. The value is accepted and then ignored.
The same clamping applies to the mouse pointer: CursorBaseSize accepts a value
below 32, stores it, and SM_CXCURSOR still reports 32 with a 32 × 32 bitmap.
So the honest conclusion is: you cannot shrink the Windows UI itself. You can only make individual applications render smaller — which, unlike a GPU downscale, is genuinely sharp because the application rasterises at the final size.
| Feature | Mechanism | Result |
|---|---|---|
| Window rescaling | SetWinEventHook on move/size events, then SetWindowPos |
A window dragged to a lower-density display keeps the same physical footprint |
| Drag preview | Layered click-through overlay | Outlines where and at what size the window will land, while you are still positioning it |
| Application zoom | Sends the app's own zoom shortcut via SendInput |
Content re-rendered natively at the corrected size — sharp, not resampled |
| Cursor scaling | SetSystemCursor with a scaled cursor set |
Pointer keeps the same apparent size across displays |
| Taskbar | MMTaskbarEnabled, with backup and restore |
Optionally hides the taskbar on secondary displays |
Zoom and geometry are independent: a maximized window keeps whatever size the display gives it, but its content is still zoomed. That is the common case of a maximized chat or browser window moved to the other screen.
The window itself cannot be resized mid-drag — Windows runs a modal move loop
that reapplies the size captured when the drag began, so any SetWindowPos
issued during a drag is silently overwritten. The overlay exists precisely
because of that.
The correction ratio is derived from the panels themselves: Multi-Deck reads each display's EDID from the driver store, extracts its true physical dimensions, and computes the density ratio. No monitor model is hard-coded. Reinstall Windows, plug in different screens, run the setup wizard again.
- The taskbar, Explorer, dialog boxes, context menus, the notification area and system title bars cannot be rendered below 100%. Nothing in user space can change that.
- Applications with no zoom command of their own are not affected.
- Fullscreen games follow the resolution, not the DPI.
- No GPU downscaling (AMD VSR / NVIDIA DSR). It would correct everything, but it resamples the whole desktop and destroys ClearType subpixel rendering. If you want that, enable it in your driver — it is not this tool's job.
Requires the .NET 8 SDK and the MSVC toolchain (Visual Studio "Desktop development with C++") to build. The resulting executable needs neither.
git clone <this-repo>
cd Multi-Deck
.\build.ps1 -Install -RunOn first launch a setup wizard detects your displays, shows the computed ratios and asks which corrections you want.
MultiDeck.exe Run the agent (setup wizard on first launch)
MultiDeck.exe --setup Re-run the setup wizard
MultiDeck.exe --install Register autostart for the current user
MultiDeck.exe --uninstall Remove autostart, restore everything it changed
MultiDeck.exe --uninstall --purge ... and delete config and logs
MultiDeck.exe --restore Restore cursors and taskbar, keep config
MultiDeck.exe --status Print detected displays and current settings
MultiDeck.exe --diagnose Write a full diagnostics file for a bug report
--install copies the executable to %LOCALAPPDATA%\Multi-Deck\bin and points
autostart there, so the build directory can be moved or deleted afterwards.
Note that MultiDeck.exe is a GUI-subsystem binary: a shell does not block
on it. Scripts must use Start-Process -Wait around --install.
Everything else is on the notification-area icon: toggles for each correction, autostart, reload configuration, open the log, restore cursors, and exit.
config.json is watched, so hand edits apply within about a second — the
explicit Reload configuration menu item is only a fallback.
Emergency stop: Ctrl+Alt+Shift+D disables every correction and restores the
system cursors immediately. Configurable via panicHotkey.
%LOCALAPPDATA%\Multi-Deck\config.json — hand-editable, then Reload
configuration from disk in the tray menu.
Monitor keys are <EDID hardware id>#<serial>, which survive reboots, dock
cycles and display-index reshuffles.
Each profile declares the zoom factors an application can reach by pressing its step-down shortcut n times after a reset. Multi-Deck picks the entry closest to the display's ratio, so one profile works on any hardware combination.
{
"name": "Chromium and Electron",
"enabled": true,
"processes": ["chrome", "msedge", "spotify", "vesktop", "chatgpt"],
"resetKey": "ctrl+0",
"stepDownKey": "ctrl+minus",
"ladder": [1.0, 0.90, 0.80, 0.75, 0.67, 0.50]
}Chromium and Electron have a native 75% stop, so on a 1440p/1080p pair the match
is exact. Built-in profiles cover Chromium/Electron (about fifty applications),
VS Code (which zooms by powers of 1.2 and resets on Ctrl+NumPad0), Windows
Terminal (1pt font steps) and Notepad (10% steps).
Firefox ships disabled ("enabled": false), deliberately. Its zoom has no
75% stop, it only scales page content — tabs and the address bar stay at 100% —
and it is stored per site and persists, so driving it from here would quietly
rewrite the zoom level of every site you visit on the secondary display. Enable
it if you accept that.
For a whole-UI match, set layout.css.devPixelsPerPx to 0.75 in
about:config instead. That is global to Firefox, so it only makes sense if
Firefox lives on the lower-density display.
- No driver, no DLL injection, no kernel hook, no service, no elevation.
- No network access of any kind. No telemetry.
- The only registry values ever written are the autostart entry and — if you ask
for it — the multi-monitor taskbar setting, whose previous state is recorded in
state.jsonbefore the first change. - System cursors are restored on exit, on panic, on unhandled exception and on process exit.
--uninstallputs everything back.
WindowScaler measures rather than assumes. It records the window rectangle
before a move and reads it again after: Windows may already have rescaled it
(per-monitor-aware applications under mixed DPI) or left it untouched
(everything else). Dividing the desired ratio by the observed one handles both
cases without having to guess the application's DPI awareness mode.
'code' PL2745Q -> EK271 G: desired=0.750 observed=1.000 applied=0.750 880x640 -> 660x480
Display topology is rebuilt on WM_DISPLAYCHANGE, WM_DEVICECHANGE, resume
from sleep, session unlock and Explorer restart, always after a settle delay so
a burst of hot-plug events is coalesced.
- .NET 8 SDK
- MSVC toolchain with
link.exe(NativeAOT links natively) - Windows 10 1809+ or Windows 11
MIT. See LICENSE.
{ "referenceMonitor": "IVM669B#SERIAL1234", // the display everything matches "monitors": { "ACR0DF0#SERIAL5678": { "name": "EK271 G", "manage": true, "scaleOverride": null // null = derive from EDID density } }, "windowScaling": { "enabled": true, "excludedProcesses": ["steam", "cs2"] }, "zoom": { "enabled": true }, "cursor": { "enabled": true, "baseSize": 32 }, "taskbar": { "hideOnSecondaryDisplays": false }, "panicHotkey": "ctrl+alt+shift+d" }