Skip to content

[Bug] Windows cursor recording drifts significantly from actual click position on High-DPI displays (Web Player) #160

Description

@manaarqureshi

[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

  1. On a Windows machine, go to Display Settings and set "Scale and layout" to 125% or 150%.
  2. Record the screen using OpenScreen with the cursor capture mode set to editable-overlay.
  3. During the recording, click on an element on the far right edge of the screen (e.g., a Settings dropdown).
  4. Finish the recording and preview it in the OpenScreen Video Editor.
  5. 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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions