Skip to content

fix(cua-driver)(windows): correct VIRTUALDESK normalization fence-post - #1980

Merged
f-trycua merged 1 commit into
mainfrom
fix/windows-multimonitor-virtualdesk-normalize
Jun 23, 2026
Merged

fix(cua-driver)(windows): correct VIRTUALDESK normalization fence-post#1980
f-trycua merged 1 commit into
mainfrom
fix/windows-multimonitor-virtualdesk-normalize

Conversation

@f-trycua

@f-trycua f-trycua commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Closes part of #1979.

Bug

The MOUSEEVENTF_VIRTUALDESK forward mapping in send_click_synthesized / send_drag_synthesized divided by vd_w instead of vd_w − 1 (classic fence-post error). With the reporter's (-1920, 0, 3840, 1080) virtual-desktop layout this means:

  • the bottom-right corner of the virtual desktop mapped to dx=65517 instead of dx=65535
  • the inter-monitor seam at screen x=0 mapped to dx=32767 instead of dx=32768

Both off-by-(less-than-1-pixel) under normal use, but at the seam Windows rounds dx=32767 back onto the secondary monitor's last pixel instead of the primary's first — so clicks at exact monitor boundaries land on the wrong screen.

Fix

Single arithmetic correction: (p − vd_origin) * 65535 / vd_size(p − vd_origin) * 65535 / (vd_size − 1) for both X and Y axes, in both the click and drag paths. Hoisted into a new crates/platform-windows/src/virtualdesk.rs module with the formula isolated as a pure function so it's testable without a Windows runtime.

Tests

26 passing (20 pre-existing + 6 new in virtualdesk.rs). The 6 new tests cover the 8 canonical multi-monitor layouts:

  • secondary LEFT of primary (negative X origin) — the user's exact layout
  • secondary RIGHT of primary
  • secondary ABOVE / BELOW primary (negative Y origin)
  • three monitors arranged horizontally
  • single-monitor baseline
  • HiDPI secondary (different DPI scale; in-scope for normalization, out of scope for any DPI-aware scaling)

Each layout asserts:

  1. normalized output in [0, 65535]
  2. round-trip recovery within ±1 px
  3. monotonicity (x1 < x2 ⇒ normalized(x1) ≤ normalized(x2))
  4. pixels_to_the_left_of_seam_stay_on_secondary_half — the actual regression: with (-1920, 0, 3840, 1080) virt rect, screen x=0 (the seam) must produce dx ≥ 32768, NOT dx=32767. Fails under the old / vd_w formula, passes under the new / (vd_w − 1).
  5. corners_map_to_extreme_normalized_values — BR corner must map to dx=65535. Was 65517 before, 65535 now.

Honest caveat

This is a real, provable fence-post bug — but the reporter's specific symptom ("completely wrong monitor") in their log is happening on the PostMessage (post_click_screen) code path, which doesn't go through VIRTUALDESK at all. So this PR hardens the SendInput / foreground-dispatch path but is not sufficient to fully close #1979. The next thing to audit is get_element_centerdeepest_child → packed-lparam HWND resolution for elements on negative-X monitors — likely a separate bug worth its own issue. Leaving #1979 open after merge, with a comment pointing at the next code path.

Verification

  • cargo test -p platform-windows → 26 passed
  • cargo check -p cua-driver clean
  • cargo check -p platform-windows --target x86_64-pc-windows-msvc clean

Summary by CodeRabbit

  • Bug Fixes
    • Fixed multi-monitor mouse input coordinate handling to correctly map absolute positions across virtual desktop displays, including displays positioned with negative offsets.

…t for negative-offset multi-monitor (#1979)

`send_click_synthesized` / `send_drag_synthesized` mapped screen pixels to
`MOUSEEVENTF_VIRTUALDESK` absolute coords via
    (sx - vd_x) * 65535 / vd_w
which is off-by-one at the right/bottom edge of the virtual desktop: the
last pixel of a 3840-wide virt rect mapped to dx=65517 instead of 65535,
and the seam between two stitched monitors landed exactly on dx=32767
(the first pixel of the primary in a "secondary-to-left-of-primary"
layout was rounded back onto the secondary).

Windows reverses the normalization with `dx * (vw - 1) / 65535 + vx`, so
the forward map must divide by `(vw - 1)`, not `vw`. The fix extracts the
math into a new pure module `platform_windows::virtualdesk` so it can be
unit-tested on any host (no Win32 runtime needed), and adds 6 tests
covering the 8 canonical multi-monitor layouts from issue #1979 — the
reporter's exact `(-1795, 383)` pixel on a `(-1920, 0, 3840, 1080)` virt
rect now round-trips with ±1 px drift and stays on the secondary half of
the normalized band. The seam-monotonicity test fails under the old
formula and passes under the new one.

Scope: pure-math fix only. Public API of `send_click_synthesized` /
`send_drag_synthesized` unchanged. Two call sites in `mouse.rs` updated
to route through the helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qxy4BYpesmB4keyJf8DkXC
@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Jun 23, 2026 12:14am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 19d981f2-a9f0-49ca-b91e-632850d60f90

📥 Commits

Reviewing files that changed from the base of the PR and between 598e90e and 9f90f1a.

📒 Files selected for processing (3)
  • libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs
  • libs/cua-driver/rust/crates/platform-windows/src/lib.rs
  • libs/cua-driver/rust/crates/platform-windows/src/virtualdesk.rs

📝 Walkthrough

Walkthrough

A new virtualdesk Rust module is added to platform-windows that centralizes MOUSEEVENTF_VIRTUALDESK absolute-coordinate normalization using pure, Win32-runtime-free math. Inline normalization in send_click_synthesized and send_drag_synthesized is replaced by calls to the new to_virtualdesk_absolute function. A comprehensive test suite covers multi-monitor layouts, monotonicity, corner mapping, and the negative-offset regression from issue #1979.

Changes

virtualdesk coordinate normalization

Layer / File(s) Summary
virtualdesk module declaration and to_virtualdesk_absolute implementation
libs/cua-driver/rust/crates/platform-windows/src/lib.rs, libs/cua-driver/rust/crates/platform-windows/src/virtualdesk.rs
Declares pub mod virtualdesk in lib.rs outside the Windows-only input module for host-independent testability. Implements to_virtualdesk_absolute using fence-post (virt_w-1)/(virt_h-1) divisors, i64 intermediate arithmetic, negative-origin offset handling, divide-by-zero guards, and clamping to 0..=65535.
Inline math replaced by to_virtualdesk_absolute in mouse.rs
libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs
In send_click_synthesized and send_drag_synthesized, the previous inline GetSystemMetrics-plus-clamp normalization is removed and replaced with crate::virtualdesk::to_virtualdesk_absolute(sx, sy, ...). Comments updated to reference centralized math and issue #1979 coverage.
Test suite: inverse helper, layouts, and property/regression tests
libs/cua-driver/rust/crates/platform-windows/src/virtualdesk.rs
Adds from_virtualdesk_absolute (test-only round-trip inverse with round-to-nearest), a layouts() generator covering single-monitor, secondary left/right/above/below primary, mixed-sign, and HiDPI-like extents, and tests for normalized-range clamping, monotonicity across seams, corner exactness, the issue #1979 regression case, and seam-half selection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐇 Across the virtual desktop I hop,
From negative offsets I shall not drop!
With i64 math and a fence-post seam,
Each pixel normalized, not a broken dream.
Issue #1979? Fixed with a clamp and cheer—
The rabbit's coordinates are crystal clear! 🖱️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main fix: correcting a fence-post error in Windows virtual desktop coordinate normalization.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/windows-multimonitor-virtualdesk-normalize

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.

@github-actions

Copy link
Copy Markdown
Contributor

Linux visual regression artifacts

Matrix jobs now run independently. Download visual artifacts from this workflow run.
Each background-GUI job uploads a .gif of the interaction plus two annotated PNGs (<app>.png raw, <app>-atspi.png with AT-SPI element boxes); the cua-driver-linux-som-overlays artifact adds <app>-som.png cua Set-of-Marks overlays:

  • cua-driver-linux-cursor-click-gif
  • cua-driver-linux-background-terminal-gif
  • cua-driver-linux-parallel-drag-xserver
  • cua-driver-linux-background-gui-chromium
  • cua-driver-linux-background-gui-tk
  • cua-driver-linux-background-gui-gtk3-gedit
  • cua-driver-linux-background-gui-gtk3-mousepad
  • cua-driver-linux-background-gui-gtk3-scite
  • cua-driver-linux-background-gui-gtk4-characters
  • cua-driver-linux-background-gui-qt5-manuskript
  • cua-driver-linux-background-gui-qt5-klog
  • cua-driver-linux-background-gui-qt5-openambit
  • cua-driver-linux-background-gui-qt6-kate
  • cua-driver-linux-background-gui-qt6-kcalc
  • cua-driver-linux-background-gui-qt6-okular
  • cua-driver-linux-background-gui-qt6-qownnotes
  • cua-driver-linux-background-gui-electron-zettlr
  • cua-driver-linux-background-gui-electron-joplin
  • cua-driver-linux-background-gui-electron-logseq
  • cua-driver-linux-som-overlays

Open workflow run and download artifacts

@f-trycua
f-trycua merged commit ec7084e into main Jun 23, 2026
68 of 71 checks passed
@f-trycua
f-trycua deleted the fix/windows-multimonitor-virtualdesk-normalize branch June 23, 2026 01:26
f-trycua added a commit that referenced this pull request Jun 23, 2026
…egative client-area coords (#1979) (#1983)

Audit pass on the second half of #1979: the PostMessage click path
(`input::mouse::post_click_screen` → `make_lparam` → `PostMessageW`) was
the next suspect for the multi-monitor wrong-screen symptom after the
SendInput VIRTUALDESK normalization fix in #1980.

Traced the full call chain from `tools::impl_::ClickTool` /
`DoubleClickTool` (e.g. `impl_.rs:3655`) → `input::mouse::post_click_screen`
(`mouse.rs:83`) → `deepest_child` → `ScreenToClient` → `make_lparam`
(`mouse.rs:226`) → `PostMessageW(WM_LBUTTONDOWN, ..., lparam)`. For every
coordinate that fits in `i16` range — including the reporter's exact
`(-1198, 292)` and `(-1795, 383)` examples after the post-`ScreenToClient`
conversion to window-local coords — the existing implicit `i16` truncation
in `make_lparam` round-trips correctly through the receiver's
`GET_X_LPARAM` / `GET_Y_LPARAM` sign-extension. The bit-pattern lives in
the low 16 bits of each half-word and `(short)LOWORD(lp)` recovers the
original signed `i32` value. Verified empirically; no bug in the packing.

But this is exactly the kind of pure-math invariant that's load-bearing
and easy to silently regress (a future "simplification" dropping the
implicit `i16` truncation would still compile and still work for positive
coords). Extract the bit-math into a new `crate::lparam` pure module
mirroring `crate::virtualdesk`, with cross-platform tests that lock in:

  - round-trip for the reporter's `(-1198, 292)` and `(-1795, 383)`
  - full `i16` range round-trip at the fence-posts
  - sign-stability: every negative `x` decodes to the same negative `x`
    (no sign-flip into the primary-monitor half)
  - screen→window-local→pack monotonicity across the secondary monitor
  - end-to-end reconstruction of the reporter's `(-1795, 383)` scenario
  - explicit `Err` on out-of-i16-range inputs (vs the previous silent
    truncation) — the new `pack_xy` surface fails fast so a future
    >32k-px virtual-desktop click can't reach a PostMessage handler
    interpreting the wrong bit pattern

`make_lparam` now delegates to `lparam::pack_xy` and clamps + warns on the
(currently unreachable) out-of-range path. Public API of `make_lparam`
unchanged — no caller signature touched.

Tests run on the macOS host via `cargo test -p platform-windows`:
6 new `lparam` tests + the existing 26 pass (32 total).

Scope: regression coverage for the PostMessage half of #1979 only. The
visible-wrong-screen symptom the reporter saw must originate elsewhere
(top candidates: stale element-cache center after a window move, or
a receiver app that reads `GetCursorPos` instead of the lParam — neither
fixable in `make_lparam`). Filed as a follow-up note for further audit.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

Windows multi-monitor: clicks land on wrong screen (negative coordinates)

1 participant