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.)
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:
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.
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With