Change your views: OS X tags exploited
Apple windows insider
Mac Secrets Time to conclude our exploration of Apple's mysterious CoreGraphics framework. This time I'll show you how to change the behavior of windows by setting so-called window tags, and I'll exploit window properties to show how to snoop on the windows of another process.
Window tags represent another interesting little corner of the CoreGraphics subsystem. You can think of tags as essentially bit-flags that are associated with each window, but the way in which those bits are set and cleared is somewhat quirky, to say the least.
To set tags, you'd use this routine:
extern CGError CGSSetWindowTags (const CGSConnectionID cid, CGWindowID wid, int * tag, int tagSize);
In part one of this three-part mini-series I provided an explanation of the wid and cid parameters, which relate to the connection and ID of the window that you're concerned with.
The tag parameter is a pointer to a buffer that holds the tag information you want to set. The window server process allows for up to 64 bits of tag information. Most of the time, you're only concerned with the lower 32 bits in which case you'd point the tag parameter at a 32-bit integer containing the bit flags you want to set, and you'd set the tagSize parameter to 32. To write the higher-order tags, you'd point the tag parameter at a 64-bit quantity and set tagSize to 64.
I emphasize this because some folks think the last parameter should always be 32 - not true. It's usually 32, but I've seen 64 used in plenty of Apple code. If you set tagSize to less than 32, the routine will fail.
I suspect the reason for this rather odd interface is extensibility: it allows the number of tags to be arbitrarily increased without changing the interface. Needless to say, simply passing a 64-bit value would be a lot more straightforward.
To clear one or more tags, you'd use the CGSClearWindowTags routine defined below. In this case, you'd set all the bit flags that you want to clear in the memory pointed to by the tag parameter.
extern CGError CGSClearWindowTags (const CGSConnectionID cid, CGWindowID wid, int * tag, int tagSize);
Finally, to simply read the existing tag values, you'd use this routine:
extern CGError CGSGetWindowTags (const CGSConnectionID cid, CGWindowID wid, int * tag, int tagSize);
Note that when setting tag bits, you don't need to call CGSGetWindowTags and "OR in" the bit flags you want before calling CGSSetWindowTags. I've seen code that does this at www.cocoadev.com and it really represents a misunderstanding of how these routines work. The internal OR'ing and AND'ing is done for you - all you need do is supply a mask of the bits you want to set or clear.
All very interesting, but what does this buy us? As an example of what can be done, our demo uses a routine called CGSSetPreventsActivation to determine whether or not a window should activate when it's clicked.
The way this works should be pretty obvious. What's maybe not so obvious is that - internally - CGSSetPreventsActivation is simply a wrapper around CGSSetWindowTags, passing a tag value of 0x00010000 to the inner routine. You'll notice, by the way, that the window can still be activated by Alt-Tab'ing to it, even though it doesn't respond to mouse click activations.
In a similar vein, there are a group of related routines, CGSSetIgnoresCycle, CGSSetIgnoreAsFrontWindow, CGSSetDeferOrdering, CGSSetDeferActivation, which all work by calling CGSSetWindowTags. I don't pretend to understand what they all do - CGSSetIgnoresCycle disables Ctrl-F4 participation, by the way - but I'm on the case. For the terminally curious, the corresponding bit tag mask is show alongside each routine declaration in CGSPrivate.h.
Desktop icons and Exposé thumbnails on display
There's really far too much in CoreGraphics to cover in a few short instalments, but there's one other interesting aspect I want to mention here: properties.
In effect, the window server maintains what you can think of as an NSDictionary object for each connection, and for each window. The per-connection properties stored in this dictionary have interesting key names such as "EnableWindowDoubleBuffering", "DisableDeferredUpdates" . "ClientMayIgnoreEvents" and "ClientIsOppositeEndian". I'd particularly advise you not to fool around with that last one.
You can read and write the connection properties using these two routines:
extern CGError CGSGetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef *outValue); extern CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value);
The cid and targetCID parameters should normally be identical and obtained from CGSDefaultConnection. That's because only privileged programs like the Dock are able to manipulate the data relating to other connections than their own.
Finally, this month's demo project shows how to retrieve the window titles of all the windows that the Finder has open by using window properties. I would imagine that this is easily possible using AppleScript, but really that's not the point.
The key issue here is to demonstrate some of the undocumented CoreGraphics routines in action. If you step through the code in the debugger, you'll find that there are a surprisingly large number of Finder windows - each desktop icon, for example, is a Finder window. Use Quartz Debug and you'll find the same thing.
By using the undocumented property keys I've provided in CGSPrivate.h many other interesting window attributes can be retrieved.
As ever, this month's demo code can be downloaded from here. ®