fix(cua-driver-rs)(windows): bitmap-pixel click/drag coords + dispatch:background UIA hit-test docs - #1708
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis 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. ChangesBitmap-to-Screen Coordinate Mapping and Dispatch Contract
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
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
…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>
This branch bundles two related Windows changes.
1. Fix:
click(x, y)/dragcoord-space mismatch (the actual ship-blocker)tools/impl_.rspreviously calledClientToScreen(px, py)on the bitmap pixels returned byget_window_state. ButClientToScreeninterprets 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 intools/impl_.rsthat usesDwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) + DWM_CROP_INSET_PXto compute the bitmap's actual screen origin, then adds the caller's (px, py). Falls back toGetWindowRect.top-left + (px, py)when the DWM call fails (matching the capture path's fallback).Replaces 4
ClientToScreensites:click(x, y)modedouble_click(x, y)moderight_click(x, y)modedrag(from_x, from_y) / (to_x, to_y)Also switches the PostMessage path in those tools from
post_click(which re-appliesClientToScreeninternally) topost_click_screenso 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 clicksThe click tool's
(x, y)background path runstry_invoke_in_window_at_pointBEFORE PostMessage delivery: if the deepest invokable element at the resolved screen position exposesInvokePattern, the click is delivered through the UIA accessibility channel (same path aselement_indexmode — 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.mdpreviously didn't mention this hit-test fallback at all — agents reading the docs would conclude they have to usedispatch:\"foreground\"for any XAML host and accept the visible flash. Both updated:tools/impl_.rsclick schema docstring —(x, y)paragraph now explains the UIA hit-test fallback and recommendsbackgroundas the default even on XAML hosts.Skills/cua-driver/WINDOWS.md—dispatchtable's\"background\"row mentions the hit-test; new "Always trydispatch:'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 withdispatch:\"foreground\"flashing the Calculator window for ~40 ms each.Test plan
(470, 244)dismisses (was off by ~30 px before)2 + 1 =via pixel clicks withdispatch:\"background\"— all four resolved asUIA Invoke at (sx,sy), display =3, no flashcargo build --bin cua-driverclean🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation