Controlling Your Mac with AppleScript and JavaEditor's note -- Even though there isn't a lot of formal support for Java in AppleScript, you can execute AppleScripts from Java. Why would you want to do this? Because by combining these technologies, you can easily interact with and control your Mac from remote locations. What follows is a look at how to set that up.
With the introduction of Mac OS X, Apple has made the Java language a first-class citizen in the Mac development world. The Cocoa framework ships by default with bindings for both Objective-C (a very well-designed language, started in the NeXT world) and Java. Developers who want to write a native OS X application can use either or both, for that matter, and they will look and feel the exact same way.
Apple has given Java developers total access to the Cocoa libraries and frameworks. This means that Java developers are no longer limited to the Sun-specified APIs for interacting with the operating system; Apple has opened up a whole new world of functionality to us.
Another incredibly powerful tool in the Mac OS X arsenal is AppleScript. Virtually every Apple-produced application, including the Finder, is controllable through AppleScript. iTunes, iPhoto, Mail: they're all scriptable. But AppleScript, while a fantastic language for running scripts locally, has minimal support for doing anything else, like sockets or serving web pages. This is where Cocoa and Java come in.
Cocoa has a class, NSAppleScript, which allows a Cocoa
application to interact with scriptable applications. So now we can
combine the power of Java with the power of AppleScript to make a remote
control center for any AppleScriptable application on your computer. In
this article, we'll examine the Cocoa classes that deal with AppleScript,
and then we'll create a simple web-based application that enables you to
control iTunes from any computer on your network.
There are two primary Cocoa/Java classes that we will be using when
interacting with AppleScripts: NSAppleScript
and NSAppleEventDescriptor. For
dealing with AppleScripts that send only, NSAppleScript is
all you need. Using NSAppleScript is extremely simple: in
its constructor, it takes a String containing the text of the AppleScript
that you wish to execute. You then can call the execute() method and the
AppleScript gets executed.
|
Related Reading AppleScript in a Nutshell
Table of Contents
Index Sample Chapter Author's Article Read Online--Safari Search this book on Safari: |
Before executing a script in Java, it's often helpful to try out the script in ScriptEditor first. The ScriptEditor application is found in /Applications/AppleScript/ScriptEditor; launch it and you will get a window that looks like this:
|
|
You can type the AppleScript in the top and any results will come back at the bottom. Here's a simple AppleScript that will make a new folder on your desktop:
tell application "Finder"
make new folder at desktop
end tell
Type this into the ScriptEditor, and press Run. You should see a new folder named "untitled folder" appear on your desktop:
|
|
Now let's see how to do this in Java. The first thing we want to do is
create an empty Java class as a framework. Open up your favorite text
editor. If you use Apple's Text Edit, be sure to go to the Format menu
and choose "Make Plain Text" or it will not let you save it as a .java
file. Here is the framework of the Java class:
import com.apple.cocoa.application.NSApplication;
import com.apple.cocoa.foundation.*;
public class AppleScriptTest
{
public static void main(String[] args)
{
// Our code will go here
System.out.println("Yay AppleScript!");
}
}
Copy or type this in to your file, and save it as
AppleScriptTest.java. Next we need to compile and run our framework. Do this
by opening up Terminal (if you're using Terminal to edit the file, press
Command-N to open up a new terminal window) and changing to the
directory where you have AppleScriptTest.java. Type the following
command at the prompt (all on one line):
% javac -classpath /System/Library/Java:. AppleScriptTest.java
This will compile your class. To run it, type the following:
% java -classpath /System/Library/Java:. AppleScriptTest
It should output "Yay AppleScript!".
|
|
Now we need to add the code to execute the AppleScript that we wrote above. When writing AppleScripts in Java, you should keep in mind that AppleScript needs the quotes and line breaks to understand the script. So we must use Java's escape characters for quotes (\") and newlines (\n) to make sure the script goes through properly. Here is the code to insert that will run a simple AppleScript:
// This line of code is necessary because
// of a change introduced with QuickTime 6.3
// This line loads the Cocoa libraries.
NSApplication.sharedApplication();
// This is the text of the AppleScript
String script = "tell application \"Finder\" \n"
+ " make new folder at desktop \n"
+ "end tell";
// This creates a new NSAppleScript
// object to execute the script
NSAppleScript myScript =
new NSAppleScript(script);
// This dictionary holds any errors that are
// encountered during script execution
NSMutableDictionary errors =
new NSMutableDictionary();
// Execute the script!
myScript.execute(errors);
You should compile and run your script again using the javac and java commands from above. Not only should you see "Yay AppleScript" in the Terminal, but if you look at your Desktop, you should see another new folder, too.
|
|
Next we turn to reading AppleScript replies.
|
In the code that we just wrote, we executed a script that performed
an action. The call to execute() actually did return a
reply, but we ignored it, because it was not important to our script.
Now we want to be able to execute scripts and do things with the output. The easiest replies to deal with are the ones that are just one single string. For example, if you type this script into ScriptEditor, and run it, you will get the name of the first disk on your desktop:
tell application "Finder"
get the name of the first item in the desktop
end tell
Mine returns "Greater Scott".
|
|
Now, let's do this in Java. We can use the same file, with a few tweaks. Change the script string to be
String script = "tell application \"Finder\" \n"
+ " get the name of the first item "
+ " in the desktop \n" + "end tell";
In order to get the reply back, we need to assign a variable to the
value that execute() returns. Make the line with
execute() read as follows:
NSAppleEventDescriptor results = myScript.execute(errors);
This will capture the AppleScript result, and allow us to access it via
Java. Add these lines after the call to execute():
String resultString = results.stringValue();
System.out.println("The first item is:"
+ resultString);
Compile and run your application, and you should see it tell you the name of the first item on your desktop. Congratulations! Your Mac is talking, and you are listening!
Right now we can ask AppleScript to return one value, and we can read that value. But that's pretty limiting. AppleScript has the concept of lists, so it stands to reason that we would want to be able to get back a list of things from AppleScript. Continuing on with the Finder interaction, we will now get a list of every file that sits on your desktop.
In ScriptEditor, write the following script:
tell application "Finder"
get the name of every item in the desktop
end tell
This should return you a list of everything (disks, files, folders, etc.) on your desktop. Now let's see how to parse this reply using Java.
The NSAppleEventDescriptor is a catch-all class that
represents how AppleEvents are represented internally. They can contain a
multitude of different types: enumerations, lists, strings, numbers, you
name it. There are several methods in the
NSAppleEventDescriptor class that allow you to determine the
type of descriptor; for now, we're going to just assume that it's either a
list or a string.
Edit your AppleScriptTest.java file, and change the script
variable to be the script from above:
String script = "tell application \"Finder\" \n"
+ " get the name of every item "
+ " in the desktop \n" + "end tell";
Edit the lines after the execute() method. We are no
longer interested in the string value of the descriptor. Instead we want
to get the sub-descriptors. These are accessible using the method
descriptorAtIndex().
You can tell the number of sub-descriptors using the numberOfItems()
method. Note that descriptors are indexed starting at index 1, not at 0 as
Java programmers are used to. Using these two methods, we can iterate over
the subdescriptors and print out the list of everything on the desktop.
Your entire main method should now look like this:
S// This line of code is necessary because
// of a change introduced with QuickTime 6.3
// This line loads the Cocoa libraries.
NSApplication.sharedApplication();
String script = "tell application \"Finder\" \n"
+ " get the name of every item "
+ " in the desktop \n" + "end tell";
// This creates a new NSAppleScript object
// to execute the script
NSAppleScript myScript =
new NSAppleScript(script);
// This dictionary holds any errors
// that are encountered during script execution
NSMutableDictionary errors =
new NSMutableDictionary();
// Execute the script!
NSAppleEventDescriptor results =
myScript.execute(errors);
// Print out everything on your desktop
System.out.println("Starting list of items "
+ "on the desktop: ");
int numberOfDesktopItems = results.numberOfItems();
for(int i = 1; i <= numberOfDesktopItems; i++)
{
NSAppleEventDescriptor subDescriptor =
results.descriptorAtIndex(i);
System.out.println(" " +
subDescriptor.stringValue());
}
System.out.println("Yay AppleScript!");
On my desktop, here are the results:
|
|
As you can see, for scripts that return a list of items, those items are contained as subdescriptors underneath the result descriptor. Depending on the complexity of the script, descriptors could be nested many levels deep.
Now you should be able to see some of the potential of combining AppleScript and Java. AppleScript gives you the fantastic power to interact with your applications in a way that is unmaginable when using plain Java, and Java gives you all the power of an enterprise-quality, full-fledged programming language.
Used together, you can set up your Mac as a sort of command center, and use Java to access it from anywhere. Think about editing your iCal calendars from your office, or controlling iTunes from another room in your house. Almost anything that you can do on your Mac, now you can do from somewhere else.
Scott D.W. Rankin is a Senior Software Engineer with Back Bay Technologies and a die-hard Mac fan(atic).
Read more AppleScripting Mac OS X columns.
Return to the Mac DevCenter.
Copyright © 2007 O'Reilly Media, Inc.