Files
Kelvin Amoaba 62b22aeab0 fix(computer-use): read minimized, hidden and other-Space macOS windows without --restore-window (#42182)
* fix(computer-use): observe minimized, hidden and other-Space windows on macOS without a restore

macOS only enumerated on-screen windows and dropped minimized AX windows,
so an app whose window was minimized, hidden or on another Space (any
full-screen app) could not be observed, and agents fell back to
--restore-window, which activated every window of the app and switched
the user's Space.

Enumerate every window, keep the off-screen ones accessibility can still
read (other-Space windows by remote token, closed windows dropped by
their Space membership), and report their real minimized and off-screen
state. Background pointer and key input on such a window refuses before
sending anything. --restore-window now raises only the target window,
once per request, and delivery is measured from whether the target
became frontmost.

* fix(computer-use): pin the restored window and prefer visible windows by default

* fix(computer-use): keep window-index cache keys and stale-element checks across a restore

* fix(computer-use): restore only this app's resolved window and refuse a closed target before raising

* docs(computer-use): say macOS reads minimized, hidden and other-Space windows
2026-11-14 00:58:33 +00:00

41 lines
1.8 KiB
Swift

import CoreGraphics
import Darwin
import Foundation
/// Which windows belong to a Space. Minimized, hidden and other-Space windows do; a closed window an app keeps around does not.
public enum WindowSpaces {
/// Nil when SkyLight's lookup is missing, so closed windows can't be told apart from other-Space ones.
public static func windowsInAnySpace(_ windowIDs: [CGWindowID]) -> Set<CGWindowID>? {
guard let symbols else { return nil }
let connection = symbols.mainConnection()
return Set(windowIDs.filter { windowID in
let spaces = symbols.copySpacesForWindows(connection, allSpacesMask, [NSNumber(value: windowID)] as CFArray)?
.takeRetainedValue() as? [NSNumber]
return spaces?.isEmpty == false
})
}
private static let allSpacesMask: Int32 = 0x7
private typealias MainConnectionID = @convention(c) () -> Int32
private typealias CopySpacesForWindows = @convention(c) (Int32, Int32, CFArray) -> Unmanaged<CFArray>?
private struct Symbols {
let mainConnection: MainConnectionID
let copySpacesForWindows: CopySpacesForWindows
}
private static let symbols: Symbols? = {
let skyLight = dlopen("/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight", RTLD_LAZY)
guard let main = dlsym(skyLight, "SLSMainConnectionID"),
let copy = dlsym(skyLight, "SLSCopySpacesForWindows")
else { return nil }
// SAFETY: SkyLight exports CGSConnectionID SLSMainConnectionID(void) and
// CFArrayRef SLSCopySpacesForWindows(CGSConnectionID, CGSSpaceMask, CFArrayRef), returning a +1 array (as yabai uses them).
return Symbols(
mainConnection: unsafeBitCast(main, to: MainConnectionID.self),
copySpacesForWindows: unsafeBitCast(copy, to: CopySpacesForWindows.self)
)
}()
}