Listen Print

Introducing Mac::Glue

by Simon Cozens, Chris Nandor
January 23, 2004

Thanks to the popularity of Mac OS X, the new iBook, and the PowerBook G4, it's no longer uncool to talk about owning an Apple. Longtime Mac devotees have now been joined by longtime Unix devotees and pretty much anyone who wants computers to be shiny, and speakers at conferences such as the Open Source Convention are beginning to get used to looking down over a sea of Apple laptops.

One of the great features about Apple's Mac OS is its support for flexible inter-process communication (IPC), which Apple calls inter-application communication (IAC). One of the components of IAC is called Apple events, and allows applications to command each other to perform various tasks. On top of the raw Apple events layer, Apple has developed the Open Scripting Architecture, an architecture for scripting languages such as Apple's own AppleScript.

But this is perl.com, and we don't need inferior scripting languages! The Mac::Glue module provides OSA compatibility and allows us to talk to Mac applications with Perl code. Let's take a look at how to script Mac tools at a high level in Perl.

The Pre-History of Mac::Glue

In the beginning, there was Mac::AppleEvents. This module wrapped the raw Apple events API, with its cryptic four-character codes to describe applications and their capabilities, and its collection of awkward constants. You had to find out the four-character identifiers yourself, you had to manage and dispose of memory yourself, but at least it got you talking Apple events. Here's some Mac::AppleEvents code to open your System Folder in the Finder::


use Mac::AppleEvents;

my $evt = AEBuildAppleEvent('aevt', 'odoc', typeApplSignature, 
             'MACS', kAutoGenerateReturnID, kAnyTransactionID,
             "'----': obj{want:type(prop), from:'null'()," .
                "form:prop, seld:type(macs)}"
          );
my $rep = AESend($evt, kAEWaitReply);

AEDisposeDesc($evt);
AEDisposeDesc($rep);

Obviously this isn't putting the computer to its full use; in a high-level language like Perl, we shouldn't have to concern ourselves with clearing up descriptors when they're no longer in use, or providing low-level flags. We just want to send the message to the Finder. So along came Mac::AppleEvents::Simple, which does more of the work:


use Mac::AppleEvents::Simple;
do_event(qw(aevt odoc MACS),
     "'----': obj{want:type(prop), from:'null'()," .
     "form:prop, seld:type(macs)}"
);

This is a bit better; at least we're just talking the IAC language now, instead of having to emulate the raw API. But those troublesome identifiers -- "aevt" for the Finder, "odoc" to open a document, and "MACS" for the System folder.

Maybe we'd be better off in AppleScript after all -- the AppleScript code for the same operation looks like this:


tell application "Finder" to open folder "System Folder"

And before Mac::Glue was ported to Mac OS X, this is exactly what we had to do:


use Mac::AppleScript qw(RunAppleScript);
RunAppleScript('tell application "Finder" to open folder "System Folder"');

This is considerably easier to understand, but it's just not Perl. Mac::Glue uses the same magic that allows AppleScript to use names instead of identifiers, but wraps it in Perl syntax:


use Mac::Glue;
my $finder = Mac::Glue->new('Finder');
$finder->open( $finder->prop('System Folder') );

Related Reading

Running Mac OS X Panther

Running Mac OS X Panther
Inside Mac OS X's Core
By James Duncan Davidson

Table of Contents

Read Online--Safari Search this book on Safari:
 

Code Fragments only

Setting Up and Creating Glues

On Mac OS 9, MacPerl comes with Mac::Glue. However, OS X users will need to install it themselves. Mac::Glue requires several other CPAN modules to be installed, including the Mac-Carbon distribution.

Because this in turn requires the Carbon headers to be available, you need to install the correct Apple developer kits; if you don't have the Developer Tools installed already, you can download them from the ADC site.

Once you have the correct headers installed, the best way to get Mac::Glue up and running is through the CPAN or CPANPLUS modules:


% perl -MCPAN -e 'install "Mac::Glue"'

This should download and install all the prerequisites and then the Mac::Glue module itself.

When it installs itself, Mac::Glue also creates "glue" files for the core applications -- Finder, the System Events library, and so on. A glue file is used to describe the resources available to an application and what can be done to the properties that it has.

If you try to use Mac::Glue to control an application for which it doesn't currently have a glue file, it will say something like this:


No application glue for 'JEDict' found in 
'/Library/Perl/5.8.1/Mac/Glue/glues' at -e line 1

To create glues for additional applications that are not installed by default, you can drop them onto the Mac OS 9 droplet "macglue." On Mac OS X, run the gluemac command.

What's a Property?

Once you have all your glues set up, you can start scripting Mac applications in Perl. It helps if you already have some knowledge of how AppleScript works before doing this, because sometimes Mac::Glue doesn't behave the way you expect it to.

For instance, we want to dump all the active to-do items from iCal. To-dos are associated with calendars, so first we need a list of all the calendars:


use Mac::Glue;
my $ical = new Mac::Glue("iCal");

my @cals = $ical->prop("calendars");

The problem we face immediately is that $ical->prop("calendars") doesn't give us the calendars. Instead, it gives us a way to talk about the calendars' property. It's an object. To get the value of that property, we call its get method:


my @cals = $ical->prop("calendars")->get;

This returns a list of objects that allow us to talk about individual calendars. We can get their titles like so:


for my $cal (@cals) {
    my $name = $cal->prop("title")->get;

And now we want to get the to-dos in each calendar that haven't yet been completed or have no completion date:


    my @todos = grep { !$_->prop("completion_date")->get }
                       $cal->prop("todos")->get;

If we then store the summary for each of the to-do items in a hash keyed by the calendar name:


    $todos{$name} = [ map { $_->prop("summary")->get } @todos ]
	if @todos;
}

Then we can print out the summary of all the outstanding to-do items in each calendar:


for my $cal(keys %todo) {
    print "$cal:\n";
    print "\t$_\n" for @{$todo{$cal}};
}

Putting it all together, the code looks like:


use Mac::Glue;
my $ical = new Mac::Glue("iCal");

my @cals = $ical->prop("calendars")->get;
for my $cal (@cals) {
    my $name = $cal->prop("title")->get;
    my @todos = map  { $_->prop("summary")->get }
                grep { !$_->prop("completion_date")->get }
                       $cal->prop("todos")->get;
    $todo{$name} = \@todos if @todos;
}

for my $cal(keys %todo) {
    print "$cal:\n";
    print "\t$_\n" for @{$todo{$cal}};
}

The question is, where did we get the property names like summary and completion_date from? How did we know that the calendars had titles but the to-do items had summaries, and so on?

There are two answers to this: the first is to use the documentation created when the glue is installed. Typing gluedoc iCal on Mac OS X or using Shuck on Mac OS 9, you will find the verbs, properties, and objects that the application supports. For instance, under the calendar class, you should see:

This class represents a calendar

Properties:

    description (wr12/utxt): This is the calendar
description. (read-only)
    inheritance (c@#^/item): All of the properties of the
superclass. (read-only)
    key (wr03/utxt): An unique calendar key (read-only)
    tint (wr04/utxt): The calendar color (read-only)
    title (wr02/utxt): This is the calendar title.
    writable (wr05/bool): If this calendar is writable
(read-only)

Elements:

    event, todo

This tells us that we can ask a calendar for its title property, and also for the events or todos contained within it.

Similarly, when we get the events back, we can look up the "event" class in the documentation and see what properties are available on it.

The second, and perhaps easier, way to find out what you can do with an application is to open the AppleScript Script Editor application, select Open Dictionary from the File menu, and choose the application you want to script. Now you can browse a list of the classes and commands associated with the application:

When you need to know how to translate those back into Perl, you can then consult the glue documentation. It takes a few attempts to get used to the way Mac::Glue works, but once you've done that, you'll find that you can translate between the AppleScript documentation and a Mac::Glue equivalent in your head.

Pages: 1, 2

Next Pagearrow





Contact Us | Advertise with Us | Privacy Policy | Press Center | Jobs | Submissions Guidelines

Copyright © 2000-2008 O’Reilly Media, Inc. All Rights Reserved. | (707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on the O'Reilly Network are the property of their respective owners.

For problems or assistance with this site, email