Skip to content

fix(cua-driver-rs)(windows): bitmap-pixel click/drag coords + dispatch:background UIA hit-test docs - #1708

Merged
f-trycua merged 1 commit into
mainfrom
cua-driver-rs-docs-bg-uia-hittest
May 26, 2026
Merged

fix(cua-driver-rs)(windows): bitmap-pixel click/drag coords + dispatch:background UIA hit-test docs#1708
f-trycua merged 1 commit into
mainfrom
cua-driver-rs-docs-bg-uia-hittest

Conversation

@f-trycua

@f-trycua f-trycua commented May 26, 2026

Copy link
Copy Markdown
Collaborator

This branch bundles two related Windows changes.

1. Fix: click(x, y) / drag coord-space mismatch (the actual ship-blocker)

tools/impl_.rs previously called ClientToScreen(px, py) on the bitmap pixels returned by get_window_state. But ClientToScreen interprets its argument as CLIENT-area-relative — top-left of the client area, EXCLUDING the title bar — while the bitmap covers the full window (top-left = DWM-cropped frame top-left, INCLUDING the title bar). The title-bar height (~30 px on Win11 dialogs) got double-counted, so every pixel click landed ~30 px below the agent's intended spot.

Concretely caught when trying to click Save on the LibreOffice Document Recovery dialog: the crosshair marker on the screenshot landed cleanly on the button, but the actual click went to the empty space just below it. PR #1697's DWM crop made the mismatch visible (was previously masked by the 7 px shadow margin partially absorbing the title-bar offset).

Fix: new bitmap_to_screen(hwnd, px, py) helper in tools/impl_.rs that uses DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) + DWM_CROP_INSET_PX to compute the bitmap's actual screen origin, then adds the caller's (px, py). Falls back to GetWindowRect.top-left + (px, py) when the DWM call fails (matching the capture path's fallback).

Replaces 4 ClientToScreen sites:

  • click (x, y) mode
  • double_click (x, y) mode
  • right_click (x, y) mode
  • drag (from_x, from_y) / (to_x, to_y)

Also switches the PostMessage path in those tools from post_click (which re-applies ClientToScreen internally) to post_click_screen so the bitmap → screen mapping happens exactly once.

Verified end-to-end against the LO Document Recovery dialog: pixel click at bitmap (470, 244) now correctly maps to screen (988, 575) (the Save button's center) and the dialog dismisses.

2. Docs: dispatch:\"background\" already does a UIA hit-test for pixel clicks

The click tool's (x, y) background path runs try_invoke_in_window_at_point BEFORE PostMessage delivery: if the deepest invokable element at the resolved screen position exposes InvokePattern, the click is delivered through the UIA accessibility channel (same path as element_index mode — no foreground swap, no flash). PostMessage is only the fallback.

That means pixel clicks on UWP / WinUI3 / Win11 packaged apps (Calculator, modern Notepad, etc.) work flash-free out of the box with the default dispatch:\"background\" — no need to escalate to \"foreground\".

The schema description and WINDOWS.md previously didn't mention this hit-test fallback at all — agents reading the docs would conclude they have to use dispatch:\"foreground\" for any XAML host and accept the visible flash. Both updated:

  • tools/impl_.rs click schema docstring — (x, y) paragraph now explains the UIA hit-test fallback and recommends background as the default even on XAML hosts.
  • Skills/cua-driver/WINDOWS.mddispatch table's \"background\" row mentions the hit-test; new "Always try dispatch:'background' first" section with the recommendation and the empirical UWP-Calculator result.

Empirical evidence (verified in this session): 4 pixel clicks against the UWP Calculator numpad with dispatch:\"background\" produced \"✅ Performed UIA Invoke at (sx,sy) for pid X.\" with zero visible flash, vs. the same coords with dispatch:\"foreground\" flashing the Calculator window for ~40 ms each.

Test plan

  • LO Document Recovery dialog: pixel click at bitmap (470, 244) dismisses (was off by ~30 px before)
  • UWP Calculator: 2 + 1 = via pixel clicks with dispatch:\"background\" — all four resolved as UIA Invoke at (sx,sy), display = 3, no flash
  • cargo build --bin cua-driver clean

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed coordinate conversion for click, double-click, right-click, and drag operations to accurately translate screenshot pixel coordinates to screen coordinates, improving compatibility across Windows application types.
  • Documentation

    • Updated dispatch contract documentation with improved descriptions of background and foreground behavior, including error handling and guidance for coordinate-based operations.

Review Change Stack

…est fallback for pixel clicks

The click tool's `(x, y)` mode already runs `try_invoke_in_window_at_point`
on `dispatch:"background"` (default) and `"auto"` — if the deepest
invokable element at the resolved screen position exposes
`InvokePattern`, the click goes through the UIA accessibility channel
(same background-safe path as the `element_index` mode). PostMessage is
only the fallback when the UIA hit-test misses.

That means agents should default to `dispatch:"background"` for pixel
clicks ON ANY surface — including UWP / WinUI3 / Win11 packaged apps
whose CoreInput dispatcher drops raw PostMessage. cua-driver translates
the pixel coord into a UIA Invoke and delivers through the
accessibility channel without a foreground swap. Empirically verified
against the UWP Calculator: every numpad / operator click resolves
through this path with zero visible flash.

This commit just updates the docs so the contract matches reality:

1. `tools/impl_.rs` `click` schema description — the `(x, y)` paragraph
   now explains the UIA hit-test fallback and recommends `background`
   as the default even on XAML hosts. `foreground` is positioned as
   the fallback only when there's no UIA peer (canvas / video / custom-
   drawn surfaces).

2. `Skills/cua-driver/WINDOWS.md` — `dispatch` table's `"background"`
   row mentions the hit-test, plus a new "Always try
   dispatch:'background' first" section with the recommendation and
   the empirical Calculator result.

No code changes; the behavior was already there. Just stops anyone
(human or LLM) reading the docs and wrongly concluding they have to
escalate to `dispatch:"foreground"` for UWP pixel clicks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel

vercel Bot commented May 26, 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 May 26, 2026 10:48am

Request Review

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR unifies Windows screenshot-to-screen coordinate conversion by introducing a DWM-based mapping helper and applying it across click, double-click, right-click, and drag tools. The dispatch contract documentation is clarified to specify UIA hit-test-first behavior with conditional PostMessage fallback and structured error handling.

Changes

Bitmap-to-Screen Coordinate Mapping and Dispatch Contract

Layer / File(s) Summary
Dispatch contract and guidance documentation
libs/cua-driver/rust/Skills/cua-driver/WINDOWS.md
Updated dispatch:"background" to specify UIA hit-test at the resolved screen point with InvokePattern invocation, conditional PostMessage fallback on UIA miss, and structured background_unavailable error when the fallback is known to fail. Guidance updated to prefer dispatch:"background" first, escalate to dispatch:"foreground" only after background_unavailable via bring_to_front flow.
DWM-based coordinate conversion helper
libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
Introduced bitmap_to_screen(hwnd, px, py) that maps screenshot bitmap pixels to screen coordinates using DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) plus 1px crop inset, with fallback to GetWindowRect on DWM query failure.
Click tool with bitmap-to-screen mapping
libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
Updated click description to document the new screenshot-bitmap-to-screen mapping and UIA-first dispatch layering. Replaced ClientToScreen with bitmap_to_screen in pixel mode; replaced post_click with post_click_screen in the non-UIA PostMessage fallback path to use DWM-mapped screen coordinates.
Double-click and right-click coordinate mapping
libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
Applied bitmap_to_screen mapping to double_click and right_click pixel modes, replacing ClientToScreen. Updated dispatch:"foreground" SendInput paths to use DWM-mapped screen coordinates and changed PostMessage fallbacks to call post_click_screen with the converted coordinates.
Drag endpoint coordinate mapping
libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
Updated drag pixel-coordinate endpoint computation to use bitmap_to_screen for both start and end positions, aligning drag synthesis with the screenshot bitmap coordinate origin.

Sequence Diagram(s)

No sequence diagram generated; the changes are coordinate mapping refactors and documentation updates without multi-component interactions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • trycua/cua#1697: Both PRs build on the DWMWA_EXTENDED_FRAME_BOUNDS-based "inner rect" concept—fix(cua-driver-rs)(windows): crop screenshots to DWMWA_EXTENDED_FRAME_BOUNDS to remove invisible-shadow black trim #1697 crops the captured bitmap to that rect, while this PR adds bitmap_to_screen using the same bounds and applies it to click/drag coordinate mapping so screenshot pixels align with the cropped capture.
  • trycua/cua#1622: This PR changes how click(x,y) translates screenshot bitmap pixels into screen coordinates for dispatch, while #1622 changes when click(x,y) should avoid UIA Invoke() on certain control types—both affect pixel-accurate routing of coordinate clicks.
  • trycua/cua#1669: Both PRs modify the Windows ClickTool flow in platform-windows/src/tools/impl_.rs—this PR handles pixel (x,y) to screen coordinate conversion via bitmap_to_screen, while #1669 addresses Chromium click synthesis with foreground-restore polling.

Poem

🐰 Pixels dance from bitmap's frame,
Through DWM's lens, they find their aim—
Screenshot coordinates align with screen,
No more confusion in between!
Click, drag, dispatch: harmonized,
A coordinate truth, crystallized! 🎯

🚥 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 accurately captures both main changes: the bitmap-pixel coordinate fix and the dispatch:background UIA hit-test documentation clarification, using precise technical terminology.
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 cua-driver-rs-docs-bg-uia-hittest

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 and usage tips.

@f-trycua
f-trycua merged commit 87da87d into main May 26, 2026
2 of 3 checks passed
@f-trycua
f-trycua deleted the cua-driver-rs-docs-bg-uia-hittest branch May 26, 2026 10:51
f-trycua added a commit that referenced this pull request May 26, 2026
…ows (#1709)

* test(cua-driver-rs)(harness): LibreOffice Writer VCL/SAL gap regressions

Two `#[ignore]`-tagged integration tests that drive a live LO Writer
through cua-driver to guard the VCL/SAL findings from the vision-only
LO Writer exploration that followed PR #1708:

1. `harness_lo_vcl_font_color_split_button_DOCUMENTED_no_expand` —
   inverted-assertion regression guard documenting that VCL toolbar
   SplitButtons (e.g. "Font Color") expose only `actions=[invoke]`
   in UIA, with no separable child for the dropdown arrow and no
   ExpandCollapse. Means the agent cannot open the color picker via
   the toolbar at all and has to route through Format → Character.
   Test will fail loudly if VCL ever exposes the dropdown child.

2. `harness_lo_vcl_modal_input_roundtrip_works` — positive assertion
   confirming that SAL/VCL modal dialogs (SALSUBFRAME class) DO
   accept SendInput-foreground input. Opens Find & Replace via
   `hotkey(ctrl+h, foreground)`, snapshots it to verify the
   documented `uia/mod.rs` SAL-skip stub, then closes it via
   `press_key(escape, foreground)`. Catches regressions in
   (a) Writer SALFRAME accelerator dispatch, (b) the SAL-skip stub
   wording, and (c) SAL modal Escape handling — all of which would
   silently break the documented escape hatches for LO automation.

Both tests skip cleanly when LO is not installed and honour
`LO_SWRITER_EXE` for non-default install paths. `-norestore` +
`-nologo` are passed to swriter to skip Document Recovery and the
splash screen. Drop impl sweeps `soffice.bin` post-run so the next
test starts clean.

Run locally:
  cargo test --test harness_lo_vcl_test -- --ignored --nocapture

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(cua-driver-rs)(platform-windows): MSAA fallback for SAL/VCL windows unlocks LO Writer toolbar SplitButton dropdowns

LibreOffice / OpenOffice (VCL) windows go through Windows' built-in MSAA→UIA
proxy when queried via IUIAutomation. The proxy is lossy: it collapses
`ROLE_SYSTEM_BUTTONDROPDOWN` (0x38) to a featureless `SplitButton` with no
separable dropdown affordance — leaving cua-driver unable to programmatically
open the "Font Color" picker (or any of the 20+ toolbar SplitButtons).

This PR adds an MSAA tree walker (`platform-windows/src/msaa.rs`) that
talks to LO's IAccessible bridge directly via oleacc.dll's
`AccessibleObjectFromWindow` + recursive `accChild`, preserving the
BUTTONDROPDOWN role. cua-driver maps the role to `actions=[invoke,expand]`
on the element line, and the `click` tool gains an `action:"expand"` arg
that clicks the right-edge of the cached element rect (the dropdown arrow
half) via SendInput.

End-to-end: `click(element_index=<Font Color>, action:"expand")` now opens
the SALTMPSUBFRAME color picker.

Bonus payoff: the MSAA walker doesn't hit the UIA-provider hang on
`BuildUpdatedCache(TreeScope.Subtree)` that affected SALSUBFRAME modals,
so dialogs that previously returned "SAL/VCL target, UIA walk skipped"
stubs now expose their full element tree. The Find & Replace dialog goes
from 0 actionable elements to 14 (Find/Replace ComboBoxes, Match-case /
Whole-words / Other-options CheckBoxes, Find-All / Find-Previous /
Find-Next / Replace / Replace-All / Help / Close Buttons).

## How it routes

`uia::walk_tree_unsafe` detects a SAL-class window (`GetClassNameW` starts
with "SAL") and short-circuits to `crate::msaa::walk_msaa_tree`. Element
indices stay sequential and addressable through the same `ElementCache`,
which grows a `SnapshotKind { Uia, Msaa }` discriminator so the right COM
interface (`IUIAutomationElement` vs `IAccessible`) is released on Drop,
and a `msaa_roles` vector so the click tool can route by role.

The `click` tool now has a leading MSAA branch:
- `(SnapshotKind::Msaa, role=BUTTONDROPDOWN, action:"expand")` →
  SendInput at `(rect.right - 4, center_y)`
- otherwise (default invoke) → SendInput at center
This sidesteps `accDoDefaultAction` for MSAA elements — empirically LO's
implementation applies VCL state asynchronously and returns S_OK either
way, so a center click is equivalent and works through `dispatch:"foreground"`.

## Tests

`harness_lo_vcl_test.rs` is updated:
- `harness_lo_vcl_font_color_split_button_DOCUMENTED_no_expand` →
  `harness_lo_vcl_font_color_split_button_exposes_expand` (positive guard
  flipped from inverted)
- New `harness_lo_vcl_font_color_expand_opens_picker` — calls click with
  `action:"expand"` and asserts a new "Font Color" SALTMPSUBFRAME window
  appears
- `harness_lo_vcl_modal_input_roundtrip_works` — updated to assert the
  Find & Replace dialog snapshot includes a "Close" Button and ≥4
  actionable elements (the pre-MSAA stub had zero)

All three tests pass on a live LO Writer in ~30 s.

## Discovery

The fix path was bracketed by these PowerShell PoCs (kept in
`flash-repro/` as exploratory artifacts, not part of the build):
- `ia2_probe.ps1` — walked MSAA, found 21 BUTTONDROPDOWN roles UIA hid
- `ia2_actions.ps1` — proved LO doesn't expose IAccessibleAction
- `ia2_pixel_click.ps1` — proved right-edge SendInput opens the picker
- `ia2_open_picker.ps1` — ruled out F4 / Alt+Down accelerators

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* test(cua-driver-rs)(harness): 4 more MSAA coverage tests for LO Writer + Calc + Recovery

Adds end-to-end and regression-shaped coverage for the MSAA fallback
landed in c514a0f, going beyond the single-element Font Color guard:

- `harness_lo_vcl_all_toolbar_split_buttons_expose_expand` — asserts
  ALL toolbar SplitButtons (≥15) report `actions=[invoke,expand]`,
  not just Font Color. Guards `msaa::actions_for` against accidentally
  dropping one of the BUTTONDROPDOWN / BUTTONMENU / BUTTONDROPDOWNGRID
  / SPLITBUTTON role variants.

- `harness_lo_vcl_color_pick_green_end_to_end` — types a sentence,
  selects it, opens Font Color via `action:"expand"`, picks "Green"
  from the picker tree, asserts the picker closes (the canonical
  "color applied" signal from LO). Catches breakage in the full
  workflow in one fast test.

- `harness_lo_vcl_recovery_dialog_walks_via_msaa` — forces a Recovery
  state by killing soffice.bin mid-edit, re-launches, asserts the
  Document Recovery dialog (SALFRAME class) walks via MSAA exposing
  the "Discard All" and "Recover Selected" buttons by name. Catches
  regressions to the pre-existing Recovery-dialog flow that now
  routes through MSAA instead of UIA. Logs and returns OK if LO
  doesn't enter recovery state (recovery feature disabled in user
  config / our trigger didn't take effect) — this is a regression
  guard for the MSAA walk, not for LO's recovery-trigger behavior.

- `harness_lo_vcl_calc_msaa_smoke` — launches LO Calc (different
  application on the same VCL base), asserts ≥15 SplitButtons with
  `expand` in the toolbar tree. Confirms the MSAA path generalizes
  beyond Writer. Skips cleanly when scalc isn't installed
  (`LO_SCALC_EXE` env override supported, mirroring `LO_SWRITER_EXE`).

All 7 LO/VCL tests pass on a live LO 26.2 install in ~80 s end-to-end.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

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.

1 participant