File Coverage

blib/lib/Mac/Glue.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Mac::Glue;
2              
3             # the code below is SCARY. please consider your loved ones before
4             # venturing within. it might seem reasonable at first, but then you
5             # get sucked in and it's all over.
6              
7 1     1   30507 use Mac::Glue::Common qw($MACGLUEDIR);
  0            
  0            
8             use Carp;
9             use Exporter;
10             use Fcntl;
11             use File::Basename;
12             use File::Spec::Functions;
13             use Mac::AppleEvents::Simple 1.14 ':all';
14             use Mac::Apps::Launch 1.90;
15             use Mac::Errors qw(%MacErrors $MacError);
16             use Mac::Files 1.09;
17             use Mac::Gestalt;
18             use Mac::Memory 1.20 ();
19             use Mac::OSA 1.05 ();
20             use Mac::Processes 1.04;
21             use Mac::Types;
22             use MLDBM ('DB_File', $Mac::Glue::Common::SERIALIZER);
23             use Time::Epoch 'perl2epoch';
24              
25             use strict;
26             use vars qw(
27             $REVISION $VERSION $AUTOLOAD %AE_PUT %AE_GET @SYMS @METHS
28             @EXPORT @EXPORT_OK %EXPORT_TAGS @ISA $GLUEDIR
29             $GENPKG $GENSEQ %OPENGLUES $MERGEDCLASSES $OTHEREVENT
30             $OTHERCLASS %SPECIALEVENT %SPECIALCLASS %DESCS
31             $MERGEDENUM $OTHERENUM %INSL %DESC_TYPE %COMP %LOGI
32             $RESERVED $ENCODE $SYSEVT
33             );
34              
35             #=============================================================================#
36             # $Id: Glue.pm,v 1.32 2007/01/03 22:12:24 pudge Exp $
37             ($REVISION) = ' $Revision: 1.32 $ ' =~ /\$Revision:\s+([^\s]+)/;
38             $VERSION = '1.30';
39             @ISA = 'Exporter';
40             @EXPORT = ();
41             $RESERVED = 'REPLY|SWITCH|MODE|PRIORITY|TIMEOUT|RETOBJ|ERRORS|CALLBACK|CLBK_ARG';
42             @SYMS = qw(
43             obj_form param_type enum whose range location
44             glueTrue glueFalse glueNext gluePrevious
45             glueFirst glueMiddle glueLast glueAny glueAll
46             gTrue gFalse gNext gPrevious
47             gFirst gMiddle gLast gAny gAll
48             );
49             @METHS = qw( ADDRESS AUTOLOAD can launch obj prop version app_process );
50              
51             @EXPORT_OK = ( @Mac::AppleEvents::EXPORT, '%MacErrors', '$MacError', @SYMS );
52             %EXPORT_TAGS = (
53             all => [@EXPORT, @EXPORT_OK],
54             glue => [@EXPORT, @SYMS],
55             long => [grep !/^g[A-Z]/, @EXPORT, @SYMS],
56             longall => [grep !/^g[A-Z]/, @EXPORT, @EXPORT_OK],
57             );
58              
59             $GENPKG = __PACKAGE__;
60             $GENSEQ = 0;
61             $ENCODE = eval { require Encode };
62              
63             #=============================================================================#
64             # exported functions
65             sub obj_form ($$;$) { bless [@_], 'Mac::AEObjDescForm' }
66             sub param_type ($$) { bless [@_], 'Mac::AEParamType' }
67             sub enum ($) { bless [@_], 'Mac::AEEnum' }
68             sub whose { bless [formTest, @_], 'Mac::AEObjDescType' }
69             sub range ($$) { bless [formRange, @_], 'Mac::AEObjDescType' }
70             sub location ($;$); *location = *_do_loc{CODE};
71              
72             #=============================================================================#
73             # constants
74             use constant glueTrue => enum('true');
75             use constant glueFalse => enum('false');
76              
77             use constant glueFirst => obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAEFirst);
78             use constant glueMiddle => obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAEMiddle);
79             use constant glueLast => obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAELast);
80             use constant glueAny => obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAEAny);
81             use constant glueAll => obj_form(formAbsolutePosition, typeAbsoluteOrdinal, kAEAll);
82             use constant glueNext => obj_form(formRelativePosition, typeEnumerated, kAENext);
83             use constant gluePrevious => obj_form(formRelativePosition, typeEnumerated, kAEPrevious);
84              
85             use constant glueNull => new AEDesc typeNull;
86              
87             use constant glueAnd => new AEDesc typeEnumerated, kAEAND;
88             use constant glueOr => new AEDesc typeEnumerated, kAEOR;
89             use constant glueNot => new AEDesc typeEnumerated, kAENOT;
90              
91             use constant glueGT => new AEDesc typeEnumerated, kAEGreaterThan;
92             use constant glueGE => new AEDesc typeEnumerated, kAEGreaterThanEquals;
93             use constant glueEquals => new AEDesc typeEnumerated, kAEEquals;
94             use constant glueLT => new AEDesc typeEnumerated, kAELessThan;
95             use constant glueLE => new AEDesc typeEnumerated, kAELessThanEquals;
96             use constant glueBeginsWith => new AEDesc typeEnumerated, kAEBeginsWith;
97             use constant glueEndsWith => new AEDesc typeEnumerated, kAEEndsWith;
98             use constant glueContains => new AEDesc typeEnumerated, kAEContains;
99              
100             # short names
101             use constant gTrue => glueTrue();
102             use constant gFalse => glueFalse();
103              
104             use constant gFirst => glueFirst();
105             use constant gMiddle => glueMiddle();
106             use constant gLast => glueLast();
107             use constant gAny => glueAny();
108             use constant gAll => glueAll();
109             use constant gNext => glueNext();
110             use constant gPrevious => gluePrevious();
111              
112             use constant gNull => glueNull();
113              
114             use constant gAnd => glueAnd();
115             use constant gOr => glueOr();
116             use constant gNot => glueNot();
117              
118             use constant gGT => glueGT();
119             use constant gGE => glueGE();
120             use constant gEquals => glueEquals();
121             use constant gLT => glueLT();
122             use constant gLE => glueLE();
123             use constant gBeginsWith => glueBeginsWith();
124             use constant gEndsWith => glueEndsWith();
125             use constant gContains => glueContains();
126              
127             #=============================================================================#
128             _open_others();
129             #=============================================================================#
130              
131             sub new {
132             my($class, $app, $addtype, @add) = @_;
133             my($self, $glue, $db, $app1, $app2);
134              
135             # find glue, try a few different names just in case
136             ($app1 = $app) =~ tr/ /_/;
137             ($app2 = $app) =~ tr/_/ /;
138             for (map { catfile($MACGLUEDIR, $_) } $app, $app1, $app2) {
139             if (-e) {
140             $glue = $_;
141             last;
142             }
143             }
144             croak "No application glue for '$app' found in '$MACGLUEDIR'" unless $glue;
145              
146             # if not already opened, open and store reference to db
147             unless (exists $OPENGLUES{$glue}) {
148             tie my %db, 'MLDBM', $glue, O_RDONLY or confess "Can't tie '$glue': $!";
149             $OPENGLUES{$glue} = \%db;
150             }
151             $db = $OPENGLUES{$glue};
152              
153             # create new class to put this in, add the symbols we want,
154             # nyah nyah nyah (gosh, I love Perl)
155             $class = $GENPKG . '::GLUE' . $GENSEQ++;
156             {
157             no strict 'refs';
158             for (@METHS) {
159             *{$class . '::' . $_} = *{'Mac::Glue::' . $_}{CODE};
160             }
161             }
162              
163             $self = {
164             _DB => $db,
165             ID => $db->{ID},
166             CREATOR_ID => $db->{CREATOR_ID} || $db->{ID},
167             BUNDLE_ID => $db->{BUNDLE_ID},
168             GLUENAME => $app,
169             APPNAME => $db->{APPNAME},
170             VERSION => $db->{VERSION},
171             SWITCH => 0,
172             };
173              
174             ADDRESS($self, $addtype, @add);
175              
176             @{$self}{qw(CLASS NAMES IDS)} = _merge_classes($db);
177             _merge_enums($db, $self);
178              
179             bless($self, $class);
180             }
181              
182             #=============================================================================#
183             # set target address
184             sub ADDRESS {
185             my($self, $addtype, @add) = @_;
186              
187             $self->{ADDRESS} = defined $addtype
188             ? $addtype eq 'ppc' || $addtype eq typeTargetID
189             ? { typeTargetID() => pack_ppc($self->{CREATOR_ID}, @add) }
190              
191             : $addtype eq 'eppc' && $^O eq 'MacOS'
192             ? { typeTargetID() => pack_eppc($self->{CREATOR_ID}, @add) }
193              
194             : $addtype eq 'eppc'
195             ? { typeApplicationURL() => pack_eppc_x(@add) }
196              
197             : $addtype eq 'psn' || $addtype eq typeProcessSerialNumber
198             ? { typeProcessSerialNumber() => pack_psn($add[0]) }
199              
200             : $addtype eq 'pid' || $addtype eq typeKernelProcessID
201             ? { typeKernelProcessID() => pack_pid($add[0]) }
202              
203             : $addtype eq 'bundle' || $addtype eq typeApplicationBundleID
204             ? { _get_bundle($add[0]) }
205              
206             : $addtype eq 'path'
207             ? { typeProcessSerialNumber() => _path_to_psn($add[0]) }
208              
209             : { $addtype => $add[0] }
210              
211             : $self->{BUNDLE_ID}
212             ? { _get_bundle($self->{BUNDLE_ID}) }
213             : { typeApplSignature() => $self->{CREATOR_ID} };
214              
215             # $self->{ID} will only be '????' if we could not identify
216             # a creator ID *or* a bundle ID (or for older glues that
217             # don't do bundle ID)
218             if (! defined $addtype && $self->{ID} eq '????') {
219             $self->{ADDRESS} = 'PSN';
220             }
221             }
222              
223             # Jaguar can't target bundles natively, so convert to a path
224             sub _get_bundle {
225             my($bundle_id) = @_;
226             if ($Gestalt{sysv} >= 4144) {
227             return(typeApplicationBundleID, $bundle_id);
228             } else {
229             my $path = LSFindApplicationForInfo('', $bundle_id);
230             return(typeProcessSerialNumber, _path_to_psn($path));
231             }
232             }
233              
234             #=============================================================================#
235             # help UNIVERSAL::can out
236              
237             sub can {
238             my($self, $meth) = @_;
239             return unless @_ == 2;
240             my $can = UNIVERSAL::can($self, $meth);
241             unless ($can) {
242             $AUTOLOAD = ref($self) . '::' . $meth;
243             $can = AUTOLOAD('AUTOLOAD::can', $self);
244             }
245             return $can;
246             }
247              
248             #=============================================================================#
249             # define event calls as subs
250              
251             sub AUTOLOAD {
252             my $can = $_[0] eq 'AUTOLOAD::can' ? shift : 0;
253             my $self = $_[0];
254             (my $name = $AUTOLOAD) =~ s/^.*://;
255             my $sub;
256              
257             # catch reserved "method" names
258             if ($name eq 'DESTROY') {
259             return;
260             } elsif ($name =~ /^(?:$RESERVED)$/) {
261             $sub = sub { $_[0]->{$name} = $_[1] if defined $_[1]; $_[0]->{$name} };
262             }
263              
264             # catch other-case versions of already-installed methods
265             unless ($sub) {
266             (my $auto = $AUTOLOAD) =~ s/:([^:]+)$/:\L$1/;
267             if ($auto !~ /^can|obj|prop|launch|version$/) {
268             $sub = $auto if defined &$auto;
269             }
270             }
271              
272             # define method if we can find it in the glue table
273             unless ($sub) {
274             if (my $event = _find_event($self, lc $name)) {
275             $sub = sub { _primary($_[0], $event, lc $name, @_[1 .. $#_]) }
276             } elsif (! $can) {
277             # should this croak? probably. complain and come
278             # up with another idea if you don't like it.
279             croak "No event '$name' available from glue for '$self->{GLUENAME}'";
280             }
281             }
282              
283             # install new sub, only do AUTOLOAD for it once!
284             if ($sub) {
285             no strict 'refs';
286             *{$AUTOLOAD} = $sub;
287             }
288              
289             return $sub if $can;
290             goto &$sub;
291             }
292              
293             #=============================================================================#
294             # login using GTQ Login As OSAX
295             # will NOT return error if exists, because MacPerl does not handle replies well
296              
297             # this should be removed, it is a bad idea.
298              
299             sub login {
300             my($self, $user, $pass) = @_;
301              
302             my $evt = build_event(qw(gtqp lgin McPL), q{'----':TEXT(@), pwrd:TEXT(@)},
303             $user, $pass);
304             $evt->send_event(kAENoReply);
305              
306             return 1;
307             }
308              
309             #=============================================================================#
310             # basic subroutine building and sending every event call
311             # (see sub AUTOLOAD)
312              
313             sub _primary {
314             my($self, $e, $name, @args) = @_;
315             my($evt, %xargs, $dobj, @origargs);
316              
317             my($class, $event, $reply, $params) = @{$e}{qw(class event reply params)};
318              
319             $dobj = shift @args if @args % 2;
320              
321             %xargs = @args;
322             for (keys %xargs) {
323             delete $xargs{$_} unless /^(?:CALLBACK|CLBK_ARG)$/;
324             }
325              
326             if ($self->{ADDRESS} eq 'PSN') {
327             while (my($psn, $psi) = each %Process) {
328             if ($psi->processName eq $self->{APPNAME}) {
329             $self->{ADDRESS} = { typeProcessSerialNumber, pack_psn($psn) };
330             last;
331             }
332             }
333             if ($^O ne 'MacOS') {
334             my $path = LSFindApplicationForInfo('', '', $self->{APPNAME})
335             || LSFindApplicationForInfo('', '', $self->{APPNAME} . '.app');
336             my $psn = _path_to_psn($path);
337             $self->{ADDRESS} = { typeProcessSerialNumber, $psn } if $psn;
338             }
339             croak "App not running" if $self->{ADDRESS} eq 'PSN';
340             }
341              
342             @xargs{keys %{$self->{ADDRESS}}} = values %{$self->{ADDRESS}};
343              
344             # create event (Mac::AppleEvents::Simple object)
345             $evt = build_event($class, $event, \%xargs);
346              
347             # prepare parameters (direct object)
348             if (defined $dobj) {
349             croak "Direct object parameter not present"
350             unless exists $params->{keyDirectObject()};
351             _params($self, $evt, $params->{keyDirectObject()}, $dobj, $class, $event);
352             push @origargs, 'DOBJ', $dobj;
353             }
354              
355             # prepare parameters (all the rest)
356             my $hash = {@args};
357             if ($hash) {
358             for my $p (keys %$hash) {
359             next if $p =~ /^(?:$RESERVED)$/;
360             my $pp = $p eq 'DOBJ' ? keyDirectObject : lc $p;
361             croak "'$p' parameter not available" unless exists $params->{$pp};
362             _params($self, $evt, $params->{$pp}, $hash->{$p}, $class, $event);
363             push @origargs, $pp, $hash->{$p};
364             }
365             }
366              
367             # prepare send parameters and send event
368             local $Mac::AppleEvents::Simple::SWITCH =
369             $hash->{SWITCH} ? $hash->{SWITCH} : $self->{SWITCH};
370              
371             # we'll wait if REPLY not set and TIMEOUT is set
372             if (!exists $hash->{REPLY} && exists $hash->{TIMEOUT}) {
373             $self->{REPLY} = 1;
374             }
375              
376             my $mode =
377             (ref $hash->{CALLBACK} eq 'CODE'
378             ? kAEQueueReply
379             : (exists $hash->{REPLY} # check event setting
380             ? $hash->{REPLY}
381             : exists $self->{REPLY} # check global setting
382             ? $self->{REPLY}
383             : 1 # default to wait
384             )
385             ? kAEWaitReply
386             : kAENoReply)
387              
388             | (exists $hash->{MODE}
389             ? $hash->{MODE}
390             : exists $self->{MODE}
391             ? $self->{MODE}
392             : kAECanSwitchLayer);
393              
394             my $priority =
395             exists $hash->{PRIORITY}
396             ? $hash->{PRIORITY}
397             : exists $self->{PRIORITY}
398             ? $self->{PRIORITY}
399             : kAENormalPriority;
400              
401             my $timeout =
402             exists $hash->{TIMEOUT}
403             ? 60 * $hash->{TIMEOUT} # convert seconds to ticks
404             : exists $self->{TIMEOUT}
405             ? 60 * $self->{TIMEOUT}
406             : kNoTimeOut;
407              
408             print AEPrint($evt->{EVT}), "\n" if $self->{_print_aes};
409              
410             # load scripting additions
411             # XXX: do this only if necessary?
412             # kASAppleScriptSuite\kGetAEUT
413             do_event(qw(ascr gdut), \%xargs);
414              
415             $evt->send_event($mode, $priority, $timeout);
416              
417             my $retobj = exists $hash->{RETOBJ}
418             ? $hash->{RETOBJ}
419             : exists $self->{RETOBJ}
420             ? $self->{RETOBJ}
421             : 0;
422              
423             my $error_handler = exists $hash->{ERRORS}
424             ? $hash->{ERRORS}
425             : exists $self->{ERRORS}
426             ? $self->{ERRORS}
427             : 0;
428              
429             local $AE_GET{typeObjectSpecifier()} = sub {
430             return(_obj_desc($self, $_[0]), 1);
431             };
432              
433             local $Mac::AppleEvents::Simple::CLASSREC = sub {
434             return _is_class($self, $_[0]);
435             };
436              
437             local $Mac::AppleEvents::Simple::ENUMREC = sub {
438             return _is_enum($self, $_[0]);
439             };
440              
441             my @return;
442             if ($retobj) {
443             @return = $evt;
444             } elsif (my $type = $evt->type) {
445             @return = $evt->get;
446             @return = _fix_reco($self, {@return}) if $type eq typeAERecord;
447             @return = @{_fix_reco($self, \@return)} if $type eq typeAEList;
448             }
449              
450             $^E = my $errno = exists $evt->{ERRNO} ? $evt->{ERRNO} : 0; # restore errno
451              
452             my $return = 1;
453             # if error handler, only return if error handler returns true
454             # what should error handler be passed?
455             if ($errno && $error_handler) {
456             my($package, $filename, $line) = caller(1);
457              
458             $error_handler = \&_default_error_handler
459             if $error_handler eq 1;
460              
461             my($err, $errs, $errc);
462             $err = $MacErrors{ $errno };
463             $errs = $err ? $err->description : '';
464             $errc = $err ? $err->symbol : '';
465              
466             $return = $error_handler->({
467             _glue => $self,
468             _event => $evt,
469             glue => $self->{GLUENAME},
470             event => $name,
471             errs => $errs,
472             errn => $errno,
473             errc => $errc,
474             line => $line,
475             'package' => $package,
476             filename => $filename,
477             }, @origargs);
478             }
479              
480             $^E = exists $evt->{ERRNO} ? $evt->{ERRNO} : 0; # really restore errno
481              
482             return(wantarray ? @return : $return[0]) if $return;
483             }
484              
485             #=============================================================================#
486              
487             sub _default_error_handler {
488             my($err, @args) = @_;
489             my $args = join ', ', @args;
490             warn sprintf("%s->%s(%s) event failed:\n%s (%d)\n%s\n",
491             $err->{glue}, $err->{event}, $args,
492             $err->{errc}, $err->{errn}, $err->{errs}
493             );
494             return 1;
495             }
496              
497             #=============================================================================#
498             # prepare all event parameters
499              
500             sub _params {
501             my($self, $evt, $p, $data, $class, $event) = @_;
502             my($key, $type) = @{$p}[0, 1];
503              
504             if (ref $data eq 'Mac::AEParamType') {
505             ($data, $type) = @{$data}[1, 0];
506             } elsif ($type eq typeObjectSpecifier && ref $data ne 'Mac::AEObjDesc') {
507             $type = _check_default_type($data);
508              
509             # if we have a set data event, refer to direct object for type to use
510             } elsif ($type eq typeWildCard && $class eq 'core' && $event eq 'setd' && $key eq 'data' && ref $data ne 'Mac::AEObjDesc') {
511             if (my $dobj = AEGetParamDesc($evt->{EVT}, keyDirectObject)) {
512             if ($dobj->type eq typeObjectSpecifier) {
513             if (my $keydata = AEGetKeyDesc($dobj, keyAEKeyData)) {
514             $type = _get_type($self, $data, $type, _get_name($self, $keydata->get));
515             AEDisposeDesc $keydata;
516             }
517             }
518             AEDisposeDesc $dobj;
519             }
520             }
521              
522             my($desc, $dispose) = _get_desc($self, $data, $type, $key);
523             AEPutParamDesc($evt->{EVT}, $key, $desc)
524             or confess "Can't put $key/$desc into event: $MacError";
525              
526             if ($self->{_print_aes}) {
527             print <
528              
529             AEPutParamDesc($evt->{EVT}, $key, $desc);
530             EOT
531             print <
532             AEDisposeDesc($desc);
533             EOT
534             }
535              
536             AEDisposeDesc $desc if $dispose;
537             }
538              
539             #=============================================================================#
540             # Put anon array parameter data into AE list
541              
542             sub _do_list {
543             my($self, $data, $type, $key) = @_;
544             my $list = AECreateList('', 0) or confess "Can't create list: $MacError";
545             if ($self->{_print_aes}) {
546             print <
547              
548             $list = AECreateList('', 0);
549             EOT
550             }
551             my $count = 0;
552              
553             for my $d (@{$data}) {
554             my $t = _get_type($self, $d, '', $key);
555             my($desc, $dispose) = _get_desc($self, $d, $t); #$type);
556             AEPutDesc($list, ++$count, $desc)
557             or confess "Can't put $desc into $list: $MacError";
558             if ($self->{_print_aes}) {
559             print <
560             AEPutDesc($list, $count, $desc);
561             EOT
562             print <
563             AEDisposeDesc($desc);
564             EOT
565             }
566             AEDisposeDesc $desc if $dispose;
567             }
568              
569             return $list;
570             }
571              
572             #=============================================================================#
573             # Put anon hash parameter data into AE record
574              
575             sub _do_rec {
576             my($self, $data, $type) = @_;
577             my $reco = AECreateList('', 1) or confess "Can't create record: $MacError";
578             if ($self->{_print_aes}) {
579             print <
580              
581             $reco = AECreateList('', 1);
582             EOT
583             }
584             my $class;
585              
586             while (my($k, $d) = each %{$data}) {
587             if ($k =~ /^class$/i) {
588             $class = _get_id($self, $d);
589             next;
590             }
591             my $key = _get_id($self, $k);
592             my $t = _get_type($self, $d, '', $k);
593             my($desc, $dispose) = _get_desc($self, $d, $t);
594             AEPutKeyDesc($reco, $key, $desc)
595             or confess "Can't put $key/$desc into $reco: $MacError";
596             if ($self->{_print_aes}) {
597             print <
598             AEPutKeyDesc($reco, $key, $desc);
599             EOT
600             print <
601             AEDisposeDesc($desc);
602             EOT
603             }
604             AEDisposeDesc $desc if $dispose;
605             }
606              
607             if ($class) {
608             my $nreco = AECoerceDesc($reco, $class)
609             or confess "Can't coerce to '$class': $MacError";
610             if ($self->{_print_aes}) {
611             print <
612             $nreco = AECoerceDesc($reco, $class);
613             AEDisposeDesc($reco);
614             EOT
615             }
616             AEDisposeDesc $reco;
617             $reco = $nreco;
618             }
619              
620             return $reco;
621             }
622              
623             #=============================================================================#
624             # create AE descriptor record
625              
626             sub _do_obj {
627             my($self, $data, $class, $from) = @_;
628              
629             if (ref $data eq 'ARRAY') {
630             my @list;
631             for (@$data) {
632             push @list, _do_obj($self, $_, $class, $from);
633             }
634             return _obj_desc($self, _do_list($self, \@list));
635             }
636              
637             my($list, $obj, $form, $dataform, $d, $ref);
638             $class = 'property' if $class =~ /^(?:of|in|prop)$/;
639             confess "Class '$class' does not exist for '$data'.\n"
640             unless exists $self->{NAMES}{$class};
641              
642             $data = _get_objdesc($data);
643             $ref = ref $data;
644              
645             if ($class eq 'property') {
646             my $prop = $data;
647             $data = _get_id($self, $data) or croak "Can't find property '$prop'.\n";
648             $form = typeProperty;
649              
650             } elsif ($ref eq 'AEDesc' || $ref eq 'Mac::AEObjDescType') {
651             $data = $DESC_TYPE{$data->[0]}->($self, $class, @{$data}[1 .. $#{$data}])
652             if $ref eq 'Mac::AEObjDescType';
653             $dataform = $form = $data->type;
654             if ($form eq typeCompDescriptor || $form eq typeLogicalDescriptor) {
655             $form = formTest;
656             }
657              
658             } elsif ($ref eq 'Mac::AEObjDescForm') {
659             $form = $$data[0];
660             $dataform = $$data[1] if @$data == 3;
661             $data = $$data[-1];
662              
663             } elsif ($data =~ /^[+-]?\d+$/) {
664             $form = formAbsolutePosition;
665              
666             } else {
667             $form = formName;
668             if ($^O ne 'MacOS' && $class =~ /^f(?:ile|older)$/i) {
669             $data = Mac::Files::_Unix2Mac($data);
670             }
671             }
672              
673             $dataform ||=
674             $form eq formName ? typeChar :
675             $form eq formAbsolutePosition ? typeInteger :
676             $form eq typeProperty ? typeType :
677             $form;
678              
679             $class = $self->{NAMES}{$class};
680             $list = AECreateList('', 1) or confess "Can't create list: $MacError";
681              
682             # form / keyAEForm
683             AEPutKey($list, keyAEForm, typeEnumerated, $form)
684             or confess "Can't put form:$form into object: $MacError";
685              
686             # want / keyAEDesiredClass
687             AEPutKey($list, keyAEDesiredClass, typeType, $class->{id})
688             or confess "Can't put want:$class->{id} into object: $MacError";
689              
690             if ($self->{_print_aes}) {
691             print <
692              
693             $list = AECreateList('', 1);
694             AEPutKey($list, keyAEForm, typeEnumerated, $form);
695             AEPutKey($list, keyAEDesiredClass, typeType, $class->{id});
696             EOT
697             }
698              
699             # seld / keyAEKeyData
700             ($d, $dataform) = _get_data($self, $data, $dataform);
701             if (ref $d eq 'AEDesc') {
702             AEPutKeyDesc($list, keyAEKeyData, $d)
703             or confess "Can't put seld:$d into object: $MacError";
704             if ($self->{_print_aes}) {
705             print <
706             AEPutKeyDesc($list, keyAEKeyData, $d);
707             EOT
708             }
709              
710             } else {
711             AEPutKey($list, keyAEKeyData, $dataform, $d)
712             or confess "Can't put seld:$dataform($d) into object: $MacError";
713             if ($self->{_print_aes}) {
714             print <
715             AEPutKey($list, keyAEKeyData, $dataform, $d);
716             EOT
717             }
718             }
719              
720             # type / keyAEContainer
721             # hm. why are first two the same? why didn't i comment this to
722             # begin with?
723             if ($from && $from eq typeCurrentContainer) {
724             AEPutKey($list, keyAEContainer, $from, '')
725             or confess "Can't put from:$from into object: $MacError";
726             if ($self->{_print_aes}) {
727             print <
728             AEPutKey($list, keyAEContainer, $from, '');
729             EOT
730             }
731             } elsif ($from && $from eq typeObjectBeingExamined) {
732             AEPutKey($list, keyAEContainer, $from, '')
733             or confess "Can't put from:$from into object: $MacError";
734             if ($self->{_print_aes}) {
735             print <
736             AEPutKey($list, keyAEContainer, $from, '');
737             EOT
738             }
739             } elsif ($from) {
740             $from = _get_objdesc($from);
741             AEPutKeyDesc($list, keyAEContainer, $from)
742             or confess "Can't put from:$from into object: $MacError";
743             if ($self->{_print_aes}) {
744             print <
745             AEPutKeyDesc($list, keyAEContainer, $from);
746             EOT
747             }
748             } else {
749             AEPutKey($list, keyAEContainer, typeNull, '')
750             or confess "Can't put from:null into object: $MacError";
751             if ($self->{_print_aes}) {
752             print <
753             AEPutKey($list, keyAEContainer, typeNull, '');
754             EOT
755             }
756             }
757              
758             $obj = AECoerceDesc($list, typeObjectSpecifier)
759             or confess "Can't coerce to 'obj ': $MacError";
760             if ($self->{_print_aes}) {
761             print <
762             $obj = AECoerceDesc($list, typeObjectSpecifier);
763             AEDisposeDesc($list);
764             EOT
765             }
766             AEDisposeDesc $list;
767              
768             return _obj_desc($self, $obj);
769             }
770              
771             #=============================================================================#
772             # create insertion record
773              
774             sub _do_loc ($;$) {
775             my($pos, $obj) = @_;
776              
777             # just so we can look up _print_aes
778             my $self = ref $obj eq 'Mac::AEObjDesc' ? $obj->{GLUE} : {};
779              
780             $obj = _get_objdesc($obj);
781             my $desc = ref $obj eq 'AEDesc' ? $obj : gNull();
782             my $list = AECreateList('', 1) or confess "Can't create list: $MacError";
783              
784             AEPutKeyDesc($list, keyAEObject, $desc)
785             or confess "Can't put object in location: $MacError";
786             AEPutKey($list, keyAEPosition, typeEnumerated, $INSL{$pos} || $pos)
787             or confess "Can't put pos in location: $MacError";
788              
789             my $insl = AECoerceDesc($list, typeInsertionLoc)
790             or confess "Can't coerce $list to 'obj ': $MacError";
791              
792             if ($self->{_print_aes}) {
793             my $p = $INSL{$pos} || $pos;
794             print <
795              
796             $list = AECreateList('', 1);
797             AEPutKeyDesc($list, keyAEObject, $desc);
798             AEPutKey($list, keyAEPosition, typeEnumerated, $p);
799             $insl = AECoerceDesc($list, typeInsertionLoc);
800             AEDisposeDesc($list);
801             EOT
802             }
803              
804             AEDisposeDesc $list;
805             _save_desc($insl);
806             return $insl;
807             }
808              
809             #=============================================================================#
810             # create glue descriptor record
811              
812             sub _do_range {
813             my($self, $class, $r1, $r2) = @_;
814              
815             $r1 = _do_obj($self, $r1, $class, typeCurrentContainer);
816             $r2 = _do_obj($self, $r2, $class, typeCurrentContainer);
817              
818             my $list = AECreateList('', 1) or confess "Can't create list: $MacError";
819              
820             AEPutKeyDesc($list, keyAERangeStart, $r1->{DESC})
821             or confess "Can't add param to list: $MacError";
822             AEPutKeyDesc($list, keyAERangeStop, $r2->{DESC})
823             or confess "Can't add param to list: $MacError";
824              
825             my $rang = AECoerceDesc($list, typeRangeDescriptor)
826             or confess "Can't coerce to range: $MacError";
827              
828             if ($self->{_print_aes}) {
829             print <
830              
831             $list = AECreateList('', 1);
832             AEPutKeyDesc($list, keyAERangeStart, $r1->{DESC});
833             AEPutKeyDesc($list, keyAERangeStop, $r2->{DESC});
834             $rang = AECoerceDesc($list, typeRangeDescriptor);
835             AEDisposeDesc($list);
836             EOT
837             }
838              
839             AEDisposeDesc $list;
840             _save_desc($rang);
841              
842             return $rang;
843             }
844              
845             #=============================================================================#
846             # create comparison descriptor record
847              
848             sub _do_comp {
849             my $self = shift;
850             my $len = @_;
851             my($p1, $d1, $op, $p2, $d2, $c1, $c2, $dispose1, $dispose2);
852              
853             $p1 = $len < 4 ? 'property' : shift;
854             $d1 = shift;
855             $op = shift;
856             $p2 = $len < 5 ? undef : shift;
857             $d2 = shift;
858              
859             unless (ref $op eq 'AEDesc') {
860             my $foo = $op;
861             $op = $COMP{lc $op} or croak "Comparison operator '$foo' not recognized";
862             }
863              
864             if ($p1 eq 'property' && $d1 eq 'it') {
865             $c1 = new AEDesc typeObjectBeingExamined;
866             if ($self->{_print_aes}) {
867             print <
868              
869             $c1 = AECreateDesc(typeObjectBeingExamined);
870             EOT
871             }
872             $dispose1 = 1;
873             } else {
874             $c1 = _do_obj($self, $d1, $p1, typeObjectBeingExamined)->{DESC};
875             }
876              
877             if (defined $p2) {
878             $c2 = _do_obj($self, $d2, $p2, typeObjectBeingExamined)->{DESC};
879             } else {
880             ($c2, $dispose2) = _get_desc($self, $d2);
881             }
882              
883             my $list = AECreateList('', 1) or confess "Can't create list: $MacError";
884              
885             AEPutKeyDesc($list, keyAECompOperator, $op);
886             AEPutKeyDesc($list, keyAEObject1, $c1);
887             AEPutKeyDesc($list, keyAEObject2, $c2);
888              
889             if ($self->{_print_aes}) {
890             print <
891              
892             $list = AECreateList('', 1);
893             AEPutKeyDesc($list, keyAECompOperator, $op);
894             AEPutKeyDesc($list, keyAEObject1, $c1);
895             AEPutKeyDesc($list, keyAEObject2, $c2);
896             EOT
897            
898             print <
899             AEDisposeDesc($c1);
900             EOT
901              
902             print <
903             AEDisposeDesc($c2);
904             EOT
905             }
906              
907             AEDisposeDesc $c1 if $dispose1;
908             AEDisposeDesc $c2 if $dispose2;
909              
910             my $comp = AECoerceDesc($list, typeCompDescriptor)
911             or confess "Can't coerce list to comparison descriptor: $MacError";
912              
913             if ($self->{_print_aes}) {
914             print <
915             $comp = AECoerceDesc($list, typeCompDescriptor);
916             AEDisposeDesc($list);
917             EOT
918             }
919             AEDisposeDesc $list;
920             _save_desc($comp);
921              
922             return $comp;
923             }
924              
925             #=============================================================================#
926             # create a logical descriptor record
927              
928             sub _do_logical {
929             my($self, $op, @args) = @_;
930             my $terms = AECreateList('', 0) or confess "Can't create list: $MacError";
931              
932             if ($self->{_print_aes}) {
933             print <
934              
935             $terms = AECreateList('', 0);
936             EOT
937             }
938              
939             unless (ref $op eq 'AEDesc') {
940             my $foo = $op;
941             $op = $LOGI{uc $op} or croak "Logical operator '$foo' not recognized";
942             }
943              
944             for my $i (0 .. $#args) {
945             my $term = $args[$i];
946             my $desc;
947             croak "Each logical term must be in an anonymous array ($term)\n"
948             if ref $term ne 'ARRAY';
949              
950             if (grep { ref ne 'ARRAY' } @{$term}[1 .. $#{$term}]) {
951             $desc = _do_comp($self, @$term);
952             } else {
953             $desc = _do_logical($self, @$term);
954             }
955             AEPutDesc($terms, $i + 1, $desc);
956             if ($self->{_print_aes}) {
957             my $j = $i + 1;
958             print <
959             AEPutDesc($terms, $j, $desc);
960             EOT
961             }
962             }
963              
964             my $list = AECreateList('', 1) or confess "Can't create list: $MacError";
965             AEPutKeyDesc($list, keyAELogicalOperator, $op);
966             AEPutKeyDesc($list, keyAELogicalTerms, $terms);
967              
968             my $logi = AECoerceDesc($list, typeLogicalDescriptor)
969             or confess "Can't coerce list into logical descriptor: $MacError";
970             AEDisposeDesc $terms;
971             AEDisposeDesc $list;
972              
973             if ($self->{_print_aes}) {
974             print <
975             $list = AECreateList('', 1);
976             AEPutKeyDesc($list, keyAELogicalOperator, $op);
977             AEPutKeyDesc($list, keyAELogicalTerms, $terms);
978             $logi = AECoerceDesc($list, typeLogicalDescriptor);
979             AEDisposeDesc($terms);
980             AEDisposeDesc($list);
981             EOT
982             }
983              
984             _save_desc($logi);
985              
986             return $logi;
987             }
988              
989             #=============================================================================#
990             # help create a test object record
991              
992             sub _do_whose {
993             splice @_, 1, 1; # remove class
994             if (grep { ref ne 'ARRAY' } @_[2 .. $#_]) {
995             goto &_do_comp;
996             } else {
997             goto &_do_logical;
998             }
999             }
1000              
1001             #=============================================================================#
1002             # return descriptor as needed
1003              
1004             sub _get_desc {
1005             my($self, $data, $type, $key) = @_;
1006             my($desc, $dispose, $ref);
1007              
1008             $dispose = 1;
1009             $data = _get_objdesc($data);
1010             $ref = ref $data;
1011              
1012             if ($ref eq 'Mac::AEParamType') {
1013             ($data, $type) = @{$data}[1, 0];
1014             } elsif ($type && $type eq typeObjectSpecifier && $ref ne 'Mac::AEObjDesc') {
1015             $type = _check_default_type($data);
1016             }
1017              
1018             if ($ref eq 'ARRAY') {
1019             $desc = _do_list($self, $data, $type, $key);
1020             } elsif ($ref eq 'HASH') {
1021             $desc = _do_rec($self, $data, $type);
1022             } elsif ($ref eq 'AEDesc') {
1023             $desc = $data;
1024             $dispose = 0;
1025             _save_desc($desc);
1026             } else {
1027             my($d, $t);
1028             $t = _get_type($self, $data, $type);
1029             ($d, $t) = _get_data($self, $data, $t);
1030              
1031             if (ref $d eq 'AEDesc') {
1032             $desc = $d;
1033             $dispose = 0;
1034             _save_desc($d);
1035             } else {
1036             $desc = AEDesc->new($t, $d);
1037             if ($self->{_print_aes}) {
1038             print <
1039              
1040             $desc = AECreateDesc($t, $d);
1041             EOT
1042             }
1043             }
1044             }
1045              
1046             return($desc, $dispose);
1047             }
1048              
1049             #=============================================================================#
1050             # take a good guess at what the data type is
1051              
1052             sub _get_type {
1053             my($self, $data, $type, $key) = @_;
1054             return '' if ref $data;
1055             if (defined $key) {
1056             my $href = _get_id($self, $key, 1);
1057             if (exists $href->{types}) {
1058             my @types = grep { exists $AE_PUT{$_} } @{$href->{types}};
1059             for my $t (@types) {
1060             if ($t eq typeUnicodeText) {
1061             # only use if text is UTF, and Encode is available
1062             if ($ENCODE && Encode::is_utf8($data)) {
1063             $type = typeUnicodeText;
1064             last;
1065             }
1066             } else {
1067             $type = $t;
1068             last;
1069             }
1070             }
1071             }
1072             }
1073              
1074             if (!$type || $type eq typeWildCard) {
1075             $type = _check_default_type($data);
1076             }
1077              
1078             return $type;
1079             }
1080              
1081             # check for UTF-ness?
1082             sub _check_default_type {
1083             my($data) = @_;
1084             my $type = $data =~ /^[+-]?\d+$/
1085             ? typeInteger
1086             : $data =~ /^[+-]?\d+\.\d+$/
1087             ? typeFloat
1088             : typeChar;
1089             return $type;
1090             }
1091              
1092             #=============================================================================#
1093             # fudge the data into something that the event will be expecting
1094             # should only return data as simple non-reference scalar or AEDesc
1095              
1096             sub _get_data {
1097             my($self, $data, $type) = @_;
1098             my $t;
1099              
1100             my $ref = ref $data;
1101              
1102             if ($ref eq 'Mac::AEEnum') {
1103             my $id = _get_id($self, $data->[0]);
1104             $data = $id if defined $id;
1105             $type = typeEnumerated; # typeEnumerated or typeType ???
1106              
1107             } elsif ($type eq typeType) {
1108             my $id = _get_id($self, $data);
1109             $data = $id if defined $id;
1110              
1111             # see the %AE_PUT data structure
1112             } elsif (exists $AE_PUT{$type}) {
1113             ($data, $t) = $AE_PUT{$type}->($data);
1114             }
1115              
1116             return($data, $t || $type);
1117             }
1118              
1119             #=============================================================================#
1120             # get class / property name
1121              
1122             sub _get_name {
1123             my($self, $id) = @_;
1124             if (exists $self->{IDS}{$id}) {
1125             if (exists $self->{IDS}{$id}{name}) {
1126             return $self->{IDS}{$id}{name};
1127             }
1128             }
1129             return;
1130             }
1131              
1132             #=============================================================================#
1133             # find if ID is class
1134              
1135             sub _is_class {
1136             my($self, $id) = @_;
1137             my $name = _get_name($self, $id) or return;
1138             my $class = $self->{CLASS}{$name} or return;
1139             if (scalar keys %{$class->{properties}} > 1 ||
1140             (scalar keys %{$class->{properties}} == 1 && ! exists $class->{properties}{''})) {
1141             return 1;
1142             }
1143             }
1144              
1145             #=============================================================================#
1146             # return name if type is enum
1147              
1148             sub _is_enum {
1149             my($self, $id) = @_;
1150             return unless exists $self->{ENUM}{$id};
1151             return _get_name($self, $id);
1152             }
1153              
1154             #=============================================================================#
1155             # fix record stuff
1156              
1157             sub _fix_reco {
1158             my($self, $data) = @_;
1159              
1160             if (ref $data eq 'ARRAY') {
1161             my @narr;
1162             for my $i (@$data) {
1163             push @narr, (ref $i eq 'HASH' || ref $i eq 'ARRAY')
1164             ? _fix_reco($self, $i) : $i;
1165             }
1166             return \@narr;
1167             } elsif (ref $data eq 'HASH') {
1168             my %nreco;
1169             for my $id (keys %$data) {
1170             my $nid = _get_name($self, $id);
1171             my $i = $data->{$id};
1172             $nreco{$nid || $id} = (ref $i eq 'HASH' || ref $i eq 'ARRAY')
1173             ? _fix_reco($self, $i) : $i;
1174             }
1175             return \%nreco;
1176             }
1177             }
1178              
1179             #=============================================================================#
1180             # get class / property id
1181              
1182             sub _get_id {
1183             my($self, $name, $obj) = @_;
1184             (my $new = lc $name) =~ tr/ /_/;
1185             my $ref = $self->{NAMES}{$new} if exists $self->{NAMES}{$new};
1186             return $obj ? $ref : $ref->{id};
1187             }
1188              
1189             #=============================================================================#
1190             # get Mac::AEObjDesc
1191              
1192             sub _get_objdesc {
1193             my $ref = ref $_[0];
1194             if ($ref eq 'Mac::AEObjDesc') {
1195             return $_[0]->{DESC};
1196             } else {
1197             return $_[0];
1198             }
1199             }
1200              
1201             #=============================================================================#
1202             # create Mac::AEObjDesc
1203              
1204             sub _obj_desc {
1205             _save_desc($_[1]);
1206             my $self = bless { GLUE => $_[0], DESC => $_[1] }, 'Mac::AEObjDesc';
1207             }
1208              
1209             #=============================================================================#
1210             # save AEDesc for destruction later in Mac::AppleEvents::Simple
1211              
1212             sub _save_desc { $DESCS{$_[0]} = $_[0] }
1213              
1214             #=============================================================================#
1215             # find all information for an event, based on its "name"
1216              
1217             sub _find_event {
1218             my($self, $name) = @_;
1219             my $event;
1220              
1221             return $SPECIALEVENT{$name} if exists $SPECIALEVENT{$name};
1222              
1223             for ($self->{_DB}{EVENT}, @$OTHEREVENT) {
1224             if (exists $_->{$name}) {
1225             $event = $_->{$name};
1226             last;
1227             }
1228             }
1229              
1230             return $event;
1231             }
1232              
1233             #=============================================================================#
1234             # is class a plural of another?
1235              
1236             sub _is_plural {
1237             my($self, $class) = @_;
1238             my $pl = 'c@#!';
1239             return unless exists $self->{CLASS}{$class}{properties};
1240             my $pref = $self->{CLASS}{$class}{properties};
1241             return scalar grep { $pref->{$_}[0] && $pref->{$_}[0] eq $pl } keys %$pref;
1242             }
1243              
1244             #=============================================================================#
1245             # create an AE object
1246              
1247             # heh heh heh ... stupid little shortcut
1248             sub prop {
1249             @_ = ($_[0], 'property', @_[1 .. $#_]);
1250             goto &obj;
1251             }
1252              
1253             # this is pretty nasty, just go with it
1254             sub obj {
1255             my($self, @data, $obj, @obj) = @_;
1256              
1257             if (ref($data[-1]) =~ /^(Mac::)?AE(?:Obj)?Desc$/) {
1258             $obj = pop @data;
1259             }
1260              
1261             for (my $i = 0; $i <= $#data; $i++) {
1262             my($k, $v) = $data[$i];
1263             if (!(
1264             $data[$i+1]
1265             &&
1266             ref($data[$i+1]) =~ /^(Mac::)?AE/)
1267             &&
1268             _is_plural($self, $k)
1269             ) {
1270             $v = gAll();
1271             } else {
1272             $i++;
1273             $v = $data[$i];
1274             }
1275             push @obj, [$v, $k];
1276             }
1277              
1278             for (reverse @obj) {
1279             local $^W;
1280             $obj = _do_obj($self, @{$_}[0, 1], $obj);
1281             }
1282              
1283             return $obj;
1284             }
1285              
1286             #=============================================================================#
1287             # get app version
1288              
1289             sub version {
1290             my($self) = @_;
1291              
1292             my $reply = $self->prop('version')->get(RETOBJ => 1);
1293             my $vers;
1294              
1295             if ($reply->type eq 'vers') {
1296             my $data = $reply->get;
1297             my @l = split(//, unpack("a7", $data));
1298             $vers = unpack("x7a@{[ord($l[6])]}", $data);
1299             } else {
1300             $vers = $reply->get;
1301             }
1302              
1303             return $vers;
1304             }
1305              
1306             #=============================================================================#
1307             # helper method to get the application process object
1308              
1309             sub app_process {
1310             my($self) = @_;
1311             $SYSEVT ||= new Mac::Glue 'System Events';
1312              
1313             return $SYSEVT->obj(application_process => $self->{APPNAME})->get;
1314             }
1315              
1316             #=============================================================================#
1317             # launch the app (done automatically when an event is called if not running)
1318              
1319             sub launch {
1320             my($self, $location) = @_;
1321             if (defined $location) {
1322             LaunchSpecs($location);
1323             } elsif ($self->{BUNDLE_ID}) {
1324             $location = LSFindApplicationForInfo('', $self->{BUNDLE_ID});
1325             LaunchSpecs($location);
1326             } else {
1327             LaunchApps($self->{CREATOR_ID});
1328             }
1329             }
1330              
1331             #=============================================================================#
1332             # launch spec and then get PSN
1333              
1334             sub _path_to_psn {
1335             my($path) = @_;
1336              
1337             confess "Path '$path' does not exist" unless -e $path;
1338              
1339             my $lp = LaunchParam->new(
1340             launchControlFlags => (launchContinue | launchNoFileFlags | launchDontSwitch),
1341             launchAppSpec => $path
1342             );
1343              
1344             my $psn = LaunchApplication($lp) or confess "Cannot launch '$path': $MacError";
1345              
1346             return pack_psn($psn);
1347             }
1348              
1349             #=============================================================================#
1350             # open scripting additions and dialect files only once,
1351             # save them for further use by all Mac::Glue instances
1352              
1353             sub _open_others {
1354             chomp(my $curdir = `pwd`);
1355             my @others;
1356             for my $dir (map { catfile($MACGLUEDIR, $_) } qw[dialects additions]) {
1357             unless (-e $dir) {
1358             warn "Please run gluedialect and gluescriptadds programs"
1359             unless $Mac::Glue::CREATINGGLUES;
1360             $Mac::Glue::NEEDCREATE = 1;
1361             next;
1362             }
1363              
1364             local *DIR;
1365             opendir DIR, $dir or confess "Can't open directory '$dir': $!";
1366             chdir $dir or confess "Can't chdir directory '$dir': $!";
1367              
1368             # add file type / creator checking ???
1369             # maybe add a new file type for glues? i can do that now,
1370             # because i am special or something.
1371             for (readdir DIR) {
1372             next if -d;
1373             next if $_ eq "Icon\015";
1374             next if /\.pod$/;
1375             tie my %db, 'MLDBM', $_, O_RDONLY or confess "Can't tie '$_': $!";
1376             push @$OTHEREVENT, $db{EVENT} if $db{EVENT};
1377             push @$OTHERCLASS, $db{CLASS} if $db{CLASS};
1378             push @$OTHERENUM, $db{ENUM} if $db{ENUM};
1379             }
1380             }
1381             chdir $curdir or confess "Can't chdir to '$curdir': $!";
1382             }
1383              
1384             #=============================================================================#
1385             # merge additions, dialect, and glue classes together
1386             # wow, this is ugly. i wonder if there is a better/faster way. probably.
1387             # or maybe a way to cache the results between iterations ... ?
1388             # but then, how do we deal with added/removed classes?
1389              
1390             sub _merge_classes {
1391             my($db) = @_;
1392             if (!exists $MERGEDCLASSES->{ $db->{ID} }) {
1393             my($ids, $names) = ({}, {});
1394             my($class, @classes) = ($db->{CLASS}, @$OTHERCLASS);
1395              
1396             for my $c (keys %$class) {
1397             $names->{$c}{id} = $class->{$c}{id};
1398             $ids->{$names->{$c}{id}}{name} = $c;
1399              
1400             for my $p (keys %{$class->{$c}{properties}}) {
1401             $names->{$p}{id} ||= $class->{$c}{properties}{$p}[0];
1402             $ids->{$names->{$p}{id}}{name} ||= $p;
1403             unshift @{$names->{$p}{types}}, $class->{$c}{properties}{$p}[1];
1404             }
1405             }
1406              
1407             for my $tempc (@classes) {
1408             for my $c (keys %$tempc) {
1409             $names->{$c}{id} ||= $tempc->{$c}{id};
1410             $ids->{$names->{$c}{id}}{name} = $c;
1411              
1412             for my $p (keys %{$tempc->{$c}{properties}}) {
1413             $names->{$p}{id} ||= $tempc->{$c}{properties}{$p}[0];
1414             $ids->{$names->{$p}{id}}{name} ||= $p;
1415             unshift @{$names->{$p}{types}}, $tempc->{$c}{properties}{$p}[1];
1416              
1417             $class->{$c}{properties}{$p} = $tempc->{$c}{properties}{$p}
1418             if (exists $class->{$c} &&
1419             $class->{$c}{id} eq $tempc->{$c}{id} &&
1420             ! exists $class->{$c}{properties}{$p});
1421             }
1422              
1423             unless (exists $class->{$c}) {
1424             $class->{$c} = $tempc->{$c};
1425             }
1426             }
1427             }
1428              
1429             $MERGEDCLASSES->{ $db->{ID} } = [$class, $names, $ids];
1430             }
1431             return @{$MERGEDCLASSES->{ $db->{ID} }};
1432             }
1433              
1434             #=============================================================================#
1435             # "merge" additions, dialect, and glue enumerations together
1436             # see above about caching results, rethinking logic. for a really
1437             # really really really rainy day.
1438              
1439             sub _merge_enums {
1440             my($db, $self) = @_;
1441             if (!exists $MERGEDENUM->{ $db->{ID} }) {
1442             my $names = $self->{NAMES};
1443             my $ids = $self->{IDS};
1444              
1445             for my $tempc (grep defined, $db->{ENUM}, @$OTHERENUM) {
1446             for my $c (keys %$tempc) {
1447             $self->{ENUMTYPE}{$c} = [];
1448             for my $n (keys %{$tempc->{$c}}) {
1449             $names->{$n}{id} ||= $tempc->{$c}{$n}{id};
1450             $ids->{$names->{$n}{id}} ||= { name => $n };
1451             $self->{ENUM}{$tempc->{$c}{$n}{id}} = 1;
1452             push @{$self->{ENUMTYPE}{$c}}, $tempc->{$c}{$n}{id};
1453             }
1454             }
1455             }
1456             $MERGEDENUM->{ $db->{ID} }++;
1457             }
1458             $MERGEDENUM->{ $db->{ID} };
1459             }
1460              
1461             #=============================================================================#
1462              
1463             *AE_GET = *Mac::AppleEvents::Simple::AE_GET{HASH};
1464             *DESCS = *Mac::AppleEvents::Simple::DESCS{HASH};
1465              
1466             %INSL = (
1467             end => kAEEnd,
1468             before => kAEBefore,
1469             beginning => kAEBeginning,
1470             front => kAEBeginning,
1471             after => kAEAfter,
1472             back => kAEAfter,
1473             behind => kAEAfter,
1474             );
1475              
1476             %COMP = (
1477             g_t => gGT(),
1478             g_e => gGE(),
1479             equals => gEquals(),
1480             l_t => gLT(),
1481             l_e => gLE(),
1482             begins_with => gBeginsWith(),
1483             ends_with => gEndsWith(),
1484             contains => gContains(),
1485             );
1486              
1487             %LOGI = (
1488             AND => gAnd(),
1489             OR => gOr(),
1490             NOT => gNot(),
1491             );
1492              
1493             %AE_PUT = (
1494             typeShortFloat() => sub {MacPack(typeShortFloat, $_[0])},
1495             typeFloat() => sub {MacPack(typeFloat, $_[0])},
1496             typeMagnitude() => sub {MacPack(typeMagnitude, $_[0])},
1497             typeShortInteger() => sub {MacPack(typeShortInteger, $_[0])},
1498             typeInteger() => sub {MacPack(typeInteger, $_[0])},
1499             typeBoolean() => sub {MacPack(typeBoolean, $_[0])},
1500             typeChar() => sub {MacPack(typeChar, $_[0])},
1501             typeFSS() => sub {MacPack(typeFSS, $_[0])},
1502             typeAlias() => sub {
1503             my $alis = -e $_[0]
1504             ? NewAliasMinimal($_[0])
1505             : NewAliasMinimalFromFullPath($_[0]);
1506             croak "Can't create alias for '$_[0]': $MacError" unless $alis;
1507             return $alis->get;
1508             },
1509             typeWildCard() => sub {
1510             my $type = _get_type('', $_[0], typeWildCard);
1511             my $data = _get_data('', $_[0], $type);
1512             return($data, $type);
1513             },
1514             typeProcessSerialNumber() => sub { pack_psn($_[0]) },
1515             typeLongDateTime() => sub {
1516             my $ldt = $^O eq 'MacOS' ? $_[0] : perl2epoch($_[0], 'macos');
1517             require Config;
1518             if ($Config::Config{byteorder} eq '1234') {
1519             return pack 'LL', $ldt, 0;
1520             # this may break if some platform uses neither 1234 or 4321!
1521             } else {
1522             return pack 'LL', 0, $ldt;
1523             }
1524             },
1525              
1526             'file' => sub {
1527             if ($^O ne 'MacOS') {
1528             return Mac::Files::_Unix2Mac($_[0]);
1529             } else {
1530             return $_[0];
1531             }
1532             },
1533              
1534             # empty four bytes for lang code
1535             typeIntlText() => sub {' ' . MacPack(typeChar, $_[0])},
1536             typeUnicodeText() => sub {
1537             if ($ENCODE) {
1538             # BOM sometimes causes problems, so it is optional, and
1539             # we leave it off, and use native ordering explicitly
1540             require Config;
1541             my $bom = $Config::Config{byteorder} eq '1234' ? 'LE' : 'BE';
1542             return new AEDesc typeUnicodeText,
1543             Encode::encode('UTF-16'.$bom, $_[0]);
1544             } else { # oh well!
1545             return new AEDesc typeChar, $_[0];
1546             }
1547             }
1548             );
1549             $AE_PUT{folder} = $AE_PUT{file};
1550              
1551             %DESC_TYPE = (
1552             formRange() => \&_do_range,
1553             formTest() => \&_do_whose,
1554             );
1555              
1556             %SPECIALEVENT = (
1557             'set' => {
1558             'class' => 'core',
1559             'event' => 'setd',
1560             'reply' => ['****', 0, 0, 0],
1561             'params' => {
1562             '----' => [keyDirectObject, 'obj ', 1, 0, 0, 1],
1563             'to' => ['data', '****', 1, 0, 0],
1564             }
1565             },
1566             'get' => {
1567             'class' => 'core',
1568             'event' => 'getd',
1569             'reply' => ['****', 1, 0, 0],
1570             'params' => {
1571             '----' => [keyDirectObject, 'obj ', 1, 0, 0, 0],
1572             'as' => ['rtyp', 'type', 0, 1, 0],
1573             }
1574             },
1575             );
1576              
1577             %SPECIALCLASS = (
1578              
1579             );
1580              
1581             #=============================================================================#
1582             # other glue* stuff
1583             for (gNull(), gAnd(), gOr(), gNot(), gGT(), gGE(), gEquals(),
1584             gLT(), gLE(), gBeginsWith(), gEndsWith(), gContains()) {
1585             _save_desc($_);
1586             }
1587              
1588             #=============================================================================#
1589              
1590             package Mac::AEObjDesc;
1591              
1592             use Carp;
1593             use vars '$AUTOLOAD';
1594              
1595             use Mac::AppleEvents;
1596              
1597             sub getdata {
1598             my($self, $key) = @_;
1599             my $desc = $self->{DESC};
1600             return unless $desc && $desc->type eq typeObjectSpecifier;
1601              
1602             my $data = AEGetKeyDesc($desc, $key || keyAEKeyData);
1603             my $return = $data->Mac::AppleEvents::Simple::_getdata;
1604             AEDisposeDesc $data;
1605             return $return;
1606             }
1607              
1608             sub AUTOLOAD { # can?
1609             my($self, @args) = @_;
1610              
1611             (my $name = $AUTOLOAD) =~ s/^.*://;
1612             return if $name eq 'DESTROY';
1613              
1614             my $sub = $self->{GLUE}->can($name);
1615             if ($sub) {
1616             if ($name eq 'obj' || $name eq 'prop') {
1617             push @args, $self;
1618             } else {
1619             unshift @args, $self;
1620             }
1621             $sub->($self->{GLUE}, @args);
1622             } else {
1623             # should this croak? probably. complain and come
1624             # up with another idea if you don't like it.
1625             croak "No event '$name' available from glue for '$self->{GLUE}{GLUENAME}'";
1626             }
1627             }
1628              
1629              
1630             1;
1631              
1632             __END__