File Coverage

blib/lib/Mac/AppleEvents/Simple.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Mac::AppleEvents::Simple;
2             require 5.004;
3 1     1   22844 use strict;
  1         2  
  1         32  
4 1         154 use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION $SWITCH
5 1     1   5 $CLASSREC $ENUMREC %AE_GET %DESCS $DESCCOUNT $WARN $DEBUG $REVISION);
  1         1  
6              
7 1     1   6 use Carp;
  1         2  
  1         106  
8 1     1   6 use Exporter;
  1         2  
  1         43  
9 1     1   383 use Mac::AppleEvents 1.30;
  0            
  0            
10             use Mac::Apps::Launch 1.90;
11             use Mac::Processes 1.04;
12             use Mac::Files;
13             use Mac::Types;
14             use Mac::Errors qw(%MacErrors $MacError);
15             use Time::Epoch 'epoch2perl';
16              
17             #-----------------------------------------------------------------
18              
19             @ISA = qw(Exporter Mac::AppleEvents);
20             @EXPORT = qw(
21             do_event build_event handle_event
22             pack_ppc pack_eppc pack_eppc_x pack_psn pack_pid
23             );
24             @EXPORT_OK = (@EXPORT, @Mac::AppleEvents::EXPORT);
25             %EXPORT_TAGS = (all => [@EXPORT, @EXPORT_OK]);
26              
27             $REVISION = '$Id: Simple.pm,v 1.26 2006/07/07 06:44:37 pudge Exp $';
28             $VERSION = '1.18';
29             $DEBUG ||= 0;
30             $SWITCH ||= 0;
31             $WARN ||= 0;
32              
33             # some users won't have Carp::cluck ...
34             sub cluck;
35             *cluck = *Carp::cluck{CODE} || sub { warn Carp::longmess @_ };
36              
37             #-----------------------------------------------------------------
38             # Main public methods and functions
39             #-----------------------------------------------------------------
40              
41             sub do_event {
42             my $self = bless _construct(@_), __PACKAGE__;
43             $self->_build_event and return $self->_warn;
44             $self->_send_event and return $self->_warn;
45             $self->_sending and return $self->_warn;
46             $self;
47             }
48              
49             #-----------------------------------------------------------------
50              
51             sub build_event {
52             my $self = bless _construct(@_), __PACKAGE__;
53             $self->_build_event and return $self->_warn;
54             # $self->_print_desc('EVT') and return $self->_warn;
55             $self;
56             }
57              
58             #-----------------------------------------------------------------
59              
60             sub handle_event {
61             my($class, $event, $code, $sys) = @_;
62             my $hash = $sys ? \%SysAppleEvent : \%AppleEvent;
63             my $handler = bless [$hash, $class, $event],
64             __PACKAGE__ . '::Handler';
65              
66             $hash->{$class, $event} = sub {
67             my($evt, $rep, $key) = @_;
68             # make 'em backward!
69             my $obj = bless {
70             REP => $evt,
71             EVT => $rep,
72             HANDLER => $handler
73             }, __PACKAGE__;
74             $code->($obj, split /$;/, $key);
75             0;
76             };
77              
78             return;
79             }
80              
81             #-----------------------------------------------------------------
82              
83             sub send_event {
84             my $self = shift;
85             $self->_send_event(@_) and return $self->_warn;
86             $self->_sending and return $self->_warn;
87             $self;
88             }
89              
90             #-----------------------------------------------------------------
91              
92             sub type {
93             my($self, $key) = @_;
94             my($d, $desc);
95             $d = ref $self eq __PACKAGE__ ? $self->{REP} : $self;
96             return unless ref $d eq 'AEDesc';
97             return unless
98             defined($desc = AEGetParamDesc($d, $key || keyDirectObject));
99              
100             my $type = $desc->type;
101             AEDisposeDesc($desc);
102             return $type;
103             }
104              
105             #-----------------------------------------------------------------
106              
107             sub data {
108             my($self, $key) = @_;
109             my($d, $desc, $num, @ret);
110              
111             $d = ref $self eq __PACKAGE__ ? $self->{REP} : $self;
112             return unless ref $d eq 'AEDesc';
113             return unless
114             defined($desc = AEGetParamDesc($d, $key || keyDirectObject));
115              
116             # special-case typeAERecord here, too?
117             if ($num = AECountItems($desc)) {
118             for (1 .. $num) {
119             my $d = AEGetNthDesc($desc, $_);
120             push @ret, $d;
121             }
122             return wantarray ? @ret : $ret[0];
123             } else {
124             return $desc;
125             }
126             }
127              
128             #-----------------------------------------------------------------
129              
130             sub get {
131             my($self, $key) = @_;
132             my($d, $desc, $num);
133              
134             $d = ref $self eq __PACKAGE__ ? $self->{REP} : $self;
135             return unless ref $d eq 'AEDesc';
136             return unless
137             defined($desc = AEGetParamDesc($d, $key || keyDirectObject));
138              
139             if ($num = AECountItems($desc)) {
140             if ($desc->type eq typeAEList) {
141             my @ret;
142             for (1..$num) {
143             push @ret, _getdata(AEGetNthDesc($desc, $_));
144             }
145             # if scalar context, return ref instead?
146             AEDisposeDesc($desc);
147             return wantarray ? @ret : $ret[0];
148              
149             } elsif ($desc->type eq typeAERecord) {
150             my %ret;
151             for (1..$num) {
152             my @d = AEGetNthDesc($desc, $_);
153             $ret{$d[1]} = _getdata($d[0]);
154             }
155             AEDisposeDesc($desc);
156             # if scalar context, return ref instead?
157             return %ret;
158             }
159             }
160             return _getdata($desc);
161             }
162              
163             #-----------------------------------------------------------------
164              
165             sub pack_psn {
166             my($psn) = @_;
167             return pack 'll', 0, $psn;
168             }
169              
170             #-----------------------------------------------------------------
171              
172             sub pack_pid {
173             my($pid) = @_;
174             return pack 'l', $pid;
175             }
176              
177             #-----------------------------------------------------------------
178              
179             sub pack_eppc_x {
180             my($name, $server, $uid, $pid, $user, $pass) = @_;
181              
182             my $info;
183             $info = $user if defined $user;
184             $info .= ':' . $pass if defined $user && defined $pass;
185              
186             my @query;
187             push @query, uid => $uid if $uid;
188             push @query, pid => $pid if $pid;
189              
190             require URI;
191             my $uri = new URI;
192             $uri->scheme('http');
193             $uri->path($name);
194             $uri->host($server);
195             $uri->query_form(@query) if @query;
196             $uri->userinfo($info) if defined $info;
197             $uri =~ s/http/eppc/;
198              
199             return $uri;
200             }
201              
202             #-----------------------------------------------------------------
203              
204             sub pack_ppc { _pack_ppc('ppc', @_) }
205             sub pack_eppc { _pack_ppc('eppc', @_) }
206              
207             sub _pack_ppc {
208             my($type, $id, $name, $server, $zone) = @_;
209             my %TargetID;
210              
211             $TargetID{sess} = ['l', 0]; # long sessionID
212             $TargetID{name} = ['sca33sca33', # PPCPortRec name
213             0, # ScriptCode nameScript
214             length($name), $name, # Str32Field name
215             2, # PPCPortKinds ppcByString
216             length($id . 'ep01'), $id . 'ep01', # Str32 portTypeStr
217             # why ep01? dick karpinski suggests it might be
218             # "Ethernet port 01", so maybe we need to find out
219             # the port automatically ... ick.
220             ];
221              
222             if ($type eq 'ppc') {
223             my $atype = 'PPCToolbox';
224             $zone ||= '*';
225             $TargetID{loca} = ['sca33ca33ca33', # LocationNameRec location
226             1, # PPCLocationKind ppcNBPLocation
227             # EntityName
228             length($server), $server, # Str32Field objStr
229             length($atype), $atype, # Str32Field typeStr
230             length($zone), $zone, # Str32Field zoneStr
231             ];
232             } elsif ($type eq 'eppc') {
233             $TargetID{loca} = ['ssssa*', # LocationNameRec location
234             3, # PPCLocationKind
235             # ppcXTIAddrLocation
236             # PPCAddrRec xtiType
237             0, # UInt8 Reserved (0)
238             2 + length($server), # UInt8 xtiAddrLen
239             # PPCXTIAddress xtiAddr
240             42, # PPCXTIAddressType
241             # kDNSAddrType
242             $server # UInt8 fAddress[96]
243             ];
244             } else {
245             carp "Type $type not recognized\n";
246             }
247              
248             my($format, @args, $targ);
249             for (qw[sess name loca]) {
250             my @foo = @{$TargetID{$_}};
251             $format .= shift @foo;
252             push @args, @foo;
253             }
254             $targ = pack $format, @args;
255              
256             printf("> %s\n< %s\n\n", $targ, join("|", unpack $format, $targ))
257             if $DEBUG > 1;
258             return $targ;
259             }
260              
261             #-----------------------------------------------------------------
262             # Private methods
263             #-----------------------------------------------------------------
264              
265             sub _getdata {
266             my($desc) = @_;
267             my $type = $desc->type;
268             my($ret, $keep);
269              
270             if ($type eq typeEnumerated && $ENUMREC && defined &$ENUMREC) {
271             $ret = $ENUMREC->($desc->get);
272             }
273              
274             if (!$ret && !exists $AE_GET{$type} && !exists $MacUnpack{$type} && defined &$CLASSREC) {
275             if ($CLASSREC->($type)) {
276             my $tmp = AECoerceDesc($desc, typeAERecord); # or die "Type [$type]: $MacError\n";
277             if ($tmp) {
278             AEDisposeDesc $desc;
279             $desc = $tmp;
280             $type = typeAERecord;
281             }
282             }
283             }
284              
285             if (!$ret) {
286             ($ret, $keep) = exists($AE_GET{$type})
287             ? $AE_GET{$type}->($desc)
288             : $desc->get;
289             }
290              
291             AEDisposeDesc $desc unless $keep;
292             return $ret;
293             }
294              
295             #-----------------------------------------------------------------
296              
297             sub _sending {
298             my $self = shift;
299             # $self->_print_desc('EVT');
300             # $self->_print_desc('REP');
301             $self->_event_error;
302             }
303              
304             #-----------------------------------------------------------------
305              
306             sub _construct {
307             my $self = {};
308             $self->{CLASS} = shift or croak 'Not enough parameters in AE build';
309             $self->{EVNT} = shift or croak 'Not enough parameters in AE build';
310             $self->{APP} = shift or croak 'Not enough parameters in AE build';
311              
312             if (ref $self->{APP} eq 'HASH') {
313             for (keys %{$self->{APP}}) {
314             $self->{ADDTYPE} = $_;
315             $self->{ADDRESS} = $self->{APP}{$_};
316             next;
317             }
318             } else {
319             $self->{ADDTYPE} = typeApplSignature;
320             $self->{ADDRESS} = $self->{APP};
321             }
322              
323             $self->{DESC} = shift || '';
324             $self->{PARAMS} = [@_];
325             $self;
326             }
327              
328             #-----------------------------------------------------------------
329              
330             sub _print_desc {
331             my $self = shift;
332             my %what = (EVT => 'EVENT', REP => 'REPLY');
333             $self->{$what{$_[0]}} = AEPrint $self->{$_[0]};
334             }
335              
336             #-----------------------------------------------------------------
337              
338             sub _build_event {
339             my $self = shift;
340             $self->{TRNS_ID} ||= kAnyTransactionID;
341             $self->{EVT} = AEBuildAppleEvent(
342             $self->{CLASS}, $self->{EVNT}, $self->{ADDTYPE},
343             $self->{ADDRESS}, kAutoGenerateReturnID, $self->{TRNS_ID},
344             $self->{DESC}, @{$self->{PARAMS}}
345             );
346             $self->{ERRNO} = $^E+0;
347             $self->{ERROR} = $MacError;
348             return $self->{ERRNO};
349             }
350              
351             #-----------------------------------------------------------------
352              
353             sub _send_event {
354             my $self = $_[0];
355             my $launch = $_[4];
356              
357             if ($self->{ADDTYPE} eq typeApplSignature) {
358             if (! IsRunning($self->{ADDRESS})) { # $launch
359             LaunchApps($self->{ADDRESS}, $SWITCH) or
360             warn "Can't launch '$self->{ADDRESS}': $MacError";
361             } elsif ($SWITCH) {
362             SetFront($self->{ADDRESS});
363             }
364              
365             } elsif ($self->{ADDTYPE} eq typeApplicationBundleID) {
366             my $path = $self->{PATH} || LSFindApplicationForInfo('', $self->{ADDRESS});
367             if (! IsRunning($path, 1)) { # $launch
368             LaunchSpecs($path, $SWITCH) or
369             warn "Can't launch '$self->{ADDRESS}': $MacError";
370             } elsif ($SWITCH) {
371             SetFront($path, 1);
372             }
373             }
374              
375             $self->{R} = defined $_[1] ? $_[1] : $self->{GETREPLY} || kAEWaitReply;
376             $self->{P} = defined $_[2] ? $_[2] : $self->{PRIORITY} || kAENormalPriority;
377             $self->{T} = defined $_[3] ? $_[3] : $self->{TIMEOUT} || kNoTimeOut;
378              
379             $self->{REP} = AESend(@{$self}{'EVT', 'R', 'P', 'T'});
380             $self->{ERRNO} = $^E+0;
381             $self->{ERROR} = $MacError;
382              
383             # if (!$launch && $self->{ERRNO} == -600) { #$MacErrors{connectionInvalid}->number) {
384             # $self->_send_event(@_[1..3], 1);
385             # }
386              
387             return $self->{ERRNO};
388             }
389              
390             #-----------------------------------------------------------------
391              
392             sub _event_error {
393             my($self) = @_;
394             my($event, $error);
395              
396             delete $self->{ERRNO};
397             $event = $self->{REP};
398             return unless $event;
399              
400             {
401             local $^E;
402             if (my $errn = AEGetParamDesc($event, keyErrorNumber)) {
403             $self->{ERRNO} = $errn->get;
404             AEDisposeDesc($errn);
405             }
406              
407             if (my $errs = AEGetParamDesc($event, keyErrorString)) {
408             $self->{ERROR} = $errs->get;
409             AEDisposeDesc($errs);
410             }
411             }
412              
413             $self->{ERRNO} ||= $^E+0;
414             $self->{ERROR} ||= $MacError;
415             return $self->{ERRNO};
416             }
417              
418             #-----------------------------------------------------------------
419              
420             sub _warn {
421             my $self = $_[0];
422             if ($WARN) {
423             my $warn = $WARN > 1 ? \&cluck : \&carp;
424             if ($self->{ERROR}) {
425             $warn->($self->{ERROR});
426             } elsif ($self->{ERRNO}) {
427             local $^E = $self->{ERRNO};
428             $self->{ERROR} = $MacError;
429             $warn->("Error $self->{ERRNO}: $self->{ERROR}");
430             }
431             }
432             $self;
433             }
434              
435             #-----------------------------------------------------------------
436              
437             sub Mac::AppleEvents::Simple::Handler::DESTROY {
438             my $self = shift;
439             delete $self->[0]{$self->[1], $self->[2]};
440             }
441              
442             #-----------------------------------------------------------------
443              
444             DESTROY {
445             my $self = shift;
446             local $^E; # save $^E
447             unless ($self->{HANDLER}) {
448             AEDisposeDesc $self->{EVT} if $self->{EVT};
449             AEDisposeDesc $self->{REP} if $self->{REP};
450             }
451             }
452              
453             #-----------------------------------------------------------------
454              
455             END {
456             foreach my $desc (keys %DESCS) {
457             print "Destroying $desc\n" if $DEBUG;
458             if ($desc) {
459             eval { print "\t", AEPrint($DESCS{$desc}), "\n" } if $DEBUG;
460             AEDisposeDesc $DESCS{$desc} or warn "Can't dispose $desc: $MacError";
461             }
462             }
463             }
464              
465             #-----------------------------------------------------------------
466              
467             BEGIN {
468             %AE_GET = (
469             typeComp() => sub {
470             return _get_coerce($_[0], typeFloat);
471             },
472              
473             typeAlias() => sub {
474             my $alis = $_[0]->data;
475             my $return = ResolveAlias($alis) or die "Can't resolve alias: $MacError";
476             $alis->dispose;
477             return $return;
478             },
479              
480             typeObjectSpecifier() => sub {
481             $DESCS{ $_[0] } = $_[0];
482             return($_[0], 1);
483             },
484              
485             typeAEList() => sub {
486             my $list = $_[0];
487             my @data;
488             for (1 .. AECountItems($list)) {
489             my $d = AEGetNthDesc($list, $_) or die "Can't get desc: $MacError";
490             push @data, _getdata($d);
491             }
492             return \@data;
493             },
494              
495             typeAERecord() => sub {
496             my $reco = $_[0];
497             my %data;
498             for (1 .. AECountItems($reco)) {
499             my @d = AEGetNthDesc($reco, $_) or die "Can't get desc: $MacError";
500             $data{$d[1]} = _getdata($d[0]);
501             }
502             return \%data;
503             },
504              
505             typeProcessSerialNumber() => sub {
506             my $handle = $_[0]->data;
507             my $num = $handle->get;
508             $handle->dispose;
509             my $psn = join '', unpack 'LL', $num;
510             $psn =~ s/^0+//;
511             return $psn;
512             },
513              
514             typeLongDateTime() => sub {
515             my $handle = $_[0]->data;
516             my $num = $handle->get;
517             $handle->dispose;
518             # typeLongDateTime is 64 bits, and the grep tosses out
519             # the high bits, which will be good for as long
520             # as the Unix 32-bit epoch lasts, which is good
521             # enoguh for now; but this is not 64-bit safe, at all;
522             # for that, we will use unpack 'Q' probably
523             my($ldt) = grep $_, unpack 'LL', $num;
524             return $^O eq 'MacOS' ? $ldt : epoch2perl($ldt, 'macos');
525             },
526              
527             typeStyledText() => sub {
528             return _get_coerce($_[0], typeChar);
529             },
530              
531             typeQDPoint() => sub {
532             my $handle = $_[0]->data;
533             my $point = $handle->get;
534             $handle->dispose;
535             return [reverse unpack "s4s4", $point];
536             },
537              
538             typeQDRectangle() => sub {
539             return [ ($_[0]->get)[1,0,3,2] ];
540             },
541              
542             );
543              
544             %AE_GET = (%AE_GET,
545             typeUnicodeText() => $AE_GET{typeStyledText()},
546             typeIntlText() => $AE_GET{typeStyledText()},
547             typeAEText() => $AE_GET{typeStyledText()},
548             # UREC => sub {
549             # $AE_GET{typeAERecord()}->(AECoerceDesc(shift, typeAERecord));
550             # },
551             );
552              
553             }
554              
555             sub _get_coerce {
556             my $data = AECoerceDesc(@_) or die $^E+0;
557             return $data->get;
558             }
559              
560             1;
561              
562             __END__