Mac::Glue - Control Mac apps with Apple event terminology
use Mac::Glue; my $glue = Mac::Glue->new('Finder'); $glue->open( $glue->prop('System Folder') ); # see rest of docs for lots more info
*** 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.
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);
use Mac::AppleEvents::Simple; do_event(qw(aevt odoc MACS), "'----': obj{want:type(prop), from:'null'()," . "form:prop, seld:type(macs)}" );
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.
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.
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).
new
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.
$psn
You can also pass a path to an application:
my $glue = Mac::Glue->new('My App', path => $path_to_file);
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:
reply
$glue->Reply; $glue->reply; $glue->RePLY;
However, it could not be called with $glue->REPLY, since that is reserved.
$glue->REPLY
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.
open
$file
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:
obj
prop
enum
$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.
AEObjDesc
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:
as
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.
$^E
$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").
RETOBJ
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.
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.
obj_form
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:
of
in
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');
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.
obj_form(formPropertyID, typeType, 'property_name')
Just pass the data as text. If there is some ambiguity, you may explicitly use obj_form(formName, typeChar, 'string').
obj_form(formName, typeChar, 'string')
Could be anything.
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 with window => obj_form(formAbsolutePosition, typeLongInteger, 1).
window => 1
window => obj_form(formAbsolutePosition, typeLongInteger, 1)
For other absolutes, you may use constants, such as window => gLast. Choices are gFirst, gMiddle, gLast, gAny, gAll.
window => gLast
gFirst
gMiddle
gLast
gAny
gAll
These are just shortcuts for explicit forms like obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAEAll).
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');
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 are gNext and gPrevious.
file =
gNext
gPrevious
The explicit form is obj_form(formRelativePosition, typeEnumerated, kAENext).
obj_form(formRelativePosition, typeEnumerated, kAENext)
The range function accepts two arguments, the start and stop ranges.
range
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');
The whose function accepts either logical records or comparison records.
whose
# 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>.
property =
OPERATOR is contains, equals, begins_with, ends_with, l_t, l_e, g_t, or g_e. VALUE is the value to compare to.
contains
equals
begins_with
ends_with
l_t
l_e
g_t
g_e
# 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, or NOT. LIST is any number of other logical records or comparison records, contained in anonymous arrays. So you can join any number of records together:
AND
OR
NOT
# 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.
before
after
beginning
end
front
back
behind
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 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
Boolean, for whether or not to wait for a reply. Default is to wait.
Set other modes, such as kAENeverInteract. This value is OR'd together with the REPLY value. Default is kAECanInteract | kAECanSwitchLayer.
kAENeverInteract
kAECanInteract | kAECanSwitchLayer
Switch to the application being called. Usually more efficient to use the activate event:
activate
$glue->activate;
Set the event priority. Default is kAENormalPriority.
kAENormalPriority
Number of seconds to wait before timing out. Default is a couple hundred thousand seconds or so.
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.
Eventually we'll have droplets for editing glues.
use Mac::Glue; use Mac::Apps::Launch; $a = new Mac::Glue 'Acrobat Exchange'; $a->launch; Hide($a->{ID}); # now do your thing ...
I will probably make a separate document at some point. I dunno.
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.
glue
location
all
use Mac::Glue ':glue'; # good for most things use Mac::Glue ':all'; # for more advanced things
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)
AE*
Added more documentation about using AEObjDesc objects. (Jeff Lowrey)
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.
login
Changed ordering of search in _find_event.
_find_event
Fixed doc problems in Mac::AETE::Format::Glue: inheritance classes are named, and optional parameters are properly denoted.
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.
:long
:longall
:glue
:all
Gone to beta! Woo!
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.
Changed function names: glueInsertion is now location, glueRange is now range.
glueInsertion
glueRange
Added whose function.
Added can method which correctly finds available events.
can
Made special parameters, formerly with leading underscore and lowercase, to all uppercase with no underscore (i.e., _retobj is now RETOBJ).
_retobj
Added of and in as synonyms for property in obj method calls.
Put AEObjDesc back in! Will use in the future, maybe, to use objects as targets for events.
Return all descriptors from obj and prop, and all objects returned from events, as AEObjDesc objects.
Added glueTrue and glueFalse constants.
glueTrue
glueFalse
Tried again to suppress warnings during initial scripting additions and dialect creation.
Tons of internal cleaning up.
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, and glueNull.
glueNull
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 from obj and prop and others are AEDesc objects.
AEDesc
Changed ordering of items in creating object specifiers in _do_obj to match AppleScript, so comparing to Capture AE output would be easier.
_do_obj
Put %AE_PUT back in Mac::Glue and left %AE_GET in Mac::AppleEvents::Simple.
%AE_PUT
%AE_GET
Switched DOBJ, {PARAM1 => DATA1} to DOBJ, PARAM1 => DATA1 in event calls.
DOBJ, {PARAM1 => DATA1}
DOBJ, PARAM1 => DATA1
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.
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 and p back as obj and prop.
o
p
Other miscellaneous changes. Lots of cleaning up.
Complete rewrite. Too many changes to bother mentioning, because I am lazy.
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'));
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.
-w
obj_form is exported from the glue modules, and all of the functions and constant from Mac::AppleEvents can be imported from a glue module with the :all tag:
Mac::AppleEvents
use Mac::Glue::SomeApp qw(:all);
More documentation and bugfixes. Having serious problems with AEObjDesc::DESTROY.
AEObjDesc::DESTROY
Whole bunches of changes. Note that glues made under 0.05 no longer work.
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.
Matthias Neeracher <neeri@iis.ee.ethz.ch>, David Schooley <dcschooley@mediaone.net>, Graham Barr <gbarr@pobox.com>, John W Baxter <jwblist@olympus.net>, 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.)
Mac::AppleEvents, Mac::AppleEvents::Simple, macperlcat, Inside Macintosh: Interapplication Communication.
0.56, Friday, September 10, 1999
To install Mac::Glue, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Mac::Glue
CPAN shell
perl -MCPAN -e shell install Mac::Glue
For more information on module installation, please visit the detailed CPAN module installation guide.