From a5bb6ae5a1d8806ce28d94c465f0d68966aad29a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 23:56:56 +0000 Subject: [PATCH] feat(linux): use AT-SPI Component.GrabFocus for GTK4 focus-free writes GTK4 gates EditableText on widget focus, unlike Qt6 which exposes it regardless of focus state. When a GTK4 window is in the background, the AT-SPI tree contains entry/text widgets (so reads work) but EditableText is unavailable, blocking focus-free writes. Call Component.GrabFocus on the target widget before accessing EditableText. This gives the widget internal keyboard focus without activating its window, allowing GTK4 to expose EditableText on the focused widget. The approach is: 1. Find target editable widget (same priority as before) 2. If it has Component interface, call GrabFocus on it 3. Proceed to call EditableText.InsertText as usual Benefits: - No window activation: GrabFocus works at widget level, not window level - Toolkit-agnostic: Component.GrabFocus is standard AT-SPI - Non-breaking: if GrabFocus fails/unavailable, still try EditableText (Qt6+) - Diagnostic logging shows GrabFocus success/failure for debugging This should allow the gtk4 background-GUI test to pass with true focus-free writes: the control terminal stays active throughout, the GTK4 entry gains internal focus via GrabFocus, and EditableText.InsertText succeeds. Co-Authored-By: Claude Sonnet 4.5 --- .../crates/platform-linux/src/atspi/native.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/libs/cua-driver/rust/crates/platform-linux/src/atspi/native.rs b/libs/cua-driver/rust/crates/platform-linux/src/atspi/native.rs index 9749dfa5f0..806c7993af 100644 --- a/libs/cua-driver/rust/crates/platform-linux/src/atspi/native.rs +++ b/libs/cua-driver/rust/crates/platform-linux/src/atspi/native.rs @@ -389,8 +389,8 @@ pub fn insert_text(pid: u32, text: &str) -> Result { None => return Ok(false), }; dlog!( - "insert target: role={:?} in_web_doc={} focused={}", - target.role, target.in_web_doc, target.focused + "insert target: role={:?} in_web_doc={} focused={} has_component={}", + target.role, target.in_web_doc, target.focused, target.has_component ); let proxies = target @@ -398,6 +398,26 @@ pub fn insert_text(pid: u32, text: &str) -> Result { .proxies() .await .map_err(|e| anyhow!("interface proxies unavailable: {e}"))?; + + // Try to grab focus on the widget via AT-SPI Component.GrabFocus. + // This should give the widget internal keyboard focus without activating + // the window, allowing GTK4 (and similar toolkits) to expose EditableText + // on an unfocused window's focused widget. + if target.has_component { + if let Ok(comp) = proxies.component().await { + match call(comp.grab_focus()).await { + Some(Ok(true)) => dlog!("GrabFocus succeeded on {:?}", target.role), + Some(Ok(false)) => dlog!("GrabFocus returned false on {:?}", target.role), + Some(Err(e)) => dlog!("GrabFocus failed on {:?}: {}", target.role, e), + None => dlog!("GrabFocus timed out on {:?}", target.role), + } + } else { + dlog!("Component interface unavailable despite has_component=true"); + } + } else { + dlog!("Target has no Component interface, skipping GrabFocus"); + } + let et = proxies .editable_text() .await