You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:16:10