From d0371caaec30fb8e49ede8bc4df371de062cfd1e Mon Sep 17 00:00:00 2001 From: Lahfir Date: Wed, 10 Jun 2026 03:54:10 -0700 Subject: [PATCH] feat: focus the target window before ref-addressed physical input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref-action physical fallbacks (click/scroll/type) already brought the target app frontmost before synthesizing CGEvents. The raw-input commands (drag, hover, mouse-*) resolving a point from a ref did not — the resolver had the pid but discarded it, so the adapter saw only coordinates and synthetic events could land on whatever window happened to be frontmost. Add a best-effort focus_app(pid) to PlatformAdapter (macOS uses ensure_app_focused; other adapters default to not_supported). The point resolver now focuses the ref's app before returning, so every physical interaction that targets a known element raises its window first. Coordinate (--xy) input is unchanged: the caller owns the target there. --- crates/core/src/adapter.rs | 8 ++++++++ crates/core/src/commands/helpers.rs | 4 ++-- crates/macos/src/adapter.rs | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/core/src/adapter.rs b/crates/core/src/adapter.rs index 952633a..aaa2c79 100644 --- a/crates/core/src/adapter.rs +++ b/crates/core/src/adapter.rs @@ -224,6 +224,14 @@ pub trait PlatformAdapter: Send + Sync { Err(AdapterError::not_supported("focus_window")) } + /// Brings the application owning `pid` to the foreground. Best-effort guard + /// invoked before physical (cursor/keyboard) input that targets a known + /// element, so synthetic events land on the intended window rather than + /// whatever happens to be frontmost. + fn focus_app(&self, _pid: i32) -> Result<(), AdapterError> { + Err(AdapterError::not_supported("focus_app")) + } + fn launch_app(&self, _id: &str, _timeout_ms: u64) -> Result { Err(AdapterError::not_supported("launch_app")) } diff --git a/crates/core/src/commands/helpers.rs b/crates/core/src/commands/helpers.rs index 226e112..3d3e6c8 100644 --- a/crates/core/src/commands/helpers.rs +++ b/crates/core/src/commands/helpers.rs @@ -163,11 +163,11 @@ pub(crate) fn resolve_point_from_ref_or_xy_with_context( context: &CommandContext, ) -> Result { if let Some(ref_id) = args.ref_id { - let (_entry, handle) = - resolve_ref_with_context(ref_id, args.snapshot_id, adapter, context)?; + let (entry, handle) = resolve_ref_with_context(ref_id, args.snapshot_id, adapter, context)?; let bounds = adapter .get_element_bounds(handle.handle())? .ok_or_else(|| AppError::invalid_input(format!("Element {ref_id} has no bounds")))?; + let _ = adapter.focus_app(entry.pid); return Ok(Point { x: bounds.x + bounds.width / 2.0, y: bounds.y + bounds.height / 2.0, diff --git a/crates/macos/src/adapter.rs b/crates/macos/src/adapter.rs index 975d874..f7e1716 100644 --- a/crates/macos/src/adapter.rs +++ b/crates/macos/src/adapter.rs @@ -119,6 +119,10 @@ impl PlatformAdapter for MacOSAdapter { crate::system::app_ops::focus_window_impl(win) } + fn focus_app(&self, pid: i32) -> Result<(), AdapterError> { + crate::system::app_ops::ensure_app_focused(pid) + } + fn launch_app(&self, id: &str, timeout_ms: u64) -> Result { crate::system::app_ops::launch_app_impl(id, timeout_ms) }