I created an AX observer for kAXMainWindowChangedNotification.
In the callback function I call CGWindowListCopyWindowInfo
with kCGWindowListOptionOnScreenOnly
option to get the id of the current main window (Windows are filtered by PID because I am only interested in the frontmost app). What I experienced is that the received window order reflects the previous state (before the window switch). If I delay the call of CGWindowListCopyWindowInfo
with a few milliseconds then the order is perfect but it does not seem to be a stable solution.
Is there any other way to wait until every API is notified about the changes then call CGWindowListCopyWindowInfo
to get the most recent window information?
Subscription:
AXUIElementRef appElem = AXUIElementCreateApplication(processId.intValue);
CFArrayRef windows;
AXError copyResult = AXUIElementCopyAttributeValues(appElem, kAXWindowsAttribute, 0, 1, &windows);
AXError createResult = AXObserverCreate(processId.intValue, windowSwitchedCallback, &observer);
if (copyResult != createResult != kAXErrorSuccess) {
return;
}
AXObserverAddNotification(observer, appElem, kAXMainWindowChangedNotification, (__bridge void *)(self));
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode);
Callback:
void windowSwitchedCallback(AXObserverRef observer, AXUIElementRef element, CFStringRef notificationName, void *refCon) {
if (CFStringCompare(notificationName, kAXMainWindowChangedNotification, 0) == kCFCompareEqualTo) {
NSTimeInterval delayInMSec = 10;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInMSec * NSEC_PER_MSEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSDictionary *activeWindow = [(__bridge ActiveWindowObserver*)(refCon) getActiveWindow];
NSLog(@"%@ windowChanged",activeWindow);
});
}
}
ActiveWindowObserver:
- (NSDictionary*) getActiveWindow
{
CFArrayRef windowsRef = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSArray *windowsArray = (NSArray *)CFBridgingRelease(windowsRef);
NSPredicate *pIdPredicate = [NSPredicate predicateWithFormat:@"kCGWindowOwnerPID == %@ && kCGWindowLayer == 0", processId];
NSArray *filteredWindows = [windowsArray filteredArrayUsingPredicate:pIdPredicate];
id activeWindow = filteredWindows.count > 0 ? filteredWindows.firstObject : nil;
return activeWindow;
}