Skip to content

fix: make Windows capture and HUD drag DPI-safe#110

Open
YoneRai12 wants to merge 9 commits into
getopenscreen:mainfrom
YoneRai12:agent/fix-windows-hud-drag
Open

fix: make Windows capture and HUD drag DPI-safe#110
YoneRai12 wants to merge 9 commits into
getopenscreen:mainfrom
YoneRai12:agent/fix-windows-hud-drag

Conversation

@YoneRai12

@YoneRai12 YoneRai12 commented Jul 17, 2026

Copy link
Copy Markdown

Summary

  • make both Windows native capture helpers Per-Monitor-V2 DPI aware before capture or coordinate APIs initialize
  • sample cursor coordinates in physical pixels against the exact physical monitor/window rectangle used by WGC
  • resolve displays across mixed DPI, rotated monitors, and negative virtual-screen origins
  • make the Windows HUD handle reliably grabbable and drive dragging from immutable start coordinates
  • keep the full BrowserWindow width and height immutable throughout a drag
  • defer content-driven HUD resizing while dragging, then flush the real content size after pointer-up
  • reset renderer viewport compensation when switching horizontal/vertical layouts and ignore large intentional layout resizes
  • measure shifted notices from the compensated center and use the same exclusive right/bottom bounds in renderer and main
  • explicitly enable Media Foundation hardware transforms for the normal H.264 path and report the selected encoder class
  • remove the ineffective cursor-sampler display-id argument and send static display bounds once at readiness

Root cause

Capture crossed Electron DIPs, potentially DPI-virtualized Win32 coordinates, and WGC physical pixels. At fractional scaling or on a non-primary/rotated display, the cursor could be normalized against a rectangle from a different coordinate space. An in-flight request could also be combined with later mutable source state.

The HUD had two coupled feedback loops. Moving a transparent non-resizable BrowserWindow at fractional DPI could publish a slightly different Chromium viewport, and content measurement could feed that transient viewport back into the native window size. Renderer compensation also remained alive when the tray changed from vertical back to horizontal, so the next layout inherited a stale vertical offset. Fixed notices were shifted by the compensation but measured against the unshifted center, which could grow the requested width again.

The recording helper created its normal Media Foundation sink writer without MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS. Sink writers do not use hardware encoders by default, so the previous implementation could not guarantee GPU encoding even though its UI and diagnostics described the default path as GPU-backed.

Implementation

Capture now remains in physical coordinates through monitor/window selection and cursor sampling. Conversion back to Electron DIPs happens only at Electron API boundaries, and cursor recording receives one immutable request/source/bounds snapshot.

On Windows, each HUD frame is calculated from the immutable drag-start cursor and complete window bounds. The main process calls setBounds with the original width and height on every frame. The renderer freezes content measurement while the pointer is down, preserves only small post-drag DPI rounding compensation, clears all compensation before a layout change, and performs one final content measurement after drag end. Notice and popup widths use the compensated center, and large horizontal/vertical layout changes are never treated as DPI rounding.

The native H.264 path now creates sink-writer attributes for every attempt. Normal recording sets hardware transforms to TRUE; explicit or automatic software paths set them to FALSE. After writing starts, the helper queries IMFSinkWriterEx::GetTransformForStream and checks MFT_ENUM_HARDWARE_URL_Attribute on the selected video encoder. If Windows still chooses software on the hardware-enabled path, the existing CPU-usage notice is shown.

Cursor-sampler arguments now contain only the physical bounds snapshot used by the native monitor matcher. Display capture reports those static physical bounds once in the ready event. Window capture continues reporting bounds per sample because the captured window can move or resize during recording.

Scale and multi-monitor behavior

The physical normalization path has no scale-specific branches. Regression cases cover 100%, 110%, 125%, 133.33%, 150%, 175%, 200%, 225%, and 300%, plus rotated displays, negative origins, stale source state, post-drag delayed resize delivery, compensated notice measurement, vertical tray sizing, and vertical-to-horizontal switching.

Validation

  • full Vitest suite: 58 files, 457 tests passed
  • latest focused regression suite: 3 files, 40 tests passed
  • tsc --noEmit
  • targeted Biome check for every TypeScript/JavaScript file in the latest patches
  • git diff --check
  • Vite renderer, Electron main, and preload production build
  • Windows WGC and cursor helper native build
  • packaged ASAR main, preload, HTML, and referenced renderer asset hashes matched the production build
  • installed ASAR SHA-256: 59C8F30F9CDE8B83A007482A906AFF133731F901F3DE646FB8CCA4A569197A3F
  • installed WGC helper SHA-256: 77D603434AEC5E25AC9D51A38F6E84CF4C2FE9F5D3A95234A7B4912B450332A4
  • installed cursor helper SHA-256: 6001E823FF0B64D15B2247A69CADBF8313050F6699643CD6E0DCC230ADC74C47
  • installed app, WGC helper, and cursor helper all passed local Authenticode verification
  • installed app restarted with four responsive processes
  • no new recording was started without user consent; actual encoder classification for a fresh recording on this machine remains to be observed

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

Windows cursor coordinate pipeline

Layer / File(s) Summary
Physical coordinate contracts and normalization
electron/native-bridge/cursor/recording/windowsCursorCoordinates.ts, electron/native-bridge/cursor/recording/windowsCursorCoordinates.test.ts, electron/native-bridge/cursor/recording/*types.ts
Adds physical geometry types, shared normalization, display targeting options, and coverage for scaling, rotated monitors, and out-of-bounds points.
Display selection and session wiring
electron/ipc/handlers.ts, electron/native-bridge/cursor/recording/factory.ts, electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts, electron/native-bridge/cursor/recording/cursorRecordingTarget.ts
Passes selected display IDs and physical bounds through cursor session creation, display lookup, and sample normalization.
Native physical-pixel sampling
electron/native/wgc-capture/CMakeLists.txt, electron/native/wgc-capture/src/*
Enables Per-Monitor-V2 DPI awareness, resolves physical window or display bounds, samples physical cursor positions, and emits physical coordinates.

HUD interaction policy

Layer / File(s) Summary
Polling-based HUD mouse policy
electron/hudOverlayMousePolicy.ts, electron/hudOverlayMousePolicy.test.ts, electron/windows.ts
Adds HUD bounds hit-testing and combines renderer intent with cursor position while polling and cleaning up overlay mouse-event state.
Native HUD drag handle
electron/electron-env.d.ts, electron/preload.ts, electron/hudOverlayDrag.ts, electron/hudOverlayBounds.ts, electron/windows.ts, src/components/launch/*
Replaces delta-based HUD movement with validated drag lifecycle events, platform-specific drag handling, viewport compensation, updated resizing, and interaction tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ElectronHandlers
  participant WindowsNativeRecordingSession
  participant CursorSampler
  ElectronHandlers->>WindowsNativeRecordingSession: create session with displayId
  ElectronHandlers->>CursorSampler: pass physical display bounds
  WindowsNativeRecordingSession->>CursorSampler: start sampling with physical bounds
  CursorSampler->>WindowsNativeRecordingSession: emit physical cursor sample
  WindowsNativeRecordingSession->>WindowsNativeRecordingSession: normalizePhysicalPoint
Loading

Possibly related PRs

Suggested reviewers: etiennelescot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main Windows DPI-safe capture and HUD drag change.
Description check ✅ Passed It covers summary, rationale, implementation, scope, and validation, but omits the template's issue/link and checklist sections.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@YoneRai12 YoneRai12 changed the title fix: prevent Windows HUD drag drift across display scales fix: make Windows capture and HUD drag DPI-safe Jul 17, 2026
@YoneRai12
YoneRai12 marked this pull request as ready for review July 17, 2026 13:13
@YoneRai12
YoneRai12 requested a review from EtienneLescot as a code owner July 17, 2026 13:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 855-857: Replace the native Electron drag-region behavior on the
launch drag handle with the existing JS/IPC-based dragging flow, using
moveHudOverlayBy and its associated pointer handlers. Ensure dragging remains
compatible with the HUD’s dynamic setIgnoreMouseEvents behavior, and remove the
native drag-region styling from this handle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fcf68ac-9a39-4f56-ad45-75598a0306b8

📥 Commits

Reviewing files that changed from the base of the PR and between d5966ed and 1c1cbb1.

📒 Files selected for processing (16)
  • electron/electron-env.d.ts
  • electron/ipc/handlers.ts
  • electron/native-bridge/cursor/recording/factory.ts
  • electron/native-bridge/cursor/recording/windowsCursorCoordinates.test.ts
  • electron/native-bridge/cursor/recording/windowsCursorCoordinates.ts
  • electron/native-bridge/cursor/recording/windowsNativeRecordingSession.ts
  • electron/native-bridge/cursor/recording/windowsNativeRecordingSession.types.ts
  • electron/native/wgc-capture/CMakeLists.txt
  • electron/native/wgc-capture/src/cursor-sampler.cpp
  • electron/native/wgc-capture/src/dpi_awareness.h
  • electron/native/wgc-capture/src/main.cpp
  • electron/preload.ts
  • electron/windows.ts
  • src/components/launch/LaunchWindow.module.css
  • src/components/launch/LaunchWindow.test.tsx
  • src/components/launch/LaunchWindow.tsx
💤 Files with no reviewable changes (3)
  • electron/preload.ts
  • electron/windows.ts
  • electron/electron-env.d.ts

Comment thread src/components/launch/LaunchWindow.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/launch/LaunchWindow.tsx (1)

344-415: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Measure shifted notices from the compensated center.

When hudViewportCompensation.x is nonzero, the notice is shifted at Lines 708-710, but Lines 392 and 406 measure it against the original viewport center. Each resize therefore adds the compensation to the requested width, potentially producing another resize/compensation cycle.

Proposed fix
 const centerX = window.innerWidth / 2;
+const compensatedCenterX = centerX - hudViewportCompensationRef.current.x;

-	halfWidth = Math.max(halfWidth, centerX - rect.left, rect.right - centerX);
+	halfWidth = Math.max(
+		halfWidth,
+		compensatedCenterX - rect.left,
+		rect.right - compensatedCenterX,
+	);

Apply the compensated center to both fixed-notice width calculations.

Also applies to: 708-710

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/launch/LaunchWindow.tsx` around lines 344 - 415, Update the
fixed-notice width calculations in the measurement flow around
systemLocalePromptRef and softwareFallbackNoticeRef to use the compensated
horizontal center from hudViewportCompensation.x instead of the original
centerX. Apply the same compensated-center logic to both notices and preserve
the existing height calculations and width bounds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 344-415: Update the fixed-notice width calculations in the
measurement flow around systemLocalePromptRef and softwareFallbackNoticeRef to
use the compensated horizontal center from hudViewportCompensation.x instead of
the original centerX. Apply the same compensated-center logic to both notices
and preserve the existing height calculations and width bounds.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1610b295-14e8-4d2b-9cdd-e7840ca60edc

📥 Commits

Reviewing files that changed from the base of the PR and between 4292678 and db47e36.

📒 Files selected for processing (11)
  • electron/electron-env.d.ts
  • electron/hudOverlayDrag.test.ts
  • electron/hudOverlayDrag.ts
  • electron/hudOverlayMousePolicy.test.ts
  • electron/hudOverlayMousePolicy.ts
  • electron/preload.ts
  • electron/windows.ts
  • src/components/launch/LaunchWindow.test.tsx
  • src/components/launch/LaunchWindow.tsx
  • src/components/launch/hudViewportCompensation.test.ts
  • src/components/launch/hudViewportCompensation.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • electron/hudOverlayMousePolicy.test.ts
  • electron/windows.ts

@EtienneLescot EtienneLescot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — REQUEST CHANGES

Good shape overall: PMv2 DPI awareness in the native helper, physical-pixel end-to-end, click-through HUD drag anchored to a cursor position (Windows) plus viewport-enlargement compensation (renderer), and solid test coverage across the new modules.

Three correctness bugs and a few smaller concerns need addressing before merge:

  • halfWidth feedback loop with viewport compensation (biggest one). The notice-column measurement still uses the un-compensated centerX, but the column is now shifted by -compensation.x. As compensation grows, the requested window width grows by ~2x compensation, which enlarges the viewport, which enlarges compensation. The 0.01px deadband in updateHudDragViewportCompensation only damps it; the window ends up ~2 × compensation.x wider than required after every drag. Apply the compensated centre to all three notice measurements.
  • <= vs < HUD-bounds inclusivity mismatch between the renderer (line 690/692) and hudOverlayMousePolicy.ts:34. Pick one — strict < matches the policy module.
  • displayId argv is dead code in cursor-sampler.cpp. Parsed from argv[3], passed to findMonitorForCapture, but bounds-first matching ignores it; the fallback path errors out when bounds are missing. Either remove or actually use it.

Smaller items also noted inline. The PR adds many inline comments — AGENTS.md says "no comments unless asked"; please trim or fold into top-of-function docstrings only.

Full per-PR review (with all numbered findings) was already shared with the maintainers; happy to discuss any of these.

Comment thread src/components/launch/LaunchWindow.tsx Outdated
Comment thread src/components/launch/LaunchWindow.tsx Outdated
Comment thread src/components/launch/LaunchWindow.tsx Outdated
Comment thread src/components/launch/LaunchWindow.tsx Outdated
Comment thread src/components/launch/LaunchWindow.tsx
Comment thread electron/native/wgc-capture/src/cursor-sampler.cpp Outdated
Comment thread electron/native/wgc-capture/src/cursor-sampler.cpp Outdated
Comment thread electron/windows.ts
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.

2 participants