aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378import AppKitimport Commonimport PrivateApi
@MainActorfunc waitForAccessibilityPermission_nonCancellable() async { let options = [axTrustedCheckOptionPrompt: true] while true { let status = TrayMenuModel.shared.axPermissionStatus == .waitingWithPrompt ? AXIsProcessTrustedWithOptions(options as CFDictionary) : AXIsProcessTrusted() if status { TrayMenuModel.shared.axPermissionStatus = .granted break } if TrayMenuModel.shared.axPermissionStatus == .waitingWithPrompt { resetAccessibility() // Because macOS doesn't reset it for us when the app signature changes... } TrayMenuModel.shared.axPermissionStatus = .waiting try? await Task.sleep(for: .seconds(1)) }}
private func resetAccessibility() { _ = try? Process.run(URL(filePath: "/usr/bin/tccutil"), arguments: ["reset", "Accessibility", aeroSpaceAppId])}
protocol ReadableAttr: Sendable { associatedtype T var getter: @Sendable (AnyObject) -> T? { get } var key: String { get }}
protocol WritableAttr: ReadableAttr, Sendable { var setter: @Sendable (T) -> CFTypeRef? { get }}
// Quick reference://// // informational attributes// kAXRoleAttribute// kAXSubroleAttribute// kAXRoleDescriptionAttribute// kAXTitleAttribute// kAXDescriptionAttribute// kAXHelpAttribute//// // hierarchy or relationship attributes// kAXParentAttribute// kAXChildrenAttribute// kAXSelectedChildrenAttribute// kAXVisibleChildrenAttribute// kAXWindowAttribute// kAXTopLevelUIElementAttribute// kAXTitleUIElementAttribute// kAXServesAsTitleForUIElementsAttribute// kAXLinkedUIElementsAttribute// kAXSharedFocusElementsAttribute//// // visual state attributes// kAXEnabledAttribute// kAXFocusedAttribute// kAXPositionAttribute// kAXSizeAttribute//// // value attributes// kAXValueAttribute// kAXValueDescriptionAttribute// kAXMinValueAttribute// kAXMaxValueAttribute// kAXValueIncrementAttribute// kAXValueWrapsAttribute// kAXAllowedValuesAttribute//// // text-specific attributes// kAXSelectedTextAttribute// kAXSelectedTextRangeAttribute// kAXSelectedTextRangesAttribute// kAXVisibleCharacterRangeAttribute// kAXNumberOfCharactersAttribute// kAXSharedTextUIElementsAttribute// kAXSharedCharacterRangeAttribute//// // window, sheet, or drawer-specific attributes// kAXMainAttribute// kAXMinimizedAttribute// kAXCloseButtonAttribute// kAXZoomButtonAttribute// kAXMinimizeButtonAttribute// kAXToolbarButtonAttribute// kAXProxyAttribute// kAXGrowAreaAttribute// kAXModalAttribute// kAXDefaultButtonAttribute// kAXCancelButtonAttribute//// // menu or menu item-specific attributes// kAXMenuItemCmdCharAttribute// kAXMenuItemCmdVirtualKeyAttribute// kAXMenuItemCmdGlyphAttribute// kAXMenuItemCmdModifiersAttribute// kAXMenuItemMarkCharAttribute// kAXMenuItemPrimaryUIElementAttribute//// // application element-specific attributes// kAXMenuBarAttribute// kAXWindowsAttribute// kAXFrontmostAttribute// kAXHiddenAttribute// kAXMainWindowAttribute// kAXFocusedWindowAttribute// kAXFocusedUIElementAttribute// kAXExtrasMenuBarAttribute//// // date/time-specific attributes// kAXHourFieldAttribute// kAXMinuteFieldAttribute// kAXSecondFieldAttribute// kAXAMPMFieldAttribute// kAXDayFieldAttribute// kAXMonthFieldAttribute// kAXYearFieldAttribute//// // table, outline, or browser-specific attributes// kAXRowsAttribute// kAXVisibleRowsAttribute// kAXSelectedRowsAttribute// kAXColumnsAttribute// kAXVisibleColumnsAttribute// kAXSelectedColumnsAttribute// kAXSortDirectionAttribute// kAXColumnHeaderUIElementsAttribute// kAXIndexAttribute// kAXDisclosingAttribute// kAXDisclosedRowsAttribute// kAXDisclosedByRowAttribute//// // matte-specific attributes// kAXMatteHoleAttribute// kAXMatteContentUIElementAttribute//// // ruler-specific attributes// kAXMarkerUIElementsAttribute// kAXUnitsAttribute// kAXUnitDescriptionAttribute// kAXMarkerTypeAttribute// kAXMarkerTypeDescriptionAttribute//// // miscellaneous or role-specific attributes// kAXHorizontalScrollBarAttribute// kAXVerticalScrollBarAttribute// kAXOrientationAttribute// kAXHeaderAttribute// kAXEditedAttribute// kAXTabsAttribute// kAXOverflowButtonAttribute// kAXFilenameAttribute// kAXExpandedAttribute// kAXSelectedAttribute// kAXSplittersAttribute// kAXContentsAttribute// kAXNextContentsAttribute// kAXPreviousContentsAttribute// kAXDocumentAttribute// kAXIncrementorAttribute// kAXDecrementButtonAttribute// kAXIncrementButtonAttribute// kAXColumnTitleAttribute// kAXURLAttribute// kAXLabelUIElementsAttribute// kAXLabelValueAttribute// kAXShownMenuUIElementAttribute// kAXIsApplicationRunningAttribute// kAXFocusedApplicationAttribute// kAXElementBusyAttribute// kAXAlternateUIVisibleAttributeenum Ax { struct ReadableAttrImpl<T>: ReadableAttr { var key: String var getter: @Sendable (AnyObject) -> T? }
struct WritableAttrImpl<T>: WritableAttr { var key: String var getter: @Sendable (AnyObject) -> T? var setter: @Sendable (T) -> CFTypeRef? }
static let parentWindowRecursive = ReadableAttrImpl<AXUIElement>( key: kAXWindowAttribute, getter: { ($0 as! AXUIElement) }, ) static let titleAttr = WritableAttrImpl<String>( key: kAXTitleAttribute, getter: { $0 as? String }, setter: { $0 as CFTypeRef }, ) static let roleAttr = WritableAttrImpl<String>( key: kAXRoleAttribute, getter: { $0 as? String }, setter: { $0 as CFTypeRef }, ) static let subroleAttr = WritableAttrImpl<String>( key: kAXSubroleAttribute, getter: { $0 as? String }, setter: { $0 as CFTypeRef }, ) static let identifierAttr = ReadableAttrImpl<String>( key: kAXIdentifierAttribute, getter: { $0 as? String }, ) // static let modalAttr = ReadableAttrImpl<Bool>( // key: kAXModalAttribute, // getter: { $0 as? Bool }, // ) static let enabledAttr = ReadableAttrImpl<Bool>( key: kAXEnabledAttribute, getter: { $0 as? Bool }, ) static let enhancedUserInterfaceAttr = WritableAttrImpl<Bool>( key: "AXEnhancedUserInterface", getter: { $0 as? Bool }, setter: { $0 as CFTypeRef }, ) static let minimizedAttr = WritableAttrImpl<Bool>( key: kAXMinimizedAttribute, getter: { $0 as? Bool }, setter: { $0 as CFTypeRef }, ) //static let minimizedAttr = ReadableAttrImpl<Bool>( // key: kAXMinimizedAttribute, // getter: { $0 as? Bool } //) static let isFullscreenAttr = WritableAttrImpl<Bool>( key: "AXFullScreen", getter: { $0 as? Bool }, setter: { $0 as CFTypeRef }, ) static let isFocused = ReadableAttrImpl<Bool>( key: kAXFocusedAttribute, getter: { $0 as? Bool }, ) static let isMainAttr = WritableAttrImpl<Bool>( key: kAXMainAttribute, getter: { $0 as? Bool }, setter: { $0 as CFTypeRef }, ) static let sizeAttr = WritableAttrImpl<CGSize>( key: kAXSizeAttribute, getter: { var raw: CGSize = .zero check(unsafe AXValueGetValue($0 as! AXValue, .cgSize, &raw)) return raw }, setter: { var size = $0 return unsafe AXValueCreate(.cgSize, &size) as CFTypeRef }, ) static let topLeftCornerAttr = WritableAttrImpl<CGPoint>( key: kAXPositionAttribute, getter: { var raw: CGPoint = .zero check(unsafe AXValueGetValue($0 as! AXValue, .cgPoint, &raw)) return raw }, setter: { var size = $0 return unsafe AXValueCreate(.cgPoint, &size) as CFTypeRef }, ) /// Returns windows visible on all monitors /// If some windows are located on not active macOS Spaces then they won't be returned static let windowsAttr = ReadableAttrImpl<[WindowIdAndAxUiElement]>( key: kAXWindowsAttribute, getter: { ($0 as? NSArray)?.compactMap(windowOrNil).map { ($0.windowId, $0.ax.cast) } ?? [] }, ) static let focusedWindowAttr = ReadableAttrImpl<WindowIdAndAxUiElementMock>( key: kAXFocusedWindowAttribute, getter: windowOrNil, ) //static let mainWindowAttr = ReadableAttrImpl<AXUIElement>( // key: kAXMainWindowAttribute, // getter: tryGetWindow //) static let closeButtonAttr = ReadableAttrImpl<any AxUiElementMock>( key: kAXCloseButtonAttribute, getter: castToAxUiElementMock, ) // Note! fullscreen is not the same as "zoom" (green plus) static let fullscreenButtonAttr = ReadableAttrImpl<any AxUiElementMock>( key: kAXFullScreenButtonAttribute, getter: castToAxUiElementMock, ) // green plus static let zoomButtonAttr = ReadableAttrImpl<any AxUiElementMock>( key: kAXZoomButtonAttribute, getter: castToAxUiElementMock, ) static let minimizeButtonAttr = ReadableAttrImpl<any AxUiElementMock>( key: kAXMinimizeButtonAttribute, getter: castToAxUiElementMock, ) //static let growAreaAttr = ReadableAttrImpl<AXUIElement>( // key: kAXGrowAreaAttribute, // getter: { ($0 as! AXUIElement) } //)}
let kAXAeroSynthetic = "Aero.synthetic"
private func castToAxUiElementMock(_ a: AnyObject) -> AxUiElementMock { if isUnitTest { if let str = a as? String, let commaIndex = str.firstIndex(of: ",") { let windowId = UInt32.init(String(str.prefix(upTo: commaIndex)).removePrefix("AXUIElement(AxWindowId=")) if let windowId { return castToAxUiElementMock([ "Aero.axWindowId": Json.int(windowId), kAXAeroSynthetic: Json.bool(true), ] as AnyObject) } } if let dict = a as? [String: Json] { // Convert from _SwiftDeferredNSDictionary<String, Json> return dict as? AxUiElementMock ?? dieT("Cannot cast \(type(of: a)) to AxUiElementMock") } die("Can't convert \(a) to AxUiElementMock") } return a as! AXUIElement}
typealias WindowIdAndAxUiElement = (windowId: UInt32, ax: AXUIElement)typealias WindowIdAndAxUiElementMock = (windowId: UInt32, ax: AxUiElementMock)
private func windowOrNil(_ any: Any?) -> WindowIdAndAxUiElementMock? { guard let any else { return nil } let potentialWindow = castToAxUiElementMock(any as AnyObject) // Filter out non-window objects (e.g. Finder's desktop) return switch potentialWindow.containingWindowId() { case let windowId?: (windowId, potentialWindow) case nil: nil }}
extension AXUIElement: AxUiElementMock { func get<Attr: ReadableAttr>(_ attr: Attr) -> Attr.T? { let state = signposter.beginInterval(#function, "attr: \(attr.key) axTaskLocalAppThreadToken: \(axTaskLocalAppThreadToken?.idForDebug)") defer { signposter.endInterval(#function, state) } var raw: AnyObject? return unsafe AXUIElementCopyAttributeValue(self, attr.key as CFString, &raw) == .success ? raw.flatMap(attr.getter) : nil }
@discardableResult func set<Attr: WritableAttr>(_ attr: Attr, _ value: Attr.T) -> Bool { if serverArgs.isReadOnly { return false } let state = signposter.beginInterval(#function, "attr: \(attr.key) axTaskLocalAppThreadToken: \(axTaskLocalAppThreadToken?.idForDebug)") defer { signposter.endInterval(#function, state) } guard let value = attr.setter(value) else { return false } return AXUIElementSetAttributeValue(self, attr.key as CFString, value) == .success }
func containingWindowId() -> CGWindowID? { let state = signposter.beginInterval(#function, "axTaskLocalAppThreadToken: \(axTaskLocalAppThreadToken?.idForDebug)") defer { signposter.endInterval(#function, state) } var cgWindowId = CGWindowID() return unsafe _AXUIElementGetWindow(self, &cgWindowId) == .success && cgWindowId != kCGNullWindowID ? cgWindowId : nil }}
extension AXObserver { static func new(_ pid: pid_t, _ handler: AXObserverCallback) -> AXObserver? { var observer: AXObserver? = nil return unsafe AXObserverCreate(pid, handler, &observer) == .success ? observer : nil }}