14
votes

I'm using the Accessibility API to detect when a certain application opens windows, closes windows, when the windows are moved or resized, or made main and/or focused. However the client app seems to move a window to front without an Accessibility API notification being fired.

How can my application detect when another application brings a window to front, without making it key?

I'm hoping to find a solution that works on OS X 10.4 and 10.5

More info: I'm using these statements at the moment. They work fine when the user manually selects a window to bring it to front. But it doens't work when the app itself is bringing the window to the front.

AXObserverAddNotification(observer, element, kAXMainWindowChangedNotification, 0);
AXObserverAddNotification(observer, element, kAXFocusedWindowChangedNotification, 0);
4

4 Answers

8
votes

I've been unable to subscribe to current window changes, but you can ask the accessibility API for the current application, and the current applications most foreground window.

Imagine you have a class called CurrentAppData, with the following data:

@interface CurrentAppData : NSObject {
    NSString* _title;
    AXUIElementRef _systemWide;
    AXUIElementRef _app;
    AXUIElementRef _window;
}

The code to find the current application looks something like this:

-(void) updateCurrentApplication {
   // get the currently active application  
   _app = (AXUIElementRef)[CurrentAppData
                           valueOfExistingAttribute:kAXFocusedApplicationAttribute 
                                        ofUIElement:_systemWide];

   // Get the window that has focus for this application
   _window = (AXUIElementRef)[CurrentAppData 
                              valueOfExistingAttribute:kAXFocusedWindowAttribute 
                                           ofUIElement:_app];

   NSString* appName = [CurrentAppData descriptionOfValue:_window
                                             beingVerbose:TRUE];    

   [self setTitle:appName];
}

In this example the _systemWide variable is initialized in the classes init function as: _system = AXUIElementCreateSystemWide();

The class function valueOfExistingAttribute looks like this:

// -------------------------------------------------------------------------------
//  valueOfExistingAttribute:attribute:element
//
//  Given a uiElement and its attribute, return the value of an accessibility
//  object's attribute.
// -------------------------------------------------------------------------------
+ (id)valueOfExistingAttribute:(CFStringRef)attribute ofUIElement:(AXUIElementRef)element
{
    id result = nil;
    NSArray *attrNames;

    if (AXUIElementCopyAttributeNames(element, (CFArrayRef *)&attrNames) == kAXErrorSuccess) 
    {
        if ( [attrNames indexOfObject:(NSString *)attribute] != NSNotFound
                &&
            AXUIElementCopyAttributeValue(element, attribute, (CFTypeRef *)&result) == kAXErrorSuccess
        ) 
        {
            [result autorelease];
        }
        [attrNames release];
    }
    return result;
}

The previous function was taken from the Apple UIElementInspector example, which is also a great resource for learning about the Accessibility API.

5
votes

In Mac OS X, applications and windows are completely separate things, with applications containing windows; they are not formerly-the-same-thing as in Microsoft Windows. You need to detect the activation and deactivation of each application.

You'll do that by observing for kAXApplicationActivatedNotification and kAXApplicationDeactivatedNotification. The object of these notifications is the application being activated and deactivated. You'll also need to detect applications launching and quitting; you can do this using the Process Manager or NSWorkspace. Both of those APIs can give you a process ID, which you can use to create an AXApplication object.

4
votes

Take a look at iChatStatusFromApplication example in the developer documentation. It's exactly what you need :)