Writing internal commands for Quicksilver


Internal commands in Quicksilver are essentially actions that don’t require a subject (direct object) defined by the user. Most tasks, such as “Force Catalog Rescan”, and some commands like “QS Preferences” are internal commands. The Clipboard plugin also adds a large number of internal commands so as you can see, they’re quite useful in some circumstances.

[Quicksilver internal commands screenshot]
Internal commands are found in the Quicksilver section of the Catalog.

There’s absolutely no documentation on how to write an internal command for Quicksilver anywhere and figuring out the internal workings of the application can be frustrating at best. This tutorial aims to fill that void and, like some of my other Quicksilver-related articles, provide you with the knowledge and resources to make a working internal command.

Prerequisites

An internal command is written much like a standard Quicksilver action, so the only thing required is the Xcode project template for creating Quicksilver plugins. (However, if you really need another project template for writing internal commands, just ask)

It would also be a good idea to familiarize yourself with writing a Quicksilver action before going on.

Info.plist keys

Somewhere in the plist, you need to set QSLoadImmediately to true for Quicksilver to load your internal command properly. That’s simply:

<key>QSLoadImmediately</key>
<true/>

To register our internal command with Quicksilver, we need to add QSInternalMessages to the QSRegistration key. So, find the QSRegistrationTemplate key, rename it to QSRegistration, and configure the command in the following manner:

<key>QSRegistration</key>
<dict>
    <key>QSInternalMessages</key>
    <dict>
        <key>testInternalCommand</key>
        <dict>
            <key>target</key>
            <string>testCommand</string>
            <key>action</key>
            <string>doTestCommand:</string>
            <key>name</key>
            <string>Just a Test</string>
            <key>icon</key>
            <string></string>
        </dict>
        ...

What do the keys mean?

target

The class that will be running the internal command. This needs to be a singleton. More on that later.

action

An instance method that the target class responds to; this is the command.

name

A brief and meaningful name for your internal command.

icon

An icon for your command. You can use an identifier like com.blacktree.Quicksilver or com.apple.AddressBook and Quicksilver will load up the appropriate icon for you.

The class

As I mentioned earlier, the class that runs the command should be a singleton - that means only one copy of the class is instantiated and used (Apple’s documentation on singletons). The internal command will be called like: [[targetClass sharedInstance] doActionMethod:nil] where targetClass is the class you specified under target and doActionMethod: is the method for the key action in the Info.plist.

From the example Info.plist excerpt given above, the class that would be written would look something like this:

// testCommand.h

#import <QSCore/QSActionProvider.h>

@interface testCommand : QSActionProvider
{
}
+ (id)sharedInstance;
-(IBAction)doTestCommand:(id)sender;
@end

// testCommand.m

#import "testCommand.h"

@implementation testCommand

// Following few methods to make this class a singleton

static testCommand *sharedTestCommandManager = nil;

+ (id)sharedInstance {
    if (sharedTestCommandManager == nil) {
        [[self alloc] init]; // assignment not done here
    }
    return sharedTestCommandManager;
}
+ (id)allocWithZone:(NSZone *)zone{
    //    @synchronized(self) {
    if (sharedTestCommandManager == nil) {
        sharedTestCommandManager = [super allocWithZone:zone];
        return sharedTestCommandManager;  // assignment and return on first allocation
    }
    //      }
    return nil; //on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone{
    return self;
}
- (id)retain{
    return self;
}
- (unsigned)retainCount{
    return UINT_MAX;  //denotes an object that cannot be released
}
- (void)release{
    //do nothing
}
- (id)autorelease{
    return self;
}

// Method to perform the internal command

-(IBAction)doTestCommand:(id)sender
{
    NSLog(@"Running test internal command...");
    // do something useful
}

@end

And that’s it. Possibly the shortest ever Quicksilver development tutorial you’re going to see in a while.

I thought I’d drive home the usefulness of internal commands by showing off a little something I’ve been playing with for a while.

[Quicksilver Cube Smoking]
A simple internal command allows any interface to smoke

Yeah, it’s the cube interface. Smoking.

In fact, with this internal command, I can make anything smoke.

[Primer interface smoking]

Summary

  • Add QSLoadImmediately key, set to true
  • Add QSInternalMessages to the QSRegistration key
  • Write singleton class that runs the command

As always, I’ll be thrilled to see what you come up with.


Back to Top ↑

2 Comments so far

Leave a comment
  1. 1

    Would you mind sharing your smoke plugin? I’d love to be able to make my flame-colored cube smoke.

  2. 2

    Casey, see here

RSS feed for comments on this post. TrackBack URI

Leave a comment

Comments may be edited for formatting.