Skip to content

feat(windows): background input without z-raise + multi-cursor demos + overlay improvements - #1809

Merged
ddupont808 merged 10 commits into
mainfrom
feat/windows-background-input-no-zraise
Jun 5, 2026
Merged

feat(windows): background input without z-raise + multi-cursor demos + overlay improvements#1809
ddupont808 merged 10 commits into
mainfrom
feat/windows-background-input-no-zraise

Conversation

@ddupont808

@ddupont808 ddupont808 commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

portla.radio.dem.o.2.mp4

Windows background computer-use (click/type without z-raise or cursor movement), two multi-cursor demos, and several cua-driver improvements: fixed-duration glides, configurable turn radius, multi-window z-order fix, click latency decoupling, tangent-tracking cursors.

Key capabilities:

  • Background input on Windows: Click/type into Chromium, WPF, WinForms, Electron, GTK apps in the background (no z-raise, no cursor movement). Automatic fallback when PostMessage would be silently dropped; touch injection under a NoActivateGuard RAII wrapper prevents target self-activation.
  • Multi-cursor demos: Two demos (multi-cursor across 5 UI frameworks, MIDI-driven jukebox) showcase coordinated multi-agent orchestration with per-cursor glide pools, timing stats, and drag-drop MIDI.
  • Overlay improvements: Fixed-duration glides (deterministic timing), configurable turn radius, multi-window z-order (cursors visible across many tiles), tangent-tracking arrow heading, click latency decoupling (sleep known duration vs await render thread when glide_duration_ms > 0).

Changes

Core: Background Input (Windows)

  • libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs (+770): Touch injection actuator (inject_click_screen, inject_text_cloaked, etc.) with NoActivateGuard RAII to prevent target self-activation.
  • libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs: click, type_text, press_key, hotkey, drag, set_value route through injection when dispatch:"background" + target would silently drop PostMessage (Chromium/WPF/GTK). Capability extension: previously errored with background_unavailable, now succeeds.
  • libs/cua-driver/rust/crates/platform-windows/src/input/dispatch.rs: Heuristics for when PostMessage works vs when injection is needed.

Demos

  • demo/multi-cursor/ (+1654 lines): Orchestrator drives 5 UI frameworks (WinForms, WPF, Electron, legacy Win32, Qt) with per-cursor glide pools, adaptive lead (EMA), timing stats. Showcases coordinated multi-agent background computer-use.
  • demo/jukebox/ (+2749 lines): MIDI-driven multi-cursor demo — one agent cursor per MIDI track, each actuating a minigame tile (pitch strip, drum pad). Drag-drop MIDI, PLAY/PAUSE/STOP, per-track cursor pools, timing reports. Proves off-the-beat glide-then-click at scale (20+ cursors, ~0 median error).

Overlay Improvements (cross-platform, no breaking changes)

  • cursor-overlay/src/render_state.rs: Fixed-duration glides (glide_duration_ms > 0 → path completes in exactly that wall-clock time, enabling deterministic orchestration). Default 0.0 = unchanged speed-based mode. Both tick_motion (Windows/Linux) and tick_swift_constants (macOS) updated identically.
  • cursor-overlay/src/motion.rs: New turn_radius field (default 80.0, clamped 1–1000) so callers can tighten glide curves. Exposed on both Windows and macOS set_agent_cursor_motion schemas (parity).
  • platform-windows/src/overlay.rs: Multi-window z-order fix — overlay pins above the topmost driven window (walks z-order via GetTopWindow/GW_HWNDNEXT) rather than flipping to last-active, so cursors stay visible across many tiles but below the user's foreground. Fixes blink-out when >1 window driven concurrently.
  • platform-windows/src/tools/impl_.rs: Click latency decoupling — when glide_duration_ms > 0, overlay_glide_to sleeps the known duration instead of awaiting render thread's arrival oneshot, so click dispatch is deterministic and FPS-independent. Speed-based glides (== 0, the default) keep precise arrival-await.
  • cursor-overlay/src/render_state.rs: Arrow heading now points exactly along path tangent (direct assignment) instead of rate-limited rotation, so the tip tracks the trajectory on fast/short glides.

Backwards Compatibility

Zero breaking changes (confirmed by comprehensive review):

  • All new params optional with safe defaults (turn_radius: 80.0, glide_duration_ms: 0.0)
  • Background input is an automatic fallback (code that errored before now works)
  • Default behavior unchanged for existing users (speed-based glides, single-cursor z-order, etc.)
  • Platform parity maintained (macOS/Windows schemas in sync, shared render logic identical)
  • Demos isolated (no shared-code contamination)

Minor visual-only change: cursor tip now tracks tangent exactly (improved fidelity, no coordinate/timing impact).

Testing

  • Jukebox demo: 20+ concurrent cursors, 200ms fixed-duration glides, ~0 median error, no z-order occlusion, no flicker
  • Multi-cursor demo: Background clicks across 5 UI frameworks (WinForms, WPF, Electron, Win32, Qt)
  • Unit tests: render_state.rs glide timing tests (804–850)
  • E2E: e2e_windows_bg_input_test.rs (558 lines, Chromium/WPF/Electron scenarios)

Commit Breakdown

  1. 88bf542b feat: background click/type without z-raise (inject.rs, dispatch routing)
  2. 4f8de048 demo: multi-cursor across 5 UI frameworks
  3. a83e1bc5 feat: honor glide_duration_ms in shared render core + click latency decouple
  4. fdee125c demo: CUA Jukebox — MIDI-driven multi-cursor
  5. 8d5469cd fix: cursor arrow points along path tangent while gliding
  6. 763f6610 feat: configurable turn_radius + multi-window z-order fix
  7. eb493fd6 demo(jukebox): set turn_radius=40 for tighter curves

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Jukebox demo: MIDI-driven multi-cursor background actuation with polyphonic audio playback via coordinated instrument windows.
    • Added multi-cursor demo: replicate single-user actions to multiple background windows across WinForms, WPF, Electron, and custom UI frameworks.
    • Enhanced Windows background input with synthetic pointer/touch injection, eliminating "silent drops" for previously unsupported target types.
    • Added configurable turn radius for cursor motion paths.
  • Documentation

    • Added comprehensive READMEs for both demo applications with build/run instructions.
    • Added Windows background input implementation documentation.

ddupont808 and others added 7 commits June 1, 2026 19:25
…ithout z-raise or cursor movement

Unifies the Windows input path so a caller just targets an app and plays
click/type actions — no `dispatch` knob, no app-internals knowledge — and the
target window is never raised to the foreground.

Mechanism (input/inject.rs):
- NoActivateGuard: per-window WS_EX_NOACTIVATE for the duration of a background
  click/type. Windows then refuses to make the target foreground at all
  (click-activation, WM_MOUSEACTIVATE, and a WPF/XAML/Tauri handler's own
  SetForegroundWindow(self) are all denied) while the window still RECEIVES the
  input. Per-window, reverted on drop — no session side effects.
- Coordinate-routed, cursor-free delivery, layered by the default background
  path: UIA Invoke (Chromium DOM / WebView2 / UWP / controls) -> PostMessage
  (plain Win32 + WM_CHAR text) -> touch injection (canvas left-click) / pen
  injection (right-click). ZorderGuard (DWM cloak + SetWindowPos SWP_NOACTIVATE)
  hides any residual z movement.
- Keyboard accelerators are capability-first: inject_key_cloaked cloaks the
  target, takes focus via the AttachThreadInput trick (beats the foreground-lock
  without UIAccess), SendInputs the combo so GetKeyState/TranslateAccelerator
  fire, then restores the user's foreground. The keystroke is always delivered;
  the brief focus is hidden and reverted. Falls back to PostMessage if focus
  can't be obtained — never drops the action.

The global SPI_SETFOREGROUNDLOCKTIMEOUT freeze was tried and rejected: it is a
session-wide setting that leaks if the daemon dies mid-action, and is
ineffective anyway (our own injected input legitimizes the target's foreground
claim even under a maxed lock).

Verified end-to-end in crates/cua-driver/tests/e2e_windows_bg_input_test.rs
(6/6, #[ignore], serial): Electron/Chromium, Tauri/WebView2, and Win32 — left
click, right click, text type, and Ctrl+A — all stay background (oracle: target
pid never becomes foreground) with the keystroke/click delivered. The suite
auto-downloads the trycua test apps, runs every child under a
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE Job Object plus a ChildBag/broker-pid
teardown so nothing orphans (even on panic or Ctrl-C), discovers multi-process
app windows via a new-window diff, and bounds every JSON-RPC call with a 25s
timeout.

Also adds examples/zdrop_probe.rs (z-order/foreground probe) and
docs/windows-background-input-re-plan.md (RE methodology, findings, and the
implemented solution).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ross 5 UI frameworks

A live demo of background actuation: one action in a foreground "master" window
is replayed onto FOUR background windows simultaneously, each driven by its own
cua-driver session = its own uniquely-coloured agent cursor — with no window
raised and the user's mouse never moved.

Layout: 2x2 corners + a center master (the foreground window the human drives).
Five UI frameworks, spanning the with/without-accessibility-tree axis:
  - Win32 + GDI custom-drawn  — NO a11y tree   -> pixel/injection path (crimson)
  - .NET WinForms             — MSAA/UIA        -> UIA Invoke           (amber)
  - .NET WPF (XAML)           — UIA             -> UIA Invoke           (aqua)
  - Electron (Chromium)       — UIA             -> UIA Invoke           (mint_lime)
  - Win32 standard controls   — center/master (instrumented, emits user actions)

The Rust orchestrator starts the cua-driver daemon, launches + positions the
five windows, reads the master's emitted click/type events, and fans each one
out to the four corners over four concurrent `cua-driver call ... {session:<color>}`
invocations. Every spawned process is assigned to a KILL_ON_JOB_CLOSE Job Object
so closing the master (or killing the orchestrator) tears the whole tree down
with no orphans.

Verified (--auto self-play, 3 cycles): all four corners driven concurrently
(TYPE + CLICK) — including the no-a11y GDI corner via the pixel path — with the
foreground staying on the center master throughout (no corner ever z-raised),
and a clean teardown (no orphaned processes).

Components: legacy-app (Rust Win32, gdi + master modes), orchestrator (Rust),
dotnet/{winforms,wpf}, electron/, README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…overlay perf

cursor-overlay: glide_duration_ms now drives a fixed-duration glide in the
shared render path (tick_motion + tick_swift_constants), so a move takes that
exact time regardless of distance on macOS/Windows/Linux alike; 0 (the default)
keeps the original speed-based timing — no platform drift. Adds regression tests.

platform-windows/overlay: repaint-gate — skip the full-virtual-screen
composite + UpdateLayeredWindow on frames where nothing changed (no command, no
cursor mid-glide/spring/click), so a resting overlay costs ~nothing instead of
blitting at the timer rate. Adds an env-gated overlay-FPS probe
(CUA_DRIVER_RS_OVERLAY_FPS_FILE) for diagnosing render throughput.

platform-macos + platform-windows: correct the glide_duration_ms tool doc
(0 = speed-based default), matched across both surfaces.

Also carries this branch's in-progress windows background-input refinements
(input/inject, dispatch, mouse, mod) and the RE plan doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…computer-use

A grid of miniwob-style minigame windows, one per MIDI track, each actuated by
its own coloured cua-driver agent cursor in the background — the click is what
triggers the sound. One cursor per part, one colour per agent, off a single
clock: coordinated multi-cursor computer-use as music.

- jukebox-app: one Win32/GDI binary, two modes. Controller = a 600x80 transport
  bar (white "CUA JUKEBOX", a single ▶/⏸ icon toggle, track-colour swatches,
  playhead; accepts a .mid drop). Instrument = a 200x160 borderless tile: a
  pitch strip (click-X = semitone, fitted per-track so wide ranges aren't
  clamped), a kick/snare/hat drum pad (MIDI ch.10 → zones), or a brightness pad.
  Each owns a rodio synth voice (sine/square/saw/triangle + drum waves) so
  notes mix at the OS mixer. Double-buffered, region-clipped repaints.
- orchestrator: parses a .mid (midly) or a built-in demo loop, infers each
  track's voice from its name, lays out the grid, and pins each track to a
  fixed-duration glide. Per-track cursor POOLS grow to the same colour when
  notes fall within the glide window (chords / fast lines fan out). Drives every
  note over a persistent per-voice named-pipe connection to the daemon (no
  per-note process spawn), with a per-voice adaptive lead (EMA) that tracks the
  measured actuation error to ~0 median; reports the timing diff at song end.
  PLAY/PAUSE/STOP; dropping a .mid re-execs the demo onto that track.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…liding

The arrow heading was rate-limited toward the motion tangent (rotate_toward at
14 rad/s), so on fast/short glides — especially the fixed-duration ones — it
couldn't turn quickly enough and lagged behind (or pointed the wrong way)
during the move. Assign the heading directly to the path tangent each frame
instead, so the tip tracks the trajectory. Applied to both shared render paths
(tick_motion for Windows/Linux, tick_swift_constants for macOS) — no drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ click latency decouple

cursor-overlay/motion: Added `turn_radius` field to MotionConfig (default 80,
clamped 1–1000) so callers can tighten glide curves. Exposed on both Windows
and macOS `set_agent_cursor_motion` schemas (parity). The shared path planner
reads it; no platform drift.

platform-windows/overlay: Multi-window z-order fix — when >1 distinct window is
driven, pin the overlay above the TOPMOST driven window (walks z-order top→down
via GetTopWindow/GW_HWNDNEXT) rather than flipping to the last-active one. This
keeps the overlay above all driven windows but below anything stacked above
them (the user's foreground), fixing the cursor blink-out when many windows are
actuated concurrently (e.g. jukebox's 8 tiles). Removed the earlier repaint-gate
(skip composite when idle) — starting/stopping the full-screen
UpdateLayeredWindow as activity changed made resting cursors flicker when any
one clicked; steady per-frame blit is flicker-free and costs little during
playback since something's almost always active.

platform-windows/tools/impl_: Click latency decoupling — when
`glide_duration_ms > 0` (fixed-duration glide), `overlay_glide_to` now sleeps
the known duration instead of awaiting the render thread's arrival oneshot, so
click dispatch is deterministic (glide_duration_ms wall-clock) and independent
of overlay FPS. Under many concurrent cursors (10–12 fps debug, ~60 fps
release) the arrival-await coupled click latency to render-thread scheduling;
the sleep decouples it. Speed-based glides (glide_duration_ms == 0, the
default) keep the precise arrival-await since their duration depends on
distance — non-breaking. Per-cursor fade in/out already respected (each
cursor's idle_alpha is applied in paint_cursor), and now animates correctly
every frame with the repaint-gate removed.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…iles

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 2, 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 Preview Jun 2, 2026 9:31pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dfb84257-4433-420b-9798-a903a6f59fc6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds two complete demo applications (jukebox and multi-cursor) showcasing coordinated multi-window background input, and implements comprehensive Windows background click/drag/text/key injection without visible foreground stealing via synthetic pointer/touch delivery, per-window no-activate guards, cloaked focus, and tool fallback routing.

Changes

Demo Applications: Jukebox and Multi-Cursor Showcases

Layer / File(s) Summary
Jukebox audio synthesis and controller/instrument UI
demo/jukebox/app/Cargo.toml, demo/jukebox/app/src/main.rs
Win32 application with dual modes (controller transport bar + instrument miniwob tiles), rodio audio synthesis with configurable waveforms, MIDI-mapped note playback, and GDI double-buffered rendering with per-track visuals.
Jukebox MIDI orchestration and cursor scheduling
demo/jukebox/orchestrator/Cargo.toml, demo/jukebox/orchestrator/src/main.rs
Orchestrator that parses MIDI files, spawns per-track instrument windows, manages cursor pools per voice, schedules adaptive-lead click timing, and coordinates play/pause/stop via cua-driver daemon.
Multi-cursor legacy app with master and GDI modes
demo/multi-cursor/legacy-app/Cargo.toml, demo/multi-cursor/legacy-app/src/main.rs
Win32 application supporting master mode (real EDIT/BUTTON controls) and gdi mode (custom-drawn UI), both emitting click/type events for orchestrator replication.
Multi-cursor orchestrator with 4-way concurrent replay
demo/multi-cursor/orchestrator/Cargo.toml, demo/multi-cursor/orchestrator/src/main.rs
Spawns one master and four background instances across UI frameworks (GDI, WinForms, WPF, Electron), fans out master input events to corner threads via cua-driver calls.
Multi-cursor .NET WinForms/WPF and Electron UIs
demo/multi-cursor/dotnet/winforms/*, demo/multi-cursor/dotnet/wpf/*, demo/multi-cursor/electron/*
.NET WinForms, WPF, and Electron HTML+JS applications providing identical UI layouts for background input target testing across framework boundaries.
Demo workspace configuration and documentation
demo/*/Cargo.toml, demo/*/.gitignore, demo/*/README.md
Cargo workspace manifests with resolver v2 and release optimization, gitignore files for build artifacts, and comprehensive READMEs describing architecture and usage.

Windows Background Input Infrastructure and Hardening

Layer / File(s) Summary
Motion configuration turn_radius parameter
libs/cua-driver/rust/crates/cursor-overlay/src/motion.rs, libs/cua-driver/rust/crates/platform-macos/src/tools/cursor_tools.rs
Add turn_radius: f64 field to MotionConfig with default 80.0 and clamp range [1.0, 1000.0]; wire through with_overrides builder and macOS tool schema.
Render state fixed-duration glides and heading behavior
libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs
Add glide_duration_ms override for distance-independent timing in both Windows/Linux and macOS paths; change heading from rate-limited rotation to direct path-tangent tracking; use runtime turn_radius in MoveTo path planning; add glide_duration unit tests.
Windows background injection APIs and RAII guards
libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs
Implement NoActivateGuard (prevents target activation), ZorderGuard (temporary z-order raise), inject_click_screen (pen taps), inject_drag_screen (pen/touch drag), inject_key_cloaked (SendInput with cloaking), inject_text_cloaked (Unicode SendInput), plus foreground-forcing helpers and persistent touch device.
Windows dispatch schema and WPF detection
libs/cua-driver/rust/crates/platform-windows/src/input/dispatch.rs
Add is_wpf_target_window helper (detects HwndWrapper class prefix); update would_be_silently_dropped to flag WPF mouse/scroll PostMessage as silently dropped.
Windows tool integration of injection fallbacks
libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs, libs/cua-driver/rust/crates/platform-windows/src/input/mod.rs, libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs
Integrate injection APIs into click, type_text, press_key, hotkey, set_value, drag tools: add NoActivateGuard for non-foreground, use inject_click_screen on silent-drop, route type_text through UIA→cloaked-inject→WM_CHAR chain, use cloaked injection for key combos, fallback drag to synthetic injection.
Windows overlay z-order pinning and FPS logging
libs/cua-driver/rust/crates/platform-windows/src/overlay.rs
Rework z-order pinning from last-touched to topmost among visible cursors; add optional per-second FPS probe logging to CUA_DRIVER_RS_OVERLAY_FPS_FILE; add topmost_of helper and command drain counting.
End-to-end Windows background input test
libs/cua-driver/rust/crates/cua-driver/tests/e2e_windows_bg_input_test.rs, libs/cua-driver/rust/crates/cua-driver/Cargo.toml
Comprehensive e2e test validating background click/right-click/text/key delivery to Electron, Tauri, Win32 Notepad without foreground stealing; uses Job Objects, driver JSON-RPC, HTTP DOM event oracle, focus-monitor sentinel, and foreground-stay invariant assertions.
Windows background input planning and architecture documentation
libs/cua-driver/rust/docs/windows-background-input-re-plan.md
Planning document describing implementation: per-window WS_EX_NOACTIVATE gating, delivery mechanisms (UIA, PostMessage, synthetic injection), cloaked focus with serialization, keyboard accelerators, root-cause analysis, reverse-engineering methodology, solution tracks, and sequencing.
Windows z-drop probe diagnostic example
libs/cua-driver/rust/crates/platform-windows/examples/zdrop_probe.rs
Diagnostic tool measuring whether background input causes z-order raise of target above user foreground; supports control (foreground-based click) and inject (touch injection) actuators.
Windows PostMessage drag and feature flags
libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs, libs/cua-driver/rust/crates/platform-windows/Cargo.toml
Add post_drag_screen helper for screen-coordinate PostMessage drag routing; extend Windows Cargo features to include Job Objects and pointer/touch injection modules.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • trycua/cua#1629: Both PRs update Windows hotkey tool behavior documentation/implementation in platform-windows/src/tools/impl_.rs.
  • trycua/cua#1688: Both PRs modify overlay.rs Win32 z-order pinning for the cursor overlay.
  • trycua/cua#1708: Both PRs modify platform-windows/src/tools/impl_.rs click/drag tool background dispatch fallback logic.

Poem

🐰 Two demos dance in background delight,
While cursors click through screens, none the wiser—
No flash, no steal, no windows in sight,
Just graceful injection, the ultimate riser.
Glides with duration and guards that won't break,
The forest of foreground left clean in our wake! 🎯

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/windows-background-input-no-zraise

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (1)
libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs (1)

4058-4118: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return turn_radius in the motion payloads too.

Line 4094 applies the new turn_radius, but Lines 4098-4118 still serialize the old motion shape. That makes the field look like it was ignored, and get_agent_cursor_state later in this file has the same gap. Please add turn_radius to the setter response and getter payload so callers can verify what was applied.

🤖 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 `@libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs` around lines
4058 - 4118, The motion's turn_radius is applied but not returned: update the
invoke() response and the get_agent_cursor_state getter to include the
turn_radius field; specifically, add updated.turn_radius into the formatted
summary (similar to glideDurationMs/dwellAfterClickMs) and include
"turn_radius": updated.turn_radius in the structured JSON returned by
ToolResult::text(...).with_structured(...) and likewise add turn_radius to the
payload produced by get_agent_cursor_state so callers can verify the applied
value.
🟡 Minor comments (8)
demo/jukebox/README.md-15-23 (1)

15-23: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add fence languages to satisfy markdownlint.

Lines 15 and 96 start unlabeled fenced blocks, which is why markdownlint is already flagging MD040 here. Adding a language like text keeps the README lint-clean without changing the rendered output.

Also applies to: 96-98

🤖 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 `@demo/jukebox/README.md` around lines 15 - 23, Add a language label to the
unlabeled fenced code blocks to satisfy markdownlint MD040: replace the opening
``` fences for the ASCII-art block that begins with
"┌───────────────────────────────────────────────────────────────┐" and the
other unlabeled fenced block further down with a labeled fence such as ```text
(and keep the corresponding closing ``` unchanged) so the rendered output is
identical but markdownlint no longer flags the blocks.
libs/cua-driver/rust/crates/platform-windows/src/input/dispatch.rs-94-105 (1)

94-105: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten the shared dispatch description.

This string is now out of sync with the shipped behavior: inject_key_cloaked / inject_text_cloaked temporarily foreground the target, and inject_click_screen supports right-click. Leaving the schema text as “background never swaps foreground” and “right/middle click” unavailable will misstate the tool contract to callers.

🤖 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 `@libs/cua-driver/rust/crates/platform-windows/src/input/dispatch.rs` around
lines 94 - 105, Update the shared "dispatch" description string in dispatch.rs
to reflect current behavior: remove the absolute claim that "background" never
swaps foreground (acknowledge that inject_key_cloaked and inject_text_cloaked
may temporarily foreground targets), remove the statement that right/middle
clicks are unavailable (inject_click_screen supports right-click), and clarify
the fallback behavior and error conditions (e.g., background_unavailable for
inputs that cannot be expressed). Edit the description text associated with the
dispatch schema so it mentions these exceptions and accurately describes modes
'background', 'foreground', and 'auto'; locate and update the string literal
named or used for the shared dispatch description in this file.
libs/cua-driver/rust/docs/windows-background-input-re-plan.md-18-27 (1)

18-27: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the implementation notes to match the code that landed.

This section still says left-clicks use InjectTouchInput and that ZorderGuard hides residual z movement via cloaking, but the current implementation uses synthetic pen taps for clicks and the guard only toggles topmost z-order. Keeping this stale will send the next investigation down the wrong path.

🤖 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 `@libs/cua-driver/rust/docs/windows-background-input-re-plan.md` around lines
18 - 27, The docs are stale: update the implementation notes so they match the
landed code by replacing references to touch-based left-clicks
(InjectTouchInput) with the current synthetic-pen tap approach
(InjectSyntheticPointerInput / PT_PEN used for clicks), and change the
description of ZorderGuard from a cloak that hides residual z movement to the
actual behavior where it only toggles topmost z-order. Also note that
dispatch:"background" still transparently selects the appropriate delivery
method (UIA Invoke, PostMessage, or synthetic pen injection) so callers don't
pass a dispatch knob.
libs/cua-driver/rust/crates/cua-driver/tests/e2e_windows_bg_input_test.rs-67-81 (1)

67-81: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Job object configuration failure is silently ignored.

If SetInformationJobObject fails, the job won't have KILL_ON_JOB_CLOSE and child processes could orphan on test crash. Consider logging the failure for diagnostic visibility.

Suggested fix
-        let _ = SetInformationJobObject(
+        if SetInformationJobObject(
             h,
             JobObjectExtendedLimitInformation,
             &info as *const _ as *const c_void,
             std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
-        );
+        ).is_err() {
+            eprintln!("[e2e] WARN: SetInformationJobObject failed — orphan protection may not work");
+        }
🤖 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 `@libs/cua-driver/rust/crates/cua-driver/tests/e2e_windows_bg_input_test.rs`
around lines 67 - 81, The job() helper currently ignores the return value of
SetInformationJobObject so the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag may not
be applied; update job() to check the result of SetInformationJobObject (called
after CreateJobObjectW and creating JOBOBJECT_EXTENDED_LIMIT_INFORMATION with
BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) and on
failure either log the error details (using the test logger) or panic with a
descriptive message including the Windows error code so test runs surface the
configuration failure and avoid orphaned child processes.
demo/multi-cursor/README.md-15-28 (1)

15-28: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to this fenced block.

This trips the documented markdownlint rule and also drops syntax-highlighting/hinting for the layout snippet. text would be enough here.

🤖 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 `@demo/multi-cursor/README.md` around lines 15 - 28, The fenced code block
containing the ASCII diagram (the triple backticks wrapping the layout with
lines like "MASTER", "Win32 GDI", "WinForms", etc.) needs a language tag to
satisfy markdownlint and enable highlighting; update the opening fence from ```
to ```text so the block is treated as plain text and the lint rule/syntax
highlighting are applied.
demo/multi-cursor/README.md-46-58 (1)

46-58: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the .NET 10 prerequisite in the build steps.

Both dotnet demo projects target net10.0-windows, but the README only says dotnet build. Calling out the required SDK version here will save readers from a generic “target framework not supported” failure.

🤖 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 `@demo/multi-cursor/README.md` around lines 46 - 58, Update the Build section
to call out that both dotnet demo projects target net10.0-windows and therefore
require the .NET 10 SDK to be installed; mention the exact target
"net10.0-windows" and add a short note/example command to check/install the SDK
(for example `dotnet --list-sdks` or a link to Microsoft .NET 10 download) so
users know to install .NET 10 before running the existing `dotnet build`
commands for the WinForms and WPF projects.
demo/multi-cursor/orchestrator/src/main.rs-305-311 (1)

305-311: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The DWM fallback returns screen-space coordinates, not window-local coordinates.

When DwmGetWindowAttribute fails, this returns (pt.x, pt.y) directly after ClientToScreen, so the fallback path feeds absolute screen coordinates into a helper that otherwise returns window-local screenshot pixels. Any window on that path will click the wrong location.

Proposed fix
-        if DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS,
-            &mut dwm as *mut _ as *mut core::ffi::c_void,
-            std::mem::size_of::<RECT>() as u32).is_err()
-        {
-            return Some((pt.x, pt.y));
-        }
+        if DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS,
+            &mut dwm as *mut _ as *mut core::ffi::c_void,
+            std::mem::size_of::<RECT>() as u32).is_err()
+        {
+            return Some((pt.x - cr.left, pt.y - cr.top));
+        }
🤖 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 `@demo/multi-cursor/orchestrator/src/main.rs` around lines 305 - 311, The
fallback branch after DwmGetWindowAttribute currently returns screen-space
coords (pt.x, pt.y); instead convert those screen coords to window-local coords
like the success path does by querying the window rectangle and subtracting its
origin. Replace the early return with a GetWindowRect (or equivalent) call for
hwnd, store into a RECT (e.g., rect), and return Some((pt.x - rect.left - 1,
pt.y - rect.top - 1)); if GetWindowRect itself fails you can fall back to
returning the original pt but prefer using the rect subtraction to keep
coordinates consistent with the DwmGetWindowAttribute path (symbols:
DwmGetWindowAttribute, ClientToScreen, hwnd, pt, RECT).
demo/multi-cursor/legacy-app/src/main.rs-232-243 (1)

232-243: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve the full WM_CHAR code unit here.

wparam.0 as u8 as char drops everything above 0xFF, so any non-ASCII text typed into the GDI target is corrupted before it reaches st.text.

Proposed fix
-                    let ch = wparam.0 as u8 as char;
+                    let Some(ch) = char::from_u32(wparam.0 as u32) else {
+                        return LRESULT(0);
+                    };
🤖 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 `@demo/multi-cursor/legacy-app/src/main.rs` around lines 232 - 243, The WM_CHAR
handler currently truncates characters with `wparam.0 as u8 as char`, losing
codepoints above 0xFF; change how `ch` is derived so the full Unicode code unit
is preserved (e.g., convert `wparam.0` to u32 and use char::from_u32 or
equivalent to get an Option<char>`), then handle the Option (ignore invalid
codepoints) and keep the existing logic that treats backspace, newline, and
non-control characters when updating `st.text` in the STATE/Mode::Gdi branch.
🧹 Nitpick comments (2)
libs/cua-driver/rust/crates/platform-windows/src/overlay.rs (1)

604-608: 💤 Low value

Unused drained counter.

The drained variable is incremented on each message processed but only consumed by let _ = drained; which discards it. Either remove the tracking or use it (e.g., in the FPS diagnostic output).

Option A: Remove dead code
-                    let mut drained = 0u32;
                     if let Ok(rx_guard) = CMD_RX_WIN.try_lock() {
                         if let Some(ref rx) = *rx_guard {
                             while let Ok(m) = rx.try_recv() {
-                                drained += 1;
                                 if let Some(k) = apply_msg(map, m) {
                                     map.last_active = Some(k);
                                 }
                             }
                         }
                     }
...
-                    let _ = drained;
Option B: Include in FPS diagnostic
-                            let _ = writeln!(f, "overlay fps={fps:.1} avg_dt_ms={:.1} cursors={cursors}", secs * 1000.0 / n as f64);
+                            let _ = writeln!(f, "overlay fps={fps:.1} avg_dt_ms={:.1} cursors={cursors} drained={drained}", secs * 1000.0 / n as f64);

Also applies to: 663-663

🤖 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 `@libs/cua-driver/rust/crates/platform-windows/src/overlay.rs` around lines 604
- 608, The local counter `drained` is unused—remove the dead variable and its
increments in the CMD_RX_WIN try_lock / try_recv loop (the `drained`
declaration, the `drained += 1;` line and the later `let _ = drained;` discard)
so the loop simply drains messages without tracking, or alternatively, if you
prefer to keep the metric, wire `drained` into the FPS/diagnostic output where
FPS is computed; locate the try_recv loop guarded by CMD_RX_WIN and either
delete the `drained` handling or replace the discard with emitting the value
into the existing FPS diagnostic path.
libs/cua-driver/rust/crates/platform-windows/examples/zdrop_probe.rs (1)

213-226: 💤 Low value

Dead code: x, y parameters are captured but explicitly discarded.

The x and y parameters are passed to run_trial but then let _ = (x, y); discards them. They're used by the closure, but this explicit discard suggests incomplete refactoring. Either remove the line or the parameters if they're truly unused in this scope.

Remove the dead binding
     fn run_trial(name: &str, baseline: HWND, target: HWND, x: i32, y: i32, actuate: impl FnOnce()) {
         let b = baseline.0 as usize;
         let t = target.0 as usize;
         let poller = thread::spawn(move || poll(b, t, Duration::from_millis(1200)));
         thread::sleep(Duration::from_millis(120)); // let poller establish "baseline on top"
-        let _ = (x, y);
         actuate();
🤖 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 `@libs/cua-driver/rust/crates/platform-windows/examples/zdrop_probe.rs` around
lines 213 - 226, run_trial currently takes x and y but then immediately discards
them with let _ = (x, y); which prevents any closure from capturing those
parameters — remove that dead binding so the closure passed as actuate can
capture and use x and y, or if no caller/closure actually needs them, remove x
and y from run_trial's signature and update all callers accordingly; locate the
function run_trial and either delete the let _ = (x, y); line or remove the
parameters and fix call sites.
🤖 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 `@demo/jukebox/app/src/main.rs`:
- Around line 354-356: The WM_KEYDOWN branch that calls PostQuitMessage when
wp.0 == 0x1B should only run for the controller's main window; change the
condition to also check the message's hwnd against the stored controller window
handle (e.g., compare the wndproc's hwnd param to your controller window
variable like controller_hwnd or MAIN_WINDOW_HANDLE) before calling
PostQuitMessage. Locate the WM_KEYDOWN arm containing wp.0 == 0x1B in main.rs
and modify it to require hwnd == controller_hwnd (or an equivalent identity
check) so Esc quits only the controller window.

In `@demo/jukebox/orchestrator/src/main.rs`:
- Around line 217-224: The cleanup loop currently calls
Command::new("taskkill").args(["/F", "/IM", img]) which indiscriminately kills
every process by image name; instead record and target only the PIDs/process
handles you spawned for this run. Change the launcher code that spawns
cua-driver.exe and jukebox-app.exe to store their Child objects or PIDs (e.g., a
Vec of Child or pid_t) and replace the taskkill-by-name loop with termination
logic that iterates only over those stored children (use taskkill /PID or
Child::kill on Windows), or create and use a Job Object / unique tag so you can
reap processes created by this process only. Ensure the code that currently uses
Command::new("taskkill") and the for loop over
["cua-driver.exe","jukebox-app.exe"] is updated to reference the stored
PIDs/Child handles instead of image names.
- Around line 635-652: The active-note map `on` is keyed only by pitch so
overlapping notes or same pitch on different channels collide; change the key to
include channel (e.g., use a tuple (channel.as_int(), key.as_int()) as the
HashMap key) and update all insert/remove accesses in the event loop (the
TrackEventKind::Midi NoteOn and NoteOff arms that call on.insert and on.remove)
to use that composite key, ensuring you still push Note entries using the
original pitch and velocity and preserve drum_notes logic for channel 9.

In `@demo/multi-cursor/dotnet/winforms/Program.cs`:
- Around line 12-13: Program.Main currently calls
Application.EnableVisualStyles() and
Application.SetCompatibleTextRenderingDefault(false) but does not initialize the
WinForms bootstrap; call ApplicationConfiguration.Initialize() at the start of
Program.Main (before EnableVisualStyles/SetCompatibleTextRenderingDefault) so
the project-wide <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
is applied; update Program.Main to invoke ApplicationConfiguration.Initialize()
(or alternatively call Application.SetHighDpiMode(...) using the same
PerMonitorV2 mode) to ensure PerMonitorV2 DPI behavior for the app.

In `@demo/multi-cursor/orchestrator/src/main.rs`:
- Around line 338-346: find_window_by_title currently only matches window
captions and can return windows from unrelated processes; change its signature
to accept the expected child PID and filter EnumWindows by that PID. Pass the
expected pid into the Finder struct (or add an expected_pid field), then in
enum_cb call GetWindowThreadProcessId for each HWND and only consider matches
where the process id equals expected_pid and the caption contains needle; keep
the existing timeout/sleep behavior and return the (HWND, pid) when both pid and
title match. Ensure callers pass the spawned child's PID to the updated
find_window_by_title.
- Around line 190-203: Remove the unsupported "session" property from the JSON
payloads sent by run_call in the click and type_text paths (e.g., the click call
in the Action::Type branch and the subsequent type_text call) so the payloads
match the tool schemas; keep pid, window_id (hwnd_addr), x/y or text and ensure
json_escape is used for text. Update window selection logic in
find_window_by_title/EnumWindows to prefer the spawned window by verifying the
window's process id (GetWindowThreadProcessId) matches the spawn pid (or use the
exact HWND returned at spawn) instead of relying solely on
title.contains(&needle). Fix client_rel_to_local_px so that when
DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) fails you still compute and
subtract the window origin and the 1px inset (e.g., fall back to GetWindowRect
and subtract left/top +1) before returning (pt.x - origin.x - 1, pt.y - origin.y
- 1) to ensure coordinates map correctly.

In `@libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs`:
- Around line 304-312: The guard created in arm() currently always forces
HWND_TOPMOST and drop() always sets HWND_NOTOPMOST, which loses the window’s
original topmost state; modify arm() (in the same impl that defines arm and
Drop) to detect and store the target’s original topmost status (e.g., call the
existing set_topmost/inspection helper or use GetWindowLongPtr/WS_EX_TOPMOST
check) as a new field like original_topmost on the returned Self, and then
change drop() to restore the recorded original_topmost (call set_topmost(target,
original_topmost)) instead of unconditionally using HWND_NOTOPMOST so the
window’s prior z-order class is preserved.
- Around line 304-312: ZorderGuard::arm currently sets set_topmost for any
non-foreground target, causing visible z-raises; change arm to only promote the
target when it is actually occluded by other windows (not merely
not-foreground). Replace the raised condition (!target.0.is_null() && target !=
prev_fg) with a call to a helper like is_obscured(target, prev_fg) that returns
true only if the target's client/target area is covered (e.g., check
IsWindowVisible, GetWindowRect and whether WindowFromPoint(s) over the target
return a different window, or test several sample points including center and
edges), then call set_topmost(target, true) only when that helper reports
occlusion and set raised accordingly; ensure prev_fg, target, and raised reflect
the actual action taken so no unnecessary z-raise occurs.
- Around line 390-407: inject_click_screen (and the related coordinate-routed
entry point around lines 585-629, e.g., inject_drag_screen) currently proceeds
when target == 0 or the HWND is stale, letting coordinate-based injection affect
whatever window is under (sx,sy); reject null/invalid targets early by
validating the target HWND before any guards or coordinate routing.
Specifically, in inject_click_screen and the counterpart function, check if
target == 0 and/or call a Windows validity check on the constructed HWND (e.g.,
IsWindow) immediately after computing target_h and before calling
NoActivateGuard::arm or ZorderGuard::arm; if the HWND is null or not a valid
window, return an Err/bail! with a clear message (e.g., "invalid or null target
HWND") so injection is aborted early.
- Around line 224-232: The guard incorrectly skips applying WS_EX_NOACTIVATE
when prev == 0; update NoActivateGuard so it doesn't gate on prev != 0: compute
want = WS_EX_NOACTIVATE.0 as isize and set applied = (prev & want) == 0 && {
SetWindowLongPtrW(root, GWL_EXSTYLE, prev | want); (GetWindowLongPtrW(root,
GWL_EXSTYLE) & want) != 0 }; keep prev_exstyle = prev and root_addr as before so
we correctly record the original style and whether the change was applied even
when prev was zero.

In `@libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs`:
- Around line 204-206: The first PostMessageW call sends WM_MOUSEMOVE before the
down_msg but uses the existing wparam (mk_flag); change it to send the pre-drag
WM_MOUSEMOVE with a wParam of 0 (no button state) instead of wparam so the
sequence is: PostMessageW(target, WM_MOUSEMOVE, 0, make_lparam(c_from.x,
c_from.y)) followed by PostMessageW(target, down_msg, wparam,
make_lparam(c_from.x, c_from.y)); keep the rest of the parameters (target,
down_msg, make_lparam) unchanged.

In `@libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs`:
- Around line 1850-1862: The NoActivateGuard is currently armed only when an
explicit window_id (hwnd_opt) is present, leaving pid-resolved clicks
unprotected; change the logic so you defer creating _noact until after the
actual target HWND is resolved and then arm crate::input::NoActivateGuard::arm
with that resolved HWND (converted to windows::Win32::Foundation::HWND).
Specifically, remove or relocate the current match that sets _noact around
hwnd_opt and instead construct _noact after the code path that resolves hwnd
(the later resolution logic around the places that reference hwnd or resolve by
pid), guarding both explicit hwnd_opt and pid-resolved HWNDs while preserving
the DispatchMode::Foreground no-op behavior.
- Around line 3736-3772: The injected drag path is missing duration_ms: when
switching to synthetic injection in the background branch you must thread the
existing duration_ms into the injection helper so the wall-clock drag duration
remains consistent. Update the call to
crate::input::inject::inject_drag_screen(...) to accept and forward duration_ms
(and update that function signature/implementations where defined), and ensure
any spawn_blocking closure and callers (e.g., the branch in impl_.rs around
dispatch == DispatchMode::Background, overlay_glide_to, and the
inject::inject_drag_screen function) propagate the duration_ms parameter so both
PostMessage and injected paths honor the same duration contract.

---

Outside diff comments:
In `@libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs`:
- Around line 4058-4118: The motion's turn_radius is applied but not returned:
update the invoke() response and the get_agent_cursor_state getter to include
the turn_radius field; specifically, add updated.turn_radius into the formatted
summary (similar to glideDurationMs/dwellAfterClickMs) and include
"turn_radius": updated.turn_radius in the structured JSON returned by
ToolResult::text(...).with_structured(...) and likewise add turn_radius to the
payload produced by get_agent_cursor_state so callers can verify the applied
value.

---

Minor comments:
In `@demo/jukebox/README.md`:
- Around line 15-23: Add a language label to the unlabeled fenced code blocks to
satisfy markdownlint MD040: replace the opening ``` fences for the ASCII-art
block that begins with
"┌───────────────────────────────────────────────────────────────┐" and the
other unlabeled fenced block further down with a labeled fence such as ```text
(and keep the corresponding closing ``` unchanged) so the rendered output is
identical but markdownlint no longer flags the blocks.

In `@demo/multi-cursor/legacy-app/src/main.rs`:
- Around line 232-243: The WM_CHAR handler currently truncates characters with
`wparam.0 as u8 as char`, losing codepoints above 0xFF; change how `ch` is
derived so the full Unicode code unit is preserved (e.g., convert `wparam.0` to
u32 and use char::from_u32 or equivalent to get an Option<char>`), then handle
the Option (ignore invalid codepoints) and keep the existing logic that treats
backspace, newline, and non-control characters when updating `st.text` in the
STATE/Mode::Gdi branch.

In `@demo/multi-cursor/orchestrator/src/main.rs`:
- Around line 305-311: The fallback branch after DwmGetWindowAttribute currently
returns screen-space coords (pt.x, pt.y); instead convert those screen coords to
window-local coords like the success path does by querying the window rectangle
and subtracting its origin. Replace the early return with a GetWindowRect (or
equivalent) call for hwnd, store into a RECT (e.g., rect), and return Some((pt.x
- rect.left - 1, pt.y - rect.top - 1)); if GetWindowRect itself fails you can
fall back to returning the original pt but prefer using the rect subtraction to
keep coordinates consistent with the DwmGetWindowAttribute path (symbols:
DwmGetWindowAttribute, ClientToScreen, hwnd, pt, RECT).

In `@demo/multi-cursor/README.md`:
- Around line 15-28: The fenced code block containing the ASCII diagram (the
triple backticks wrapping the layout with lines like "MASTER", "Win32 GDI",
"WinForms", etc.) needs a language tag to satisfy markdownlint and enable
highlighting; update the opening fence from ``` to ```text so the block is
treated as plain text and the lint rule/syntax highlighting are applied.
- Around line 46-58: Update the Build section to call out that both dotnet demo
projects target net10.0-windows and therefore require the .NET 10 SDK to be
installed; mention the exact target "net10.0-windows" and add a short
note/example command to check/install the SDK (for example `dotnet --list-sdks`
or a link to Microsoft .NET 10 download) so users know to install .NET 10 before
running the existing `dotnet build` commands for the WinForms and WPF projects.

In `@libs/cua-driver/rust/crates/cua-driver/tests/e2e_windows_bg_input_test.rs`:
- Around line 67-81: The job() helper currently ignores the return value of
SetInformationJobObject so the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag may not
be applied; update job() to check the result of SetInformationJobObject (called
after CreateJobObjectW and creating JOBOBJECT_EXTENDED_LIMIT_INFORMATION with
BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE) and on
failure either log the error details (using the test logger) or panic with a
descriptive message including the Windows error code so test runs surface the
configuration failure and avoid orphaned child processes.

In `@libs/cua-driver/rust/crates/platform-windows/src/input/dispatch.rs`:
- Around line 94-105: Update the shared "dispatch" description string in
dispatch.rs to reflect current behavior: remove the absolute claim that
"background" never swaps foreground (acknowledge that inject_key_cloaked and
inject_text_cloaked may temporarily foreground targets), remove the statement
that right/middle clicks are unavailable (inject_click_screen supports
right-click), and clarify the fallback behavior and error conditions (e.g.,
background_unavailable for inputs that cannot be expressed). Edit the
description text associated with the dispatch schema so it mentions these
exceptions and accurately describes modes 'background', 'foreground', and
'auto'; locate and update the string literal named or used for the shared
dispatch description in this file.

In `@libs/cua-driver/rust/docs/windows-background-input-re-plan.md`:
- Around line 18-27: The docs are stale: update the implementation notes so they
match the landed code by replacing references to touch-based left-clicks
(InjectTouchInput) with the current synthetic-pen tap approach
(InjectSyntheticPointerInput / PT_PEN used for clicks), and change the
description of ZorderGuard from a cloak that hides residual z movement to the
actual behavior where it only toggles topmost z-order. Also note that
dispatch:"background" still transparently selects the appropriate delivery
method (UIA Invoke, PostMessage, or synthetic pen injection) so callers don't
pass a dispatch knob.

---

Nitpick comments:
In `@libs/cua-driver/rust/crates/platform-windows/examples/zdrop_probe.rs`:
- Around line 213-226: run_trial currently takes x and y but then immediately
discards them with let _ = (x, y); which prevents any closure from capturing
those parameters — remove that dead binding so the closure passed as actuate can
capture and use x and y, or if no caller/closure actually needs them, remove x
and y from run_trial's signature and update all callers accordingly; locate the
function run_trial and either delete the let _ = (x, y); line or remove the
parameters and fix call sites.

In `@libs/cua-driver/rust/crates/platform-windows/src/overlay.rs`:
- Around line 604-608: The local counter `drained` is unused—remove the dead
variable and its increments in the CMD_RX_WIN try_lock / try_recv loop (the
`drained` declaration, the `drained += 1;` line and the later `let _ = drained;`
discard) so the loop simply drains messages without tracking, or alternatively,
if you prefer to keep the metric, wire `drained` into the FPS/diagnostic output
where FPS is computed; locate the try_recv loop guarded by CMD_RX_WIN and either
delete the `drained` handling or replace the discard with emitting the value
into the existing FPS diagnostic path.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d7724d9-9f5d-4074-9b7b-3057f86d2d8f

📥 Commits

Reviewing files that changed from the base of the PR and between 02fdd98 and eb493fd.

⛔ Files ignored due to path filters (2)
  • demo/jukebox/Cargo.lock is excluded by !**/*.lock
  • demo/multi-cursor/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • .gitignore
  • demo/jukebox/Cargo.toml
  • demo/jukebox/README.md
  • demo/jukebox/app/Cargo.toml
  • demo/jukebox/app/src/main.rs
  • demo/jukebox/orchestrator/Cargo.toml
  • demo/jukebox/orchestrator/src/main.rs
  • demo/multi-cursor/.gitignore
  • demo/multi-cursor/Cargo.toml
  • demo/multi-cursor/README.md
  • demo/multi-cursor/dotnet/winforms/Program.cs
  • demo/multi-cursor/dotnet/winforms/winforms.csproj
  • demo/multi-cursor/dotnet/wpf/Program.cs
  • demo/multi-cursor/dotnet/wpf/wpf.csproj
  • demo/multi-cursor/electron/index.html
  • demo/multi-cursor/electron/main.js
  • demo/multi-cursor/electron/package.json
  • demo/multi-cursor/legacy-app/Cargo.toml
  • demo/multi-cursor/legacy-app/src/main.rs
  • demo/multi-cursor/orchestrator/Cargo.toml
  • demo/multi-cursor/orchestrator/src/main.rs
  • libs/cua-driver/rust/crates/cua-driver/Cargo.toml
  • libs/cua-driver/rust/crates/cua-driver/tests/e2e_windows_bg_input_test.rs
  • libs/cua-driver/rust/crates/cursor-overlay/src/motion.rs
  • libs/cua-driver/rust/crates/cursor-overlay/src/render_state.rs
  • libs/cua-driver/rust/crates/platform-macos/src/tools/cursor_tools.rs
  • libs/cua-driver/rust/crates/platform-windows/Cargo.toml
  • libs/cua-driver/rust/crates/platform-windows/examples/zdrop_probe.rs
  • libs/cua-driver/rust/crates/platform-windows/src/input/dispatch.rs
  • libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs
  • libs/cua-driver/rust/crates/platform-windows/src/input/mod.rs
  • libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs
  • libs/cua-driver/rust/crates/platform-windows/src/overlay.rs
  • libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
  • libs/cua-driver/rust/docs/windows-background-input-re-plan.md

Comment on lines +354 to +356
// Esc quits (borderless windows have no close button); closing the
// transport EOFs its stdout, which tears the whole demo down.
WM_KEYDOWN if wp.0 == 0x1B => { PostQuitMessage(0); LRESULT(0) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict Esc-to-quit to the controller window.

Line 356 currently exits any jukebox-app process, not just the transport. If an instrument tile ever gets focus, pressing Esc drops that part out of the performance while the orchestrator keeps running.

Proposed fix
-            WM_KEYDOWN if wp.0 == 0x1B => { PostQuitMessage(0); LRESULT(0) }
+            WM_KEYDOWN if wp.0 == 0x1B => {
+                if STATE.with(|s| s.borrow().as_ref().map(|st| st.mode)) == Some(Mode::Controller) {
+                    PostQuitMessage(0);
+                }
+                LRESULT(0)
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Esc quits (borderless windows have no close button); closing the
// transport EOFs its stdout, which tears the whole demo down.
WM_KEYDOWN if wp.0 == 0x1B => { PostQuitMessage(0); LRESULT(0) }
// Esc quits (borderless windows have no close button); closing the
// transport EOFs its stdout, which tears the whole demo down.
WM_KEYDOWN if wp.0 == 0x1B => {
if STATE.with(|s| s.borrow().as_ref().map(|st| st.mode)) == Some(Mode::Controller) {
PostQuitMessage(0);
}
LRESULT(0)
}
🤖 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 `@demo/jukebox/app/src/main.rs` around lines 354 - 356, The WM_KEYDOWN branch
that calls PostQuitMessage when wp.0 == 0x1B should only run for the
controller's main window; change the condition to also check the message's hwnd
against the stored controller window handle (e.g., compare the wndproc's hwnd
param to your controller window variable like controller_hwnd or
MAIN_WINDOW_HANDLE) before calling PostQuitMessage. Locate the WM_KEYDOWN arm
containing wp.0 == 0x1B in main.rs and modify it to require hwnd ==
controller_hwnd (or an equivalent identity check) so Esc quits only the
controller window.

Comment on lines +217 to +224
for img in ["cua-driver.exe", "jukebox-app.exe"] {
let _ = Command::new("taskkill").args(["/F", "/IM", img])
.stdout(Stdio::null()).stderr(Stdio::null()).status();
}
// Give the killed daemon time to release its named pipe before we start a
// fresh one — too short a wait races the pipe handoff and can wedge the new
// daemon's first connections (observed as a one-off ~10s-late performance).
thread::sleep(Duration::from_millis(800));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid killing every matching process on the machine.

Line 217 reaps by image name, so starting this demo will forcibly terminate any other cua-driver.exe or jukebox-app.exe the user already has running. That's a pretty rough side effect for a demo launcher and can also break concurrent validation runs.

Safer options are to scope cleanup to windows/PIDs created by this demo, or to tag spawned children with a unique identifier and reap only those.

🤖 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 `@demo/jukebox/orchestrator/src/main.rs` around lines 217 - 224, The cleanup
loop currently calls Command::new("taskkill").args(["/F", "/IM", img]) which
indiscriminately kills every process by image name; instead record and target
only the PIDs/process handles you spawned for this run. Change the launcher code
that spawns cua-driver.exe and jukebox-app.exe to store their Child objects or
PIDs (e.g., a Vec of Child or pid_t) and replace the taskkill-by-name loop with
termination logic that iterates only over those stored children (use taskkill
/PID or Child::kill on Windows), or create and use a Job Object / unique tag so
you can reap processes created by this process only. Ensure the code that
currently uses Command::new("taskkill") and the for loop over
["cua-driver.exe","jukebox-app.exe"] is updated to reference the stored
PIDs/Child handles instead of image names.

Comment thread demo/jukebox/orchestrator/src/main.rs Outdated
Comment on lines +12 to +13
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In an SDK-style WinForms project targeting net10.0-windows, does setting <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode> require calling ApplicationConfiguration.Initialize() or Application.SetHighDpiMode(...) from Main for the DPI mode to take effect?

💡 Result:

Yes, calling ApplicationConfiguration.Initialize in your Main method is required for the PerMonitorV2 setting in your project file to take effect [1][2][3]. In modern SDK-style WinForms projects targeting.NET 6 and later (including net10.0-windows), ApplicationConfiguration.Initialize is a source-generated method that reads your project's MSBuild properties—such as ApplicationHighDpiMode, ApplicationDefaultFont, and ApplicationVisualStyles—and automatically emits the corresponding Application.* configuration method calls at runtime [1][4][5]. Specifically, when you set PerMonitorV2, the generated ApplicationConfiguration.Initialize method internally executes the equivalent of: Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); If you do not call ApplicationConfiguration.Initialize in your Main method, these project-level settings will not be applied, and the application will default to the standard SystemAware DPI mode [1][4][2]. While you could manually call Application.SetHighDpiMode(HighDpiMode.PerMonitorV2) in your code instead of using the bootstrap API, Microsoft recommends using ApplicationConfiguration.Initialize as the standard approach to ensure all project-level configurations are applied consistently [2][3]. If you choose to use the bootstrap API, ensure it is called before Application.Run and any other UI elements are initialized [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant Program.cs portion
echo "=== Program.cs (around startup) ==="
ls -la demo/multi-cursor/dotnet/winforms/Program.cs
nl -ba demo/multi-cursor/dotnet/winforms/Program.cs | sed -n '1,120p'

echo
echo "=== winforms.csproj (look for ApplicationHighDpiMode) ==="
ls -la demo/multi-cursor/dotnet/winforms/winforms.csproj
nl -ba demo/multi-cursor/dotnet/winforms/winforms.csproj | sed -n '1,200p'

echo
echo "=== Search for ApplicationConfiguration.Initialize or SetHighDpiMode in the winforms folder ==="
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' \
  "ApplicationConfiguration\.Initialize|SetHighDpiMode|ApplicationHighDpiMode" \
  demo/multi-cursor/dotnet/winforms

Repository: trycua/cua

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Program.cs (startup) ==="
sed -n '1,140p' demo/multi-cursor/dotnet/winforms/Program.cs
echo
echo "=== winforms.csproj (DPI settings) ==="
sed -n '1,220p' demo/multi-cursor/dotnet/winforms/winforms.csproj

echo
echo "=== Search within winforms folder for bootstrap/high-DPI calls ==="
rg -n --hidden --glob '!**/bin/**' --glob '!**/obj/**' \
  "ApplicationConfiguration\.Initialize\s*\(|Application\.SetHighDpiMode\s*\(|SetHighDpiMode\s*\(|ApplicationHighDpiMode" \
  demo/multi-cursor/dotnet/winforms

Repository: trycua/cua

Length of output: 2453


Call ApplicationConfiguration.Initialize() to apply PerMonitorV2 at startup.

winforms.csproj sets <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>, but Program.Main never calls the WinForms bootstrap initializer (or Application.SetHighDpiMode(...)). This can leave the app on the default DPI mode on mixed-DPI systems, skewing WinForms scaling and breaking the replay demo’s coordinate assumptions.

Proposed fix
-        Application.EnableVisualStyles();
-        Application.SetCompatibleTextRenderingDefault(false);
+        ApplicationConfiguration.Initialize();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
ApplicationConfiguration.Initialize();
🤖 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 `@demo/multi-cursor/dotnet/winforms/Program.cs` around lines 12 - 13,
Program.Main currently calls Application.EnableVisualStyles() and
Application.SetCompatibleTextRenderingDefault(false) but does not initialize the
WinForms bootstrap; call ApplicationConfiguration.Initialize() at the start of
Program.Main (before EnableVisualStyles/SetCompatibleTextRenderingDefault) so
the project-wide <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
is applied; update Program.Main to invoke ApplicationConfiguration.Initialize()
(or alternatively call Application.SetHighDpiMode(...) using the same
PerMonitorV2 mode) to ensure PerMonitorV2 DPI behavior for the app.

Comment on lines +190 to +203
let ok = run_call(&cua, "click", &format!(
r#"{{"pid":{pid},"window_id":{hwnd_addr},"x":{x},"y":{y},"session":"{session}"}}"#));
eprintln!("[drive {session}] click ({x},{y}) -> {}", if ok { "ok" } else { "FAIL" });
}
}
Action::Type { text } => {
// Focus the field first (so the chars land), then type.
if let Some((x, y)) = client_rel_to_local_px(hwnd, 0.5, 0.28) {
let _ = run_call(&cua, "click", &format!(
r#"{{"pid":{pid},"window_id":{hwnd_addr},"x":{x},"y":{y},"session":"{session}"}}"#));
}
let esc = json_escape(&text);
let ok = run_call(&cua, "type_text", &format!(
r#"{{"pid":{pid},"window_id":{hwnd_addr},"text":"{esc}","session":"{session}"}}"#));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== click/type_text schemas =="
rg -n -C3 'stub_tool!\((click_m|type_text_m)' libs/cua-driver/rust/crates/platform-windows/src/tools/stubs.rs

echo
echo "== orchestrator payloads carrying session =="
rg -n -C2 '"session"|session"\}\)' demo/multi-cursor/orchestrator/src/main.rs

Repository: trycua/cua

Length of output: 3136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="demo/multi-cursor/orchestrator/src/main.rs"

echo "== window discovery: look for title/substring matching =="
rg -n --hidden --no-ignore-vcs "title|substring|contains|find.*window|EnumWindows|GetWindowText|GetWindowTextW|match" "$file"

echo
echo "== locate client_rel_to_local_px =="
rg -n "fn client_rel_to_local_px" "$file"

echo
echo "== view client_rel_to_local_px body (around definition) =="
line=$(rg -n "fn client_rel_to_local_px" "$file" | head -n1 | cut -d: -f1)
start=$((line-40))
end=$((line+120))
sed -n "${start},${end}p" "$file" | cat -n

echo
echo "== look for DwmGetWindowAttribute usage and fallback behavior =="
rg -n "DwmGetWindowAttribute|dwm|DWM" "$file"

echo
echo "== view around DwmGetWindowAttribute usage =="
rg -n "DwmGetWindowAttribute" "$file" | head -n1 | awk -F: '{print "line=" $1}'
dwm_line=$(rg -n "DwmGetWindowAttribute" "$file" | head -n1 | cut -d: -f1)
start=$((dwm_line-60))
end=$((dwm_line+80))
sed -n "${start},${end}p" "$file" | cat -n

Repository: trycua/cua

Length of output: 8100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="demo/multi-cursor/orchestrator/src/main.rs"

echo "== view orchestrator spawn/window tracking around corners + hwnd/pid assignment =="
sed -n '110,190p' "$file" | cat -n

echo
echo "== find bitmap_to_screen (to confirm coordinate-space subtract behavior) =="
rg -n "bitmap_to_screen" "$file"
if rg -n "bitmap_to_screen" "$file" >/dev/null; then
  line=$(rg -n "bitmap_to_screen" "$file" | head -n1 | cut -d: -f1)
  start=$((line-40)); end=$((line+120))
  sed -n "${start},${end}p" "$file" | cat -n
fi

echo
echo "== confirm any other click/type_text payload sites =="
rg -n '"click"|' demo/multi-cursor/orchestrator/src/main.rs
rg -n '"type_text"' demo/multi-cursor/orchestrator/src/main.rs

Repository: trycua/cua

Length of output: 25548


Fix click/type_text JSON payloads and the replay window/coordinate logic.

  • demo/multi-cursor/orchestrator/src/main.rs sends "session":"{session}" in both run_call(&cua, "click", ...) and run_call(&cua, "type_text", ...), but the corresponding tool schemas in libs/cua-driver/rust/crates/platform-windows/src/tools/stubs.rs use additionalProperties: false and do not define a session property; schema-based validation would reject these replay actions.
  • Window selection uses find_window_by_title with EnumWindows + title.contains(&needle); this can bind an unrelated visible window with a colliding caption instead of the intended spawned window.
  • client_rel_to_local_px returns (pt.x, pt.y) unchanged when DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) fails, skipping the normal subtraction of dwm.left/top (+ the 1px inset), so click coordinates will be misrouted in that failure path.
🤖 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 `@demo/multi-cursor/orchestrator/src/main.rs` around lines 190 - 203, Remove
the unsupported "session" property from the JSON payloads sent by run_call in
the click and type_text paths (e.g., the click call in the Action::Type branch
and the subsequent type_text call) so the payloads match the tool schemas; keep
pid, window_id (hwnd_addr), x/y or text and ensure json_escape is used for text.
Update window selection logic in find_window_by_title/EnumWindows to prefer the
spawned window by verifying the window's process id (GetWindowThreadProcessId)
matches the spawn pid (or use the exact HWND returned at spawn) instead of
relying solely on title.contains(&needle). Fix client_rel_to_local_px so that
when DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) fails you still compute
and subtract the window origin and the 1px inset (e.g., fall back to
GetWindowRect and subtract left/top +1) before returning (pt.x - origin.x - 1,
pt.y - origin.y - 1) to ensure coordinates map correctly.

Comment on lines +304 to +312
unsafe fn arm(target: HWND) -> Self {
let prev_fg = GetForegroundWindow();
// Raise a genuine *background* target into the topmost band so it wins
// the injection hit-test even over an active occluder — no activation.
let raised = !target.0.is_null() && target != prev_fg;
if raised {
set_topmost(target, true);
}
Self { prev_fg, target, raised }

@coderabbitai coderabbitai Bot Jun 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the target’s original topmost state.

arm() always pushes into HWND_TOPMOST, but drop() always demotes with HWND_NOTOPMOST. If the target started life as topmost, this guard permanently changes its z-order class after one injected action.

Also applies to: 317-325

🤖 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 `@libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs` around
lines 304 - 312, The guard created in arm() currently always forces HWND_TOPMOST
and drop() always sets HWND_NOTOPMOST, which loses the window’s original topmost
state; modify arm() (in the same impl that defines arm and Drop) to detect and
store the target’s original topmost status (e.g., call the existing
set_topmost/inspection helper or use GetWindowLongPtr/WS_EX_TOPMOST check) as a
new field like original_topmost on the returned Self, and then change drop() to
restore the recorded original_topmost (call set_topmost(target,
original_topmost)) instead of unconditionally using HWND_NOTOPMOST so the
window’s prior z-order class is preserved.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ZorderGuard still causes a visible z-raise on every background target.

This unconditionally promotes any non-foreground target into the topmost band, so even an unobscured background window will flash above the user’s current app for the duration of the action. That contradicts the “no z-raise” contract called out in the PR and in the surrounding docs/comments.

🤖 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 `@libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs` around
lines 304 - 312, ZorderGuard::arm currently sets set_topmost for any
non-foreground target, causing visible z-raises; change arm to only promote the
target when it is actually occluded by other windows (not merely
not-foreground). Replace the raised condition (!target.0.is_null() && target !=
prev_fg) with a call to a helper like is_obscured(target, prev_fg) that returns
true only if the target's client/target area is covered (e.g., check
IsWindowVisible, GetWindowRect and whether WindowFromPoint(s) over the target
return a different window, or test several sample points including center and
edges), then call set_topmost(target, true) only when that helper reports
occlusion and set raised accordingly; ensure prev_fg, target, and raised reflect
the actual action taken so no unnecessary z-raise occurs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

⏭️ Deferred (added TODO) — preserving original topmost state + occlusion detection are complex fixes requiring WindowFromPoint sampling and GetWindowLongPtr checks. Current behavior works correctly for the common case (clicking background windows). Added TODO comment for future enhancement. Non-blocking for PR merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ddupont808 Understood, acknowledged. The common-case behavior (non-topmost background windows) is unaffected by both issues, so this is a reasonable deferral.

Would you like me to open a GitHub issue to track the two follow-up items so they don't get lost?

  1. ZorderGuard: preserve original topmost state — record WS_EX_TOPMOST on arm() and restore it (instead of unconditionally HWND_NOTOPMOST) on drop().
  2. ZorderGuard: skip promotion for unoccluded targets — add an occlusion check (e.g., WindowFromPoint sampling over the target rect) so the z-raise only happens when the target is actually covered, avoiding the visible flash for unobstructed background windows.

Comment thread libs/cua-driver/rust/crates/platform-windows/src/input/inject.rs
Comment thread libs/cua-driver/rust/crates/platform-windows/src/input/mouse.rs
Comment on lines +1850 to +1862
// For every non-foreground click, mark the target window
// non-activatable (WS_EX_NOACTIVATE) for the duration so a target that
// self-activates in its UIA-Invoke / click handler (WPF
// `UIElement.Focus()`, XAML, Tauri/WebView2) CANNOT steal the user's
// foreground — the window still receives the click. Held for the whole
// invoke; a no-op for dispatch:"foreground" (which wants the swap) and
// when no window_id was given.
let _noact = match (dispatch != DispatchMode::Foreground, hwnd_opt) {
(true, Some(h)) => Some(crate::input::NoActivateGuard::arm(
windows::Win32::Foundation::HWND(h as *mut _),
)),
_ => None,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Arm the no-activate guard from the resolved HWND.

Line 1857 only guards the explicit window_id case. For pid-only pixel clicks, Lines 1872-1881 resolve the target window later, so the background path still runs without NoActivateGuard and the target can steal foreground from inside its click handler. Please move guard construction until after hwnd is resolved and arm it from that value instead.

🤖 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 `@libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs` around lines
1850 - 1862, The NoActivateGuard is currently armed only when an explicit
window_id (hwnd_opt) is present, leaving pid-resolved clicks unprotected; change
the logic so you defer creating _noact until after the actual target HWND is
resolved and then arm crate::input::NoActivateGuard::arm with that resolved HWND
(converted to windows::Win32::Foundation::HWND). Specifically, remove or
relocate the current match that sets _noact around hwnd_opt and instead
construct _noact after the code path that resolves hwnd (the later resolution
logic around the places that reference hwnd or resolve by pid), guarding both
explicit hwnd_opt and pid-resolved HWNDs while preserving the
DispatchMode::Foreground no-op behavior.

Comment thread libs/cua-driver/rust/crates/platform-windows/src/tools/impl_.rs
ddupont808 and others added 2 commits June 2, 2026 14:28
…, NoActivateGuard gate, pre-drag MOUSEMOVE wParam

- demo/jukebox: Key active-note map by (channel, pitch) to handle polyphonic same-pitch and multi-channel collisions
- libs/cua-driver(inject): Validate target HWND with IsWindow before guards in inject_click_screen/inject_drag_screen (early bail on null/stale)
- libs/cua-driver(inject): Fix NoActivateGuard to apply WS_EX_NOACTIVATE even when prev==0 (was gated incorrectly)
- libs/cua-driver(inject): Add TODO for ZorderGuard occlusion check + original topmost preservation (deferred, non-trivial)
- libs/cua-driver(mouse): Pre-drag WM_MOUSEMOVE uses wParam=0 (no buttons down yet) instead of wparam

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…_agent_cursor_state responses

Added turn_radius to both text summary and structured JSON payloads so callers can verify the applied value.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
ddupont808 added a commit that referenced this pull request Jun 4, 2026
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ddupont808
ddupont808 merged commit 53bb84c into main Jun 5, 2026
9 of 10 checks passed
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