
Keyboard Maestro 6 Plugin Actions

Introduction
Keyboard Maestro 6 brought a major and awesome new feature: Plug In Actions. What that means is that now you can create your own actions! Yes, you read that right!
In this tutorial, we will create a Comment action a bit more advanced than KM's. This will allow us to create a plugin without having to focus too much on the script.
Is this tutorial for you?
If you're comfortable with a bit of scripting and have no or little experience with plist files and XML, then yes, definitely! In fact, this tutorial was made just for you!
If you're a developer or an expert at scripting and editing plists, though, I wouldn't blame you if you found it boring and felt like jumping directly to the official documentation. But if you have a few minutes to spare, I'd suggest you to still have a quick read. It will show you how powerful and how well thought this feature is, and you'll learn a few things that are not mentioned in the documentation.
You can download the plugin we're going to make from here. Plugins aren't compiled, so the source code is entirely accessible.
Before we start
First I have to say that I find comments indispensable, especially in long macros (Peter added the Comment action a long time ago in response to a feature request of mine, BTW). But in my experience, KM's comments don't stand out quite enough to make it easy to scan a long macro by reading only the comments. So we'll start by making a comment action with a more contrasted icon. Then we'll progressively improve it, until we get an action that's also very useful to debug macros.
But before, we have to make a little tool. KM and KM Engine both keep the plugins in memory, so while developing plugins, we need a way to reload them. Otherwise, we wouldn't see our changes.
Create a new macro in a group available when KM is frontmost, and name it Reload Plugins. Give it a hot key and/or a Status Menu trigger. Then insert an "Execute AppleScript" action and set it like this:
Now we're ready, so let's start with the plugin.
Getting Started
A plugin is simply made of a set of files, stored in a folder, stored in:
So start by navigating into that "Keyboard Maestro Actions" folder and create a folder named Loud Comment
.
The only required file, in that folder (if you don't want your action to do anything, that is), is a plist file named Keyboard Maestro Action.plist
. This file tells KM how to draw the action and how to execute it. It is formatted in XML and its contents is mostly made of key/value pairs. If that sounds complicated to you, trust me, it's not.
Let's start with this:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Name</key> <string>Loud Comment</string> <key>Results</key> <string>None</string> <key>Parameters</key> <array> <dict> <key>Label</key> <string>Title</string> <key>Type</key> <string>TokenString</string> </dict> <dict> <key>Label</key> <string>Comment</string> <key>Type</key> <string>TokenText</string> </dict> </array> </dict> </plist>
See the following lines?
<key>Name</key> <string>Loud Comment</string>
This is a key/value pair. The key is named Name
, and the value is Loud Comment
. This particular pair declares the name of our action as it will appear in KM, and it should match the name of the folder that contains it.
Expose
and set it's Name attribute to Exposé
.Next we have the pair Results
/ None
. That one tells KM to do nothing with the result of the script. We have no script at this point and it defaults to None, so we could remove it. But it's a very powerful option! When used, its value must be one of these:None, Window, Briefly, Typing, Pasting, Variable, Clipboard
.
<key>name</key>
or <string>window</string>
wouldn't be recognized!And finally we have the Parameters
key, whose value is an array of dictionaries (dict for short). Each dict defines a variable and an interface element in the action:
(...) <array> <dict> <key>Label</key> <string>Title</string> <key>Type</key> <string>TokenString</string> </dict> <dict> <key>Label</key> <string>Comment</string> <key>Type</key> <string>TokenText</string> </dict> </array> (...)
Here we have two text fields, one for the title and one for the comment. Again, they're made of key/value pairs: Label
, which is the label that will appear in the action and the name of the associated variable, and Type
, that defines the type of user interface element to use (text field, checkbox, popup menu, etc). The possible values are:String, TokenString, Calculation, Text, TokenText, Checkbox, PopupMenu
.
String
and Text
, in this case, which would be rendered in the action as a one line text field for the title and a multiline field for the comment. But by using TokenString
and TokenText
instead, we get the same interface and we also make it possible to insert tokens into those fields (in the action), and have them evaluated at run time.So create a new file in your text editor of choice (making sure it's plain text, not rich text). Then copy and paste the full XML above into it, and save it as Keyboard Maestro Action.plist
into the Loud Comment
folder you created earlier. Keep that file open, as we'll edit it soon.
Take Keyboard Maestro to the front, run the Reload Plugins macro we created earlier, and create a new macro to test your action. Click the New Action button to add an action, then click on the Third Party Plug Ins folder at the top of the categories column, and here's your new action:
Double-click it to add it to the macro:
The Icon
That was easy, wasn't it? Now let's change its icon. All you need is a png image, but it's expected to be 64 x 64 pixels (otherwise it will be resized and possibly distorted). Also, having a transparent background is definitely a plus, as it will look much nicer once displayed in the action.
I made mine using the Emoji font in Pixelmator, but to each its own. Give it a name and drop it into your Loud Comment
folder. I named mine (originally enough) blueball.png
, but feel free to name yours differently if you find that too painful or if it doesn't look like a blue ball.
Then go back to the plist file in your text editor and add the Icon key/value pair that will tell KM to use your icon:
(...) <dict> <key>Name</key> <string>Comment</string> <key>Icon</key> <string>blueball.png</string> <key>Results</key> <string>None</string> (...)
<key>Name</key>
and <string>Loud Comment</string>
, for example! And don't forget that KM's key names are always capitalized and case sensitive.Save the plist, go back to KM, reload the plugin, and tadda, here's your icon:
But hang on, there's something wrong, at the moment. Can you see it?
The Title
If you collapse the action, all you can see is "Loud Comment". Hmm, not too useful, eh?
But don't worry, there's a solution: a key/value pair we haven't used yet, Title
.
%Param%XYZ%
tokens (tokens that will evaluate to the contents of other fields). That's exactly what we want!
So let's try that. Edit the plist this way:
(...) <dict> <key>Name</key> <string>Loud Comment</string> <key>Title</key> <string>%Param%Title%</string> <key>Icon</key> <string>blueball.png</string> (...)
Save it, reload the plugin in KM, and type something in the Title field:
Yay! Now we can collapse the action and still see its title.
We could have set the title to Comment '%Param%Title%'
, which would have given the same result as KM's Comment action (that is Comment 'Something', in this case) but it's not necessary, we know what it is. So let's keep more room for the title of our actual comment. We could also have used %Param%Comment%
to show the comment rather than the title, but where would be the logic in that? ;-)
OK, so our action is basically the same as KM's, but it's much more visible in the middle of a long macro. Mission accomplished!
Scripting
But we're not going to stop here, are we? "We didn't even give that action a script!", I hear you cry.
OK, so what else could our action do, in addition to standing out awesomely in the middle of a long macro? We could make it optionally display the comment at run time? Yes, and since the fields we chose support tokens, that could be useful to display variables while debugging complex macros!
Let's do that, then: We'll add a checkbox to the action, so the comment will be displayed at run time when it's checked.
So now we need a script. It can be an AppleScript script, or a shell script (that in turn could use Perl, Python, etc). If this evokes endless possibilities to you, you're absolutely right!
For this we'll use AppleScript. So launch AppleScript Editor, and paste the following into a new document:
on run set cmt_title to system attribute "KMPARAM_Title" set cmt_comment to system attribute "KMPARAM_Comment" set cmt_display to system attribute "KMPARAM_Display" tell application "System Events" if cmt_display = "1" then display dialog cmt_title & return & return & cmt_comment buttons {"OK"} ¬ default button "OK" with icon 2 end if end tell end run
The first three lines grab the values of the variables KM extracted from the action. Notice that the names KMPARAM_Title
and KMPARAM_Comment
are based on the labels of our fields, Title
and Comment
. KMPARAM_Display
should therefore give you a hint on what we'll call our checkbox.
The next four lines will just display a dialog made with the combination of our Title and Comment fields if the value of our checkbox is "1"
(that is checked).
Save the script into your Loud Comment
folder. Let's be imaginative once more and name it comment.scpt
. Again, it could be named anything, as long as the following rule is respected:
Keep that script open; we'll come back to it in a moment.
Finally, we must tell KM about that script (which we will do by adding the Script key/value pair to our plist file) and about our new checkbox (which we will do by adding a new dict to its parameters):
(...) <key>Title</key> <string>%Param%Title%</string> <key>Script</key> <string>comment.scpt</string> (...) <key>Parameters</key> <array> (...) <dict> <key>Label</key> <string>Display</string> <key>Type</key> <string>Checkbox</string> </dict> </array> (...)
It's still pretty simple. The key/value pair Script
/ comment.scpt
tells KM where the script is, and the dict adds the checkbox, setting its label and the associated variable name to Display
and it's type to Checkbox
.
If we wanted that checkbox to be checked by default, we would add to this dict a third key/value pair, whose key would be named Default
and whose value would be set to 1
. We could also set it to 0
to have the checkbox unchecked, but since this is the default and it's what we want, we just skip it.
Save the plist, reload the plugins in KM, and here's our checkbox:
Let's try it, then. Enter some text into the Title and the Comment fields, check the Display checkbox, click on Try, and you should see a dialog with the text you entered. Try putting a token in the Comments, and it will be evaluated in the dialog:
Want More?
OK, looks like that could be useful. What else could be? How about an option to speak the comment instead of displaying it in a dialog? That too might be useful while debugging.
We could add another checkbox, but a popup menu will be handier. This way, in one control we'll have the choice between none, dialog and spoken.
Let's start by editing the plist:
(...) <dict> <key>Label</key> <string>Comment</string> <key>Type</key> <string>TokenText</string> </dict> <dict> <key>Label</key> <string>Notification</string> <key>Type</key> <string>PopupMenu</string> <key>Menu</key> <string>None|Dialog|Spoken</string> <key>Default</key> <string>None</string> </dict> </array> (...)
We removed the checkbox's and replaced it with a popup menu. Its label (and the name of the corresponding variable) is Notification
and its type is PopupMenu
.
Then you see a key/value pair we haven't met before: the one whose key is named Menu
. It is required when using a popup menu as this is where its contents is defined, with the items separated by a pipe |
. In this case, we have None, Dialog and Spoken.
And finally, a key/value we mentioned earlier, Default
(which obviously enough defines the value to use by default). We could skip it again, because the default item is the first one, also our choice, but since it's good to remember it exists, this time we'll keep it.
Save the plist, go back to the script and replace it with this:
on run set cmt_title to system attribute "KMPARAM_Title" set cmt_comment to system attribute "KMPARAM_Comment" set cmt_notif to system attribute "KMPARAM_Notification" tell application "System Events" if cmt_notif = "Dialog" then display dialog cmt_title & return & return & cmt_comment buttons {"OK"} ¬ default button "OK" with icon 2 else if cmt_notif = "Spoken" then say cmt_title say cmt_comment end if end tell
Save the script (don't worry if you get an alert saying that KM modified it, that's normal; just accept overwriting the changes.). Then go back to KM, reload the plugin, and there you are:
Click Try and enjoy being told what time it is! ;-)
I don't see what else this action could do, so let's say we're done, here. And so...
What? Now who mentioned Growl?!
Never enough!
Oh, OK. It's a good idea, actually, because Growl can show the comment without interrupting or even slowing down the macro.
But the script gets more complex:
on run set cmt_title to system attribute "KMPARAM_Title" set cmt_comment to system attribute "KMPARAM_Comment" set cmt_notif to system attribute "KMPARAM_Notification" tell application "System Events" if cmt_notif = "Dialog" then display dialog cmt_title & return & return & cmt_comment buttons {"OK"} ¬ default button "OK" with icon 2 else if cmt_notif = "Spoken" then say cmt_title say cmt_comment else if cmt_notif = "Growl" then if (count of (every process whose bundle identifier is "com.Growl.GrowlHelperApp")) > 0 then tell application id "com.Growl.GrowlHelperApp" register as application "Loud Comment"¬ all notifications {"Loud Comment"} ¬ default notifications {"Loud Comment"} ¬ icon of application "Keyboard Maestro.app" notify with name "Loud Comment"¬ title cmt_title ¬ description cmt_comment ¬ application name "Loud Comment" end tell else display dialog "Growl is not running!" buttons {"Cancel"} default button "Cancel" ¬ with icon 0 end if end if end tell end run
So, if you're a Growl user, replace the script with this one, and add the "Growl" item to the menu's contents in the plist (you know how to do that, now, right?). And boom:
Happy now? :-)
Right, we're done, then! So Ladies and Gentlemen, we have just created a brand new Action for Keyboard Maestro! Can you believe that? When you think KM could hardly get any better, Peter always comes up with new mind blowing stuff! Kudos to him!
Sharing
"But wait, how do I share my plugin?", you ask. Well, again it's a piece of cake. But before we come to that, if you want to share your plugin it's time to put your name on it.
There are three more optional key/value pairs you can add to the plist to sign and polish your work: Author, URL and Help. So in my case that would give:
(...) <key>Results</key> <string>None</string> <key>Author</key> <string>Philippe Martin</string> <key>URL</key> <string>http://flipmartin.net/</string> <key>Help</key> <string>A comment action with notification options</string> (...)
If you set all or some of these attributes , they'll be displayed in a tooltip when your action is moused over in the Third Party Plug Ins folder:
Right! Now that it's ready, how do you share your plugin, then? You won't believe how simple it is: just zip your plugin's folder (Loud Comment
, in this case) and distribute the zip. That's it!
As a user, installing a plugin is just as easy: drop the zip onto KM's icon, and it will be installed automatically.
And finally, to uninstall a plugin, just delete its folder from the "Keyboard Maestro Actions" folder, and restart KM and KM Engine (or run the Reload Plugins macro if you have created it).
What next?
Well, the limits are your imagination and your scripting skills. So it all depends on you. Have fun!
You can find the official plugin documentation here:
http://www.keyboardmaestro.com/documentation/6/pluginactions.html.
And you can download some examples from here:
http://www.keyboardmaestro.com/main/third-party-actions.
And again you can download the Loud Comment plugin from here.
Conclusion
This concludes this tutorial. Thank you for reading this far! I hope it will have shown you that creating your own plugin is not that hard, if you know a bit of scripting. And if you know a lot of scripting, I hope it will have given you a hint on how powerful this stuff is.
And finally, thanks to Peter N. Lewis for this fantastic application, and for his patience with questions and feature requests! :-)
Appendix
Here's the full version of the final plist:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Name</key> <string>Loud Comment</string> <key>Title</key> <string>%Param%Title%</string> <key>Script</key> <string>comment.scpt</string> <key>Icon</key> <string>blueball.png</string> <key>Results</key> <string>None</string> <key>Author</key> <string>Philippe Martin</string> <key>URL</key> <string>http://flipmartin.net/</string> <key>Help</key> <string>A comment action with notification options</string> <key>Parameters</key> <array> <dict> <key>Label</key> <string>Title</string> <key>Type</key> <string>TokenString</string> </dict> <dict> <key>Label</key> <string>Comment</string> <key>Type</key> <string>TokenText</string> </dict> <dict> <key>Label</key> <string>Notification</string> <key>Type</key> <string>PopupMenu</string> <key>Menu</key> <string>None|Dialog|Growl|Spoken</string> <key>Default</key> <string>None</string> </dict> </array> </dict> </plist>
Comments
Previous Comments
Below are existing comments.
Comment by Stephen
1. Stephen commented :
Can not thank you enough for the time and effort you put into this very helpful step by step process!
Comment by FlipMartin
2. FlipMartin commented :
Cheers. I'm glad to know some people find it useful!
Please Log in if you wish to comment.