Controlling Your Mac with AppleScript and Java
Pages: 1, 2
NSAppleEventDescriptor: Listening to 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!
Multiple Replies
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.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 40 of 40.
-
NSAppleScript apparently crashes in Leopard
2009-03-28 17:11:47 Chris_G [Reply | View]
-
NSAppleScript apparently crashes in Leopard
2009-03-28 19:18:53 Chris_G [Reply | View]
Okay, for anyone (anyone?) who stumbles upon this...
I have dispensed with NSAppleScript because of the horrible crash bug, and I am successfully accomplishing this by using Java (running BeanShell script) that writes to a javascript file in /tmp/, then uses
rtime = Runtime.getRuntime() and
rtime.exec()
to execute the osascript command with a compiled AS script in the same folder (which just runs the javascript file in AE). Simple!
-
great stuff - can we get more event-specific?
2006-01-22 15:52:01 Chris_G [Reply | View]
Thanks to this great little article (nice work, Scott) I am doing interapplication communication between a Java app (jEdit) and Adobe After Effects. This means I'm using Java, AppleScript and JavaScript like they're old friends.
However, I suspect that what I'm doing could be done more elegantly, because I am putting stuff on the clipboard and accessing that in order run the JS as a "doScript" event in After Effects.
How about sending a specific Apple Event (code 'misc', id 'dosc') with an string argument (the JS code) to a specific app that supports the doScript event? I've been trying to work this out and I'm having trouble getting my head around all the steps required.
-
really cool article
2005-06-18 18:44:21 jreprogle [Reply | View]
Cool in the sense of java/applescript development. Maybe not cool in the "I just won the super bowl" sense, but cool nonetheless.
I read the article and spent the next couple of days integrating Java and Applescript together with iTunes. You see, I have a rackmount linux server with no sound card. However, it has an X10 cm11 unit being monitored with a program called ppowerd. ppowerd can listen to X10 remote control input and execute actions based on which signals it receives. Rather than spending actual money on a sound card for the linux box, I thought, "why not make a java program on the mac which will take output from ppowerd and play music when the X10 remote control buttons are pressed". I have 8 buttons relating to different playlists.
You may think this is a very "Rube Goldberg" approach, but it works. The interface is a very simple TCP command interface, but I can make it speak arbitrary text, play playlists, etc.
If anyone is interested, let me know and I can post the ppowerd configuration/perl scripts/java code/Makefile, etc.
-
Wrap the tell in a timeout seems to fix the hang
2004-12-16 15:00:12 mikevh [Reply | View]
I found a solution written here
You wrap the tell in a timeout block to fix the hanging issues, its the only thing thats worked for me.
-
What about date parameters?
2004-10-28 08:40:15 tomso22 [Reply | View]
I've tried this example (which worked nicely) but then tried to return a list of dates rather than a list of strings. How do I read these at the java end?
-
Anybody try to run that in Servlet?
2004-06-22 14:25:48 honghong [Reply | View]
it seems not working properly, any ideas? I can not get any return value from applescript result.
-
Panther problems
2003-12-15 04:38:33 anonymous2 [Reply | View]
I just updated to Panther and now I have the problem, that the AppleScripts I execute from Java take one minute to run. Before I took not even a second.
Scripts like "get 'hello'" will execute very fast, but if I have to add an "tell application ..." statement, it takes minutes until the result is returned.
Does anyone have an idea, what the problem is?
thanx
chaos -
Panther problems
2004-05-11 22:08:36 jtangney [Reply | View]
Yup. Me too.
I have a completely clean Panther install, and the example app takes around a minute or more to run. I put some debugging code in there, as well as acompilecall, but nothing speeds it up. Theexecuteis what takes forever.
I was hoping that Java + AS woule be useful, but this is completely unusable. Very disappointing. :-(
--johnt
-
Panther problems
2003-12-17 13:22:04 sdwr98 [Reply | View]
I'm not seeing this problem... I did an Archive and Install of Panther, then got all the latest software updates.
Which example are you trying to run? -
Panther problems
2004-01-18 18:11:53 coderanger [Reply | View]
I am seeing the same thing. I even modified your code to compile a text script and execute it. A simple finder directory item list takes over 2 minutes real time when I execute using "time" to measure it. I an runnign on a pristin 12" powebook that same with Panther installed. I am going to try doing and exec of 'osascript' to see if that is better.
Here is my code (modified from yours). It takes one command-line arg--a text fiel that is an Applescript.
public class RunScript
{
public static void main(String[] args)
{
// shared NSApplication
NSApplication.sharedApplication();
// read in script file
String scriptText = "";
try {
BufferedReader inStream = new BufferedReader(new FileReader(args[0]));
String inputLine;
while ((inputLine = inStream.readLine()) != null) {
scriptText += inputLine + "\n";
}
} catch (IOException e) {
System.out.println("IOException: " + e );
System.exit(1);
}
// Script Object creation and execution
NSAppleScript scriptObj = new NSAppleScript(scriptText);
// dict for script exec errors
NSMutableDictionary errorInfo = new NSMutableDictionary();
// compile
boolean compileResult = scriptObj.compile(errorInfo);
System.out.println("Compile status:" + compileResult);
// execute
System.out.println("Running script...");
NSAppleEventDescriptor aeResult = scriptObj.execute(errorInfo);
// status report
System.out.println("Script results complete..");
if (aeResult != null ) {
int itemCount = aeResult.numberOfItems();
System.out.println("Found " + itemCount + "items in aeResult.");
// print results
NSAppleEventDescriptor subDescriptor;
for(int i = 1; i <= itemCount; i++)
{
subDescriptor =
aeResult.descriptorAtIndex(i);
System.out.println(" " + subDescriptor.stringValue());
}
}
else {
System.out.println("aeResult was null.");
}
// status
System.out.println("Script run complete."); }
}
-
Panther problems
2004-04-14 18:01:03 TToolsSeth [Reply | View]
I'm having the same experience. A script that used to work now takes a really long time. I've discovered that it's related to timeout and that the script will return in 2 * timeout seconds (default timeout is 1 minute, which is why you see the 2 minute duration). If you wrap your script in "with timeout of 10 seconds ... end" it will only take 20 seconds to execute.
Is there any forum for reporting bugs with the Applescript glue code?
-
anybody thinking in Quark-Applescript-Java?
2003-10-27 04:42:29 flashride [Reply | View]
Hello All,
When i read this, i´m thinking about the combination of these three.
You could have so much power, combining Java with Quark. I think this would be also worth a nice article here ;-) For my shame i´m too unexperienced for that, i´m just a beginner ....
You could have then open doors to sql, xml (it seems to be quite lame what quark has done in xml support) and and and... what a nice publishing world it could be!
If anybody has interest/suggestions???
cheers, Dirk
- i´m a multi.media designer/developer with interests in publishing (Quark, Flash, Java,... ) -
anybody thinking in Quark-Applescript-Java?
2003-12-19 06:58:14 bradrice [Reply | View]
I'm using a plug-in for Quark called DesignMerge that allows merging database like copy with Quark Templates. Uising a combinatin of Perl script and applescript it is now able to be launced and merged from a web interface allowing customers to personalize pre-tagged DesignMerge Quark documents templates. Then Applescript is called again to return a pdf to the browser to receive a proof. I agree XML in Quark is lame, that is why we are using DeisgnMerge. Hopefully it will improve. -
anybody thinking in Quark-Applescript-Java?
2003-12-19 06:57:10 anonymous2 [Reply | View]
I'm using a plug-in for Quark called DesignMerge that allows merging database like copy with Quark Templates. Uising a combinatin of Perl script and applescript it is now able to be launced and merged from a web interface allowing customers to personalize pre-tagged DesignMerge Quark documents templates. Then Applescript is called again to return a pdf to the browser to receive a proof. I agree XML in Quark is lame, that is why we are using DeisgnMerge. Hopefully it will improve.
-
Ant Farm failute
2003-10-05 01:59:57 anonymous2 [Reply | View]
Tried installing Ant Farm and running the example file. Got this result:
[ip133:local/ant/example] bfr% ant
Buildfile: build.xml
BUILD FAILED
Target `osacompile' does not exist in this project.
Total time: 6 seconds
-
Error -1750
2003-06-12 07:41:11 sdwr98 [Reply | View]
Hello Everyone,
I have gotten a couple of comments from readers who at one point were able to run the examples just fine but are now getting an NSAppleScriptErrorNumber = -1750 error. Is anyone else getting this problem? It seems to have cropped up for me after upgrading to 10.2.6 - but I would like to hear from anyone else that is having this problem.
Regards,
Scott Rankin -
Error -1750
2003-06-12 13:10:47 pcbeard [Reply | View]
Yes, I see the same problem. Something in the way Cocoa works in Mac OS X 10.2.6 has triggered this. A simple work around is to add the following code to your application:
import com.apple.cocoa.application.NSApplication;
then
NSApplication.sharedApplication();
before you use the NSAppleScript class. -
Error -1750
2003-06-14 08:29:58 pcbeard [Reply | View]
The technical reason this has broken is that the Carbon.framework is no longer automatically loaded into a Java program running from the command line. I have another workaround whereby I dynamically load the library /System/Library/Frameworks/Carbon.framework/Carbon using the NSAddImage() API, and this also allows programs using NSAppleScript to work.
-
Same error
2003-06-05 15:28:59 ijames [Reply | View]
I am receiving the very same error with the second part of the example. I am very excited to see how this will work. I was sad to see the error even after cutting and pasting the other poster's code for analysis. Any help would be greatly appreciated.
Thanks in advance
import com.apple.cocoa.foundation.*;
public class AppleScriptTest
{
public static void main(String[] args)
{
String script = "tell application \"Finder\" \n"
+ " get the name of the first 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);
String resultString = results.stringValue();
System.out.println("The first item is:"
+ resultString);
System.out.println("Yay AppleSscript!");
}
}
-
what are the requirements to make this work
2003-06-03 01:19:48 anonymous2 [Reply | View]
It looks like all you have to do is run the javac on the command line with the right classpath to make it work and import the foundation package.
I am trying to add this to a project of mine that started as a project builder java swing application and it seems to not work very well. I am getting the error as it instantiates the class i created.
[LaunchRunner Error] CardAdmin.main(String[]) threw an exception:
java.lang.NoClassDefFoundError: com/apple/cocoa/foundation/NSAppleScript
Should i be using cocoa-java, or does the more pure java work. Is there something i can do to specify in my target where the classes should be, or should i add them to my project.
-
new in 10.2?
2003-05-14 14:31:25 anonymous2 [Reply | View]
I can't find information about this anywhere, but my 10.1 system has no NSAppleScript class. I assume this is a 10.2 feature; can someone confirm that?
If so, could I copy someone's NSAppleScript.class file to my 10.1 machine? Or wouldn't that work, since the corresponding Cocoa class still wouldn't exist?
-
Slow?
2003-03-10 14:09:57 anonymous2 [Reply | View]
Is it just me, or is this executing VERY slow? Takes like 5 seconds for me to run any of the applications.
Even if I just do a Beep(1) in the script...
As if applescript has to load or something. I just get this when calling applescript from java/obj.c apps, "regular" applescripts execute "speedy" as supposed. -
Slow?
2003-06-12 05:31:06 senjaz [Reply | View]
I've seen this too. Whilst in the debugger you can see that at the point you call execute (Java), executeAndReturnError (Obj-C) a bunch of shared libraries end up being loaded to support the AppleScript runtime. I'm sure this is causing the delay but as yet don't know how to remedy it.
I've looking into updating the prebindings hoping that this would have some effect but it doesn't appear to be building a prebinding for my app.
If anyone else can shed some light on this let me know:
senjaz :at: chaos-engine.com
-
Great, but how?
2003-03-10 11:58:06 anonymous2 [Reply | View]
"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."
This is exactly the thing I wanted to do, but how do I set it up practically? I can't figure it out myself how to set it up so that I can access these script-apps from the web... -
Great, but how?
2003-03-10 12:00:37 sdwr98 [Reply | View]
I jumped the gun a little in the intro - I'm working on a second article that will go into how to set up a java-based web application that you can use to control things over the web.
Keep an eye out here in the next few weeks for part 2.
Scott
-
Java error
2003-02-26 15:15:48 m_keightley [Reply | View]
I to get the same 'null pointer' error. It's not the apple script I've run than seperately and it works fine.
-
String resultString = results.stringValue(); Error Code
String resultString = results.stringValue();
2003-02-25 23:01:44 thumbsup [Reply | View]
the line
String resultString = results.stringValue();
produces the following error when I run it in Terminal.
Exception in thread "main" java.lang.NullPointerException
at AppleScriptTest.main(AppleScriptTest.java:32)
Do you have any suggestions on how to remove this error. -
String resultString = results.stringValue(); Error CodeString resultString = results.stringValue();
2003-02-26 08:25:46 sdwr98 [Reply | View]
thumbsup,
I assume that this is running the second Java example, where you get the name of the first item on your desktop. Did the corresponding AppleScript run successfully?
Scott -
String resultString = results.stringValue(); Error CodeString resultString = results.stringValue();
2003-02-26 21:02:41 thumbsup [Reply | View]
Yes, the corresponding AppleScript ran successfully. -
String resultString = results.stringValue(); Error CodeString resultString = results.stringValue();
2003-02-27 15:47:55 sdwr98 [Reply | View]
Can you post or email me the entire .java file that's causing the error?
Thanks,
Scott -
String resultString = results.stringValue(); Error CodeString resultString = results.stringValue();
2003-02-27 18:18:02 thumbsup [Reply | View]
Scott,
I am including the entire .java file that is causing an error. Thanks for your help.
import com.apple.cocoa.foundation.*;
public class AppleScriptTest
{
public static void main(String[] args)
{
String script = "tell application \"Finder\" \n"
+ " get the name of the every item \n"
+ " 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!");
}
} -
String resultString = results.stringValue(); Error CodeString resultString = results.stringValue();
2003-02-27 20:50:42 sdwr98 [Reply | View]
I found the problem - on line 9 of your code above, you added an extra "\n" right after the word item. There should only be 2 line breaks in the string, after the word Finder and after the word desktop.
The whole string assignment line should read:
String script = "tell application \"Finder\" \n"
+ " get the name of the every item "
+ " in the desktop \n" + "end tell";
Let me know if this works for you,
Scott
-
How to do AppleScript in Pure Java
2003-02-25 17:34:47 anonymous2 [Reply | View]
If you don't want to bother with cocoa, it's also possible to use AppleScript in a Pure Java program. the key thing is the command line program "osascript", which takes in an applescript file (or stdin). Thus you can use the standard java "Runtime" class and pass in the script, followed by EOF, to stdin and it will do the same.
I use this sometimes to remotely control my mac without java, as you can ssh to the other box and type something like
tell iTunes to playpause








Well, I've been trying to figure out a way to use another technique because apparently, in Leopard, this will not work with garbage-collected apps (like jEdit I suppose) and will crash the host app. It is a "known bug" and Apple seems to be dragging their feet regarding this NSAppleScript bug.
Any clues as to what to do instead? If anything?
thanks