我的应用程序如何检测到另一个应用程序窗口的更改?

问题描述 投票:0回答:3

在 Mac 上的 Cocoa 中,我想检测属于另一个应用程序的窗口何时被移动、调整大小或重新绘制。我该怎么做?

objective-c cocoa macos
3个回答
51
投票

您需要使用 Accessibility API,它们是纯 C 语言,位于 ApplicationServices 框架内。例如:

首先创建一个应用程序对象:

AXUIElementRef app = AXUIElementCreateApplication( targetApplicationProcessID );

然后你就可以从中得到窗口。您可以请求窗口列表并枚举,也可以获取最前面的窗口(在 AXAttributeConstants.h 中查找您要使用的所有属性名称)。

AXUIElementRef frontWindow = NULL;
AXError err = AXUIElementCopyAttributeValue( app, kAXMainWindowAttribute, &frontWindow );
if ( err != kAXErrorSuccess )
    // it failed -- maybe no main window (yet)

现在,当该窗口的属性发生更改时,您可以通过 C 回调函数请求通知。这是一个四步过程:

首先你需要一个回调函数来接收通知:

void MyAXObserverCallback( AXObserverRef observer, AXUIElementRef element,
                           CFStringRef notificationName, void * contextData )
{
    // handle the notification appropriately
    // when using ObjC, your contextData might be an object, therefore you can do:
    SomeObject * obj = (SomeObject *) contextData;
    // now do something with obj
}

接下来您需要一个 AXObserverRef,它管理回调例程。这需要与您用于创建上面“app”元素的进程 ID 相同的进程 ID:

AXObserverRef observer = NULL;
AXError err = AXObserverCreate( applicationProcessID, MyObserverCallback, &observer );
if ( err != kAXErrorSuccess )
    // handle the error

获得观察者后,下一步是请求某些事情的通知。有关完整列表,请参阅 AXNotificationConstants.h,但对于窗口更改,您可能只需要这两个:

AXObserverAddNotification( observer, frontWindow, kAXMovedNotification, self );
AXObserverAddNotification( observer, frontWindow, kAXResizedNotification, self );

请注意,最后一个参数传递一个假定的“self”对象作为 contextData。这不会被保留,因此当该对象消失时调用

AXObserverRemoveNotification
很重要。

获得观察者并添加通知请求后,您现在希望将观察者附加到运行循环,以便您可以以异步方式(或实际上)发送这些通知:

CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop],
                    AXObserverGetRunLoopSource(observer),
                    kCFRunLoopDefaultMode );

AXUIElementRef
是CoreFoundation风格的对象,所以你需要使用
CFRelease()
来干净地处理它们。例如,为了保持此处的整洁,一旦获得 frontWindow 元素,您将使用
CFRelease(app)
,因为您将不再需要该应用程序。

关于垃圾收集的注意事项:要将 AXUIElementRef 保留为成员变量,请像这样声明它:

__strong AXUIElementRef frontWindow;

这指示垃圾收集器跟踪对其的引用。分配时,为了兼容GC和非GC,使用这个:

frontWindow = (AXUIElementRef) CFMakeCollectable( CFRetain(theElement) );

3
投票

进一步研究发现了“石英显示服务”

满足我需求的有趣函数是 CGRegisterScreenRefreshCallback。


0
投票

我知道这是一个老问题,在谷歌上深入研究后发现这篇有用的文章在这里 https://scriptide.tech/blog/tracking-active-window-macos-objective-c-electron

© www.soinside.com 2019 - 2024. All rights reserved.