Find.swift
1 import ApplicationServices 2 3 func findElements(pid: Int, query: String?, role: String?, identifier: String?) -> [FindResult] { 4 let appRef = AXUIElementCreateApplication(Int32(pid)) 5 guard let windows = axValue(appRef, "AXWindows") as? [AXUIElement] else { 6 return [] 7 } 8 9 var results: [FindResult] = [] 10 for (winIdx, window) in windows.enumerated() { 11 searchTree(window, path: "window[\(winIdx)]", query: query, role: role, 12 identifier: identifier, results: &results, limit: 50) 13 } 14 return results 15 } 16 17 private func searchTree(_ el: AXUIElement, path: String, query: String?, role: String?, 18 identifier: String?, results: inout [FindResult], limit: Int) { 19 guard results.count < limit else { return } 20 21 let elRole = axString(el, "AXRole") ?? "" 22 let title = axString(el, "AXTitle") ?? "" 23 let desc = axString(el, "AXDescription") ?? "" 24 let value: String 25 if let v = axValue(el, "AXValue") { 26 value = "\(v)" 27 } else { 28 value = "" 29 } 30 let ident = axString(el, "AXIdentifier") 31 32 // Match by identifier (exact) 33 if let id = identifier, let actualIdent = ident, actualIdent == id { 34 var r = FindResult(path: path, role: elRole, title: title) 35 if !desc.isEmpty { r.desc = desc } 36 if !value.isEmpty { r.value = String(value.prefix(200)) } 37 results.append(r) 38 return 39 } 40 41 // Match by role + query 42 let roleMatch = role == nil || elRole == role 43 var textMatch = query == nil 44 if let q = query?.lowercased() { 45 textMatch = title.lowercased().contains(q) 46 || desc.lowercased().contains(q) 47 || value.lowercased().contains(q) 48 } 49 50 if roleMatch && textMatch { 51 var r = FindResult(path: path, role: elRole, title: title) 52 if !desc.isEmpty { r.desc = desc } 53 if !value.isEmpty { r.value = String(value.prefix(200)) } 54 results.append(r) 55 } 56 57 // Recurse 58 guard let children = axChildren(el) else { return } 59 var childIndex: [String: Int] = [:] 60 for child in children { 61 guard let childRole = axString(child, "AXRole") else { continue } 62 let idx = childIndex[childRole, default: 0] 63 childIndex[childRole] = idx + 1 64 searchTree(child, path: "\(path)/\(childRole)[\(idx)]", query: query, 65 role: role, identifier: identifier, results: &results, limit: limit) 66 } 67 }