[Bug] Windows cursor recording drifts significantly from actual click position on High-DPI displays (Web Player)
Description
When recording the screen on Windows with a custom scaling factor (e.g., 125%, 150%), the cursor displayed in the Web Player editor drifts significantly from the actual location of the mouse clicks. The offset gets worse the further right/down you move the mouse on the screen.
This causes UI elements (like menus on the right side of the screen) to appear as if the cursor is hovering far away from them, even though the clicks register correctly.
Steps to Reproduce
- On a Windows machine, go to Display Settings and set "Scale and layout" to 125% or 150%.
- Record the screen using OpenScreen with the cursor capture mode set to
editable-overlay.
- During the recording, click on an element on the far right edge of the screen (e.g., a Settings dropdown).
- Finish the recording and preview it in the OpenScreen Video Editor.
- Notice that the green cursor points significantly to the left/top of the element that was actually clicked.
Root Cause
The native C++ cursor-sampler.exe returns cursor coordinates (payload.x and payload.y) via Windows APIs (GetCursorInfo). Because the sampler process either lacks a manifest or is not fully DPI-aware in the same context as Electron, Windows virtualizes these coordinates based on the system's primary DPI.
However, Electron's window boundaries (screen.getPrimaryDisplay().bounds) are returned in Device-Independent Pixels (DIPs). Mixing the raw/virtualized system coordinates from C++ with Electron's DIP boundaries to calculate the normalizedX/Y ratio results in an incorrect mathematical proportion.
Proposed Fix
We can bypass the Windows DPI virtualization mismatch completely by using Electron's native screen.getCursorScreenPoint() to fetch the mouse position. Since this API also returns coordinates in DIPs, both the cursor position and the screen bounds remain in the exact same coordinate system.
File: electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts
Modification in normalizeSample:
private normalizeSample(
payload: Extract<WindowsCursorEvent, { type: "sample" }>,
): NormalizedSample {
const targetBounds = payload.bounds ?? this.options.getDisplayBounds() ?? screen.getPrimaryDisplay().bounds;
// Fix: Use Electron's native getCursorScreenPoint to bypass Windows DPI virtualization.
// This guarantees the coordinates perfectly match Chromium's DIP bounds.
const cursor = screen.getCursorScreenPoint();
const logicalWidth = Math.max(1, targetBounds.width);
const logicalHeight = Math.max(1, targetBounds.height);
let normalizedX = (cursor.x - targetBounds.x) / logicalWidth;
let normalizedY = (cursor.y - targetBounds.y) / logicalHeight;
// Clamp to [0, 1] to prevent Web Player from discarding the cursor.
normalizedX = Math.max(0, Math.min(1, normalizedX));
normalizedY = Math.max(0, Math.min(1, normalizedY));
Additionally, replace the payload.x usages in the asset resolution logic and the logDiagnostic blocks within the same file with cursor.x and cursor.y.
(Note for maintainers: The macOS session macNativeCursorRecordingSession.ts already correctly uses screen.getCursorScreenPoint() in a similar fashion.)
[Bug] Windows cursor recording drifts significantly from actual click position on High-DPI displays (Web Player)
Description
When recording the screen on Windows with a custom scaling factor (e.g., 125%, 150%), the cursor displayed in the Web Player editor drifts significantly from the actual location of the mouse clicks. The offset gets worse the further right/down you move the mouse on the screen.
This causes UI elements (like menus on the right side of the screen) to appear as if the cursor is hovering far away from them, even though the clicks register correctly.
Steps to Reproduce
editable-overlay.Root Cause
The native C++
cursor-sampler.exereturns cursor coordinates (payload.xandpayload.y) via Windows APIs (GetCursorInfo). Because the sampler process either lacks a manifest or is not fully DPI-aware in the same context as Electron, Windows virtualizes these coordinates based on the system's primary DPI.However, Electron's window boundaries (
screen.getPrimaryDisplay().bounds) are returned in Device-Independent Pixels (DIPs). Mixing the raw/virtualized system coordinates from C++ with Electron's DIP boundaries to calculate thenormalizedX/Yratio results in an incorrect mathematical proportion.Proposed Fix
We can bypass the Windows DPI virtualization mismatch completely by using Electron's native
screen.getCursorScreenPoint()to fetch the mouse position. Since this API also returns coordinates in DIPs, both the cursor position and the screen bounds remain in the exact same coordinate system.File:
electron/native-bridge/cursor/recording/windowsNativeRecordingSession.tsModification in
normalizeSample:Additionally, replace the
payload.xusages in theassetresolution logic and thelogDiagnosticblocks within the same file withcursor.xandcursor.y.(Note for maintainers: The macOS session
macNativeCursorRecordingSession.tsalready correctly usesscreen.getCursorScreenPoint()in a similar fashion.)