Mac-Glue

 view release on metacpan or  search on metacpan

Glue.pm  view on Meta::CPAN

		LaunchApps($self->{CREATOR_ID});
	}
}

#=============================================================================#
# launch spec and then get PSN

sub _path_to_psn {
	my($path) = @_;

	confess "Path '$path' does not exist" unless -e $path;

	my $lp = LaunchParam->new(
		launchControlFlags => (launchContinue | launchNoFileFlags | launchDontSwitch),
		launchAppSpec => $path
	);

	my $psn = LaunchApplication($lp) or confess "Cannot launch '$path': $MacError";

	return pack_psn($psn);
}

#=============================================================================#
# open scripting additions and dialect files only once,
# save them for further use by all Mac::Glue instances

sub _open_others {
	chomp(my $curdir = `pwd`);
	my @others;
	for my $dir (map { catfile($MACGLUEDIR, $_) } qw[dialects additions]) {
		unless (-e $dir) {
			warn "Please run gluedialect and gluescriptadds programs"
				unless $Mac::Glue::CREATINGGLUES;
			$Mac::Glue::NEEDCREATE = 1;
			next;
		}

		local *DIR;
		opendir DIR, $dir or confess "Can't open directory '$dir': $!";
		chdir $dir or confess "Can't chdir directory '$dir': $!";

		# add file type / creator checking ???
		# maybe add a new file type for glues?  i can do that now,
		# because i am special or something.
		for (readdir DIR) {
			next if -d;
			next if $_ eq "Icon\015";
			next if /\.pod$/;
			tie my %db, 'MLDBM', $_, O_RDONLY or confess "Can't tie '$_': $!";
			push @$OTHEREVENT, $db{EVENT} if $db{EVENT};
			push @$OTHERCLASS, $db{CLASS} if $db{CLASS};
			push @$OTHERENUM,  $db{ENUM}  if $db{ENUM};
		}
	}
	chdir $curdir or confess "Can't chdir to '$curdir': $!";
}

#=============================================================================#
# merge additions, dialect, and glue classes together
# wow, this is ugly.  i wonder if there is a better/faster way.  probably.
# or maybe a way to cache the results between iterations ... ?
# but then, how do we deal with added/removed classes?

sub _merge_classes {
	my($db) = @_;
	if (!exists $MERGEDCLASSES->{ $db->{ID} }) {
		my($ids, $names) = ({}, {});
		my($class, @classes) = ($db->{CLASS}, @$OTHERCLASS);

		for my $c (keys %$class) {
			$names->{$c}{id} = $class->{$c}{id};
			$ids->{$names->{$c}{id}}{name} = $c;

			for my $p (keys %{$class->{$c}{properties}}) {
				$names->{$p}{id} ||= $class->{$c}{properties}{$p}[0];
				$ids->{$names->{$p}{id}}{name} ||= $p;
				unshift @{$names->{$p}{types}}, $class->{$c}{properties}{$p}[1];
			}
		}

		for my $tempc (@classes) {
			for my $c (keys %$tempc) {
				$names->{$c}{id} ||= $tempc->{$c}{id};
				$ids->{$names->{$c}{id}}{name} = $c;

				for my $p (keys %{$tempc->{$c}{properties}}) {
					$names->{$p}{id} ||= $tempc->{$c}{properties}{$p}[0];
					$ids->{$names->{$p}{id}}{name} ||= $p;
					unshift @{$names->{$p}{types}}, $tempc->{$c}{properties}{$p}[1];

					$class->{$c}{properties}{$p} = $tempc->{$c}{properties}{$p}
						if (exists $class->{$c} &&
							$class->{$c}{id} eq $tempc->{$c}{id} &&
							! exists $class->{$c}{properties}{$p});
				}

				unless (exists $class->{$c}) {
					$class->{$c} = $tempc->{$c};
				}
			}
		}

		$MERGEDCLASSES->{ $db->{ID} } = [$class, $names, $ids];
	}
	return @{$MERGEDCLASSES->{ $db->{ID} }};
}

#=============================================================================#
# "merge" additions, dialect, and glue enumerations together
# see above about caching results, rethinking logic.  for a really
# really really really rainy day.

sub _merge_enums {
	my($db, $self) = @_;
	if (!exists $MERGEDENUM->{ $db->{ID} }) {
		my $names = $self->{NAMES};
		my $ids = $self->{IDS};

		for my $tempc (grep defined, $db->{ENUM}, @$OTHERENUM) {
			for my $c (keys %$tempc) {
				$self->{ENUMTYPE}{$c} = [];

Glue.pm  view on Meta::CPAN

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.

=over 4

=item Raw Mac::AppleEvents method

	use Mac::AppleEvents;
	use Mac::Errors '$MacError';

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

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

=item 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)}"
	);

=item Cool Mac::Glue method

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

=back

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.


=head2 Mac OS X

Mac OS X is supported by Mac::Glue now.  Note that some glues and methods
will behave differently, due to differences in application implementation
(for example, the Finder's "clean up" event is not supported in Mac OS X
at this writing).


=head2 Creating a Glue

In order to script an application with Mac::Glue, a glue must be created
first.  For that, the application is passed to the F<gluemac> script.
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 for MacPerl, glues are stored in
F<:site_perl:Mac:Glue:glues:>, or in F<./Glue/glues> relative to
F<Glue.pm> for Unix (Mac OS X).

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 F<gluescriptadds> and F<gluedialect> programs, which are
similar to the F<gluemac> program.  They are saved in the directories
F<$ENV{MACGLUEDIR}additions> and F<$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.


=head2 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 C<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.



( run in 1.356 second using v1.01-cache-2.11-cpan-4ab04211f4c )