30

I’m trying to patch an application that resizes windows using the accessibility API.

I need to maintain a dictionary with the previous sizes of windows. The key needs to identify the currently active window. At the moment, this active window is retrieved via NSAccessibilityFocusedWindowAttribute upon the press of a hotkey.

However, every time this method is called, the returned AXUIElementRef which identifies the window is different! This of course means that I cannot use it as a dictionary key – the dictionary won’t find the corresponding entry.

The following code reproduces the problem:

-(IBAction)testWindowIdentification:(id)sender{
    AXUIElementRef focusedApp;
    AXUIElementRef focusedWindow;

    AXUIElementCopyAttributeValue(_systemWideElement,
                                  (CFStringRef) kAXFocusedApplicationAttribute,
                                  (CFTypeRef*) &focusedApp);
    AXUIElementCopyAttributeValue((AXUIElementRef) focusedApp,
                                  (CFStringRef) NSAccessibilityFocusedWindowAttribute,
                                  (CFTypeRef*) &focusedWindow);
    CFShow(focusedWindow);
}

_systemWideElement has been initialised in the init method using a call to AXUIElementCreateSystemWide().

The CFShow statement clearly shows different IDs every time the method is called (even though the same window is active), which is useless for me:

<AXUIElement 0x47e850> {pid=42463}
<AXUIElement 0x47e890> {pid=42463}
<AXUIElement 0x47e2c0> {pid=42463}
…

The documentation on AXUIElement shows no method that retrieves a unique attribute for the UI element, and neither does that of the NSAccessibility protocol. The unique PID is not enough for me, since a process can have multiple windows.

How can I retrieve some unique identifier of the active window in Cocoa?

(By the way, the real code is checking the return codes in the above calls; there is no error, the calls succeed.)

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
  • 2
    @JeremyBanks The original answerer has the right idea here. You can indeed use Quartz to get a `CGWindowID` once you've determined the focused window, if [this answer](http://stackoverflow.com/a/312099/517815) is to be believed. This _should_ give you the unique window identifier that you're hoping for, which you can pass around with impunity in the context of your current application. Do let me know if you'd like a more coherent and complete version of this as an actual answer. – MrGomez Apr 12 '12 at 08:26
  • @MrGomez Sure, an answer like that would be great. :) – Jeremy Apr 12 '12 at 19:21
  • @JeremyBanks Will do. I'm a bit overloaded today, but I'll try to get an answer in to this sometime later this evening (PST). :) – MrGomez Apr 12 '12 at 21:28
  • @MrGomez Sure, no hurry unless you're worried that somebody else might snag it. :) – Jeremy Apr 12 '12 at 21:31
  • @JeremyBanks Since the bounty period has ended, do you want to award it to an answer now? Otherwise it will expire in 22 hours and half the reputation will go to waste. – Konrad Rudolph Apr 19 '12 at 09:02

2 Answers2

22

Rob Keniger has the right strategy with his answer here. The only thing missing from this answer (and indeed, the reason for the bounty placement) is a workable implementation that takes the current active window and translates it into a unique key suitable for indexing in the context of the current working application.

Rob's solution sketches this out through use of the CGWindowID given in the context of Quartz Window Services. It is, of course, strongly implied that this window reference is only useful for your current application.

Getting this window reference is tricky, because no strong guarantees exist between the Accessibility API and Quartz Window Services. However, you can work around this in the following ways:

  1. Use extern "C" AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID* out);, as documented here. This isn't guaranteed to work, but it works as a ground-floor test to get things started if it works in your version of OSX.

  2. Get the CGWindowID directly, using, for example, HIWindowGetCGWindowID(). More details about selecting the active window and extracting the ID can be found in the reference manual for the Carbon Window Manager (warning: large PDF).

  3. Catalog your CGWindowID set using something like CGWindowListCreateDescriptionFromArray, exactly as Rob suggested. The goal here is then to find some scheme for bridging the Accessibility API and Quartz, but this is conceivable by utilizing, for example, a callback bound to the context of your current active window. I honestly don't know an optimal example of this that's properly future-proofed, however.

Of the options, I recommend going with 2. for your current needs, if you're unable to create some other decorator for your windows to uniquely identify them. It's currently defined in the legacy code base, but it will do what you desire.

Best of luck with your application.

Community
  • 1
  • 1
MrGomez
  • 23,788
  • 45
  • 72
  • How to detect that no window is selected or all the windows are inactive (i.e. control is on desktop) ? – Piyush Mathur Feb 03 '15 at 11:57
  • Excuse me, how to use "extern "C" AXError _AXUIElementGetWindow(AXUIElementRef, CGWindowID* out);" in Swift? – allenlinli Aug 06 '16 at 06:44
  • http://developer.apple.com/legacy/mac/library/documentation/Carbon/reference/Window_Manager/Window_Manager.pdf link dead. Does anyone have a copy? – JBis Mar 24 '22 at 22:51
11

I think you might be able to use the Quartz Window Services functions, specifically CGWindowListCreateDescriptionFromArray to enumerate the currently active windows in a particular app.

This call is lower-level than AppKit and isn't going to tell you which is the active window, but it will give you window IDs that are unique for the current user session. It's not a great solution, but you could compare the window bounds information with what you receive from the accessibility APIs to associate windows with their real IDs.

Rob Keniger
  • 45,830
  • 6
  • 101
  • 134