Ariver
2026-08-31 cae8575c671f1cc09f3e4c049c8a30b7a6414160
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import ApplicationServices
import Foundation
 
struct Candidate {
    let role: String
    let exactIdentifier: String
    let selected: Bool?
    let rect: CGRect?
    let hasParentWindow: Bool
}
 
func selectUnique(_ candidates: [Candidate], expected: String) -> Candidate? {
    let matches = candidates.filter {
        $0.role == (kAXTextFieldRole as String) && $0.exactIdentifier == expected
    }
    guard matches.count == 1,
          matches[0].hasParentWindow,
          let rect = matches[0].rect,
          rect.origin.x.isFinite,
          rect.origin.y.isFinite,
          rect.width.isFinite,
          rect.height.isFinite,
          rect.width > 0,
          rect.height > 0 else { return nil }
    return matches[0]
}
 
func selfTest() -> Int32 {
    let rect = CGRect(x: 10, y: 20, width: 30, height: 40)
    let good = Candidate(role: kAXTextFieldRole as String, exactIdentifier: "fixture", selected: true, rect: rect, hasParentWindow: true)
    guard selectUnique([good], expected: "fixture") != nil,
          selectUnique([good, good], expected: "fixture") == nil,
          selectUnique([good], expected: "fix") == nil,
          selectUnique([Candidate(role: good.role, exactIdentifier: good.exactIdentifier, selected: true, rect: .zero, hasParentWindow: true)], expected: "fixture") == nil,
          selectUnique([Candidate(role: good.role, exactIdentifier: good.exactIdentifier, selected: true, rect: rect, hasParentWindow: false)], expected: "fixture") == nil else { return 1 }
    print("self_test=PASS exact_unique_binding=true minimal_output=true")
    return 0
}
 
if CommandLine.arguments == [CommandLine.arguments[0], "--self-test"] {
    exit(selfTest())
}
 
guard CommandLine.arguments.count == 3,
      let pid = pid_t(CommandLine.arguments[1]),
      !CommandLine.arguments[2].isEmpty else {
    fputs("usage: AXInspect pid exactSourceIdentifier\n", stderr)
    exit(64)
}
let expected = CommandLine.arguments[2]
 
enum AXFailure: Error {
    case operation(String, AXError)
}
 
func attribute(_ element: AXUIElement, _ name: String) throws -> CFTypeRef? {
    var value: CFTypeRef?
    let error = AXUIElementCopyAttributeValue(element, name as CFString, &value)
    if error == .success { return value }
    if error == .noValue || error == .attributeUnsupported { return nil }
    throw AXFailure.operation(name, error)
}
 
func stringValue(_ value: CFTypeRef?) -> String? {
    guard let value, CFGetTypeID(value) == CFStringGetTypeID() else { return nil }
    return value as? String
}
 
func boolValue(_ value: CFTypeRef?) -> Bool? {
    guard let value, CFGetTypeID(value) == CFBooleanGetTypeID() else { return nil }
    return CFBooleanGetValue((value as! CFBoolean))
}
 
func rect(_ element: AXUIElement) throws -> CGRect? {
    guard let pv = try attribute(element, kAXPositionAttribute), CFGetTypeID(pv) == AXValueGetTypeID(),
          let sv = try attribute(element, kAXSizeAttribute), CFGetTypeID(sv) == AXValueGetTypeID() else { return nil }
    var point = CGPoint.zero
    var size = CGSize.zero
    guard AXValueGetValue(pv as! AXValue, .cgPoint, &point),
          AXValueGetValue(sv as! AXValue, .cgSize, &size) else { return nil }
    return CGRect(origin: point, size: size)
}
 
func parentWindow(_ element: AXUIElement) throws -> AXUIElement? {
    var current = element
    for _ in 0..<16 {
        if stringValue(try attribute(current, kAXRoleAttribute)) == (kAXWindowRole as String) {
            return current
        }
        guard let parent = try attribute(current, kAXParentAttribute),
              CFGetTypeID(parent) == AXUIElementGetTypeID() else { return nil }
        current = parent as! AXUIElement
    }
    return nil
}
 
func collectMatches(_ element: AXUIElement, depth: Int = 0, into matches: inout [AXUIElement]) throws {
    let role = stringValue(try attribute(element, kAXRoleAttribute))
    if role == (kAXTextFieldRole as String),
       stringValue(try attribute(element, kAXValueAttribute)) == expected {
        matches.append(element)
    }
    guard depth < 14,
          let children = try attribute(element, kAXChildrenAttribute) as? [AXUIElement] else { return }
    for child in children { try collectMatches(child, depth: depth + 1, into: &matches) }
}
 
do {
    var matches: [AXUIElement] = []
    try collectMatches(AXUIElementCreateApplication(pid), into: &matches)
    let descriptors = try matches.map { element in
        Candidate(
            role: stringValue(try attribute(element, kAXRoleAttribute)) ?? "",
            exactIdentifier: stringValue(try attribute(element, kAXValueAttribute)) ?? "",
            selected: boolValue(try attribute(element, kAXSelectedAttribute)),
            rect: try rect(element),
            hasParentWindow: try parentWindow(element) != nil
        )
    }
    guard matches.count == 1,
          selectUnique(descriptors, expected: expected) != nil,
          let targetRect = try rect(matches[0]),
          let window = try parentWindow(matches[0]),
          let windowRect = try rect(window),
          targetRect.origin.x.isFinite, targetRect.origin.y.isFinite,
          targetRect.width.isFinite, targetRect.height.isFinite,
          targetRect.width > 0, targetRect.height > 0 else {
        fputs("exact source binding failed\n", stderr)
        exit(67)
    }
    let selected = descriptors[0].selected
    print("target=<QA_SOURCE> role=AXTextField selected=\(selected.map(String.init) ?? "unknown")")
    print("target_rect=\(targetRect.origin.x),\(targetRect.origin.y),\(targetRect.width),\(targetRect.height)")
    print("parent_window=<QA_WINDOW> role=AXWindow")
    print("window_rect=\(windowRect.origin.x),\(windowRect.origin.y),\(windowRect.width),\(windowRect.height)")
} catch {
    fputs("AX inspection failed\n", stderr)
    exit(70)
}