247

Chapter 18:

AppleScript, Etc.

The purpose of Newspeak was not only to provide a medium of expression
for the world-view and mental habits proper to the devotees of Ingsoc,
but to make all other modes of thought impossible.

- George Orwell, The Principles of Newspeak

One of the main purposes of scripting languages is to talk to other programs
or processes. On Unix systems, this is often called IPC(interprocess commu-
nication); Apple calls it IAC
1(interapplication communication). IAC lets
two programs share data, which typically includes a command, list, string,
number, Boolean, or file alias.

There are several components to the IAC architecture in Mac OS. The one
we will deal with here is Apple Events. Apple Events are a popular, high-
level form of IAC; most Mac OS applications have Apple Event capabili-
ties built in, so that they can communicate directly with each other.

Apple's proprietary scripting language, AppleScript, also speaks Apple
Events. In fact, when an application is commonly referred to as "Apple-
Scriptable", that really means that it is scriptable with Apple Events.
Thus, AppleScript is just one language that can "speak" Apple Events.

Many Mac OS scripting languages are called OSA(Open Scripting Archi-
tecture) languages. These languages can be embedded into applications
with a special component protocol; normally, these languages all speak
Apple Events. AppleScript is the most popular of the OSA languages.

IMAGE imgs/345.AppleScript01.gif

1A complete Inside Macintoshbook is devoted to IAC, most of it relating to Apple
Events. Obviously, we cannot cover all of Apple Events here in this book, but we
attempt to provide enough information to use Apple Events effectively with MacPerl.


IMAGE imgs/345.AppleScript02.gif

AppleScript

AppleScript is a very powerful tool. With various extensions,2it can be
enabled to perform almost any function (e.g., handling regular expressions).
It is particularly good at providing access to system resources (e.g., users and
groups, audio CD information, or monitor resolution and depth
3).

Calling AppleScript from MacPerl

The simplest way to do AppleScript-based IAC from MacPerl is through
the function
MacPerl::DoAppleScript(). The function is very simple; it
takes a single argument (the complete text of an AppleScript) and returns a
textual representation of whatever the AppleScript returns.
4

The following simple example opens the startup volume in the Finder.

$vol = MacPerl::MakePath((MacPerl::Volumes())[0]);
$script = <<EOS;
tell application "Finder"
open item "$vol"
end tell
EOS

print MacPerl::DoAppleScript($script)
or die("Could not run script\n");

Displays:

startup disk of Application "Finder"

Using AppleScript from MacPerl is quite slow , however.5Like Perl scripts,
AppleScripts must be (pre-)compiled before execution. To eliminate start-
up delays, AppleScripts are normally saved in a compiled format.

IMAGE imgs/345.AppleScript01.gif

2Called OSAXs (or OSAXen), these are stored in the Scripting Additionsfolder, which
is either in the System Folderor the Extensionsfolder.
3MacPerl can use an installed OSAX too, via Apple Events, using the Finder as the
target application.
4The AppleScript language will not be explained; the authors have neither the space
nor the inclination.
5You can speed up AppleScript calls from MacPerl using Mac::OSA.


IMAGE imgs/345.AppleScript04.gif

AppleScripts that are created by MacPerl scripts cannot take full advan-
tage of this optimization, however, because the AppleScript code is kept in
text format. So, time must be taken to compile the AppleScript before it can
be run, adding a significant amount of start-up delay.

Calling MacPerl from AppleScript

Communication can also go the other way, originating with an AppleScript
rather than MacPerl. In this case, the MacPerl script responds by means of
the
MacPerl::Reply()function as described in Chapter 12, The MacPerl
Package
. The script below could be executed from within Apple's Script
Editor
or a compatible AppleScript editor.
6

tell application "MacPerl"
return ¬
"Days until the year 2000: " & (Do Script "
use Time::Local;
$d1 = timelocal(0, 0, 0, 1, 0, 100);
$d2 = ($d1 - time()) / 60 / 60 / 24;
MacPerl::Reply(int($d2))
")
end tell

Displays (on April 1, 1998):

"Days until the year 2000: 640"

A MacPerl script invoked by the Do Scriptcommand can do pretty much
anything that a regular MacPerl script can do. If you need to use double
quotes in the MacPerl script, simply escape them (e.g.,
\"text\").

You can also embed AppleScript values into your Do Script event. We can't
do it as easily as we did with MacPerl's DoAppleScript function, which
evaluates the Perl variables before passing the string to the function, but
we can use concatenation in AppleScript to accomplish the same thing.

set mytext to "just another MacPerl hacker"
tell application "MacPerl"
return Do Script ¬

IMAGE imgs/345.AppleScript01.gif

6To "break" and continue a long line of AppleScript code across more than one line,
end each unfininished partial line of AppleScript code with the continuation charac-
ter,
¬, (formed by the sequence option-return in Script Editor).


IMAGE imgs/345.AppleScript06.gif

"MacPerl::Reply('" & mytext & "')"
end tell

Returns:

"just another MacPerl hacker"

Lists

Often, you might need a list of elements returned from MacPerl to Apple-
Script. The simplest way to do this is with delimited text that is joined by
MacPerl and split up by AppleScript.

tell application "MacPerl"
set myResult to Do Script ¬
"MacPerl::Reply(join ('|', ('a'..'g')))"
end tell

set AppleScript's text item delimiters to "|"
set myList to text items of myResult
set AppleScript's text item delimiters to ""
return myList

Returns an AppleScript list:

{"a", "b", "c", "d", "e", "f", "g"}

Or, going the other way, you might need an AppleScript list converted to a
MacPerl array. This can also be done with delimiters; in this example, we
use
Text::ParseWords. We remove the brackets with a regex before pas-
sing the text to
quotewords(), which uses ', 'as its delimiter.

use Text::ParseWords;
$script = <<EOS;
set myList to {"a", "b", "c", "d", "e", "f", "g"}
return myList
EOS
($result = MacPerl::DoAppleScript($script))
=~ s/^\{(.*)\}$/$1/;
@array = quotewords(', ', 0, $result);
print join(' ', @array);

Returns:


IMAGE imgs/345.AppleScript07.gif

a b c d e f g

Other OSA Languages

AppleScript may be the most popular of the OSA languages (largely
because it is included with every MacOS distribution) but it is not the only
OSA language. Other OSA-compliant languages are also available.

Frontier

Frontier is described by Userland as an automated Content Management
System, built around an object database, an integrated scripting language,
and an object-oriented website framework. The scripting language, User-
Talk, has a more "algebraic" syntax than AppleScript uses. That is, where
AppleScript's syntax tends to use English-like words such as
tell, Frontier
uses functions, parentheses, and argument lists.

#!perl -w
use Mac::OSA;
use Mac::Components;
use Mac::AppleEvents;
my($vol, $co, $script, $result);

$vol= MacPerl::MakePath((MacPerl::Volumes())[0]);
$co= OpenDefaultComponent
(kOSAComponentType(), 'LAND');
$script = new AEDesc('TEXT', <<EOS);
return appleEvent
(Finder.id, 'aevt', 'odoc', '----', alias("$vol"))
EOS
$result = OSADoScript($co, $script, 0, 'TEXT', 0);

print AEPrint($result);
AEDisposeDesc($script);
AEDisposeDesc($result);

Displays:

"startupDisk"

The Frontier application needs to be running before UserTalk can be called
from MacPerl. The
Mac::OSAmodule does most of the work here, and can be
used in a similar manner with any OSA language, like AppleScript.


IMAGE imgs/345.AppleScript08.gif

Apple Events

In our examples, a MacPerl script calls an AppleScript (or UserTalk script)
or an AppleScript calls a MacPerl script. In either case, Apple Events are
used to effect the interaction. The Apple Events themselves are well dis-
guised, however, by the programming interface. Although you are free to
use this interface in ignorance of the underlying structure, we suggest that
you accompany us on a brief overview tour of Apple Event structure.

Apple Events have two major components: attributes and parameters. Attri-
butes
are the portions of the event that define how it is to be used. Parame-
ters
are contained in data structures similar to Perl's hashes, with a key-
word
as the key and some data as the value of that key. Parameter key-
words are always composed of exactly four (eight-bit) characters.

Attributes

An attribute is composed primarily of an event IDand an event class(suite).
Also given as attributes are such items as the target applicationfor the
event (specified by its creator ID).

An event ID is like the name of a function in Perl; an event must be defined
in the program in order to use it. We cannot define arbitrary events for
another application; we can only use the events that application has
provided. These events all belong to some event class, or suite, much as
functions in Perl all belong to a particular class or package.

tell application "Finder"
open item "$vol"
end tell

To open an item, as we did in our earlier example, we would use the event
ID
odoc, which is in the class aevt. The target application is the Mac OS
Finder, which has
MACSas its creator ID. For example:

%ae = (
target => 'MACS',
class=> 'aevt',
id=> 'odoc',
...
);

# creator ID
# event class
# event ID


IMAGE imgs/345.AppleScript09.gif

Parameters

Parameters often describe information about an application's built-in
classes
7and propertiesof those classes. For instance, an application might
have a window class; the name of the window and its position and size on
the screen would be properties of that class.

A parameter might also be a simpler type of data, such as a string or file
alias. Each parameter is named by a unique keywordand has some data
assigned to it, like a Perl hash. Data is normally in the form of a recordor a
list, analogous to Perl scalars and arrays. Often, the most important data in
an event is passed through the direct objectparameter; the direct object
parameter, for instance, has the special keyword '
----'.

Any self-contained part of an Apple Event is a descriptor. This includes
entire events with attributes and parameters, a standalone parameter, a
record, or a list.

An Apple Event Example

Once we have our attributes and parameters, we can build an AppleEvent
using the
AEBuildAppleEvent()function which is part of the Mac::
AppleEvents
package. The event that is returned from that function can
then be sent via the
AESend()function. The AEPrint()function can be
used to print out text representations of events and descriptors.

The example below uses AEBuildAppleEvent()to do exactly the same
thing our first example did through the AppleScript interface (opening the
startup volume in the Finder). It's a lot more complicated than the previous
version, however (although it runs much faster). Don't wory too much about
the syntax of the example; it's largely here to show you that TMTOWTDI.

We set up a hash (%ae) to store our attributes and parameters for use in the
AEBuildAppleEvent()function. Our build function takes all parameters
as one argument, with additional arguments to the function if the paramet-
ers call for them, much like
sprintf()in Perl. We make the value of $ae
{'params'}
an anonymous hash, so we can add as many values as neces-
sary. In this case, the
TEXT(@)notation calls for an additional argument.

IMAGE imgs/345.AppleScript01.gif

7These are not the same as the event class. They are similar in concept to Perl classes.


IMAGE imgs/345.AppleScript11.gif

In this example, there is only one named parameter sent to this event: the
direct object parameter. The direct object parameter is passed an Apple
Event object specifier record, which is a type of descriptor record.

We're not going to get into the nitty gritty details of how this all works
here; that discussion is beyond the scope of this book. Suffice it to say that
the example below is identical in functionality to the first example we
gave in this chapter. Here goes!

#!perl -w
use Mac::AppleEvents;
my(%ae, $vol, $event, $reply);

$vol = MacPerl::MakePath((MacPerl::Volumes())[0]);
%ae = (
target => 'MACS',# creator ID
class=> 'aevt',# event class
id=> 'odoc',# event ID
params => [
"'----':obj " .# direct object keyword
"{want:type(cobj), " .# 4-char class ID
"from:null(), " .# object's container
"form:enum(name), " .# form of object data
"seld:TEXT(\@)}",# actual object data
$vol# the startup volume
]
);

# now build the event
$event= AEBuildAppleEvent(
$ae{'class'},# event class
$ae{'id'},# event ID
'sign',# appl. signature
$ae{'target'},# creator ID
0, 0,# end of this part
@{$ae{'params'}} # parameter list
) or die($^E);
$reply = AESend($event, kAEWaitReply) or die($^E);
print AEPrint($event), "\n";
print AEPrint($reply), "\n";
AEDisposeDesc($event);
AEDisposeDesc($reply);


IMAGE imgs/345.AppleScript12.gif

When run, this displays:

aevt\odoc{'----':obj {want:type(cobj),
from:'null'(), form:name, seld:"HD:"},
&inte:cans, &timo:3600}
aevt\ansr{'----':obj {want:type(prop),
from:'null'(), form:prop, seld:type(sdsk)}}

Pretty complex, isn't it?

If you're interested in learning more about Apple Event structure, and in
building Apple Events in this fashion, we recommend:

  • macperlcat.pod, on the CD-ROM
  • Inside Macintosh, especially Interapplication Communication
  • the various useful utilities such as AETE converter, on the CD-ROM

Copyright © 1997-1998 by Prime Time Freeware. All Rights Reserved.