macOS无障碍AXObserver
macOS无障碍AXObserver

macOS无障碍AXObserver

AXObserver 是 macOS 无障碍(Accessibility)API 中的一个核心类,用于监听辅助功能对象(通常是 App 窗口、UI 元素等)状态的变化。它属于 Application Services / Accessibility 框架中的一部分,定义在 AXObserver.h 中。

基本用途

1、监听某个应用窗口的位置是否发生变化;

2、监听某个控件(按钮、文本框等)值是否改变;

3、获取焦点改变通知(哪个 UI 元素获得焦点);

4、监控 UI 事件(比如按钮点击、菜单变化);

基本用法

import ApplicationServices

// 1. 定义回调函数
static let callback: AXObserverCallback = { observer, element, notification, context in
    print("收到 AX 通知: \(notification)")
}

// 2. 获取目标 App 的 AXUIElement
let pid: pid_t = ... // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)

// 3. 创建 AXObserver
var observer: AXObserver?
AXObserverCreate(pid, callback, &observer)

// 4. 添加通知监听
AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)

// 5. 将 observer 添加到主运行循环中
let runLoopSource = AXObserverGetRunLoopSource(observer!)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)

代码解析

1、引入macOS框架

import ApplicationServices

引入 macOS 系统级框架 ApplicationServices,它包含无障碍(Accessibility)接口,用于访问和监听其他 App 的 UI 状态。

2、定义回调函数

let callback: AXObserverCallback = { observer, element, notification, context in
    print("收到 AX 通知: \(notification)")
}

AXObserver 使用 C 风格的回调机制,当被监听对象发出通知时,会调用这个函数。

参数含义:

observer: 当前的 AXObserver 对象。

element: 发送通知的 AXUIElement(通常是某个窗口或控件)。

notification: 通知类型(如窗口焦点变化、控件值改变等)。

context: 用户提供的上下文(可选,便于区分不同场景)。

3、获取目标App的AXUIElement

let pid: pid_t = ... // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)

AXUIElementCreateApplication(pid):获取目标 App 的根 AXUIElement 对象,pid 是目标 App 的进程 ID。

所有对目标 App 的监听操作,都以这个 appElement 为起点。

… 需要替换成实际监听的 App 的 PID,比如用 NSRunningApplication 获取Chrome浏览器的PID:

guard let ChromeApp = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == "com.google.Chrome" }) else { return }

let pid: pid_t = ChromeApp.processIdentifier // 目标进程 ID
let appElement = AXUIElementCreateApplication(pid)

4、创建AXObserver

var observer: AXObserver?
AXObserverCreate(pid, callback, &observer)

创建 AXObserver,创建后observer绑定AXObserver对象和回调函数,关联的是指定 pid 的进程。

callback类型为AXObserverCallback:

typealias AXObserverCallback = @convention(c) (
    AXObserver,
    AXUIElement,
    CFString,
    UnsafeMutableRawPointer?
) -> Void

对应的是C函数指针,因此需要使用@convention(c) 修饰的闭包或者全局函数。

&observer 是输出参数,函数调用成功后 observer 就变成有效对象了。

5、添加通知监听

AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)

给指定元素(appElement)添加通知监听。

kAXFocusedWindowChangedNotification 是系统定义的通知类型,表示“焦点窗口变化”时,会触发observer方法进行

最后一个参数是上下文指针,一般可传 nil。

注意:如果要监听多个通知、多个元素,可以多次调用这个函数。

6、添加到主运行循环中

let runLoopSource = AXObserverGetRunLoopSource(observer!)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)

AXObserver 不会自动处理通知,必须把它的事件源添加到当前线程的 RunLoop 中,才能定期轮询事件。

通常在 UI 主线程运行(即 CFRunLoopGetCurrent())。

.defaultMode 是默认的运行模式。

7、实现效果

监听某个App(指定pid),当焦点窗口变化时,会触发回调,并在控制台打印通知名。

注意事项

1、必须获得辅助功能权限(如开启“屏幕录制”或“辅助功能”权限),可以在入口文件或AppDelegate中添加辅助功能的检测:

let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true] as CFDictionary
let isTrusted = AXIsProcessTrustedWithOptions(options)

2、可通过代码判断当前权限状态:

if !AXIsProcessTrusted() {
    print("请前往设置中授权辅助功能")
}

3、在Info.plist文件中添加NSAppleEventsUsageDescription权限:

<key>NSAppleEventsUsageDescription</key>
<string>需要访问其他应用以实现辅助功能</string>

4、监听其他应用时,需要 AXIsProcessTrusted() 返回为 true;

5、AXObserver 是 C API,Swift 中使用略显复杂,很多人封装后使用;

6、一旦 App 被关闭或重启,Observer 也需要重新创建。

7、类名和系统类型不能冲突,如果使用AXObserver作为类名,在调用AXObserverCreate方法时:

AXObserverCreate(pid, callback, &observer)

Xcode会报错:

Cannot convert value of type 'test.AXObserver' to expected argument type 'ApplicationServices.AXObserver'

常用通知

1、kAXFocusedWindowChangedNotification:焦点窗口发生变化;

AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)

2、kAXValueChangedNotification:控件的值变了;

3、kAXTitleChangedNotification:标题变化;

AXObserverAddNotification(observer!, appElement, kAXTitleChangedNotification as CFString, nil)

4、kAXWindowCreatedNotification:窗口被创建;

AXObserverAddNotification(observer!, appElement, kAXWindowCreatedNotification as CFString, nil)

5、kAXUIElementDestroyedNotification:UI 元素被销毁。

使用场景

1、开发 macOS 辅助工具,如聚焦辅助(自动获取焦点的文本框内容);

2、开发窗口管理工具(如 Rectangle、Amethyst);

3、获取 UI 状态变化用于自动化测试、UI 抓取工具;

4、监听其他应用的行为,比如微信收到消息后窗口闪动。

总结

经过实际的测试,发现只有关闭App Sandbox和Hardened Runtime的情况下:

才能实现AXObserver的监听。

默认情况下,AXObserver不会弹出辅助功能的授权,必须在入口文件或AppDelegate中添加检测代码:

class AppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        // 检测“辅助功能”的代码
        let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: true] as CFDictionary
        let isTrusted = AXIsProcessTrustedWithOptions(options)

添加检测代码后,如果没有开启辅助功能,才会弹出“辅助功能访问”的提示:

否则,不会弹出辅助功能的授权。

当关闭App Sandbox和Hardened Runtime以及授权“辅助功能”后,才可以实现AXObserver的监听:

总体来讲,可能并不适用于Swift开发新的产品,因为现在的mac应用上架App Store都需要开启App Sandbox和Hardened Runtime,这里存在矛盾。

相关文章

1、macOS获取当前运行应用NSRunningApplication:https://fangjunyu.com/2025/07/25/macos%e8%8e%b7%e5%8f%96%e5%bd%93%e5%89%8d%e8%bf%90%e8%a1%8c%e5%ba%94%e7%94%a8nsrunningapplication/

2、Swift函数类型修饰符@convention(c):https://fangjunyu.com/2025/07/26/swift%e5%87%bd%e6%95%b0%e7%b1%bb%e5%9e%8b%e4%bf%ae%e9%a5%b0%e7%ac%a6conventionc/

完整代码

AXMonitor文件代码

import ApplicationServices
import AppKit

class AXMonitor {
    var observer: AXObserver?
    var runLoopSource:CFRunLoopSource?
    
    // 定义回调函数
    let callback: AXObserverCallback = { observer, element, notification, context in
        print("Received AX notification: \(notification)")
    }

    
    func startMonitoringChrome() {
        guard let chrome = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == "com.apple.Safari" }) else { return }
        
        let pid: pid_t = chrome.processIdentifier // 目标进程 ID
        let appElement = AXUIElementCreateApplication(pid)

        // 创建 AXObserver
        let result = AXObserverCreate(pid, callback, &observer)
        if result != .success {
            print("创建 observer 失败: \(result.rawValue)")
            return
        }

        if observer != nil {
            print("observer 不为 nil")
            AXObserverAddNotification(observer!, appElement, kAXWindowCreatedNotification as CFString, nil)
            AXObserverAddNotification(observer!, appElement, kAXTitleChangedNotification as CFString, nil)
            AXObserverAddNotification(observer!, appElement, kAXFocusedWindowChangedNotification as CFString, nil)
            // 将 observer 添加到主运行循环中
            runLoopSource = AXObserverGetRunLoopSource(observer!)
            CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .defaultMode)
        }
    }
}
   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注