Mac Cocoa开发:如何检测其他应用的窗口关闭事件?
Mac Cocoa 跨应用监听其他程序窗口关闭事件实现方案
为什么找不到原生的窗口关闭AX通知
这不是你漏查了公开常量,是AX框架的设计本身就没有提供对应的关闭通知:当一个应用的窗口被关闭时,对应的AXUIElement节点会直接从该应用的UI元素树中被彻底销毁移除,框架不会在节点销毁的时机主动抛出“窗口已关闭”类别的通知事件,自然也不会有公开的kAXWindowClosedNotification常量供你注册。
基于现有AX能力的可落地实现
你已经可以通过AXObserver监听其他应用的窗口最小化、尺寸变化事件,完全可以在现有逻辑基础上扩展,不需要更换技术栈或者申请额外的特殊权限:
- 第一步:建立稳定的窗口缓存
初始化对目标应用的监听时,先通过AXUIElementCopyAttributeValue(目标应用, kAXWindowsAttribute, ...)一次性拉取该应用当前所有存活的顶层窗口列表。遍历每个窗口实例,取它的kAXWindowNumberAttribute值作为唯一标识——这个值是系统窗口服务器分配的全局唯一ID,在窗口存活期间不会变化,最适合当缓存的key,不会出现内存地址复用导致的误判。
取值的示例代码:// 遍历拿到的单个窗口AX引用 AXUIElementRef targetWindow; CFNumberRef windowIDRef = NULL; AXError error = AXUIElementCopyAttributeValue(targetWindow, kAXWindowNumberAttribute, (CFTypeRef *)&windowIDRef); if (error == kAXErrorSuccess && windowIDRef) { NSInteger windowID = [(__bridge NSNumber *)windowIDRef integerValue]; // 把windowID存入本地缓存字典,value可以存你需要的窗口标题、对应AX引用等信息 CFRelease(windowIDRef); } - 第二步:注册补充的AX通知,减少无效校验
在你现有的Observer注册逻辑里,额外给目标应用的AX实例补两个通知注册:- 注册
kAXWindowCreatedNotification:当目标应用新建窗口时,框架会主动发这个通知,触发一次本地的窗口列表全量刷新 - 注册
kAXFocusedWindowChangedNotification:当用户切换目标应用的焦点窗口时,也触发一次窗口列表校验,降低事件感知延迟
- 注册
- 第三步:实现窗口关闭的感知逻辑
搭配一个低频率的定时器(建议间隔1~2秒,绝对不会造成性能损耗),遍历本地缓存里存的所有窗口ID对应的AX引用,逐个做属性读取校验:- 如果调用AX接口读取该窗口的基础属性时,返回
kAXErrorInvalidUIElement、kAXErrorNoValue之类的错误,就说明这个窗口已经被从系统UI树中移除,也就是被用户关闭了,这时你就可以抛出自定义的“其他应用窗口关闭”事件,同时把这个失效的ID从本地缓存中删掉。 - 如果你觉得AX接口拉取窗口列表的代码比较繁琐,也可以直接用Core Graphics提供的
CGWindowListCopyWindowInfo接口,根据目标应用的进程PID过滤出所有属于该应用的窗口ID列表,和你本地缓存的列表做差集,差集里的ID就是已经被关闭的窗口,这个方法的逻辑更简单,性能开销也更低。
- 如果调用AX接口读取该窗口的基础属性时,返回
常见踩坑提醒
注意:不要试图用NSWorkspace提供的窗口状态通知做跨应用监听,这类通知只会推送给应用自身,不会传递其他应用的窗口变更事件,完全满足不了需求。
- 不要把定时器的轮询间隔设得太短,比如100ms级别,这样会额外消耗目标应用的CPU资源,甚至触发macOS的无障碍权限异常提醒,1~2秒的间隔足够覆盖绝大多数用户操作场景,感知延迟在可接受范围内。
- 当目标应用崩溃、重启时,记得清空本地的窗口缓存,避免残留的无效AX引用导致的误判。
- 部分老旧应用的窗口可能不会返回有效的
kAXWindowNumberAttribute值,这类窗口需要额外加容错判断,避免读取崩溃。
内容的提问来源于stack exchange,提问作者ssroreo
相关产品推荐
相关产品推荐

