NAME
Mac::Glue - Control Mac apps with Apple event terminology
SYNOPSIS
use Mac::Glue;
my $glue = Mac::Glue->new('Finder');
$glue->open( $glue->prop('System Folder') );
# see rest of docs for lots more info
DESCRIPTION
*** THIS IS BETA SOFTWARE. CAVEAT SCRIPTOR. ***
Mac OS apps speak to each other with a lingua franca called Apple events. The most common way to do Apple events (aside from doaing them in a precompiled application with C, Pascal, etc.) is with AppleScript. Other languages can do Apple events too, like Frontier and even Python. But we like Perl.
MacPerl has for a few years had an interface to Apple events, with the Mac::AppleEvents module, which is the basis for everything we'll do here. Mac::AppleEvents::Simple was made to simplify the process of doing Apple events in MacPerl, but even that can be too much trouble to use. One has to find out the class and event IDs, find out the keywords and data types for each parameter, etc.
So the vision was born for a framework that wouldn't take much significant work. An application's AETE resource would provide the names to match to the cryptic four-character codes we had been using. Compare.
- Raw Mac::AppleEvents method
-
use Mac::AppleEvents; $evt = AEBuildAppleEvent('aevt', 'odoc', typeApplSignature, 'MACS', kAutoGenerateReturnID, kAnyTransactionID, "'----': obj{want:type(prop), from:'null'()," . "form:prop, seld:type(macs)}" ) or die $^E; $rep = AESend($evt, kAEWaitReply) or die $^E; AEDisposeDesc($evt); AEDisposeDesc($rep);
- Easier Mac::AppleEvents::Simple method
-
use Mac::AppleEvents::Simple; do_event(qw(aevt odoc MACS), "'----': obj{want:type(prop), from:'null'()," . "form:prop, seld:type(macs)}" );
- Cool Mac::Glue method
-
use Mac::Glue; my $glue = Mac::Glue->new('Finder'); $glue->open( $glue->prop('System Folder') );
The latter is much simpler to understand, to read, to write. It leverages the user's understanding of AppleScript. And it is just more natural.
There are downsides. Mac::Glue is less powerful than the Mac::AppleEvents raw interfaces, because it offers less flexibility in how events are called. It is also slower to start a script, because the glue structures need to be loaded in. However, once a script has started, a difference in speed from the raw interfaces should be minimal (though not a lot of testing has been done on that). With the code above, on a PowerBook G3/292, running Mac OS 8.6:
Benchmark: timing 100 iterations of glue, glue2, raw, simple...
glue: 10 secs ( 9.98 usr 0.00 sys = 9.98 cpu)
glue2: 8 secs ( 8.35 usr 0.00 sys = 8.35 cpu)
raw: 8 secs ( 7.88 usr 0.00 sys = 7.88 cpu)
simple: 7 secs ( 7.50 usr 0.00 sys = 7.50 cpu)
The "glue2" entry is the same as "glue" entry, but it creates a glue object only once instead of each time through, cutting down on the overhead. It appears that Mac::Glue is a bit slower than the other methods, but not substantially, and it is cooler and easier. The one place where performance is the biggest problem is on initial execution of the program, but once it starts it is plenty fast. We'll work to cut down that start time, too.
So, now that you are convinced this is cool, let's continue.
Creating a Glue
In order to script an application with Mac::Glue, a glue must be created first. For that, the application is dropped on the gluemac droplet. A distribution called Mac::AETE, created by David Schooley, is used to parse an application's AETE resource, and the glue is written out to a file using Storable, DB_File, and MLDBM. Glues are saved in $ENV{MACGLUEDIR} (which is defined when Mac::Glue is used if it is not defined already). By default, glues are stored in :site_perl:Mac:Glue:glues:.
All glues have access to the global scripting additions and dialect information. Glues for these must be created as well, and are created with the gluescriptadds and gluedialect programs, which are similar to the gluemac program. They are saved in "$ENV{MACGLUEDIR}additions:" and "$ENV{MACGLUEDIR}dialects:".
Along with the glue file is a POD file containing documentation for the glue, listing all the events (with parameters), classes (with properties), and enumerators, and descriptions of each.
Using a Glue
The first thing you do is call the module.
use Mac::Glue;
Then you create an object for your app by passing the new
function the name of the glue (you may include or omit underscores in the name if you like).
my $glue = Mac::Glue->new('My App'); # or My_App
You can also pass in additional parameters for the type of target to use. For PPC ports, you can do this:
my $glue = Mac::Glue->new('My App', ppc => 'My App Name',
'Server Name', 'Zone');
You may also specify a process serial number:
my $glue = Mac::Glue->new('My App', psn => $psn);
Note that $psn
should be a regular long integer, and will be packed into a double long behind the scenes. If this confuses you, don't worry about it; the values returned from the Mac::Processes module are good to pass back in as $psn
.
You can also pass a path to an application:
my $glue = Mac::Glue->new('My App', path => $path_to_file);
New for Mac OS 9, you can send events over TCP/IP:
my $glue = Mac::Glue->new('My App', eppc => 'My App Name',
'some.machine.com');
Once you have your glue set up, you start calling events, as they are documented in the POD file for the glue. The events can be called case-insensitively, with the exception of those that match the names of the special methods (see "Special parameters and methods"). In that case, since the special methods are in all caps, the event methods can be called case-insensitively except for all caps. e.g., for an event named reply
, it could be called with:
$glue->Reply;
$glue->reply;
$glue->RePLY;
However, it could not be called with $glue->REPLY
, since that is reserved.
All applications respond to events differently. Something that works for one application might not work for another, so don't use any of these examples as a way you should script a specific application. They are just hyopthetical examples, for the most part.
Events sometimes accept parameters, sometimes they don't. The primary parameter of most events is a special parameter called the direct object parameter. In your event call, pass the data for that parameter first:
$glue->open($file);
Other parameters must be named and must be provided as key-value pairs, with the key as the name of the parameter, and the value as the parameter's data:
$glue->open($file, using => $myapp);
Note that the direct object parameter is the only parameter that doesn't need a name in front of it, and must come first in the list if it is supplied at all.
Mac::Glue will attempt to coerce passed data into the expected type. For example, if open
expects an alias, the file specification in $file
will be turned into an alias before being added to the event.
Each datum can be a simple scalar as above, an AEDesc object, an AEObjDesc object (returned by obj
, prop
, and event methods), an AEEnum object (returned by the enum
function), or an array or hash reference, corresponding to AE lists and records. In this example, we nest them, with an arrayref as one of the values in the hashref, so the AE list is a datum for one of the keys in the AE record:
$glue->make(new => 'window', with_properties =>
{name => "New Window", position => [100, 200]});
Events return direct object parameters, turned into suitable data for use in the program. Aliases are resolved into file specifications, AE records and lists are turned into Perl hashes and arrays (recursively, for nested lists), etc.
my @urls = $sherlock->search_internet('AltaVista',
'for' => 'Mac::Glue');
AE objects (which will be discussed later) are returned as AEObjDesc
objects, so they may be used again by being passed back to another event.
my $window_object = $glue->get( window => 1 );
$glue->save($window_object);
This allows AppleScript-like loops:
my @selection = $glue->get( $glue->prop(selection => of => window) );
my @owners;
for my $item (@selection) {
push @owners, $glue->get( $glue->obj(cell => 'Owners' => $item) );
}
Some objects may allow an easy way to get a human-readable form, with the as
parameter:
my $item = $glue->get( file => 1, as => 'string' );
Errors are returned in the special variable $^E
, which should be checked immediately after an event call.
$glue->close(window => 1);
if ($^E) {
warn "Couldn't close window: $^E\n";
}
Or, if a value is expected and none is returned:
my $file = $glue->choose_file('Select a file, please.')
or die "No file chosen: $^E";
Checking $^E
only works if the error returned is an error number. If it isn't, the actual error is available from the reply event, which can be accessed by using the RETOBJ
parameter (described below in "Special parameters and methods").
Creating object specifier records
This is one of the more complex parts of Apple events, and it is only partially implemented (though full implementation is expected eventually, and most of it is implemented now).
Object specifier records are created by the obj
method, and have four components to them.
- class
- container
- form
- data
The class and data are passed as key-value pairs, like in AE records or parameter lists. The form and the type of the data are determined by the glue data or a good guess. The container is determined by the order of the key-value pairs: each pair is contained by the pair or object that follows it.
my $obj = $glue->obj(file => 'foo', folder => 'bar', disk => 'buz');
"file", "folder", and "disk" are the classes. The data are "foo", "bar", and "baz". The form of each one is formName, and data type is typeChar (TEXT). As for containers, the last pair is the container of the middle pair, which is the container of the first pair. Easy, right? That's the idea.
The primary forms are types, names, unique IDs, absolute positions, relative positions, tests, and ranges. Normally, text data has form name and type TEXT. Integer data has absolute position form, and integer type. The obj_form
function accepts three parameters, which allows you to set the form and data, or form, type, and data, in case you want to send data different from how Mac::Glue would guess.
These two are the same, since in the second case, the other is assumed:
use Mac::Glue ':glue';
$obj1 = $glue->obj(window =>
obj_form(formAbsolutePostion, typeLongInteger, 1));
$obj2 = $glue->obj(window => 1);
Special constants are exported that specify relative positions and absolute positions.
$first = $glue->obj(file => gFirst, property => 'Desktop');
$second = $glue->obj(file => gNext, $first);
for ($first, $second) {
print $glue->get($_, as => 'string');
}
of
and in
are synonyms of property
:
$glue->obj(file => gFirst, property => 'Desktop');
$glue->obj(file => gFirst, of => 'Desktop');
$glue->obj(file => gFirst, in => 'Desktop');
The "as" parameter above has a form of type, such as:
obj_form(formPropertyID, typeType, 'string');
Then "string" is turned into a four-character ID behind the scenes (in this case, it is "TEXT").
A special method called prop
is for specifying properties. These are equivalent:
$glue->obj(property => 'Desktop');
$glue->prop('Desktop');
Descriptor types for object specifier records
- Property IDs
-
Normally, the glue will know a property is expected and coerce whatever string you provide into its four-character ID. Sometimes
obj_form(formPropertyID, typeType, 'property_name')
may be appropriate. - Name
-
Just pass the data as text. If there is some ambiguity, you may explicitly use
obj_form(formName, typeChar, 'string')
. - Unique IDs
-
Could be anything.
- Absolute position
-
As discussed above, if it is an index number, you can just pass the number, as in
window => 1
, or you can explicitly mark it withwindow => obj_form(formAbsolutePosition, typeLongInteger, 1)
.For other absolutes, you may use constants, such as
window => gLast
. Choices aregFirst
,gMiddle
,gLast
,gAny
,gAll
.These are just shortcuts for explicit forms like
obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAEAll)
.Note that if there is a plural form of the class name, you may use it to mean the same thing as "class => gAll". These are all the same:
$f->obj(files => of => 'System Folder'); $f->obj(files => gAll, of => 'System Folder'); $f->obj(file => gAll, of => 'System Folder');
- Relative position
-
Similar to absolute position, but an additional object must be specified, such as
file =
gNext, file => gMiddle>, which would return the file after the middle file. Available constants aregNext
andgPrevious
.The explicit form is
obj_form(formRelativePosition, typeEnumerated, kAENext)
. - Ranges
-
The
range
function accepts two arguments, the start and stop ranges.range(START, STOP)
Each can be a number index, an absolute position constant, a string, or another data type passed with
obj_form
. Here are a few ways to specify files in the System Folder:$f->obj(files => range(1, 5), of => 'System Folder'); $f->obj(files => range(1, "System"), of => 'System Folder'); $f->obj(files => range("Finder", "System"), of => 'System Folder'); $f->obj(files => range(gFirst, "System"), of => 'System Folder');
- Whose tests
-
The
whose
function accepts either logical records or comparison records.# comparison record $f->obj(CLASS => whose(CLASS => VALUE, OPERATOR, VALUE)); $f->obj(CLASS => whose(PROPERTY, OPERATOR, VALUE));
PROPERTY and CLASS => VALUE work like prop() and obj(). The PROPERTY form is the same as
property =
VALUE>.OPERATOR is
contains
,equals
,begins_with
,ends_with
,l_t
,l_e
,g_t
, org_e
. VALUE is the value to compare to.# files whose name begins with "foo" $f->obj(files => whose(name => begins_with => 'foo')); # rows whose first cell equals "bar" $f->obj(rows => whose(cell => 1 => equals => 'bar'));
Then there is the logical record type, for use when more than one comparison record is needed.
# logical record $f->obj(CLASS => whose(OPERATOR, LIST));
OPERATOR is
AND
,OR
, orNOT
. LIST is any number of other logical records or comparison records, contained in anonymous arrays. So you can join any number of records together:# words where it contains "e" and it begins with "p" and it does not end with "s" $aw->obj( words => whose(AND => [it => contains => 'e'], [it => begins_with => 'p'], [NOT => [it => ends_with => 's']] ), $text)
Note how each logical record and comparison record following each logical operator is in an anonymous array. Also not how the special word "it" refers to the object being examined.
There's one more record type that works similarly to the above object specifier records, but is not exactly the same thing. It's called an insertion location record, and is created like this:
location(POSITION[, OBJECT])
POSITION is a string, and can be one of before
, after
, beginning
, or end
. front
is a synonym for beginning
, and back
and behind
are synonyms for after
.
OBJECT is the object to be positioned against, and will be the null object if not supplied.
my $aw = new Mac::Glue 'AppleWorks';
my $text = $aw->prop(text_body => document => 1);
$aw->activate;
# note null object in location()
$aw->make(new => 'document', at => location('front'));
$aw->set($text, to => "foo bar buz baz.");
$aw->move(
$aw->obj(word => 4 => $text),
to => location(after => $aw->obj(word => 2 => $text))
);
Special parameters and methods
Special parameters can be passed in the event which control certain aspects of the event call's behavior. They can be passed as parameters (affecting only the one event), or called as methods (which affect every call made from that object). They are all upper case.
$glue->REPLY(1); # wait for reply on all events
$glue->close(REPLY => 0); # don't wait for this one event
- REPLY
-
Boolean, for whether or not to wait for a reply. Default is to wait.
- MODE
-
Set other modes, such as
kAENeverInteract
. This value is OR'd together with the REPLY value. Default iskAECanInteract | kAECanSwitchLayer
. - SWITCH
-
Switch to the application being called. Usually more efficient to use the
activate
event:$glue->activate;
- PRIORITY
-
Set the event priority. Default is
kAENormalPriority
. - TIMEOUT
-
Number of seconds to wait before timing out. Default is a couple hundred thousand seconds or so.
- RETOBJ
-
Boolean, for whether or not the event call will return the direct object data (the default), or a Mac::AppleEvents::Simple object, containing references to the actual event and reply, so you can do more advanced things with the data if you want to.
Editing Glues
Eventually we'll have droplets for editing glues.
TIPS
Hide background apps
use Mac::Glue;
use Mac::Apps::Launch;
$a = new Mac::Glue 'Acrobat Exchange';
$a->launch;
Hide($a->{ID});
# now do your thing ...
Other
I will probably make a separate document at some point. I dunno.
EXPORT
Mac::Glue has two export sets. glue
exports the constants and functions beginning with "glue" listed in "Creating Object Specifier Records", as well as the functions obj_form
, enum
, location
, range
, and whose
. all
exports everything from Mac::AppleEvents and Mac::AppleEvents::Simple, including all functions and constants. Nothing is exported by default.
use Mac::Glue ':glue'; # good for most things
use Mac::Glue ':all'; # for more advanced things
GOTCHAS
MAKE SURE site_perl COMES FIRST IN YOUR LIBRARY PREFERENCES. Thank you. :-)
Do NOT send an event to the MacPerl application itself and expect a reply. Instead, try
$macperlglue->REPLY(0)
. Similarly, do not drop MacPerl onto gluemac. Instead, you can make a copy of the MacPerl application, and drop that on gluemac.You should have the latest cpan-mac distribution is installed.
You should delete old dialect glue files manually if running Mac OS 9.
TODO / BUGS
Add names to glue docs, replacing four-digit codes for elements
Specifying other attributes (transactions, etc.)
Add more coercions etc. to Mac::AppleEvents::Simple (feedback wanted on this, let me know what I missed)
Add comparison operators from glues
"tell" objects to do events
New AETE flags in Mac OS 8.5, Mac OS 9? Anything else new? Anything missing in backward compatibility to 7.5?
System of versioning (for glues and target apps) for distribution of modified glues
MacPerl (I think) needs a new idle function for waiting for replies
MacPerl hangs if it waits for a reply after sending an event to itself
Handlers?
Add dynamic fetching of glues?
HISTORY
- v0.57, Tuesday, November 2, 1999
-
Added conversion of keys in returned records back into the "English" names.
Records containing
class
parameter are coerced into descriptors of that class (i.e.,{name => 'foo', class => 'disk'}
).Added support for events over TCP/IP (Mac OS 9 required).
Note: if Keychain Access is used in Mac OS 9, the
login
method may no longer be required for accessing of remote machines.Fixed bug in
can
method; also changed howcan
callsAUTOLOAD
.Added code for experimental callback stuff, undocumented, subject to change, and probably does not even work.
Changed
_get_name
to_get_id
, created new_get_name
.Updated dialect creation code for Mac OS 9 (aeut is now stored in the AppleScript extension instead of a dialect file, but for Mac::Glue is still stored in the dialects folder. Instead of being called English, it will likely be called AppleScript. You should delete (or archive) old dialect glues manually.
- v0.56, Friday, September 10, 1999
-
If plural class is used (i.e., files for file), and the following value is not an
AE*
object, then it will become "every class". (Jeff Lowrey)Added more documentation about using
AEObjDesc
objects. (Jeff Lowrey) - v0.55, Thursday, September 2, 1999
-
Added extra arguments to
new
to accept alternate targets. PPC ports, PSNs, and paths are explicitly accepted now. (Paths are first launched, then the PSN is found ... aliases won't work properly as paths.)Added
login
class method to tell MacPerl to try logging in with specified username and password. Requires Login As OSAX from the GTQ Scripting Library. - v0.51, Wednesday, September 1, 1999
-
Changed ordering of search in
_find_event
.Fixed doc problems in Mac::AETE::Format::Glue: inheritance classes are named, and optional parameters are properly denoted.
- v0.50, 12 July 1999
-
Added g* constants in addition to glue* constants. Use whichever you like, but I will use g* for everything. If you don't want the g* constants, because they conflict with something, use the
:long
and:longall
import tags instead of:glue
and:all
.Gone to beta! Woo!
- v0.31, 22 June 1999
-
Fixed bug that only found class names instead of class and property names in creation of object specifier records.
Fixed bug which changed directories on initialization, and didn't change it back.
Allow case-insensitive parameter names.
- v0.30, 16 June 1999
-
Changed function names:
glueInsertion
is nowlocation
,glueRange
is nowrange
.Added
whose
function.Added
can
method which correctly finds available events.Made special parameters, formerly with leading underscore and lowercase, to all uppercase with no underscore (i.e.,
_retobj
is nowRETOBJ
).Added
of
andin
as synonyms forproperty
inobj
method calls.Put
AEObjDesc
back in! Will use in the future, maybe, to use objects as targets for events.Return all descriptors from
obj
andprop
, and all objects returned from events, asAEObjDesc
objects.Added
glueTrue
andglueFalse
constants.Tried again to suppress warnings during initial scripting additions and dialect creation.
Tons of internal cleaning up.
- v0.26, 07 June 1999
-
Made choice of serializer for glue more intelligent: FreezeThaw automatically picked for CFM68K, Storable for PPC.
Updated Mac::AppleEvents and Mac::Memory, fixed more bugs and added constants. Fixed bug in AutoSplit.
Added
glueInsertion
,glueRange
, andglueNull
.Completely removed
AEObjDesc
package, which existed to support destruction of descriptors. Use global hash now to keep track of descriptors to destroy (Mac::AppleEvents::Simple). So all descriptors returned fromobj
andprop
and others areAEDesc
objects.Changed ordering of items in creating object specifiers in
_do_obj
to match AppleScript, so comparing to Capture AE output would be easier.Put
%AE_PUT
back in Mac::Glue and left%AE_GET
in Mac::AppleEvents::Simple.Switched
DOBJ, {PARAM1 => DATA1}
toDOBJ, PARAM1 => DATA1
in event calls.Always default to wait for reply and no timeout if unspecified by user.
Return useful errors in
$^E
.Accept and return nested arrays/lists and hashes/records.
Call events and pass classes / properties case-insensitively.
Other miscellaneous changes. Some cleaning up.
- v0.25, 30 May 1999
-
Add serializer option.
Updates to Mac::Memory and Mac::AppleEvents and Mac::AppleEvents::Simple.
Added constants for absolute and relative positions.
Added
enum
.Put
o
andp
back asobj
andprop
.Other miscellaneous changes. Lots of cleaning up.
- v0.20, 22 May 1999
-
Complete rewrite. Too many changes to bother mentioning, because I am lazy.
- v0.09, 13 October 1998
-
Added ability to use properties. These are called with the
p
method:$obj->get($obj->p('label_index', item=>'HD'));
which is equivalent to:
$obj->get($obj->o(property=>'label_index', item=>'HD'));
- v0.08, 10 October 1998
-
Unreleased.
Significant cleanup of module, in large part unfinished changes from last version.
No longer doing error checking for whether lists are allowed or objects are allowed, because these are sometimes wrong or undetectable. Also, will not raise exception on a missing required parameter, but will warn if
-w
is on.obj_form
is exported from the glue modules, and all of the functions and constant fromMac::AppleEvents
can be imported from a glue module with the:all
tag:use Mac::Glue::SomeApp qw(:all);
- v0.07, 30 September 1998
-
More documentation and bugfixes. Having serious problems with
AEObjDesc::DESTROY
. - v0.06, 29 September 1998
-
Whole bunches of changes. Note that glues made under 0.05 no longer work.
AUTHOR
Chris Nandor <pudge@pobox.com>, http://pudge.net/
Copyright (c) 1999 Chris Nandor. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License, distributed with Perl.
THANKS
Matthias Neeracher <neeri@iis.ee.ethz.ch>, David Schooley <dcschooley@mediaone.net>, Graham Barr <gbarr@pobox.com>, John W Baxter <jwblist@olympus.net>, Marion Delgado <dhp@efn.org>, Eric Dobbs <dobbs@visionlink.org>, Josh Gemmell <joshg@ola.bc.ca>, Nathaniel Irons <irons@espresso.hampshire.edu>, Dave Johnson <dave_johnson@ieee.org>, Bart Lateur <bart.mediamind@ping.be>, Jefferson R. Lowrey <lowrey@mailbag.com>, Mat Marcus <mmarcus@adobe.com>, Larry Moore <ljmoore@freespace.net>, Ricardo Muggli <rtmuggli@carlsoncraft.com>, Vincent Nonnenmacher <dpi@pobox.oleane.com>, Henry Penninkilampi <htp@metropolis.net.au>, Peter Prymmer <pvhp@best.com>, Ramesh R. <sram0mp@radon.comm.mot.com>, Stephan Somogyi <somogyi@gyroscope.net>, Kevin Walker <kwalker@xmission.com>, Matthew Wickline <mattheww@wickline.org>.
(If I left your name out, please remind me.)
SEE ALSO
Mac::AppleEvents, Mac::AppleEvents::Simple, macperlcat, Inside Macintosh: Interapplication Communication.
VERSION
v0.57, Tuesday, November 2, 1999