-
Notifications
You must be signed in to change notification settings - Fork 7
/
print_all_attributes.swift
149 lines (130 loc) · 5.34 KB
/
print_all_attributes.swift
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
140
141
142
143
144
145
146
147
148
149
import Cocoa
import ApplicationServices
import Foundation
class QueueElement {
let element: AXUIElement
let depth: Int
init(_ element: AXUIElement, depth: Int) {
self.element = element
self.depth = depth
}
}
func printAllAttributeValues(_ startElement: AXUIElement, to fileHandle: FileHandle?) {
var queue = [QueueElement(startElement, depth: 0)]
var visitedElements = Set<AXUIElement>()
var printedValues = Set<String>()
let unwantedValues = ["0", "", "", "3", ""]
while !queue.isEmpty {
let current = queue.removeFirst()
guard !visitedElements.contains(current.element) else { continue }
visitedElements.insert(current.element)
var attributeNames: CFArray?
let result = AXUIElementCopyAttributeNames(current.element, &attributeNames)
guard result == .success, let attributes = attributeNames as? [String] else { continue }
for attr in attributes {
var value: AnyObject?
let valueResult = AXUIElementCopyAttributeValue(current.element, attr as CFString, &value)
if valueResult == .success {
// Expand the list of attributes we're interested in
if ["AXDescription", "AXValue", "AXLabel", "AXRoleDescription", "AXHelp"].contains(attr) {
let valueStr = describeValue(value)
if !valueStr.isEmpty && !unwantedValues.contains(valueStr) && valueStr.count > 1 && !printedValues.contains(valueStr) { // Skip empty, unwanted, single-character, and duplicate values
let output = "[\(current.depth)] \(valueStr)\n"
print(output, terminator: "")
fileHandle?.write(output.data(using: .utf8)!)
printedValues.insert(valueStr)
}
}
// Add all child elements to the queue, regardless of attribute name
if let elementArray = value as? [AXUIElement] {
for childElement in elementArray {
queue.append(QueueElement(childElement, depth: current.depth + 1))
}
} else if let childElement = value as! AXUIElement? {
queue.append(QueueElement(childElement, depth: current.depth + 1))
}
}
}
}
}
func describeValue(_ value: AnyObject?) -> String {
switch value {
case let string as String:
return string
case let number as NSNumber:
return number.stringValue
case let point as NSPoint:
return "(\(point.x), \(point.y))"
case let size as NSSize:
return "w=\(size.width) h=\(size.height)"
case let rect as NSRect:
return "x=\(rect.origin.x) y=\(rect.origin.y) w=\(rect.size.width) h=\(rect.size.height)"
case let range as NSRange:
return "loc=\(range.location) len=\(range.length)"
case let url as URL:
return url.absoluteString
case let array as [AnyObject]:
return array.isEmpty ? "Empty array" : "Array with \(array.count) elements"
case let axValue as AXValue:
return describeAXValue(axValue)
case is AXUIElement:
return "AXUIElement"
case .none:
return "None"
default:
return String(describing: value)
}
}
func describeAXValue(_ axValue: AXValue) -> String {
let type = AXValueGetType(axValue)
switch type {
case .cgPoint:
var point = CGPoint.zero
AXValueGetValue(axValue, .cgPoint, &point)
return "(\(point.x), \(point.y))"
case .cgSize:
var size = CGSize.zero
AXValueGetValue(axValue, .cgSize, &size)
return "w=\(size.width) h=\(size.height)"
case .cgRect:
var rect = CGRect.zero
AXValueGetValue(axValue, .cgRect, &rect)
return "x=\(rect.origin.x) y=\(rect.origin.y) w=\(rect.size.width) h=\(rect.size.height)"
case .cfRange:
var range = CFRange(location: 0, length: 0)
AXValueGetValue(axValue, .cfRange, &range)
return "loc=\(range.location) len=\(range.length)"
default:
return "Unknown AXValue type"
}
}
func printAllAttributeValuesForCurrentApp() {
guard let app = NSWorkspace.shared.frontmostApplication else {
print("couldn't get frontmost application")
return
}
let pid = app.processIdentifier
let axApp = AXUIElementCreateApplication(pid)
let fileName = "accessibility_attributes.txt"
let fileManager = FileManager.default
let currentPath = fileManager.currentDirectoryPath
let outputPath = (currentPath as NSString).appendingPathComponent(fileName)
guard fileManager.createFile(atPath: outputPath, contents: nil, attributes: nil) else {
print("couldn't create file")
return
}
guard let fileHandle = FileHandle(forWritingAtPath: outputPath) else {
print("couldn't open file for writing")
return
}
defer {
fileHandle.closeFile()
}
let header = "attribute values for \(app.localizedName ?? "unknown app"):\n"
print(header, terminator: "")
fileHandle.write(header.data(using: .utf8)!)
printAllAttributeValues(axApp, to: fileHandle)
print("output written to \(outputPath) and printed to terminal")
}
// usage
printAllAttributeValuesForCurrentApp()