Triggering Dashboard, Exposé and Spaces

0x56

disassembly_post_pictureFor 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

Dashboard IconThe 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:
Expose and Spaces Icons

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:
Spaces Menu Item

$ 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 Mac OS X magic tricks. But please be careful as that all of this is a private API that could change and break anytime along the road. Here is our combined interface of functions we’ve discovered today:

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.

4 Comments »

 
  • Max Howell says:

    Great article, thanks.

  • Lundy says:

    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.

 

Leave a comment:

Tags: <a> <b> <cite> <code> <em> <i> <string> <strong>