Triggering Dashboard, Exposé and Spaces
For those who wonder how Dashboard Ignition activates the Dashboard—here is a little bit of explanatory involving some hacking and disassembling. At the same time we will also find a way to programmatically control Exposé and Spaces in your code.
So, I hope you’re friends with Terminal application because today we will use some of the basic command line tools that every Mac developer should be familiar with. They are called otool, nm and strings…
Imagine that we’ve decided to have control over triggering Dashboard, switching Spaces and making all windows fly in and out via Exposé. The best (and only) way to do this would be to understand how Apple does it in the system apps and just mimic their behavior.
Dashboard
The first piece of Apple’s code we’re going to examine will be the Dashboard.app from Applications folder. So fire up your Terminal and disassembly this app using the following command:
$ otool -tV /Applications/Dashboard.app/Contents/MacOS/Dashboard ... 00001fd4 movl $0x00002030,(%esp) 00001fdb calll 0x00003000 ;symbol stub for: _CoreDockSendNotification
Notice that we can’t use Dashboard.app as an target to command line utility because on a UNIX side it’s just a folder, and disassembly tool requires full path to an applications binary to be specified.
As you can see this little applications was made to do only one thing—send a notification to a Dock (a parent for all Dashboard widget processes). This function requires one parameter which is pulled of static memory table. So let’s check what static strings that binary file has inside of it, run the following command:
$ strings /Applications/Dashboard.app/Contents/MacOS/Dashboard com.apple.dashboard.awake
This is probably the notification name we’re gonna use in CoreDockSendNotification(). So if you already has any sandbox project you may open it and test this code:
CoreDockSendNotification(@"com.apple.dashboard.awake");
You may be wondering, where do the definition of this function came from and why Xcode linker didn’t warn you about unknown symbol? If you check for the list of linked libraries in Dashboard.app using otool -L you will find that it is linked to nothing special but ApplicationServices and CoreFoundation frameworks, something that you will be automatically connected to when using AppKit.framework in your project.
There is also a mirrored version of this notification called com.apple.dashboard.dismiss that as you can read from it’s name—dismisses the Dashboard.
Spaces and Exposé
Using similar command on both Spaces.app and Expose.app in Applications folder, you will find some of other handful notification names such as:
com.apple.workspaces.awake com.apple.expose.front.awake com.apple.expose.awake com.apple.showdesktop.awake
I guess that all of them are pretty clear and self-explanatory, so let’s move on and find the way to switch to particular space by number.
Switching Spaces
Besides the direct way to switch spaces using ctrl+N hotkey, there is a little menu item that could be turned on in the System Preferences that could do the same. So let’s grab it and find the way it works:
$ cd "/System/Library/CoreServices/Menu Extras" $ strings Spaces.menu/Contents/MacOS/Spaces ... switchToSpace: com.apple.switchSpaces ...
As you can see the first one seems to be a string copy of selector, while the second one looks a lot like a notification name we’ve seen before. While we still could send this notification using CoreDockSendNotification(), but unfortunately there is no way to pass the id of space we want to switch to.
But if we’d look more carefully at disassembled code of it, we’ll find that it uses a pair of CFNotificationCenterGetDistributedCenter and CFNotificationCenterPostNotification calls, so all we need to do is send this notification using NSDistributedNotificationCenter instead, and pass the desired number in notification optional object. So, here is the code:
NSString *noteName = @"com.apple.switchSpaces"; NSString *spaceID = [NSString stringWithFormat:@"%d", (int)spaceNumber]; NSNotificationCenter *dCenter = [NSDistributedNotificationCenter defaultCenter]; [dCenter postNotificationName:noteName object:spaceID];
Getting Spaces Layout
The function CoreDockSendNotification() came from ApplicationServices.framework which is actually an umbrella framework, containing a number of other sub-frameworks. The actual code for all Dock functionality resides in HIServices.framework, so let’s find what else does it have related to Spaces:
$ cd /System/Library/Frameworks/ApplicationServices.framework $ nm Frameworks/HIServices.framework/HIServices | grep CoreDock ... 00024c20 T _CoreDockGetWorkspacesCount 0000fc92 T _CoreDockGetWorkspacesEnabled
There are more than 80 functions to tweak Dock appearance, but right now we’ll discuss only two of them listed above. As their names suggest we could get the number of available spaces and whether they are enabled in System Preferences. So let’s disassembly the Spaces.menu bundle again and see how we should use them:
$ otool -tV Spaces.menu/Contents/MacOS/Spaces ... 00001909 calll 0x00005081 ;symbol stub for: _CoreDockGetWorkspacesEnabled 0000190e testb %al, %al ... 0000192a leal 0xe0(%ebp), %eax 0000192d movl %eax, 0x04(%esp) 00001931 leal 0xe4(%ebp), %eax 00001934 movl %eax, (%esp) 00001937 calll 0x00005077 ;symbol stub for: _CoreDockGetWorkspacesCount ...
So after calling the first CoreDockGetWorkspacesEnabled() function our code tests the return value in AX (AL) register for zero. This means that this function returns a boolean value indicating whether Spaces are enabled or not.
The second function seems to return a total count of spaces, but after looking at the code before we could see a two variables loaded into stack by their effective address. So basically it means that this function takes two integer pointers, probably the number of rows and columns in our Spaces layout.
What do we have
Now we have all notification names we could send if we need to make one of the
void CoreDockSendNotification(NSString *notificationName);
BOOL CoreDockGetWorkspacesEnabled(); void CoreDockGetWorkspacesCount(int *rows, int *columns);
P.S. In 10.4 Tiger you could use NSDistributedNotificationCenter to send all of the notification names we discovered today, while CoreDockSendNotification() is available only in 10.5 Leopard, make sure that you are linking to an appropriate SDK version.
Great article, thanks.
Don’t suppose you happen to know where the Animation for Workspace switch is triggered? HIServices appear to mostly deal with changing the option, not the actual switchSpaces code.
I haven’t dug too much into this topic, but as far as I know actual animation code resides in WindowServer process, which is mostly based on some of the private methods from CoreGraphics framework.
You could see disassembled headers sand comments on a number of CG functions on the internet, just google for ‘CGSPrivate.h’
Kinda related… You wouldn’t happen to know how to programmatically bring up the Mac Application Switcher would you? Sending it a CMD-TAB doesn’t work because as soon as you key up the CMD the switcher closes. Yet Apple does this from their own trackpads.