Use AESendMessage, which comes from CoreServices, not AESend, which comes from Carbon.
2 // GrowlApplicationBridge.m
5 // Created by Evan Schoenberg on Wed Jun 16 2004.
6 // Copyright 2004-2006 The Growl Project. All rights reserved.
9 #import "GrowlApplicationBridge.h"
10 #ifdef GROWL_WITH_INSTALLER
11 #import "GrowlInstallationPrompt.h"
12 #import "GrowlVersionUtilities.h"
14 #include "CFGrowlAdditions.h"
15 #include "CFURLAdditions.h"
16 #include "CFMutableDictionaryAdditions.h"
17 #import "GrowlDefinesInternal.h"
18 #import "GrowlPathUtilities.h"
19 #import "GrowlPathway.h"
21 #import <ApplicationServices/ApplicationServices.h>
25 * The 10.3+ exception handling can only work if -fobjc-exceptions is enabled
28 #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
31 # define CATCH @catch(NSException *localException) {
34 # define TRY NS_DURING
36 # define CATCH NS_HANDLER
37 # define ENDCATCH NS_ENDHANDLER
40 # define TRY NS_DURING
42 # define CATCH NS_HANDLER
43 # define ENDCATCH NS_ENDHANDLER
46 @interface GrowlApplicationBridge (PRIVATE)
48 * @method launchGrowlIfInstalled
49 * @abstract Launches GrowlHelperApp.
50 * @discussion Launches the GrowlHelperApp if it's not already running.
51 * GROWL_IS_READY will be posted to the distributed notification center
54 * Uses <code>+_launchGrowlIfInstalledWithRegistrationDictionary:</code>.
55 * @result Returns YES if GrowlHelperApp began launching or was already running, NO if Growl isn't installed
57 + (BOOL) launchGrowlIfInstalled;
60 * @method _launchGrowlIfInstalledWithRegistrationDictionary:
61 * @abstract Launches GrowlHelperApp and registers.
62 * @discussion Launches the GrowlHelperApp if it's not already running, and passes it a registration dictionary.
63 * If Growl is turned on in the Growl prefpane, GROWL_IS_READY will be posted
64 * to the distributed notification center when Growl is listening for
66 * @param regDict The dictionary with which to register.
67 * @result Returns YES if GrowlHelperApp began launching or was already running, NO if Growl isn't installed
69 + (BOOL) _launchGrowlIfInstalledWithRegistrationDictionary:(NSDictionary *)regDict;
71 #ifdef GROWL_WITH_INSTALLER
72 + (void) _checkForPackagedUpdateForGrowlPrefPaneBundle:(NSBundle *)growlPrefPaneBundle;
75 /*! @method _applicationNameForGrowlSearchingRegistrationDictionary:
76 * @abstract Obtain the name of the current application.
77 * @param regDict The dictionary to search, or <code>nil</code> not to.
78 * @result The name of the current application.
79 * @discussion Does not call +bestRegistrationDictionary, and is therefore safe to call from it.
81 + (NSString *) _applicationNameForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
82 /*! @method _applicationNameForGrowlSearchingRegistrationDictionary:
83 * @abstract Obtain the icon of the current application.
84 * @param regDict The dictionary to search, or <code>nil</code> not to.
85 * @result The icon of the current application, in IconFamily format (same as is used in 'icns' resources and .icns files).
86 * @discussion Does not call +bestRegistrationDictionary, and is therefore safe to call from it.
88 + (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
90 /*! @method growlProxy
91 * @abstract Obtain (creating a connection if needed) a proxy to the Growl Helper Application
93 + (NSProxy<GrowlNotificationProtocol> *) growlProxy;
96 static NSDictionary *cachedRegistrationDictionary = nil;
97 static NSString *appName = nil;
98 static NSData *appIconData = nil;
100 static id delegate = nil;
101 static BOOL growlLaunched = NO;
102 static NSProxy<GrowlNotificationProtocol> *growlProxy = nil;
104 #ifdef GROWL_WITH_INSTALLER
105 static NSMutableArray *queuedGrowlNotifications = nil;
107 static BOOL userChoseNotToInstallGrowl = NO;
108 static BOOL promptedToInstallGrowl = NO;
109 static BOOL promptedToUpgradeGrowl = NO;
112 //used primarily by GIP, but could be useful elsewhere.
113 static BOOL registerWhenGrowlIsReady = NO;
117 @implementation GrowlApplicationBridge
119 + (void) setGrowlDelegate:(NSObject<GrowlApplicationBridgeDelegate> *)inDelegate {
120 NSDistributedNotificationCenter *NSDNC = [NSDistributedNotificationCenter defaultCenter];
122 if (inDelegate != delegate) {
124 delegate = [inDelegate retain];
127 [cachedRegistrationDictionary release];
128 cachedRegistrationDictionary = [[self bestRegistrationDictionary] retain];
130 //Cache the appName from the delegate or the process name
131 [appName autorelease];
132 appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
134 NSLog(@"%@", @"GrowlApplicationBridge: Cannot register because the application name was not supplied and could not be determined");
138 /* Cache the appIconData from the delegate if it responds to the
139 * applicationIconDataForGrowl selector, or the application if not
141 [appIconData autorelease];
142 appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
144 //Add the observer for GROWL_IS_READY which will be triggered later if all goes well
145 [NSDNC addObserver:self
146 selector:@selector(_growlIsReady:)
150 /* Watch for notification clicks if our delegate responds to the
151 * growlNotificationWasClicked: selector. Notifications will come in on a
152 * unique notification name based on our app name, pid and
153 * GROWL_NOTIFICATION_CLICKED.
155 int pid = [[NSProcessInfo processInfo] processIdentifier];
156 NSString *growlNotificationClickedName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
157 appName, pid, GROWL_NOTIFICATION_CLICKED];
158 if ([delegate respondsToSelector:@selector(growlNotificationWasClicked:)])
159 [NSDNC addObserver:self
160 selector:@selector(growlNotificationWasClicked:)
161 name:growlNotificationClickedName
164 [NSDNC removeObserver:self
165 name:growlNotificationClickedName
167 [growlNotificationClickedName release];
169 NSString *growlNotificationTimedOutName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
170 appName, pid, GROWL_NOTIFICATION_TIMED_OUT];
171 if ([delegate respondsToSelector:@selector(growlNotificationTimedOut:)])
172 [NSDNC addObserver:self
173 selector:@selector(growlNotificationTimedOut:)
174 name:growlNotificationTimedOutName
177 [NSDNC removeObserver:self
178 name:growlNotificationTimedOutName
180 [growlNotificationTimedOutName release];
182 #ifdef GROWL_WITH_INSTALLER
183 //Determine if the user has previously told us not to ever request installation again
184 userChoseNotToInstallGrowl = [[NSUserDefaults standardUserDefaults] boolForKey:@"Growl Installation:Do Not Prompt Again"];
187 growlLaunched = [self _launchGrowlIfInstalledWithRegistrationDictionary:cachedRegistrationDictionary];
190 + (NSObject<GrowlApplicationBridgeDelegate> *) growlDelegate {
196 + (void) notifyWithTitle:(NSString *)title
197 description:(NSString *)description
198 notificationName:(NSString *)notifName
199 iconData:(NSData *)iconData
200 priority:(int)priority
201 isSticky:(BOOL)isSticky
202 clickContext:(id)clickContext
204 [GrowlApplicationBridge notifyWithTitle:title
205 description:description
206 notificationName:notifName
210 clickContext:clickContext
214 /* Send a notification to Growl for display.
215 * title, description, and notifName are required.
216 * All other id parameters may be nil to accept defaults.
217 * priority is 0 by default; isSticky is NO by default.
219 + (void) notifyWithTitle:(NSString *)title
220 description:(NSString *)description
221 notificationName:(NSString *)notifName
222 iconData:(NSData *)iconData
223 priority:(int)priority
224 isSticky:(BOOL)isSticky
225 clickContext:(id)clickContext
226 identifier:(NSString *)identifier
228 NSParameterAssert(notifName); //Notification name is required.
229 NSParameterAssert(title || description); //At least one of title or description is required.
231 // Build our noteDict from all passed parameters
232 NSMutableDictionary *noteDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
233 notifName, GROWL_NOTIFICATION_NAME,
236 if (title) setObjectForKey(noteDict, GROWL_NOTIFICATION_TITLE, title);
237 if (description) setObjectForKey(noteDict, GROWL_NOTIFICATION_DESCRIPTION, description);
238 if (iconData) setObjectForKey(noteDict, GROWL_NOTIFICATION_ICON, iconData);
239 if (clickContext) setObjectForKey(noteDict, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
240 if (priority) setIntegerForKey(noteDict, GROWL_NOTIFICATION_PRIORITY, priority);
241 if (isSticky) setBooleanForKey(noteDict, GROWL_NOTIFICATION_STICKY, isSticky);
242 if (identifier) setObjectForKey(noteDict, GROWL_NOTIFICATION_IDENTIFIER, identifier);
244 [self notifyWithDictionary:noteDict];
248 + (void) notifyWithDictionary:(NSDictionary *)userInfo {
251 NSProxy<GrowlNotificationProtocol> *currentGrowlProxy = [self growlProxy];
253 //Make sure we have everything that we need (that we can retrieve from the registration dictionary).
254 userInfo = [self notificationDictionaryByFillingInDictionary:userInfo];
256 if (currentGrowlProxy) {
257 //Post to Growl via GrowlApplicationBridgePathway
259 [currentGrowlProxy postNotificationWithDictionary:userInfo];
262 NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
265 //NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
267 //DNC needs a plist. this means we must pass data, not an NSImage.
268 Class NSImageClass = [NSImage class];
269 NSImage *icon = [userInfo objectForKey:GROWL_NOTIFICATION_ICON];
270 NSImage *appIcon = [userInfo objectForKey:GROWL_NOTIFICATION_APP_ICON];
271 BOOL iconIsImage = icon && [icon isKindOfClass:NSImageClass];
272 BOOL appIconIsImage = appIcon && [appIcon isKindOfClass:NSImageClass];
273 if (iconIsImage || appIconIsImage) {
274 NSMutableDictionary *mUserInfo = [userInfo mutableCopy];
277 [mUserInfo setObject:[icon TIFFRepresentation] forKey:GROWL_NOTIFICATION_ICON];
278 //per-notification application icon.
280 [mUserInfo setObject:[appIcon TIFFRepresentation] forKey:GROWL_NOTIFICATION_APP_ICON];
282 userInfo = [mUserInfo autorelease];
285 //Post to Growl via NSDistributedNotificationCenter
286 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
289 deliverImmediately:NO];
292 #ifdef GROWL_WITH_INSTALLER
293 /*if Growl launches, and the user hasn't already said NO to installing
294 * it, store this notification for posting
296 if (!userChoseNotToInstallGrowl) {
297 if (!queuedGrowlNotifications)
298 queuedGrowlNotifications = [[NSMutableArray alloc] init];
299 [queuedGrowlNotifications addObject:userInfo];
301 //if we have not already asked the user to install Growl, do it now
302 if (!promptedToInstallGrowl) {
303 [GrowlInstallationPrompt showInstallationPrompt];
304 promptedToInstallGrowl = YES;
313 + (BOOL) isGrowlInstalled {
314 return ([GrowlPathUtilities growlPrefPaneBundle] != nil);
317 + (BOOL) isGrowlRunning {
318 BOOL growlIsRunning = NO;
319 ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
321 while (GetNextProcess(&PSN) == noErr) {
322 CFDictionaryRef infoDict = ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
323 CFStringRef bundleId = CFDictionaryGetValue(infoDict, kCFBundleIdentifierKey);
325 if (bundleId && CFStringCompare(bundleId, CFSTR("com.Growl.GrowlHelperApp"), 0) == kCFCompareEqualTo) {
326 growlIsRunning = YES;
333 return growlIsRunning;
336 + (void) displayInstallationPromptIfNeeded {
337 #ifdef GROWL_WITH_INSTALLER
338 //if we have not already asked the user to install Growl, do it now
339 if (!promptedToInstallGrowl) {
340 [GrowlInstallationPrompt showInstallationPrompt];
341 promptedToInstallGrowl = YES;
348 + (BOOL) registerWithDictionary:(NSDictionary *)regDict {
350 regDict = [self registrationDictionaryByFillingInDictionary:regDict];
352 regDict = [self bestRegistrationDictionary];
354 [cachedRegistrationDictionary release];
355 cachedRegistrationDictionary = [regDict retain];
357 return [self _launchGrowlIfInstalledWithRegistrationDictionary:regDict];
360 + (void) reregisterGrowlNotifications {
361 [self registerWithDictionary:nil];
364 + (void) setWillRegisterWhenGrowlIsReady:(BOOL)flag {
365 registerWhenGrowlIsReady = flag;
367 + (BOOL) willRegisterWhenGrowlIsReady {
368 return registerWhenGrowlIsReady;
373 + (NSDictionary *) registrationDictionaryFromDelegate {
374 NSDictionary *regDict = nil;
376 if (delegate && [delegate respondsToSelector:@selector(registrationDictionaryForGrowl)])
377 regDict = [delegate registrationDictionaryForGrowl];
382 + (NSDictionary *) registrationDictionaryFromBundle:(NSBundle *)bundle {
383 if (!bundle) bundle = [NSBundle mainBundle];
385 NSDictionary *regDict = nil;
387 NSString *regDictPath = [bundle pathForResource:@"Growl Registration Ticket" ofType:GROWL_REG_DICT_EXTENSION];
389 regDict = [NSDictionary dictionaryWithContentsOfFile:regDictPath];
391 NSLog(@"GrowlApplicationBridge: The bundle at %@ contains a registration dictionary, but it is not a valid property list. Please tell this application's developer.", [bundle bundlePath]);
397 + (NSDictionary *) bestRegistrationDictionary {
398 NSDictionary *registrationDictionary = [self registrationDictionaryFromDelegate];
399 if (!registrationDictionary) {
400 registrationDictionary = [self registrationDictionaryFromBundle:nil];
401 if (!registrationDictionary)
402 NSLog(@"GrowlApplicationBridge: The Growl delegate did not supply a registration dictionary, and the app bundle at %@ does not have one. Please tell this application's developer.", [[NSBundle mainBundle] bundlePath]);
405 return [self registrationDictionaryByFillingInDictionary:registrationDictionary];
410 + (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict {
411 return [self registrationDictionaryByFillingInDictionary:regDict restrictToKeys:nil];
414 + (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict restrictToKeys:(NSSet *)keys {
415 if (!regDict) return nil;
417 NSMutableDictionary *mRegDict = [regDict mutableCopy];
419 if ((!keys) || [keys containsObject:GROWL_APP_NAME]) {
420 if (![mRegDict objectForKey:GROWL_APP_NAME]) {
422 appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] retain];
424 [mRegDict setObject:appName
425 forKey:GROWL_APP_NAME];
429 if ((!keys) || [keys containsObject:GROWL_APP_ICON]) {
430 if (![mRegDict objectForKey:GROWL_APP_ICON]) {
432 appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:regDict] retain];
434 [mRegDict setObject:appIconData forKey:GROWL_APP_ICON];
438 if ((!keys) || [keys containsObject:GROWL_APP_LOCATION]) {
439 if (![mRegDict objectForKey:GROWL_APP_LOCATION]) {
440 NSURL *myURL = copyCurrentProcessURL();
442 NSDictionary *file_data = createDockDescriptionWithURL(myURL);
444 NSDictionary *location = [[NSDictionary alloc] initWithObjectsAndKeys:file_data, @"file-data", nil];
446 [mRegDict setObject:location forKey:GROWL_APP_LOCATION];
449 [mRegDict removeObjectForKey:GROWL_APP_LOCATION];
456 if ((!keys) || [keys containsObject:GROWL_NOTIFICATIONS_DEFAULT]) {
457 if (![mRegDict objectForKey:GROWL_NOTIFICATIONS_DEFAULT]) {
458 NSArray *all = [mRegDict objectForKey:GROWL_NOTIFICATIONS_ALL];
460 [mRegDict setObject:all forKey:GROWL_NOTIFICATIONS_DEFAULT];
464 if ((!keys) || [keys containsObject:GROWL_APP_ID])
465 if (![mRegDict objectForKey:GROWL_APP_ID])
466 [mRegDict setObject:(NSString *)CFBundleGetIdentifier(CFBundleGetMainBundle()) forKey:GROWL_APP_ID];
468 return [mRegDict autorelease];
471 + (NSDictionary *) notificationDictionaryByFillingInDictionary:(NSDictionary *)notifDict {
472 NSMutableDictionary *mNotifDict = [notifDict mutableCopy];
474 if (![mNotifDict objectForKey:GROWL_APP_NAME]) {
476 appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
479 [mNotifDict setObject:appName
480 forKey:GROWL_APP_NAME];
484 if (![mNotifDict objectForKey:GROWL_APP_ICON]) {
486 appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
489 [mNotifDict setObject:appIconData
490 forKey:GROWL_APP_ICON];
494 //Only include the PID when there's a click context. We do this because NSDNC imposes a 15-MiB limit on the serialized notification, and we wouldn't want to overrun it because of a 4-byte PID.
495 if ([mNotifDict objectForKey:GROWL_NOTIFICATION_CLICK_CONTEXT] && ![mNotifDict objectForKey:GROWL_APP_PID]) {
496 NSNumber *pidNum = [[NSNumber alloc] initWithInt:[[NSProcessInfo processInfo] processIdentifier]];
498 [mNotifDict setObject:pidNum
499 forKey:GROWL_APP_PID];
504 return [mNotifDict autorelease];
507 + (NSDictionary *) frameworkInfoDictionary {
508 #ifdef GROWL_WITH_INSTALLER
509 return (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetBundleWithIdentifier(CFSTR("com.growl.growlwithinstallerframework")));
511 return (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetBundleWithIdentifier(CFSTR("com.growl.growlframework")));
516 #pragma mark Private methods
518 + (NSString *) _applicationNameForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict {
519 NSString *applicationNameForGrowl = nil;
521 if (delegate && [delegate respondsToSelector:@selector(applicationNameForGrowl)])
522 applicationNameForGrowl = [delegate applicationNameForGrowl];
524 if (!applicationNameForGrowl) {
525 applicationNameForGrowl = [regDict objectForKey:GROWL_APP_NAME];
527 if (!applicationNameForGrowl)
528 applicationNameForGrowl = [[NSProcessInfo processInfo] processName];
531 return applicationNameForGrowl;
533 + (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict {
534 NSData *iconData = nil;
537 if ([delegate respondsToSelector:@selector(applicationIconForGrowl)])
538 iconData = (NSData *)[delegate applicationIconForGrowl];
539 else if ([delegate respondsToSelector:@selector(applicationIconDataForGrowl)])
540 iconData = [delegate applicationIconDataForGrowl];
544 iconData = [regDict objectForKey:GROWL_APP_ICON];
546 if (iconData && [iconData isKindOfClass:[NSImage class]])
547 iconData = [(NSImage *)iconData TIFFRepresentation];
550 NSURL *URL = copyCurrentProcessURL();
551 iconData = [copyIconDataForURL(URL) autorelease];
558 /*Selector called when a growl notification is clicked. This should never be
559 * called manually, and the calling observer should only be registered if the
560 * delegate responds to growlNotificationWasClicked:.
562 + (void) growlNotificationWasClicked:(NSNotification *)notification {
563 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
564 [delegate growlNotificationWasClicked:
565 [[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
568 + (void) growlNotificationTimedOut:(NSNotification *)notification {
569 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
570 [delegate growlNotificationTimedOut:
571 [[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
577 //When a connection dies, release our reference to its proxy
578 + (void) connectionDidDie:(NSNotification *)notification {
579 [[NSNotificationCenter defaultCenter] removeObserver:self
580 name:NSConnectionDidDieNotification
581 object:[notification object]];
582 [growlProxy release]; growlProxy = nil;
585 + (NSProxy<GrowlNotificationProtocol> *) growlProxy {
587 NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
589 [[NSNotificationCenter defaultCenter] addObserver:self
590 selector:@selector(connectionDidDie:)
591 name:NSConnectionDidDieNotification
596 NSDistantObject *theProxy = [connection rootProxy];
597 if ([theProxy respondsToSelector:@selector(registerApplicationWithDictionary:)]) {
598 [theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
599 growlProxy = [(NSProxy<GrowlNotificationProtocol> *)theProxy retain];
601 NSLog(@"Received a fake GrowlApplicationBridgePathway object. Some other application is interfering with Growl, or something went horribly wrong. Please file a bug report.");
608 NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
618 + (void) _growlIsReady:(NSNotification *)notification {
619 #pragma unused(notification)
620 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
622 //Growl has now launched; we may get here with (growlLaunched == NO) when the user first installs
625 //Inform our delegate if it is interested
626 if ([delegate respondsToSelector:@selector(growlIsReady)])
627 [delegate growlIsReady];
629 //Post a notification locally
630 [[NSNotificationCenter defaultCenter] postNotificationName:GROWL_IS_READY
634 //Stop observing for GROWL_IS_READY
635 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self
639 //register (fixes #102: this is necessary if we got here by Growl having just been installed)
640 if (registerWhenGrowlIsReady) {
641 [self reregisterGrowlNotifications];
642 registerWhenGrowlIsReady = NO;
645 #ifdef GROWL_WITH_INSTALLER
646 //Perform any queued notifications
647 NSEnumerator *enumerator = [queuedGrowlNotifications objectEnumerator];
648 NSDictionary *noteDict;
650 //Configure the growl proxy if it isn't currently configured
651 NSProxy<GrowlNotificationProtocol> *currentGrowlProxy = [self growlProxy];
653 while ((noteDict = [enumerator nextObject])) {
654 if (currentGrowlProxy) {
655 //Post to Growl via GrowlApplicationBridgePathway
657 [currentGrowlProxy postNotificationWithDictionary:noteDict];
659 NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
662 //Post to Growl via NSDistributedNotificationCenter
663 NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
664 [[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
667 deliverImmediately:FALSE];
671 [queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
677 #ifdef GROWL_WITH_INSTALLER
678 /*Sent to us by GrowlInstallationPrompt if the user clicks Cancel so we can
679 * avoid prompting again this session (or ever if they checked Don't Ask Again)
681 + (void) _userChoseNotToInstallGrowl {
682 //Note the user's action so we stop queueing notifications, etc.
683 userChoseNotToInstallGrowl = YES;
685 //Clear our queued notifications; we won't be needing them
686 [queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
689 // Check against our current version number and ensure the installed Growl pane is the same or later
690 + (void) _checkForPackagedUpdateForGrowlPrefPaneBundle:(NSBundle *)growlPrefPaneBundle {
691 NSString *ourGrowlPrefPaneInfoPath;
692 NSDictionary *infoDictionary;
693 NSString *packagedVersion, *installedVersion;
694 BOOL upgradeIsAvailable;
696 ourGrowlPrefPaneInfoPath = [[NSBundle bundleWithIdentifier:@"com.growl.growlwithinstallerframework"] pathForResource:@"GrowlPrefPaneInfo"
699 NSObject *infoPropertyList = createPropertyListFromURL([NSURL fileURLWithPath:ourGrowlPrefPaneInfoPath],
700 kCFPropertyListImmutable,
701 /* outFormat */ NULL, /* outErrorString */ NULL);
702 NSDictionary *infoDict = ([infoPropertyList isKindOfClass:[NSDictionary class]] ? (NSDictionary *)infoPropertyList : nil);
704 packagedVersion = [infoDict objectForKey:(NSString *)kCFBundleVersionKey];
706 infoDictionary = [growlPrefPaneBundle infoDictionary];
707 installedVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey];
709 //If the installed version is earlier than our packaged version, we can offer an upgrade.
710 upgradeIsAvailable = (compareVersionStringsTranslating1_0To0_5(packagedVersion, installedVersion) == kCFCompareGreaterThan);
711 if (upgradeIsAvailable && !promptedToUpgradeGrowl) {
712 NSString *lastDoNotPromptVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"Growl Update:Do Not Prompt Again:Last Version"];
714 if (!lastDoNotPromptVersion ||
715 (compareVersionStringsTranslating1_0To0_5(packagedVersion, lastDoNotPromptVersion) == kCFCompareGreaterThan))
717 [GrowlInstallationPrompt showUpdatePromptForVersion:packagedVersion];
718 promptedToUpgradeGrowl = YES;
727 + (BOOL) _launchGrowlIfInstalledWithRegistrationDictionary:(NSDictionary *)regDict {
729 NSBundle *growlPrefPaneBundle;
730 NSString *growlHelperAppPath;
733 //For a debug build, first look for a running GHA. It might not actually be within a Growl prefpane bundle.
734 growlHelperAppPath = [[GrowlPathUtilities runningHelperAppBundle] bundlePath];
735 if (!growlHelperAppPath) {
736 growlPrefPaneBundle = [GrowlPathUtilities growlPrefPaneBundle];
737 growlHelperAppPath = [growlPrefPaneBundle pathForResource:@"GrowlHelperApp"
740 NSLog(@"Will use GrowlHelperApp at %@", growlHelperAppPath);
742 growlPrefPaneBundle = [GrowlPathUtilities growlPrefPaneBundle];
743 growlHelperAppPath = [growlPrefPaneBundle pathForResource:@"GrowlHelperApp"
747 #ifdef GROWL_WITH_INSTALLER
748 if (growlPrefPaneBundle) {
749 /* Check against our current version number and ensure the installed Growl pane is the same or later */
750 [self _checkForPackagedUpdateForGrowlPrefPaneBundle:growlPrefPaneBundle];
754 //Houston, we are go for launch.
755 if (growlHelperAppPath) {
756 //Let's launch in the background (requires sending the Apple Event ourselves, as LS may activate the application anyway if it's already running)
757 NSURL *appURL = [NSURL fileURLWithPath:growlHelperAppPath];
761 //Find the PSN for GrowlHelperApp. (We'll need this later.)
762 struct ProcessSerialNumber appPSN = {
765 while ((err = GetNextProcess(&appPSN)) == noErr) {
766 NSDictionary *dict = [(id)ProcessInformationCopyDictionary(&appPSN, kProcessDictionaryIncludeAllInformationMask) autorelease];
767 NSString *bundlePath = [dict objectForKey:@"BundlePath"];
768 if ([bundlePath isEqualToString:growlHelperAppPath]) {
775 NSURL *regItemURL = nil;
776 BOOL passRegDict = NO;
779 NSString *regDictFileName;
780 NSString *regDictPath;
782 //Obtain a truly unique file name
783 CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
784 CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuid);
786 regDictFileName = [[NSString stringWithFormat:@"%@-%u-%@", [self _applicationNameForGrowlSearchingRegistrationDictionary:regDict], getpid(), (NSString *)uuidString] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
787 CFRelease(uuidString);
788 if ([regDictFileName length] > NAME_MAX)
789 regDictFileName = [[regDictFileName substringToIndex:(NAME_MAX - [GROWL_REG_DICT_EXTENSION length])] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
791 //make sure it's within pathname length constraints
792 regDictPath = [NSTemporaryDirectory() stringByAppendingPathComponent:regDictFileName];
793 if ([regDictPath length] > PATH_MAX)
794 regDictPath = [[regDictPath substringToIndex:(PATH_MAX - [GROWL_REG_DICT_EXTENSION length])] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
796 //Write the registration dictionary out to the temporary directory
799 plistData = [NSPropertyListSerialization dataFromPropertyList:regDict
800 format:NSPropertyListBinaryFormat_v1_0
801 errorDescription:&error];
803 if (![plistData writeToFile:regDictPath atomically:NO])
804 NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@", regDictPath);
806 NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@: %@", regDictPath, error);
807 NSLog(@"GrowlApplicationBridge: Registration dictionary follows\n%@", regDict);
811 if ([[NSFileManager defaultManager] fileExistsAtPath:regDictPath]) {
812 regItemURL = [NSURL fileURLWithPath:regDictPath];
817 AEStreamRef stream = AEStreamCreateEvent(kAECoreSuite, kAEOpen,
819 typeProcessSerialNumber, &appPSN, sizeof(appPSN),
820 kAutoGenerateReturnID, kAnyTransactionID);
822 NSLog(@"%@: Could not create open-document event to register this application with Growl", [self class]);
825 NSString *regItemURLString = [regItemURL absoluteString];
826 NSData *regItemURLUTF8Data = [regItemURLString dataUsingEncoding:NSUTF8StringEncoding];
827 err = AEStreamWriteKeyDesc(stream, keyDirectObject, typeFileURL, [regItemURLUTF8Data bytes], [regItemURLUTF8Data length]);
829 NSLog(@"%@: Could not set direct object of open-document event to register this application with Growl because AEStreamWriteKeyDesc returned %li/%s", [self class], (long)err, GetMacOSStatusCommentString(err));
834 err = AEStreamClose(stream, &event);
836 NSLog(@"%@: Could not finish open-document event to register this application with Growl because AEStreamClose returned %li/%s", [self class], (long)err, GetMacOSStatusCommentString(err));
838 err = AESendMessage(&event, /*reply*/ NULL, kAENoReply | kAEDontReconnect | kAENeverInteract | kAEDontRecord, kAEDefaultTimeout);
840 NSLog(@"%@: Could not send open-document event to register this application with Growl because AESend returned %li/%s", [self class], (long)err, GetMacOSStatusCommentString(err));
844 success = (err == noErr);
853 /* + (BOOL)launchGrowlIfInstalled
855 *Returns YES if the Growl helper app began launching or was already running.
856 *Returns NO and performs no other action if the Growl prefPane is not properly
858 *If Growl is installed but disabled, the application will be registered and
859 * GrowlHelperApp will then quit. This method will still return YES if Growl
860 * is installed but disabled.
862 + (BOOL) launchGrowlIfInstalled {
863 return [self _launchGrowlIfInstalledWithRegistrationDictionary:nil];