如何通过Accessibility API获取macOS(含Sonoma)插入符光标位置?
macOS Sonoma 下获取听写状态时光标坐标的解决方案
问题分析
在macOS Sonoma系统启用听写功能时,聚焦文本框的光标上方会显示麦克风图标,此时常规Accessibility API直接读取kAXPositionAttribute无法拿到光标实时位置——核心原因是听写状态下系统会临时接管文本框的部分辅助属性,导致直接读取元素位置无法映射到光标坐标。
修正后的代码实现
要正确获取光标位置,需通过文本元素的kAXVisibleCharacterRangeAttribute结合参数化属性AXUIElementCopyParameterizedAttributeValue,获取光标所在字符的屏幕坐标。以下是修正后的代码:
private func getFocusedUIElement() { let systemWideElement = AXUIElementCreateSystemWide() var focusedElement: AnyObject? let result = AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute as CFString, &focusedElement) if result == .success, let focusedElement = focusedElement as? AXUIElement { getCaretScreenPosition(focusedElement: focusedElement) } else { print("无法获取聚焦元素或当前无聚焦元素。") } } private func getCaretScreenPosition(focusedElement: AXUIElement) { // 1. 获取光标对应的文本范围(光标位置对应range.location) var selectedTextRangeValue: AnyObject? let selectedTextRangeResult = AXUIElementCopyAttributeValue(focusedElement, kAXSelectedTextRangeAttribute as CFString, &selectedTextRangeValue) guard selectedTextRangeResult == .success, let axRangeValue = selectedTextRangeValue as? AXValue, AXValueGetType(axRangeValue) == .cfRange else { print("无法获取选中文本范围。") return } var selectedRange = CFRange() guard AXValueGetValue(axRangeValue, .cfRange, &selectedRange) else { print("解析文本范围失败。") return } // 2. 通过参数化属性获取光标所在位置的屏幕坐标 var caretPosition = CGPoint.zero let parameter = AXValueCreate(.cfRange, &selectedRange)! let positionResult = AXUIElementCopyParameterizedAttributeValue( focusedElement, kAXCharacterRangeForPositionAttribute as CFString, parameter, &caretPosition ) if positionResult == .success { DispatchQueue.main.async { [weak self] in self?.caretPosition = caretPosition print("光标坐标:\(caretPosition)") } } else { // 降级方案:读取文本框位置并估算光标偏移(适配部分不支持参数化属性的应用) var elementPositionValue: AnyObject? let elementPosResult = AXUIElementCopyAttributeValue(focusedElement, kAXPositionAttribute as CFString, &elementPositionValue) if elementPosResult == .success, let axPosValue = elementPositionValue as? AXValue, AXValueGetType(axPosValue) == .cgPoint { var elementPos = CGPoint.zero if AXValueGetValue(axPosValue, .cgPoint, &elementPos) { // 可根据文本框字体大小计算精准偏移,此处为示例值 DispatchQueue.main.async { [weak self] in self?.caretPosition = CGPoint(x: elementPos.x + 12, y: elementPos.y + 22) } } } else { print("无法获取光标屏幕位置。") } } }
关键说明
- 核心修复点:使用
kAXCharacterRangeForPositionAttribute参数化属性,传入光标所在的字符范围,直接获取该位置的屏幕坐标——这是Accessibility API中专门用于文本字符位置查询的方法,不受听写状态干扰。 - 权限要求:必须确保应用已开启系统
辅助功能权限(路径:系统设置 > 隐私与安全性 > 辅助功能),否则所有Accessibility API调用都会返回失败。 - 降级兼容:如果部分第三方应用不支持参数化属性,可以读取文本框的基础位置,结合
kAXFontAttribute获取的字体信息,计算光标相对于文本框的偏移量。
内容的提问来源于stack exchange,提问作者Mr. B
相关产品推荐
相关产品推荐

