Quicksilver

PyActionsBasicTutorial

Disclaimer: You are responsible for your actions, not me.

In this tutorial we will put together a simple PyAction.

Assumptions:

  • You know how to install Quicksilver (version >= B48)
  • You are willing to read, think, experiment and troubleshoot ;-)
  • You have some experience programming in Python
  • You have read and understood this post

Recommendations:

  • A dummy account
  • ~/Applications/ installation of Quicksilver in the dummy account

Preparation:

I will assume the above recommendations are being followed and you are not working with any of your ordinary accounts. For the sake of discussion, let's assume your dummy account has the name 'demo'.

  • In the demo account, if there is not yet a ~/Applications/ folder, create it.
  • Install a version of Quicksilver >= B48 into ~/Applications/. This should allow our experimentation to not affect existing installations of Quicksilver. Do not install any extra plug-ins just yet -- though not strictly necessary, this may cause less hassle.
  • Install Universal MacPython if it is not yet installed. You may find reading this to help in deciding whether you are willing to do this. If you are not willing to, then I'm sorry but this tutorial may not be much use to you.
  • Install the PyObjC Support plug-in via Development Versions of Universal Binary PyObjC plug-ins.
  • Install the PyActions Module via Development Versions of Universal Binary PyObjC plug-ins. You may be asked by Quicksilver whether to install/update PyObjC Support -- if you are, answer in the negative. Answering in the affirmative will likely install an older PowerPC version of the plug-in, and this is definitely not what we want.
  • Verify that the ~/Library/Application Support/Quicksilver/PyActions/ directory exists.
  • Assign a hot key to the 'Scan PyActions Directory' trigger via the Triggers pref pane -- FWIW, I use Shift-Option-Command-S

PyAction Creation Steps:

  • Create a file named MyPyAction.py in ~/Library/Application Support/Quicksilver/PyActions/.
  • Place the following code in the file and save it. The code defines a simple action.

from pyactions import *
import sys, traceback

@actionId('TextTestAction')
@actionDict({'directTypes': [NSStringPboardType],
             'displaysResult': True,
             'name': 'Text Test Action'})
def textTest_(self, dObject):
    """."""
    try:
        sys.stderr.write('received: %s\n' % dObject)
        if dObject.containsType_(NSStringPboardType):
            sys.stderr.write('got a string\n')
            return dObject
    except:
        traceback.print_exc()
    return None

  • Invoke the trigger you set up earlier to scan the PyActions directory. This should make Quicksilver aware of the defined action.

PyAction Testing Steps:

  • Launch Console.app and look for output in console.log. If the PyActions Module has difficulty with your new action, hopefully, it has reported that fact and some relevant info. If you don't see anything relevant-looking, you may be in luck -- your PyAction may have been added successfully ;-)
  • Activate Quicksilver.
  • Enter text entry mode. I do this by typing the period key.
  • Enter some text.
  • Choose 'Text Test Action' for the action (2nd pane).
  • Press the return key. The result should be that the command window is displayed again with the text that you entered in the first pane -- also, there should be some output visible via Console.app.

If any of the preceding steps failed, it's time for troubleshooting. But first, I will attempt to explain some of the code.

PyAction Code Walkthrough:

The first import line:


from pyactions import *

makes certain PyAction-specific functionality available. In general, you will want this line, or if you are using the Twisted Support plug-in, something slightly different (but we won't get into that here -- suffice it to say that it's easy to figure out by examining appropriate samples that come with the PyActions Module).

The second import line:


import sys, traceback

is for debugging purposes. Specifically, our code uses 'sys.stderr.write()' and 'traceback.print_exc()'.

The first decorator [1] section:


@actionId('TextTestAction')

specifies an identifier for the action we are defining. This is required by Quicksilver and AFAIK needs to be unique.

The second decorator section:


@actionDict({'directTypes': [NSStringPboardType],
             'displaysResult': True,
             'name': 'Text Test Action'})

specifies attributes of the action. Our action operates on strings, and this is specified via the key 'directTypes'. The associated value is an array -- in this case containing a single element specifying a string type. Upon completion of our action, the command window displays a result -- this is indicated by specifying the value 'True' for the key 'displaysResult'. The name which is displayed in the action pane of the command window is specified via a value for the 'name' key. Further details of specifiable keys may be found here -- note that not everything mentioned there may be supported by PyActions.

The first line of the function definition:


def textTest_(self, dObject):

specifies a function name 'textTest_' and two arguments 'self' and 'dObject'. The 'self' argument is required because the defined function gets added to an instance (and I haven't tried to make the PyActions Module automatically take this into account). The 'dObject' argument is roughly what is specified in the first pane of the command window -- IIUC, the 'd' stands for 'direct'. Note that the function name ends in an underscore -- I believe this is required (if you'd like more details on this, see this).

The purpose of the try/except block is to provide a certain degree of protection for Quicksilver in case code we write misbehaves.

The first line of the try section:


        sys.stderr.write('received: %s\n' % dObject)

attempts to record some debugging info visible via Console.app.

The next line:


        if dObject.containsType_(NSStringPboardType):

tests the content of dObject to see whether it contains a string -- in this case, it may be extraneous as we indicated earlier that the action operates on strings.

The next line:


            sys.stderr.write('got a string\n')

attempts to record further debugging info.

The final line of the try section:


            return dObject

passes Quicksilver something to put in the first pane. In this case, it is just what the function received via dObject to begin with.

If at any point an exception is raised, an attempt is made to record some relevant debugging info:


    except:
        traceback.print_exc()

The final line:


    return None

should only be reached if an exception was raised. If you wish to see what happens, you can test this by modifying the code appropriately ;-)

[1] In case you weren't aware, the use of a decorator begin with an at-mark.

PyAction Troubleshooting Steps:

  • Examine the output in Console.app.
  • Think about what the problem might be.
  • Assuming the problem(s) is/are in your code, fix them. If you are convinced the problem is with the PyActions Module, consider posting to the forums about it.
  • Use the trigger to scan the PyActions directory. This will instruct Quicksilver to add the new version of your action.
  • Go to 'PyAction Testing Steps'.

Closing Remarks:

  • There are numerous PyActions samples that are installed in ~/Library/Application Support/Quicksilver/PyActions/samples/ when the PyActions Module is first installed, I recommend studying and playing around with them.
  • There is a topic at the forums for PyActions. You may find some relevant info there -- it might also work as a place to ask questions.
  • The 'interface' for specifying PyActions may change over time -- you may want to check the aforementioned topic from time-to-time for indications of this.

Happy Hacking!