There’s not a lot to do, but the documentation is (1) old, (2) not maintained anymore, (3) very wordy. The steps involved are actually very simple. I’ll go through them and provide detail and links for further reading.
Open the Script Editor app. You’ll want to use that for test-driving your app.
Enable AppleScript in Your App
The steps from the now archived docs are:
- You need an
.sdeffile with the script command definitions,
- reference this file in your
- enable AppleScript in your
First, create a script definition file ending in
.sdef. It usually has the same name as your app, but you can pick whatever you want. For TableFlip, this file would be
TableFlip.sdef, for The Archive I picked
Xcode won’t help you with a template here, so you might as well use
touch from the Terminal or your favorie text editor and drag the file into your project later.
This is the bare minimum container boilerplate:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"> <dictionary xmlns:xi="http://www.w3.org/2003/XInclude" title="YOUR APP NAME HERE Terminonoly"> </dictionary>
title with the name of your app plus the word “Terminology”. That appears to be a convention.
Add this file to your Xcode project and make sure it’s part of the app target. You may want to double-check that the target’s Build Phases > Copy Bundle Resources now contains your
Second, open your app target’s
Info.plist in Xcode and add two new rows:
- In the first row, enable AppleScript support. Set the key to
NSAppleScriptEnabled(will become “Scriptable”) and the value to
YES(it’s a boolean Xcode will recognize).
- In the next row, define which
.sdeffile exposes available commands. Set the key to
OSAScriptingDefinition(will become “Scripting definition file name”) and the value to the file name of your
.sdef, e.g. “
TableFlip.sdef”. No need to specify a path.
Now build your project in Xcode.
Reveal the build product in Finder and drag the app bundle onto the Script Editor. When you drag app bundles onto the Script Editor, it’ll show a browsable scripting definition, aka the terminology your app exposes.
Since the boilerplate is empty, this should also be empty. For now.
Add default Cocoa commands to e.g. quit your app
How do you get started with defining script commands? – Thanksfully, there’s a default set of commands that is provided as a quick start.
You can open it in Xcode straight from the Terminal:
It looks like this at the moment of writing on my machine:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"> <dictionary title="Standard Terminology"> <suite name="Standard Suite" code="????" description="Common classes and commands for all applications."> <command name="open" code="aevtodoc" description="Open a document."> <direct-parameter description="The file(s) to be opened."> <type type="file"/> <type type="file" list="yes"/> </direct-parameter> <result description="The opened document(s)."> <type type="document"/> <type type="document" list="yes"/> </result> </command> ... lines 19--263 follow ... </suite> </dictionary>
That’s the same structure as the boilerplate from above. You see that inside of a terminology
<dictionary> element, there is a
<suite> element that contains many
.sdef can only have 1 root level
<dictionary> element, only one!, but that can contain multiple suited to group different purposes.
If you want, copy the whole code from the opening
<suite> until the closing
</suite> into your
.sdef file. Note that you will want to have a look at each command and change it so it fits your app. Not everything from the
CocoaStandard.sdef makes sense.
Let’s look at an example to see how you can customize this.
Customize the standard Quit command
If you look at the definition of the “quit” command in the
CocoaStandard.sdef file, you’ll see this (broken onto multiple lines to preserve space):
<command name="quit" code="aevtquit" description="Quit the application."> <cocoa class="NSQuitCommand"/> <parameter name="saving" code="savo" type="save options" optional="yes" description="Should changes be saved before quitting?"> <cocoa key="SaveOptions"/> </parameter> </command>
In my note-taking app The Archive, there’s no notion of manual saving. Files are saved as you type. Yet the command definition above offers an optional parameter to the “quit” command. I don’t need that, so it has to go. I delete the whole
.sdef looks like this afterwards:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd"> <dictionary xmlns:xi="http://www.w3.org/2003/XInclude" title="YOUR APP NAME HERE Terminonoly"> <suite name="Standard Suite" code="????" description="Common classes and commands for all applications."> <command name="quit" code="aevtquit" description="Quit the application."> <cocoa class="NSQuitCommand"/> </command> </suite> </dictionary>
You can paste this into your
.sdef, too. Save the file, build the project, then drag the app bundle onto Script Editor again. It does not auto-update just because the underlying bundle did change during compilation.
Lo and behold – your first command appears!
Test your commands
With Script Editor still open, create a new document. That’ll be the scratchpad for AppleScript interaction.
Use the power of the new “quit” command to see how things work.
tell application "THE APP NAME" to quit
You have to replace
THE APP NAME with your app’s name. It appears to work well with the product name. In my case, that’ll be
The Archive with a space, or
TableFlip without one.
First, run the app from Xcode so it is open.
Then execute the script.
Did your app close? If so, congratulations!
If not, it could be because you typed
# Warning: will not work! tell "THE APP NAME" to quit
That’ll send the “quit” message to the string. You want to send it to the application with that name.
Where to go from here
I think it’s a good first step to redact the
CocoaStandard.sdef to fit your app.
Heads up: Removing commands like the “quit” command from your app’s
.sdef will actually not disable them. The app still responds to the standard messages; apparently, these are inherited and the suite with the same name and identifier can be overridden. So the commands just don’t show up in the Script Editor terminology view.
When you have cleaned up the “Standard Suite”, you can start adding your own.
To prototype your app’s scriptability, the archived docs recommend you start with the
.sdef file first and don’t actually implement any functionality. Instead, you write test scripts in Script Editor and hit the “Compile” button. That’ll verify the syntax and show errors if you mistyped the invokation in the script, or botched the definition in you
I really like the idea of outside-in development of the syntax to see what kind of interactions you will want, and then you figure out how to create the appropriate command implementations.
You will be putting your commands into your own
</suite> node, with a unique name and a unique 8-character identifier. Whatever that is for, and why-ever that is limited to 8 characters. Legacy, I guess. We’ll have a look at custom command creation in another post.
Please note that you need to run your app from Xcode before you execute a Script Editor script. If you don’t, macOS’s launch services may figure that the best fit for an application of that name is in you
/Application folder. Run your app from Xcode to make this debug version occupy the name and receive the AppleScript events.
- Excellent example of a custom script command suite on StackOverflow.
- How to turn on script debugging to show command handling in the Xcode console output.
- Implementing a Scriptable Application from the archived docs.
Receive new posts via email.