aerospace fork
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130import AppKitimport Common
func dumpAxRecursive(_ ax: AXUIElement, _ kind: AxKind, recursionDepth: Int = 0) -> [String: Json] { if recursionDepth > 5 { return [ "dumpAxRecursive infinite recursion": .bool(true), kAXAeroSynthetic: .bool(true), ] } let recursionDepth = recursionDepth + 1 var result: [String: Json] = [:] var ignored: [String] = [] var writable: [String] = [] var failedAxRequest: [String] = [] for key: String in ax.attrs(failedAxRequest: &failedAxRequest) { if globalIgnore.contains(key) || kindSpecificIgnore[kind]?.contains(key) == true { ignored.append(key) } else { var raw: AnyObject? if let status = .some(unsafe AXUIElementCopyAttributeValue(ax, key as CFString, &raw)), status != .success { failedAxRequest.append("get.\(key)(\(status.repr))") } result[key] = prettyValue(raw as Any?, recursionDepth: recursionDepth)
var isWritable: DarwinBoolean = false if let status = .some(unsafe AXUIElementIsAttributeSettable(ax, key as CFString, &isWritable)), status != .success { failedAxRequest.append("isWritable.\(key)(\(status.repr))") } if isWritable.boolValue { writable.append(key) } } } if !writable.isEmpty { result["Aero.AxWritable"] = .string(writable.joined(separator: ", ")) } if !failedAxRequest.isEmpty { result["Aero.AxFailed"] = .string(failedAxRequest.joined(separator: ", ")) } if !ignored.isEmpty { result["Aero.AxIgnored"] = .string(ignored.joined(separator: ", ")) } return result}
enum AxKind: Hashable { case button case window case app}
private func prettyValue(_ value: Any?, recursionDepth: Int) -> Json { if let arr = value as? [Any?] { return .array(arr.map { prettyValue($0, recursionDepth: recursionDepth) }) } if let value = value as? Int { return .int(value) } if let value = value as? UInt32 { return .int(value) } if let value = value as? Bool { return .bool(value) } if let value { let ax = value as! AXUIElement if ax.get(Ax.roleAttr) == kAXButtonRole { return .dict(dumpAxRecursive(ax, .button, recursionDepth: recursionDepth)) } if let windowId = ax.containingWindowId() { let title = ax.get(Ax.titleAttr)?.doubleQuoted ?? "nil" let role = ax.get(Ax.roleAttr)?.doubleQuoted ?? "nil" let subrole = ax.get(Ax.subroleAttr)?.doubleQuoted ?? "nil" return .string("AXUIElement(AxWindowId=\(windowId), title=\(title), role=\(role), subrole=\(subrole))") } return .string(String(describing: value)) } return .null}
extension AXUIElement { fileprivate func attrs(failedAxRequest: inout [String]) -> [String] { var rawArray: CFArray? if let status = .some(unsafe AXUIElementCopyAttributeNames(self, &rawArray)), status != .success { failedAxRequest.append("AXUIElementCopyAttributeNames(\(status.repr))") } return rawArray as? [String] ?? [] }}
private let globalIgnore: Set<String> = [ "AXChildren", // too verbose "AXChildrenInNavigationOrder", // too verbose "AXFocusableAncestor", // infinite recursion kAXHelpAttribute, // localized - not helpful kAXRoleDescriptionAttribute, // localized - not helpful]
private let kindSpecificIgnore: [AxKind: Set<String>] = [ .button: [ "AXFrame", kAXEditedAttribute, kAXFocusedAttribute, kAXPositionAttribute, kAXSizeAttribute, ], .app: [ "AXEnhancedUserInterface", "AXPreferredLanguage", kAXHiddenAttribute, ],]
extension AXError { fileprivate var repr: String { switch self { case .actionUnsupported: "actionUnsupported" case .apiDisabled: "apiDisabled" case .attributeUnsupported: "attributeUnsupported" case .cannotComplete: "cannotComplete" case .failure: "failure" case .illegalArgument: "illegalArgument" case .invalidUIElement: "invalidUIElement" case .invalidUIElementObserver: "invalidUIElementObserver" case .noValue: "noValue" case .notEnoughPrecision: "notEnoughPrecision" case .notImplemented: "notImplemented" case .notificationAlreadyRegistered: "notificationAlreadyRegistered" case .notificationNotRegistered: "notificationNotRegistered" case .notificationUnsupported: "notificationUnsupported" case .parameterizedAttributeUnsupported: "parameterizedAttributeUnsupported" case .success: "success" @unknown default: rawValue.description } }}