Paul Calnan
Published August 11, 2012

This is another repost from Scriptd. Be forewarned: it's pretty hacky. That having been said, I use this every day at work.

I use the Skype 5 client pretty often while I'm at work. It pops up a Growl notification whenever a new message comes in, but if I'm not looking at the screen (or if I'm looking at another monitor), I'll oftentimes miss it. The dock icon shows a badge, but my dock is set to auto-hide so I can't see the badge until I move the mouse to the dock.

As a result of all this, I wind up missing Skype messages all of the time. What I'd really like is some sort of indicator on the menu bar to tell me when I have unread messages. Unfortunately, Skype doesn't provide this option (one of a long list of shortcomings of the Skype UI). So, I wanted to try to hack something together to fill this need.

Dock icon badges are presented in OS X via the NSDockTile class. There is a badgeLabel property that contains the text that gets shown in the little red badge bubble. Unfortunately, I couldn't figure out a way to access this information from an external script.

I had heard of F-Script a while ago as a tool for debugging Cocoa applications. I won't go into details about all you can do with F-Script (and my knowledge of the tool is pretty limited). I had played around with it enough to get a feel for some of the things it could do and figured it might be of use for this issue.

One of the cool features of F-Script is the ability to inject itself into a running Cocoa application. It's got the ability to add a console to a running app, allowing you to access objects in the app. You set it up using GDB:

$ gdb
(gdb) attach Skype
(gdb) p (char)[[NSBundle bundleWithPath:@"/Library/Frameworks/FScript.framework"] load]
(gdb) p (void)[FScriptMenuItem insertInMainMenu]
(gdb) detach
(gdb) quit

Once you do that, you get a menu item in the target application (in this case, Skype). You can then open a console and play around:

> app := NSApplication sharedApplication

> app dockTile badgeLabel
nil

This is what appears if there is no badge icon. Do this again when there is a badge (in this case, I have a “1” badge) and you see the following:

> app dockTile badgeLabel
'1'

Great, so I figured out a way to access the badge from a script. But the process is a bit clunky and certainly not something I'd want to do on a daily basis. It's a step in in the right direction, though.

F-Script also allows you to create your own Objective-C objects that interact with the running application. After a bit of trial and error, I came up with the following NSObject-derived class that accomplished what I wanted to do.

SkypeMenuUpdater : NSObject
{
    statusBarItem
    timer

    - init
    {
        self := super init.
        self ~~ nil ifTrue:
        [
            statusBarItem := NSStatusBar systemStatusBar statusItemWithLength:20.

            timer := NSTimer scheduledTimerWithTimeInterval:1
                                                     target:[self updateMenu]
                                                   selector:#value
                                                   userInfo:nil
                                                    repeats:YES.
        ].
        ^ self
    }

    - updateMenu
    {
        statusBarItem setTitle:NSApplication sharedApplication dockTile badgeLabel.
    }
}.

updater := (SkypeMenuUpdater alloc) init.

Here, I define a new class called SkypeMenuUpdater with two ivars. The constructor creates a NSStatusBarItem called statusBarItem and a NSTimer called timer set to fire once a second. When the timer fires, it calls the updateMenu method, which queries the badge label of the app's dock tile, and then sets the status bar item to reflect the value of the label. Finally, it instantiates the object, which kicks the whole thing off.

The last piece involves injecting this script into a running Skype instance. I chose Ruby, since someone had a nice gist on Github showing how to do something similar. The final script is available on Github. All you have to do is run the Ruby script and it'll do the rest of the work for you.

It requires F-Script to be installed (the script assumes it's installed in /Library/Frameworks/FScript.framework). It also requires GDB, which is installed as part of the Xcode command line tools. Also note, you'll have to run the script each time you launch Skype.

Feel free to try this out. Use it at your own risk, though. I make no guarantees that it will work perfectly under all configurations. Also, I feel that this kind of hack is generally a bad idea. It gets the job done for me, but I'd love to have a less hacky way of accomplishing this.

As always, feel free to send along any questions or comments.