5efe5e62 opted cursor-sampler.exe into per-monitor-v2 DPI awareness. wgc-capture.exe — the recorder itself — never got the same treatment, and its monitor lookup quietly depends on staying unaware.
Where
findMonitorForCapture (electron/native/wgc-capture/src/monitor_utils.cpp) picks the capture target by matching the bounds it was handed against the rects from EnumDisplayMonitors:
if (rectMatchesBounds(candidate.rect, *bounds)) { return candidate.monitor; }
Those bounds come from sourceDisplay?.bounds and are serialized as displayX/Y/W/H (electron/ipc/handlers.ts:2335). Electron's Display.bounds is in DIPs. The enumerated rects, in a DPI-unaware process, are virtualized — divided by the primary display's scale factor, whatever monitor they actually describe.
Why it works today, and where it stops
Single monitor at any scale: virtualized == DIP, so the two sides agree by accident and the exact match hits.
Mixed-DPI multi-monitor: Electron's DIP rect for each display uses that display's scale, while virtualization uses the primary's for all of them. The two spaces diverge on any non-primary display whose scale differs, rectMatchesBounds misses, and the lookup falls through to the overlap heuristic — which can land on the wrong monitor, or on the primary via MONITOR_DEFAULTTOPRIMARY.
Why it's a paired change, not a one-liner
Adding SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) to main.cpp on its own flips the enumerated rects to physical while displayX/Y/W/H stay DIPs — that breaks the single-monitor scaled case that currently works. The awareness call has to land together with screen.dipToScreenRect on the TS side (the same conversion windowsNativeRecordingSession.ts:240 already does for the cursor payload), so both sides of the match move to physical at once.
#110 proposed the awareness half (a shared dpi_awareness.h plus the call in main.cpp). That PR is closed as superseded, but this piece of it was never re-landed — hence this issue.
Repro
Needs a mixed-DPI multi-monitor Windows box; not reproducible on a single 100%-scale display, which is what the dev machine is. Same hardware gap as #252.
5efe5e62optedcursor-sampler.exeinto per-monitor-v2 DPI awareness.wgc-capture.exe— the recorder itself — never got the same treatment, and its monitor lookup quietly depends on staying unaware.Where
findMonitorForCapture(electron/native/wgc-capture/src/monitor_utils.cpp) picks the capture target by matching the bounds it was handed against the rects fromEnumDisplayMonitors:Those bounds come from
sourceDisplay?.boundsand are serialized asdisplayX/Y/W/H(electron/ipc/handlers.ts:2335). Electron'sDisplay.boundsis in DIPs. The enumerated rects, in a DPI-unaware process, are virtualized — divided by the primary display's scale factor, whatever monitor they actually describe.Why it works today, and where it stops
Single monitor at any scale: virtualized == DIP, so the two sides agree by accident and the exact match hits.
Mixed-DPI multi-monitor: Electron's DIP rect for each display uses that display's scale, while virtualization uses the primary's for all of them. The two spaces diverge on any non-primary display whose scale differs,
rectMatchesBoundsmisses, and the lookup falls through to the overlap heuristic — which can land on the wrong monitor, or on the primary viaMONITOR_DEFAULTTOPRIMARY.Why it's a paired change, not a one-liner
Adding
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)tomain.cppon its own flips the enumerated rects to physical whiledisplayX/Y/W/Hstay DIPs — that breaks the single-monitor scaled case that currently works. The awareness call has to land together withscreen.dipToScreenRecton the TS side (the same conversionwindowsNativeRecordingSession.ts:240already does for the cursor payload), so both sides of the match move to physical at once.#110 proposed the awareness half (a shared
dpi_awareness.hplus the call inmain.cpp). That PR is closed as superseded, but this piece of it was never re-landed — hence this issue.Repro
Needs a mixed-DPI multi-monitor Windows box; not reproducible on a single 100%-scale display, which is what the dev machine is. Same hardware gap as #252.