File Coverage

blib/lib/Mac/Growl.pm
Criterion Covered Total %
statement 35 55 63.6
branch 9 28 32.1
condition 4 12 33.3
subroutine 6 7 85.7
pod n/a
total 54 102 52.9


line stmt bran cond sub pod time code
1             package Mac::Growl;
2              
3 1     1   51014 use strict;
  1         3  
  1         46  
4 1     1   6 use warnings;
  1         3  
  1         53  
5              
6             our $VERSION = '0.67';
7              
8 1     1   5 use base 'Exporter';
  1         6  
  1         361  
9             our @EXPORT = qw();
10             our @EXPORT_OK = qw(RegisterNotifications PostNotification);
11             our %EXPORT_TAGS = (all => \@EXPORT_OK);
12              
13             #### we have various options here ...
14             #### in declining order of preference, we pick an implementation to use,
15             #### and stick with it.
16             our($base, $glue, $helper, $appkit, $encode);
17              
18             # we could use gotos or something, but this is the most efficient and reliable
19             # way; we could also try to put all the implementations in one function, but
20             # that's just messy, and I'd rather have it be messy here than there. -- pudge
21             sub _Define_Subs {
22 1     1   7 no warnings 'redefine';
  1         2  
  1         455  
23              
24             sub RegisterNotifications($$$;$);
25             sub PostNotification($$$$;$$$);
26              
27             sub Foundation_RegisterNotifications($$$;$);
28             sub Foundation_PostNotification($$$$;$$$);
29              
30             sub Glue_RegisterNotifications($$$;$);
31             sub Glue_PostNotification($$$$;$$$);
32              
33             sub AppleScript_RegisterNotifications($$$;$);
34             sub AppleScript_PostNotification($$$$;$$$);
35              
36 0 0   0     if ($base eq 'Foundation') {
    0          
37 0           *RegisterNotifications = *Foundation_RegisterNotifications{CODE};
38 0           *PostNotification = *Foundation_PostNotification{CODE};
39             } elsif ($base eq 'Mac::Glue') {
40 0           *PostNotification = *Glue_PostNotification{CODE};
41 0           *RegisterNotifications = *Glue_RegisterNotifications{CODE};
42             } else { # AppleScript
43 0           *PostNotification = *AppleScript_PostNotification{CODE};
44 0           *RegisterNotifications = *AppleScript_RegisterNotifications{CODE};
45             }
46              
47             sub _Fix_Glue_String(\$);
48             sub _Fix_AppleScript_String(\$);
49             sub _Fix_Encode(\$;$);
50             }
51              
52             sub BEGIN {
53 1     1   2 $encode = eval { require Encode; };
  1         1094  
54 1         16344 $helper = 'GrowlHelperApp';
55              
56 1 50 33     8 if (!$base || $base eq 'Foundation') {
57 1         73 eval 'require Foundation';
58 1 50       10 if ($@) {
59 1         4 $base = undef;
60             } else {
61 0         0 $base = 'Foundation';
62              
63             # load classes for images
64 0         0 my $path = NSString->stringWithCString_('/System/Library/Frameworks/AppKit.framework');
65 0         0 $appkit = NSBundle->alloc->init->initWithPath_($path);
66 0 0       0 $appkit->load if $appkit;
67 0 0       0 if ($appkit->isLoaded) {
68 1     1   6 no strict 'refs';
  1         1  
  1         325  
69 0         0 for my $class (qw(NSWorkspace NSImage)) {
70 0         0 @{$class . '::ISA'} = 'PerlObjCBridge';
  0         0  
71             }
72             } else {
73 0         0 undef $appkit;
74             }
75             }
76             }
77              
78 1 50 33     6 if (!$base || $base eq 'Mac::Glue') {
79 1         61 eval 'require Mac::Glue';
80 1 50       9 if ($@) {
81 1         4 $base = undef;
82             } else {
83 0         0 eval { $glue = Mac::Glue->new($helper) };
  0         0  
84 0 0       0 $base = $glue ? 'Mac::Glue' : undef;
85             }
86             }
87              
88 1         3 for my $applescript (qw(Mac::OSA::Simple MacPerl Mac::AppleScript)) {
89 3 50 33     11 if (!$base || $base eq $applescript) {
90 3         182 eval "require $applescript";
91 3 50       24 $base = $@ ? undef : $applescript;
92             }
93             }
94              
95 1 50 33     9 if (!$base || $base eq 'osascript') {
96 1         4540 chomp(my $res = `osascript -e1`);
97 1 50       57 $base = $res eq 1 ? 'osascript' : undef;
98             }
99              
100 1 50       15 if (!$base) {
101 1         205 die "No way to invoke Growl, please see `perldoc Mac::Growl`";
102             }
103              
104             # warn "Using $base for " . __PACKAGE__, "\n";
105              
106 0           _Define_Subs();
107             }
108              
109             #################################
110             ### Foundation implementation ###
111              
112             use constant GROWL_APP_NAME => "ApplicationName";
113             use constant GROWL_APP_ICON => "ApplicationIcon";
114              
115             use constant GROWL_NOTIFICATIONS_DEFAULT => "DefaultNotifications";
116             use constant GROWL_NOTIFICATIONS_ALL => "AllNotifications";
117             use constant GROWL_NOTIFICATIONS_USER_SET => "AllowedUserNotifications";
118              
119             use constant GROWL_NOTIFICATION_NAME => "NotificationName";
120             use constant GROWL_NOTIFICATION_TITLE => "NotificationTitle";
121             use constant GROWL_NOTIFICATION_DESCRIPTION => "NotificationDescription";
122             use constant GROWL_NOTIFICATION_ICON => "NotificationIcon";
123             use constant GROWL_NOTIFICATION_PRIORITY => "NotificationPriority";
124             use constant GROWL_NOTIFICATION_STICKY => "NotificationSticky";
125              
126             use constant GROWL_APP_REGISTRATION => "GrowlApplicationRegistrationNotification";
127             use constant GROWL_APP_REGISTRATION_CONF => "GrowlApplicationRegistrationConfirmationNotification";
128             use constant GROWL_NOTIFICATION => "GrowlNotification";
129              
130             use constant GROWL_PING => "Honey, Mind Taking Out The Trash";
131             use constant GROWL_PONG => "What Do You Want From Me, Woman";
132             use constant NSNotificationPostToAllSessions => 1 << 1;
133              
134             sub Foundation_RegisterNotifications($$$;$)
135             {
136             my($appName, $allNotes, $defaultNotes, $iconOfApp) = @_;
137             _Fix_Encode($_) for ($appName);
138              
139             my $appString = NSString->alloc->initWithCString_($appName);
140             my $notesArray = NSMutableArray->alloc->init;
141             my $defaultArray = NSMutableArray->alloc->init;
142              
143             $notesArray->addObject_($_) for @$allNotes;
144             $defaultArray->addObject_($_) for @$defaultNotes;
145              
146             my $regDict = NSMutableDictionary->alloc->initWithCapacity_(4);
147             $regDict->setObject_forKey_($appString, GROWL_APP_NAME);
148             $regDict->setObject_forKey_($notesArray, GROWL_NOTIFICATIONS_ALL);
149             $regDict->setObject_forKey_($defaultArray, GROWL_NOTIFICATIONS_DEFAULT);
150             $appString->release;
151             $notesArray->release;
152             $defaultArray->release;
153              
154             if ($appkit && defined $iconOfApp) {
155             my $path = NSWorkspace->sharedWorkspace->fullPathForApplication_(
156             NSString->stringWithCString_($iconOfApp)
157             );
158             if ($path && ref($path) eq 'NSCFString') {
159             my $icon = NSWorkspace->sharedWorkspace->iconForFile_($path);
160             if ($icon && $icon->isValid) {
161             $regDict->setObject_forKey_($icon->TIFFRepresentation, GROWL_APP_ICON);
162             }
163             }
164             }
165              
166             NSDistributedNotificationCenter->defaultCenter->postNotificationName_object_userInfo_options_(
167             GROWL_APP_REGISTRATION,
168             undef,
169             $regDict,
170             NSNotificationPostToAllSessions
171             );
172             $regDict->release;
173             }
174              
175             sub Foundation_PostNotification($$$$;$$$)
176             {
177             my($appName, $noteName, $noteTitle, $noteDescription, $sticky, $priority, $image) = @_;
178             _Fix_Encode($_) for ($appName, $noteName, $noteTitle, $noteDescription);
179             $sticky = $sticky ? 1 : 0;
180              
181             my $noteDict = NSMutableDictionary->alloc->initWithCapacity_(7);
182             my $title = NSString->alloc->initWithUTF8String_($noteTitle);
183             my $description = NSString->alloc->initWithUTF8String_($noteDescription);
184             $noteDict->setObject_forKey_($noteName, GROWL_NOTIFICATION_NAME);
185             $noteDict->setObject_forKey_($appName, GROWL_APP_NAME);
186             $noteDict->setObject_forKey_($title, GROWL_NOTIFICATION_TITLE);
187             $noteDict->setObject_forKey_($description, GROWL_NOTIFICATION_DESCRIPTION);
188             $title->release;
189             $description->release;
190             if (defined $sticky) {
191             my $noteSticky = NSNumber->alloc->initWithBool_($sticky);
192             $noteDict->setObject_forKey_($noteSticky, GROWL_NOTIFICATION_STICKY);
193             $noteSticky->release;
194             }
195             if (defined $priority) {
196             my $notePriority = NSNumber->alloc->initWithInt_($priority);
197             $noteDict->setObject_forKey_($notePriority, GROWL_NOTIFICATION_PRIORITY);
198             $notePriority->release;
199             }
200              
201             if ($appkit && defined $image && -e $image) {
202             my $path = NSString->alloc->initWithCString_($image);
203             if ($path) {
204             my $icon = NSImage->alloc->initWithContentsOfFile_($path);
205             if ($icon) {
206             if ($icon->isValid) {
207             $noteDict->setObject_forKey_($icon->TIFFRepresentation, GROWL_NOTIFICATION_ICON);
208             }
209             $icon->release;
210             }
211             $path->release;
212             }
213             }
214              
215             NSDistributedNotificationCenter->defaultCenter->postNotificationName_object_userInfo_options_(
216             GROWL_NOTIFICATION,
217             undef,
218             $noteDict,
219             NSNotificationPostToAllSessions
220             );
221             $noteDict->release;
222             }
223              
224              
225             ################################
226             ### Mac::Glue implementation ###
227              
228             sub Glue_RegisterNotifications($$$;$)
229             {
230             my($appName, $allNotes, $defaultNotes, $iconOfApp) = @_;
231             for ($appName) {
232             _Fix_Encode($_);
233             _Fix_Glue_String($_);
234             }
235              
236             for my $notes ($allNotes, $defaultNotes) {
237             $notes = [ map {
238             Mac::Glue::param_type(Mac::AppleEvents::typeChar(), $_)
239             } @$notes ];
240             }
241              
242             $glue->register(
243             as_application => $appName,
244             all_notifications => $allNotes,
245             default_notifications => $defaultNotes,
246             icon_of_application => $iconOfApp,
247             );
248             }
249              
250             sub Glue_PostNotification($$$$;$$$)
251             {
252             my($appName, $noteName, $noteTitle, $noteDescription, $sticky, $priority, $image) = @_;
253             for ($appName, $noteName, $noteTitle, $noteDescription) {
254             _Fix_Encode($_);
255             _Fix_Glue_String($_);
256             }
257             $sticky = $sticky ? 1 : 0;
258              
259             my %params = (
260             application_name => $appName,
261             with_name => $noteName,
262             title => $noteTitle,
263             description => $noteDescription,
264             sticky => $sticky
265             );
266              
267             $params{priority} = $priority if defined $priority;
268              
269             my $image_url = _Fix_Image_Path($image);
270             $params{image_from_location} = Mac::Glue::param_type(
271             Mac::Glue::typeChar(), $image_url
272             ) if $image_url;
273              
274             $glue->notify(%params);
275             }
276              
277             # make sure the strings are converted to Unicode, just in case
278             sub _Fix_Glue_String(\$)
279             {
280             my($string) = @_;
281             $$string = Mac::Glue::param_type(Mac::Glue::typeUnicodeText(), $$string);
282             }
283              
284             ##################################
285             ### AppleScript implementation ###
286              
287             sub AppleScript_RegisterNotifications($$$;$)
288             {
289             my($appName, $allNotes, $defaultNotes, $iconOfApp) = @_;
290             _Fix_Encode($_, 'MacRoman') for ($appName);
291              
292             # protect quotes and slashes
293             for ($appName, $iconOfApp) {
294             next unless defined;
295             _Fix_AppleScript_String($_);
296             }
297              
298             for my $list ($allNotes, $defaultNotes) {
299             _Fix_AppleScript_String($_) for @$list;
300             my $string = join('","', @$list);
301             $list = $string;
302             }
303              
304             my $script = qq'tell application "$helper" to register ' .
305             qq'as application "$appName" ' .
306             qq'all notifications ["$allNotes"] default notifications ["$defaultNotes"]';
307             $script .= qq' icon of application "$iconOfApp"' if $iconOfApp;
308              
309             _Execute_AppleScript($script);
310             }
311              
312             sub AppleScript_PostNotification($$$$;$$$)
313             {
314             my($appName, $noteName, $noteTitle, $noteDescription, $sticky, $priority, $image) = @_;
315             $sticky = $sticky ? 1 : 0;
316              
317             # protect quotes and slashes
318             for ($appName, $noteName, $noteTitle, $noteDescription) {
319             next unless defined;
320             _Fix_AppleScript_String($_);
321             _Fix_Encode($_, 'MacRoman'); # can't get to work with UTF8, so this will do
322             }
323              
324             my $script = qq'tell application "$helper" to notify ' .
325             qq'application name "$appName" with name "$noteName" ' .
326             qq'title "$noteTitle" description "$noteDescription"';
327             $script .= ' sticky true' if $sticky;
328              
329             if (defined $priority) {
330             $priority =~ s/^.*?(-?\d+).*$/$1/g;
331             $script .= " priority $priority" if $priority;
332             }
333              
334             my $image_url = _Fix_Image_Path($image);
335             $script .= qq' image from location "$image_url"' if $image_url;
336              
337             _Execute_AppleScript($script);
338             }
339              
340             sub _Fix_AppleScript_String(\$)
341             {
342             my($string) = @_;
343             $$string =~ s/\\/\\\\/g;
344             $$string =~ s/"/\\"/g;
345             }
346              
347             sub _Execute_AppleScript
348             {
349             my($script, $return) = @_;
350             my $reply;
351             # warn $script, "\n";
352              
353             if ($base eq 'Mac::OSA::Simple')
354             {
355             $reply = Mac::OSA::Simple::applescript($script);
356             }
357              
358             elsif ($base eq 'MacPerl')
359             {
360             $reply = MacPerl::DoAppleScript($script);
361             }
362              
363             elsif ($base eq 'Mac::AppleScript')
364             {
365             $reply = Mac::AppleScript::RunAppleScript($script);
366             }
367              
368             else # ($base eq 'osascript')
369             {
370             if ($return) {
371             $script =~ s/\\/\\\\/g;
372             $script =~ s/'/'\''/g;
373             chomp($reply = `osascript -ss -e '$script' 2>/dev/null`);
374             } else {
375             system('osascript', '-e', $script);
376             }
377             }
378              
379             if ($return) {
380             $reply =~ s/^"(.+)"$/$1/;
381             return $reply;
382             }
383             }
384              
385              
386             #######################
387             ### Misc. Functions ###
388              
389             {
390             my $uri_file;
391              
392             sub _Fix_Image_Path
393             {
394             require File::Spec;
395             unless (defined $uri_file) {
396             eval 'require URI::file';
397             $uri_file = $@ ? 0 : 1;
398             }
399              
400             my($image) = @_;
401             my $path;
402              
403             if (defined $image && length $image) {
404             $path = File::Spec->rel2abs($image);
405             if (-e $path) {
406             my $reply;
407             if ($uri_file) {
408             my $uri = URI::file->new($path);
409             $reply = $uri->as_string if $uri;
410             } else {
411             # URI::file will be available if Mac::Glue
412             # is, so this only needs to be implemented
413             # in AppleScript; yes, it's a hack, but
414             # it's better to work out of the box than
415             # to not
416             my $script = <
417             tell application "Finder"
418             set thisfile to POSIX file "$path" as string
419             set thisdoc to document file thisfile
420             return URL of thisdoc
421             end tell
422             EOT
423             $reply = _Execute_AppleScript($script, 1);
424             }
425             if ($reply) {
426             # Growl being excessively anal
427             $reply =~ s#^file:/(/localhost/)?(?!/)#file:///#;
428             return $reply;
429             }
430             }
431             }
432              
433             return;
434             }
435             }
436              
437             sub _Fix_Encode (\$;$)
438             {
439             my($str, $encoding) = @_;
440             $$str = Encode::decode('utf8', $$str) if $encode;
441             $$str = Encode::encode($encoding, $$str) if $encode && $encoding;
442             }
443              
444             1;
445              
446             __END__