Skip to content

Commit c5309b7

Browse files
committed
fix(desktop): refuse input when the receiving window cannot be confirmed
Clicks and keys go only to the observed window, or to one of the app's dialogs or floating panels above it. A missing window attribute is allowed only for menus, and an unreadable subrole is refused rather than treated as auxiliary.
1 parent 0a0faaa commit c5309b7

1 file changed

Lines changed: 15 additions & 7 deletions

File tree

‎apps/desktop/native/computer-use/ComputerUse.swift‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -409,15 +409,25 @@ func requireForegroundTarget(pid: pid_t, windowID: String, point: CGPoint, frame
409409
guard let hit = elementAtPoint(point) else { throw ComputerError("window_occluded", "Cannot confirm which window receives this point; observe again before retrying.") }
410410
var owner: pid_t = 0
411411
guard AXUIElementGetPid(hit, &owner) == .success, owner == pid else { throw ComputerError("window_occluded", "The target point is covered by another app's window; bring the intended window forward and observe again.") }
412-
// The app's own sheets, popovers, panels and menus may sit above the observed window; another
413-
// of its document windows may not.
412+
// The point must belong to the observed window (its sheets and popovers report it as their window),
413+
// to one of the app's dialogs or floating panels above it, or to an open menu, which has no window.
414+
// Anything unconfirmed is refused.
414415
if let rawWindow = attribute(hit, kAXWindowAttribute), CFGetTypeID(rawWindow) == AXUIElementGetTypeID() {
415-
let window = unsafeBitCast(rawWindow, to: AXUIElement.self)
416-
guard elementRect(window) == frame || attribute(window, kAXSubroleAttribute) as? String != kAXStandardWindowSubrole else {
416+
guard acceptsInput(unsafeBitCast(rawWindow, to: AXUIElement.self), observed: [frame]) else {
417417
throw ComputerError("window_occluded", "Another of this app's windows covers the target point; observe that window and target it instead.")
418418
}
419+
} else if ![kAXMenuRole, kAXMenuItemRole, kAXMenuBarRole, kAXMenuBarItemRole].contains(attribute(hit, kAXRoleAttribute) as? String ?? "") {
420+
throw ComputerError("window_occluded", "Cannot confirm which window receives this point; observe again before retrying.")
419421
}
420422
}
423+
/// Whether input aimed at an observed window may go to `window`: the observed window itself, or one of
424+
/// the app's dialogs or floating panels above it. Another document window, or one whose kind cannot be
425+
/// read, is refused.
426+
func acceptsInput(_ window: AXUIElement, observed: [CGRect]) -> Bool {
427+
if let frame = elementRect(window), observed.contains(frame) { return true }
428+
let auxiliary: Set<String> = [kAXDialogSubrole, kAXSystemDialogSubrole, kAXFloatingWindowSubrole, kAXSystemFloatingWindowSubrole]
429+
return (attribute(window, kAXSubroleAttribute) as? String).map(auxiliary.contains) ?? false
430+
}
421431
/// The element that receives a click at a global point. Notification Center and the Dock keep
422432
/// full-screen click-through windows above every app, so the topmost window in the window list is not
423433
/// the click target; Accessibility hit-testing follows the window server's routing. Without an answer
@@ -702,9 +712,7 @@ final class Driver {
702712
// but never another document window the snapshot did not observe.
703713
guard let rawWindow = attribute(root, kAXFocusedWindowAttribute), CFGetTypeID(rawWindow) == AXUIElementGetTypeID() else { throw ComputerError("focus_failed", "The app has no focused window to receive keys; click a window or call activate_app, then observe again.") }
704714
let focusedWindow = unsafeBitCast(rawWindow, to: AXUIElement.self)
705-
let focusedFrame = elementRect(focusedWindow)
706-
let observed = frame.map { focusedFrame == $0 } ?? snapshot.windowFrames.values.contains { focusedFrame == $0 }
707-
guard observed || attribute(focusedWindow, kAXSubroleAttribute) as? String != kAXStandardWindowSubrole else { throw ComputerError("focus_failed", "Another document window has keyboard focus; observe that window and target it instead.") }
715+
guard acceptsInput(focusedWindow, observed: frame.map { [$0] } ?? Array(snapshot.windowFrames.values)) else { throw ComputerError("focus_failed", "Another document window has keyboard focus; observe that window and target it instead.") }
708716
if let focused = attribute(root, kAXFocusedUIElementAttribute), CFGetTypeID(focused) == AXUIElementGetTypeID() { try requireNonSecure(unsafeBitCast(focused, to: AXUIElement.self)) }
709717
let (code, flags) = try parseKey(required(p.key, "key"))
710718
var selectedAll = false

0 commit comments

Comments
 (0)