You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
3.8 KiB
107 lines
3.8 KiB
import Cocoa
|
|
import ApplicationServices
|
|
|
|
// Comprehensive recursive search for any element with AXFocused == true
|
|
func findActiveFocusedElement(_ elem: AXUIElement) -> AXUIElement? {
|
|
var isFocusedObj: CFTypeRef?
|
|
if AXUIElementCopyAttributeValue(elem, kAXFocusedAttribute as CFString, &isFocusedObj) == .success,
|
|
let isFocused = isFocusedObj as? Bool, isFocused {
|
|
var roleObj: CFTypeRef?
|
|
AXUIElementCopyAttributeValue(elem, kAXRoleAttribute as CFString, &roleObj)
|
|
let role = (roleObj as? String) ?? ""
|
|
if role != "AXWindow" && role != "AXApplication" && role != "AXGroup" && role != "AXSplitGroup" && role != "AXScrollArea" {
|
|
return elem
|
|
}
|
|
}
|
|
|
|
var childrenObj: CFTypeRef?
|
|
if AXUIElementCopyAttributeValue(elem, kAXChildrenAttribute as CFString, &childrenObj) == .success,
|
|
let children = childrenObj as? [AXUIElement] {
|
|
for child in children {
|
|
if let found = findActiveFocusedElement(child) {
|
|
return found
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getFocusedTextInfo() -> (String, String, Int, Int)? {
|
|
guard let frontApp = NSWorkspace.shared.frontmostApplication else { return nil }
|
|
let appName = frontApp.localizedName ?? "App"
|
|
let appElem = AXUIElementCreateApplication(frontApp.processIdentifier)
|
|
|
|
// 1. Try systemWide
|
|
let sysWide = AXUIElementCreateSystemWide()
|
|
var focusedElemObj: CFTypeRef?
|
|
var targetElem: AXUIElement?
|
|
|
|
if AXUIElementCopyAttributeValue(sysWide, kAXFocusedUIElementAttribute as CFString, &focusedElemObj) == .success,
|
|
let obj = focusedElemObj {
|
|
targetElem = (obj as! AXUIElement)
|
|
}
|
|
|
|
// 2. Try App focused element
|
|
if targetElem == nil {
|
|
if AXUIElementCopyAttributeValue(appElem, kAXFocusedUIElementAttribute as CFString, &focusedElemObj) == .success,
|
|
let obj = focusedElemObj {
|
|
targetElem = (obj as! AXUIElement)
|
|
}
|
|
}
|
|
|
|
// 3. Try Recursive search in app tree
|
|
if targetElem == nil {
|
|
targetElem = findActiveFocusedElement(appElem)
|
|
}
|
|
|
|
guard let elem = targetElem else { return nil }
|
|
|
|
// Extract text
|
|
var text = ""
|
|
var valObj: CFTypeRef?
|
|
if AXUIElementCopyAttributeValue(elem, kAXValueAttribute as CFString, &valObj) == .success,
|
|
let val = valObj {
|
|
if let str = val as? String {
|
|
text = str
|
|
} else if let attrStr = val as? NSAttributedString {
|
|
text = attrStr.string
|
|
}
|
|
}
|
|
|
|
// Try parameterized string for range
|
|
if text.isEmpty {
|
|
var countObj: CFTypeRef?
|
|
if AXUIElementCopyAttributeValue(elem, kAXNumberOfCharactersAttribute as CFString, &countObj) == .success,
|
|
let count = countObj as? Int, count > 0 {
|
|
var range = CFRange(location: 0, length: count)
|
|
if let axRange = AXValueCreate(.cfRange, &range) {
|
|
var strObj: CFTypeRef?
|
|
if AXUIElementCopyParameterizedAttributeValue(elem, kAXStringForRangeParameterizedAttribute as CFString, axRange, &strObj) == .success,
|
|
let str = strObj as? String {
|
|
text = str
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cursor & Selection
|
|
var cursor = text.count
|
|
var selLen = 0
|
|
var rangeObj: CFTypeRef?
|
|
if AXUIElementCopyAttributeValue(elem, kAXSelectedTextRangeAttribute as CFString, &rangeObj) == .success,
|
|
let axRange = rangeObj {
|
|
var range = CFRange()
|
|
if AXValueGetValue(axRange as! AXValue, .cfRange, &range) {
|
|
cursor = range.location
|
|
selLen = range.length
|
|
}
|
|
}
|
|
|
|
return (appName, text, cursor, selLen)
|
|
}
|
|
|
|
if let (app, text, cursor, selLen) = getFocusedTextInfo() {
|
|
print("Found! App: \(app), Text: '\(text)', Cursor: \(cursor), SelLen: \(selLen)")
|
|
} else {
|
|
print("No focused text element found")
|
|
}
|