Skip to content

Commit da5392e

Browse files
committed
Massage the Swift file and add a help parameter and a version.
1 parent c115366 commit da5392e

File tree

1 file changed

+75
-27
lines changed

1 file changed

+75
-27
lines changed

ax/Sources/AXHelper/main.swift

Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ let kAXActionsAttribute = "AXActions"
88
let kAXWindowsAttribute = "AXWindows"
99
let kAXPressAction = "AXPress"
1010

11+
// Configuration Constants
12+
let MAX_COLLECT_ALL_HITS = 100000
13+
let AX_BINARY_VERSION = "1.0.0"
14+
1115
// Helper function to get AXUIElement type ID
1216
func AXUIElementGetTypeID() -> CFTypeID {
1317
return AXUIElementGetTypeID_Impl()
@@ -257,30 +261,36 @@ func getElementAttributes(_ element: AXUIElement, attributes: [String]) -> Eleme
257261
}
258262
else if attr == "AXPosition" || attr == "AXSize" {
259263
// Handle AXValue types (usually for position and size)
260-
// Safely check if it's an AXValue
261-
let axValueType = AXValueGetType(unwrappedValue as! AXValue)
262-
263-
if attr == "AXPosition" && axValueType.rawValue == AXValueType.cgPoint.rawValue {
264-
// It's a position value
265-
var point = CGPoint.zero
266-
if AXValueGetValue(unwrappedValue as! AXValue, AXValueType.cgPoint, &point) {
267-
extractedValue = ["x": Int(point.x), "y": Int(point.y)]
268-
} else {
269-
extractedValue = ["error": "Position data (conversion failed)"]
264+
if CFGetTypeID(unwrappedValue) == AXValueGetTypeID() {
265+
let axValue = unwrappedValue as! AXValue
266+
let axValueType = AXValueGetType(axValue)
267+
268+
if attr == "AXPosition" && axValueType.rawValue == AXValueType.cgPoint.rawValue {
269+
// It's a position value
270+
var point = CGPoint.zero
271+
if AXValueGetValue(axValue, AXValueType.cgPoint, &point) {
272+
extractedValue = ["x": Int(point.x), "y": Int(point.y)]
273+
} else {
274+
extractedValue = ["error": "Position data (conversion failed)"]
275+
}
276+
}
277+
else if attr == "AXSize" && axValueType.rawValue == AXValueType.cgSize.rawValue {
278+
// It's a size value
279+
var size = CGSize.zero
280+
if AXValueGetValue(axValue, AXValueType.cgSize, &size) {
281+
extractedValue = ["width": Int(size.width), "height": Int(size.height)]
282+
} else {
283+
extractedValue = ["error": "Size data (conversion failed)"]
284+
}
270285
}
271-
}
272-
else if attr == "AXSize" && axValueType.rawValue == AXValueType.cgSize.rawValue {
273-
// It's a size value
274-
var size = CGSize.zero
275-
if AXValueGetValue(unwrappedValue as! AXValue, AXValueType.cgSize, &size) {
276-
extractedValue = ["width": Int(size.width), "height": Int(size.height)]
277-
} else {
278-
extractedValue = ["error": "Size data (conversion failed)"]
286+
else {
287+
// It's some other kind of AXValue
288+
extractedValue = ["error": "AXValue type: \(axValueType.rawValue)"]
279289
}
280-
}
281-
else {
282-
// It's some other kind of AXValue
283-
extractedValue = ["error": "AXValue type: \(axValueType.rawValue)"]
290+
} else {
291+
let typeDescriptionCF = CFCopyTypeIDDescription(CFGetTypeID(unwrappedValue))
292+
let typeDescription = String(describing: typeDescriptionCF ?? "Unknown CFType" as CFString)
293+
extractedValue = ["error": "Expected AXValue for attribute \(attr) but got different type: \(typeDescription)"]
284294
}
285295
}
286296
else if attr == "AXTitleUIElement" || attr == "AXLabelUIElement" {
@@ -696,8 +706,8 @@ func collectAll(element: AXUIElement,
696706
maxDepth: Int = 200) {
697707

698708
// Safety limit on matches - increased to handle larger web pages
699-
if hits.count > 100000 {
700-
debug("Safety limit of 100000 matching elements reached, stopping search")
709+
if hits.count > MAX_COLLECT_ALL_HITS {
710+
debug("Safety limit of \(MAX_COLLECT_ALL_HITS) matching elements reached, stopping search")
701711
return
702712
}
703713

@@ -868,7 +878,7 @@ func collectAll(element: AXUIElement,
868878

869879
let childrenToProcess = uniqueElements.prefix(maxChildrenToProcess)
870880
for (i, child) in childrenToProcess.enumerated() {
871-
if hits.count > 100000 { break } // Safety check
881+
if hits.count > MAX_COLLECT_ALL_HITS { break } // Safety check - Use constant
872882

873883
// Safety check - skip this step instead of validating type
874884
// The AXUIElement type was already validated during collection
@@ -1036,8 +1046,46 @@ func handlePerform(cmd: CommandEnvelope) throws -> PerformResponse {
10361046

10371047
let decoder = JSONDecoder()
10381048
let encoder = JSONEncoder()
1039-
if #available(macOS 10.15, *) {
1040-
encoder.outputFormatting = [.withoutEscapingSlashes]
1049+
encoder.outputFormatting = [.withoutEscapingSlashes]
1050+
1051+
// Check for command-line arguments like --help before entering main JSON processing loop
1052+
if CommandLine.arguments.contains("--help") || CommandLine.arguments.contains("-h") {
1053+
// Placeholder for help text
1054+
// We'll populate this in the next step
1055+
let helpText = """
1056+
ax Accessibility Helper v\(AX_BINARY_VERSION)
1057+
1058+
This command-line utility interacts with the macOS Accessibility framework.
1059+
It is typically invoked by a parent process and communicates via JSON on stdin/stdout.
1060+
1061+
Usage:
1062+
<json_command> | ./ax
1063+
1064+
Input JSON Command Structure:
1065+
{
1066+
"cmd": "query" | "perform",
1067+
"locator": {
1068+
"app": "<BundleID_or_AppName>",
1069+
"role": "<AXRole>",
1070+
"match": { "<Attribute>": "<Value>", ... },
1071+
"pathHint": ["<element>[index]", ...]
1072+
},
1073+
"attributes": ["<AXAttributeName>", ...], // For cmd: "query"
1074+
"action": "<AXActionName>", // For cmd: "perform"
1075+
"multi": true | false, // For cmd: "query", to get all matches
1076+
"requireAction": "<AXActionName>" // For cmd: "query", filter by action support
1077+
}
1078+
1079+
Example Query:
1080+
echo '{"cmd":"query","locator":{"app":"Safari","role":"AXWindow","match":{"AXMain": "true"}},"attributes":["AXTitle"]}' | ./ax
1081+
1082+
Permissions:
1083+
Ensure the application that executes 'ax' (e.g., Terminal, an IDE, or a Node.js process)
1084+
has 'Accessibility' permissions enabled in:
1085+
System Settings > Privacy & Security > Accessibility.
1086+
"""
1087+
print(helpText)
1088+
exit(0)
10411089
}
10421090

10431091
// Check for accessibility permissions before starting

0 commit comments

Comments
 (0)