Framework/Source/GrowlApplicationBridge.m
author Peter Hosey <hg@boredzo.org>
Thu Sep 24 14:08:19 2009 -0700 (2009-09-24)
changeset 4429 58720a0d2d1f
parent 4427 1df5769e87e1
child 4430 0f739ceca8b4
permissions -rw-r--r--
This is an Apple Event, not a Carbon Event, so we want keyDirectObject, not kEventParamDirectObject.
boredzo@1385
     1
//
boredzo@1385
     2
//  GrowlApplicationBridge.m
boredzo@1385
     3
//  Growl
boredzo@1385
     4
//
boredzo@1385
     5
//  Created by Evan Schoenberg on Wed Jun 16 2004.
ingmarstein@3040
     6
//  Copyright 2004-2006 The Growl Project. All rights reserved.
boredzo@1385
     7
//
boredzo@1385
     8
boredzo@1385
     9
#import "GrowlApplicationBridge.h"
boredzo@1385
    10
#ifdef GROWL_WITH_INSTALLER
boredzo@1385
    11
#import "GrowlInstallationPrompt.h"
boredzo@1385
    12
#import "GrowlVersionUtilities.h"
boredzo@1385
    13
#endif
ingmarstein@2641
    14
#include "CFGrowlAdditions.h"
ingmarstein@2639
    15
#include "CFURLAdditions.h"
ingmarstein@2641
    16
#include "CFMutableDictionaryAdditions.h"
boredzo@1490
    17
#import "GrowlDefinesInternal.h"
boredzo@2461
    18
#import "GrowlPathUtilities.h"
ingmarstein@1941
    19
#import "GrowlPathway.h"
boredzo@1385
    20
boredzo@1385
    21
#import <ApplicationServices/ApplicationServices.h>
boredzo@1385
    22
evands@3430
    23
evands@3430
    24
/*!
evands@3430
    25
 * The 10.3+ exception handling can only work if -fobjc-exceptions is enabled
evands@3430
    26
 */
evands@3430
    27
#if 0
evands@3430
    28
	#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_3
evands@3430
    29
	# define TRY		@try {
evands@3430
    30
	# define ENDTRY		}
evands@3430
    31
	# define CATCH		@catch(NSException *localException) {
evands@3430
    32
	# define ENDCATCH	}
evands@3430
    33
	#else
evands@3430
    34
	# define TRY		NS_DURING
evands@3430
    35
	# define ENDTRY
evands@3430
    36
	# define CATCH		NS_HANDLER
evands@3430
    37
	# define ENDCATCH	NS_ENDHANDLER
evands@3430
    38
	#endif
ingmarstein@2410
    39
#else
evands@3430
    40
	# define TRY		NS_DURING
evands@3430
    41
	# define ENDTRY
evands@3430
    42
	# define CATCH		NS_HANDLER
evands@3430
    43
	# define ENDCATCH	NS_ENDHANDLER
ingmarstein@2410
    44
#endif
ingmarstein@2410
    45
boredzo@1385
    46
@interface GrowlApplicationBridge (PRIVATE)
boredzo@1385
    47
/*!
boredzo@1385
    48
 *	@method launchGrowlIfInstalled
boredzo@1776
    49
 *	@abstract Launches GrowlHelperApp.
boredzo@1385
    50
 *	@discussion Launches the GrowlHelperApp if it's not already running.
boredzo@1385
    51
 *	 GROWL_IS_READY will be posted to the distributed notification center
boredzo@1385
    52
 *	 once it is ready.
boredzo@1776
    53
 *
boredzo@1776
    54
 *	 Uses <code>+_launchGrowlIfInstalledWithRegistrationDictionary:</code>.
boredzo@1385
    55
 *	@result Returns YES if GrowlHelperApp began launching or was already running, NO if Growl isn't installed
boredzo@1385
    56
 */
boredzo@1385
    57
+ (BOOL) launchGrowlIfInstalled;
boredzo@1385
    58
boredzo@1776
    59
/*!
boredzo@1776
    60
 *	@method _launchGrowlIfInstalledWithRegistrationDictionary:
boredzo@1776
    61
 *	@abstract Launches GrowlHelperApp and registers.
boredzo@1776
    62
 *	@discussion Launches the GrowlHelperApp if it's not already running, and passes it a registration dictionary.
boredzo@1776
    63
 *	 If Growl is turned on in the Growl prefpane, GROWL_IS_READY will be posted
boredzo@1776
    64
 *	 to the distributed notification center when Growl is listening for
boredzo@1776
    65
 *	 notifications.
boredzo@1776
    66
 *	@param regDict The dictionary with which to register.
boredzo@1776
    67
 *	@result Returns YES if GrowlHelperApp began launching or was already running, NO if Growl isn't installed
boredzo@1776
    68
 */
boredzo@1776
    69
+ (BOOL) _launchGrowlIfInstalledWithRegistrationDictionary:(NSDictionary *)regDict;
boredzo@1385
    70
boredzo@1385
    71
#ifdef GROWL_WITH_INSTALLER
boredzo@1385
    72
+ (void) _checkForPackagedUpdateForGrowlPrefPaneBundle:(NSBundle *)growlPrefPaneBundle;
boredzo@1385
    73
#endif
boredzo@1385
    74
boredzo@1776
    75
/*!	@method	_applicationNameForGrowlSearchingRegistrationDictionary:
boredzo@1776
    76
 *	@abstract Obtain the name of the current application.
boredzo@1776
    77
 *	@param regDict	The dictionary to search, or <code>nil</code> not to.
boredzo@1776
    78
 *	@result	The name of the current application.
boredzo@1776
    79
 *	@discussion	Does not call +bestRegistrationDictionary, and is therefore safe to call from it.
boredzo@1776
    80
 */
boredzo@1776
    81
+ (NSString *) _applicationNameForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
boredzo@1776
    82
/*!	@method	_applicationNameForGrowlSearchingRegistrationDictionary:
boredzo@1776
    83
 *	@abstract Obtain the icon of the current application.
boredzo@1776
    84
 *	@param regDict	The dictionary to search, or <code>nil</code> not to.
boredzo@1776
    85
 *	@result	The icon of the current application, in IconFamily format (same as is used in 'icns' resources and .icns files).
boredzo@1776
    86
 *	@discussion	Does not call +bestRegistrationDictionary, and is therefore safe to call from it.
boredzo@1776
    87
 */
boredzo@1776
    88
+ (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict;
boredzo@1385
    89
evands@3425
    90
/*! @method growlProxy
evands@3425
    91
 *  @abstract Obtain (creating a connection if needed) a proxy to the Growl Helper Application
evands@3425
    92
 */
evands@3425
    93
+ (NSProxy<GrowlNotificationProtocol> *) growlProxy;
boredzo@1385
    94
@end
boredzo@1385
    95
boredzo@4030
    96
static NSDictionary *cachedRegistrationDictionary = nil;
boredzo@1385
    97
static NSString	*appName = nil;
boredzo@1385
    98
static NSData	*appIconData = nil;
boredzo@1385
    99
boredzo@1385
   100
static id		delegate = nil;
boredzo@1385
   101
static BOOL		growlLaunched = NO;
evands@3425
   102
static NSProxy<GrowlNotificationProtocol> *growlProxy = nil;
boredzo@1385
   103
boredzo@2046
   104
#ifdef GROWL_WITH_INSTALLER
boredzo@1385
   105
static NSMutableArray	*queuedGrowlNotifications = nil;
boredzo@1385
   106
boredzo@1385
   107
static BOOL				userChoseNotToInstallGrowl = NO;
boredzo@1385
   108
static BOOL				promptedToInstallGrowl = NO;
boredzo@1385
   109
static BOOL				promptedToUpgradeGrowl = NO;
boredzo@1385
   110
#endif
boredzo@1385
   111
boredzo@1776
   112
//used primarily by GIP, but could be useful elsewhere.
boredzo@1776
   113
static BOOL		registerWhenGrowlIsReady = NO;
boredzo@1776
   114
boredzo@1776
   115
#pragma mark -
boredzo@1776
   116
boredzo@1776
   117
@implementation GrowlApplicationBridge
boredzo@1776
   118
boredzo@1385
   119
+ (void) setGrowlDelegate:(NSObject<GrowlApplicationBridgeDelegate> *)inDelegate {
boredzo@1385
   120
	NSDistributedNotificationCenter *NSDNC = [NSDistributedNotificationCenter defaultCenter];
boredzo@1385
   121
ingmarstein@2320
   122
	if (inDelegate != delegate) {
ingmarstein@2320
   123
		[delegate release];
ingmarstein@2320
   124
		delegate = [inDelegate retain];
ingmarstein@2320
   125
	}
boredzo@1385
   126
boredzo@4030
   127
	[cachedRegistrationDictionary release];
boredzo@4030
   128
	cachedRegistrationDictionary = [[self bestRegistrationDictionary] retain];
boredzo@1776
   129
boredzo@1385
   130
	//Cache the appName from the delegate or the process name
boredzo@1385
   131
	[appName autorelease];
boredzo@4030
   132
	appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
ingmarstein@2320
   133
	if (!appName) {
boredzo@1789
   134
		NSLog(@"%@", @"GrowlApplicationBridge: Cannot register because the application name was not supplied and could not be determined");
ingmarstein@2320
   135
		return;
ingmarstein@2320
   136
	}
ingmarstein@2320
   137
ingmarstein@2320
   138
	/* Cache the appIconData from the delegate if it responds to the
ingmarstein@2320
   139
	 * applicationIconDataForGrowl selector, or the application if not
ingmarstein@2320
   140
	 */
boredzo@1385
   141
	[appIconData autorelease];
boredzo@4030
   142
	appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
boredzo@1385
   143
boredzo@1385
   144
	//Add the observer for GROWL_IS_READY which will be triggered later if all goes well
ingmarstein@1905
   145
	[NSDNC addObserver:self
boredzo@1385
   146
			  selector:@selector(_growlIsReady:)
boredzo@1385
   147
				  name:GROWL_IS_READY
ingmarstein@1905
   148
				object:nil];
boredzo@1385
   149
ingmarstein@2320
   150
	/* Watch for notification clicks if our delegate responds to the
ingmarstein@2320
   151
	 * growlNotificationWasClicked: selector. Notifications will come in on a
ingmarstein@2320
   152
	 * unique notification name based on our app name, pid and
ingmarstein@2320
   153
	 * GROWL_NOTIFICATION_CLICKED.
ingmarstein@2320
   154
	 */
ingmarstein@1997
   155
	int pid = [[NSProcessInfo processInfo] processIdentifier];
ingmarstein@1997
   156
	NSString *growlNotificationClickedName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
ingmarstein@1997
   157
		appName, pid, GROWL_NOTIFICATION_CLICKED];
ingmarstein@2320
   158
	if ([delegate respondsToSelector:@selector(growlNotificationWasClicked:)])
boredzo@1385
   159
		[NSDNC addObserver:self
ingmarstein@2397
   160
				  selector:@selector(growlNotificationWasClicked:)
ingmarstein@1893
   161
					  name:growlNotificationClickedName
boredzo@1385
   162
					object:nil];
ingmarstein@2320
   163
	else
boredzo@1385
   164
		[NSDNC removeObserver:self
boredzo@1385
   165
						 name:growlNotificationClickedName
boredzo@1385
   166
					   object:nil];
ingmarstein@2395
   167
	[growlNotificationClickedName release];
ingmarstein@2320
   168
ingmarstein@1997
   169
	NSString *growlNotificationTimedOutName = [[NSString alloc] initWithFormat:@"%@-%d-%@",
ingmarstein@1997
   170
		appName, pid, GROWL_NOTIFICATION_TIMED_OUT];
ingmarstein@2320
   171
	if ([delegate respondsToSelector:@selector(growlNotificationTimedOut:)])
ingmarstein@1893
   172
		[NSDNC addObserver:self
ingmarstein@2397
   173
				  selector:@selector(growlNotificationTimedOut:)
ingmarstein@1893
   174
					  name:growlNotificationTimedOutName
ingmarstein@1893
   175
					object:nil];
ingmarstein@2320
   176
	else
ingmarstein@1893
   177
		[NSDNC removeObserver:self
ingmarstein@1893
   178
						 name:growlNotificationTimedOutName
ingmarstein@1893
   179
					   object:nil];
ingmarstein@2395
   180
	[growlNotificationTimedOutName release];
boredzo@1385
   181
boredzo@1385
   182
#ifdef GROWL_WITH_INSTALLER
boredzo@1385
   183
	//Determine if the user has previously told us not to ever request installation again
boredzo@2064
   184
	userChoseNotToInstallGrowl = [[NSUserDefaults standardUserDefaults] boolForKey:@"Growl Installation:Do Not Prompt Again"];
boredzo@1385
   185
#endif
boredzo@1385
   186
boredzo@4030
   187
	growlLaunched = [self _launchGrowlIfInstalledWithRegistrationDictionary:cachedRegistrationDictionary];
boredzo@1385
   188
}
boredzo@1385
   189
boredzo@1385
   190
+ (NSObject<GrowlApplicationBridgeDelegate> *) growlDelegate {
boredzo@1385
   191
	return delegate;
boredzo@1385
   192
}
boredzo@1385
   193
boredzo@1776
   194
#pragma mark -
boredzo@1776
   195
boredzo@1385
   196
+ (void) notifyWithTitle:(NSString *)title
boredzo@1385
   197
			 description:(NSString *)description
boredzo@1385
   198
		notificationName:(NSString *)notifName
ingmarstein@1905
   199
				iconData:(NSData *)iconData
boredzo@1385
   200
				priority:(int)priority
boredzo@1385
   201
				isSticky:(BOOL)isSticky
boredzo@1385
   202
			clickContext:(id)clickContext
boredzo@1385
   203
{
ingmarstein@2289
   204
	[GrowlApplicationBridge notifyWithTitle:title
ingmarstein@2289
   205
								description:description
ingmarstein@2289
   206
						   notificationName:notifName
ingmarstein@2289
   207
								   iconData:iconData
ingmarstein@2289
   208
								   priority:priority
ingmarstein@2289
   209
								   isSticky:isSticky
ingmarstein@2289
   210
							   clickContext:clickContext
ingmarstein@2400
   211
								 identifier:nil];
ingmarstein@2392
   212
}
ingmarstein@2392
   213
bgannin@3404
   214
/* Send a notification to Growl for display.
bgannin@3404
   215
 * title, description, and notifName are required.
bgannin@3404
   216
 * All other id parameters may be nil to accept defaults.
bgannin@3404
   217
 * priority is 0 by default; isSticky is NO by default.
bgannin@3404
   218
 */
ingmarstein@2392
   219
+ (void) notifyWithTitle:(NSString *)title
ingmarstein@2392
   220
			 description:(NSString *)description
ingmarstein@2392
   221
		notificationName:(NSString *)notifName
ingmarstein@2392
   222
				iconData:(NSData *)iconData
ingmarstein@2392
   223
				priority:(int)priority
ingmarstein@2392
   224
				isSticky:(BOOL)isSticky
ingmarstein@2392
   225
			clickContext:(id)clickContext
ingmarstein@2392
   226
			  identifier:(NSString *)identifier
ingmarstein@2392
   227
{
boredzo@1385
   228
	NSParameterAssert(notifName);	//Notification name is required.
boredzo@1385
   229
	NSParameterAssert(title || description);	//At least one of title or description is required.
boredzo@1385
   230
boredzo@1385
   231
	// Build our noteDict from all passed parameters
boredzo@1776
   232
	NSMutableDictionary *noteDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
boredzo@1776
   233
		notifName,	 GROWL_NOTIFICATION_NAME,
boredzo@1776
   234
		nil];
boredzo@1385
   235
ingmarstein@2641
   236
	if (title)			setObjectForKey(noteDict, GROWL_NOTIFICATION_TITLE, title);
ingmarstein@2641
   237
	if (description)	setObjectForKey(noteDict, GROWL_NOTIFICATION_DESCRIPTION, description);
ingmarstein@2641
   238
	if (iconData)		setObjectForKey(noteDict, GROWL_NOTIFICATION_ICON, iconData);
ingmarstein@2641
   239
	if (clickContext)	setObjectForKey(noteDict, GROWL_NOTIFICATION_CLICK_CONTEXT, clickContext);
ingmarstein@2641
   240
	if (priority)		setIntegerForKey(noteDict, GROWL_NOTIFICATION_PRIORITY, priority);
ingmarstein@2641
   241
	if (isSticky)		setBooleanForKey(noteDict, GROWL_NOTIFICATION_STICKY, isSticky);
ingmarstein@2641
   242
	if (identifier)		setObjectForKey(noteDict, GROWL_NOTIFICATION_IDENTIFIER, identifier);
boredzo@1385
   243
boredzo@1789
   244
	[self notifyWithDictionary:noteDict];
ingmarstein@1637
   245
	[noteDict release];
boredzo@1385
   246
}
boredzo@1385
   247
boredzo@1385
   248
+ (void) notifyWithDictionary:(NSDictionary *)userInfo {
boredzo@2156
   249
	//post it.
evands@3425
   250
	if (growlLaunched) {		
evands@3425
   251
		NSProxy<GrowlNotificationProtocol> *currentGrowlProxy = [self growlProxy];
evands@3425
   252
boredzo@3238
   253
		//Make sure we have everything that we need (that we can retrieve from the registration dictionary).
boredzo@3238
   254
		userInfo = [self notificationDictionaryByFillingInDictionary:userInfo];
boredzo@3238
   255
evands@3425
   256
		if (currentGrowlProxy) {
ingmarstein@1972
   257
			//Post to Growl via GrowlApplicationBridgePathway
ingmarstein@2410
   258
			TRY
evands@3425
   259
				[currentGrowlProxy postNotificationWithDictionary:userInfo];
ingmarstein@2410
   260
			ENDTRY
ingmarstein@2410
   261
			CATCH
ingmarstein@1941
   262
				NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
ingmarstein@2410
   263
			ENDCATCH
ingmarstein@1941
   264
		} else {
ingmarstein@2410
   265
			//NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
boredzo@2239
   266
boredzo@2239
   267
			//DNC needs a plist. this means we must pass data, not an NSImage.
boredzo@2239
   268
			Class     NSImageClass = [NSImage class];
boredzo@2239
   269
			NSImage          *icon = [userInfo objectForKey:GROWL_NOTIFICATION_ICON];
boredzo@2239
   270
			NSImage       *appIcon = [userInfo objectForKey:GROWL_NOTIFICATION_APP_ICON];
boredzo@2239
   271
			BOOL       iconIsImage =    icon &&    [icon isKindOfClass:NSImageClass];
boredzo@2239
   272
			BOOL    appIconIsImage = appIcon && [appIcon isKindOfClass:NSImageClass];
ingmarstein@2220
   273
			if (iconIsImage || appIconIsImage) {
ingmarstein@2220
   274
				NSMutableDictionary *mUserInfo = [userInfo mutableCopy];
ingmarstein@2220
   275
				//notification icon.
boredzo@2239
   276
				if (iconIsImage)
ingmarstein@2220
   277
					[mUserInfo setObject:[icon TIFFRepresentation] forKey:GROWL_NOTIFICATION_ICON];
ingmarstein@2220
   278
				//per-notification application icon.
boredzo@2239
   279
				if (appIconIsImage)
ingmarstein@2558
   280
					[mUserInfo setObject:[appIcon TIFFRepresentation] forKey:GROWL_NOTIFICATION_APP_ICON];
ingmarstein@2220
   281
ingmarstein@2220
   282
				userInfo = [mUserInfo autorelease];
ingmarstein@2220
   283
			}
ingmarstein@2220
   284
ingmarstein@1941
   285
			//Post to Growl via NSDistributedNotificationCenter
evands@3160
   286
			[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
evands@3160
   287
																		   object:nil
evands@3160
   288
																		 userInfo:userInfo
evands@3160
   289
															   deliverImmediately:NO];
ingmarstein@1941
   290
		}
boredzo@1385
   291
	} else {
boredzo@1385
   292
#ifdef GROWL_WITH_INSTALLER
boredzo@1385
   293
		/*if Growl launches, and the user hasn't already said NO to installing
boredzo@1385
   294
		 *	it, store this notification for posting
boredzo@1385
   295
		 */
boredzo@1385
   296
		if (!userChoseNotToInstallGrowl) {
ingmarstein@2647
   297
			if (!queuedGrowlNotifications)
boredzo@1385
   298
				queuedGrowlNotifications = [[NSMutableArray alloc] init];
boredzo@1385
   299
			[queuedGrowlNotifications addObject:userInfo];
ingmarstein@1637
   300
boredzo@1385
   301
			//if we have not already asked the user to install Growl, do it now
boredzo@1385
   302
			if (!promptedToInstallGrowl) {
evands@1555
   303
				[GrowlInstallationPrompt showInstallationPrompt];
boredzo@1385
   304
				promptedToInstallGrowl = YES;
boredzo@1385
   305
			}
boredzo@1385
   306
		}
boredzo@1385
   307
#endif
boredzo@1385
   308
	}
boredzo@1385
   309
}
boredzo@1385
   310
boredzo@1776
   311
#pragma mark -
boredzo@1385
   312
boredzo@1385
   313
+ (BOOL) isGrowlInstalled {
boredzo@2677
   314
	return ([GrowlPathUtilities growlPrefPaneBundle] != nil);
boredzo@1385
   315
}
boredzo@1385
   316
boredzo@1385
   317
+ (BOOL) isGrowlRunning {
boredzo@1385
   318
	BOOL growlIsRunning = NO;
boredzo@1776
   319
	ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
ingmarstein@1905
   320
boredzo@1385
   321
	while (GetNextProcess(&PSN) == noErr) {
ingmarstein@3035
   322
		CFDictionaryRef infoDict = ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
ingmarstein@3035
   323
		CFStringRef bundleId = CFDictionaryGetValue(infoDict, kCFBundleIdentifierKey);
ingmarstein@3035
   324
ingmarstein@3035
   325
		if (bundleId && CFStringCompare(bundleId, CFSTR("com.Growl.GrowlHelperApp"), 0) == kCFCompareEqualTo) {
boredzo@1385
   326
			growlIsRunning = YES;
ingmarstein@3035
   327
			CFRelease(infoDict);
boredzo@1385
   328
			break;
boredzo@1385
   329
		}
ingmarstein@3035
   330
		CFRelease(infoDict);
boredzo@1385
   331
	}
ingmarstein@1905
   332
boredzo@1385
   333
	return growlIsRunning;
boredzo@1385
   334
}
boredzo@1385
   335
evands@2920
   336
+ (void) displayInstallationPromptIfNeeded {
evands@2920
   337
#ifdef GROWL_WITH_INSTALLER
evands@2920
   338
    //if we have not already asked the user to install Growl, do it now
evands@2920
   339
    if (!promptedToInstallGrowl) {
evands@2920
   340
        [GrowlInstallationPrompt showInstallationPrompt];
evands@2920
   341
        promptedToInstallGrowl = YES;
evands@2920
   342
    }
evands@2920
   343
#endif
evands@2920
   344
}
evands@2920
   345
boredzo@1776
   346
#pragma mark -
boredzo@1776
   347
boredzo@1776
   348
+ (BOOL) registerWithDictionary:(NSDictionary *)regDict {
boredzo@1776
   349
	if (regDict)
boredzo@1789
   350
		regDict = [self registrationDictionaryByFillingInDictionary:regDict];
boredzo@1776
   351
	else
boredzo@1789
   352
		regDict = [self bestRegistrationDictionary];
boredzo@4030
   353
boredzo@4030
   354
	[cachedRegistrationDictionary release];
boredzo@4030
   355
	cachedRegistrationDictionary = [regDict retain];
boredzo@4030
   356
boredzo@1789
   357
	return [self _launchGrowlIfInstalledWithRegistrationDictionary:regDict];
boredzo@1776
   358
}
ingmarstein@1941
   359
boredzo@1385
   360
+ (void) reregisterGrowlNotifications {
boredzo@1789
   361
	[self registerWithDictionary:nil];
boredzo@1776
   362
}
boredzo@1776
   363
boredzo@1776
   364
+ (void) setWillRegisterWhenGrowlIsReady:(BOOL)flag {
boredzo@1776
   365
	registerWhenGrowlIsReady = flag;
boredzo@1776
   366
}
boredzo@1776
   367
+ (BOOL) willRegisterWhenGrowlIsReady {
boredzo@1776
   368
	return registerWhenGrowlIsReady;
boredzo@1776
   369
}
boredzo@1776
   370
boredzo@1776
   371
#pragma mark -
boredzo@1776
   372
boredzo@1776
   373
+ (NSDictionary *) registrationDictionaryFromDelegate {
boredzo@1776
   374
	NSDictionary *regDict = nil;
boredzo@1776
   375
boredzo@1776
   376
	if (delegate && [delegate respondsToSelector:@selector(registrationDictionaryForGrowl)])
boredzo@1776
   377
		regDict = [delegate registrationDictionaryForGrowl];
boredzo@1776
   378
boredzo@1776
   379
	return regDict;
boredzo@1776
   380
}
boredzo@1776
   381
boredzo@1776
   382
+ (NSDictionary *) registrationDictionaryFromBundle:(NSBundle *)bundle {
boredzo@1776
   383
	if (!bundle) bundle = [NSBundle mainBundle];
boredzo@1776
   384
boredzo@1776
   385
	NSDictionary *regDict = nil;
boredzo@1776
   386
boredzo@1776
   387
	NSString *regDictPath = [bundle pathForResource:@"Growl Registration Ticket" ofType:GROWL_REG_DICT_EXTENSION];
boredzo@1776
   388
	if (regDictPath) {
boredzo@1776
   389
		regDict = [NSDictionary dictionaryWithContentsOfFile:regDictPath];
boredzo@1776
   390
		if (!regDict)
boredzo@1776
   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]);
boredzo@1776
   392
	}
boredzo@1776
   393
boredzo@1776
   394
	return regDict;
boredzo@1776
   395
}
boredzo@1776
   396
boredzo@1776
   397
+ (NSDictionary *) bestRegistrationDictionary {
boredzo@1789
   398
	NSDictionary *registrationDictionary = [self registrationDictionaryFromDelegate];
ingmarstein@2565
   399
	if (!registrationDictionary) {
boredzo@1789
   400
		registrationDictionary = [self registrationDictionaryFromBundle:nil];
ingmarstein@2565
   401
		if (!registrationDictionary)
ingmarstein@2565
   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]);
boredzo@1776
   403
	}
boredzo@1776
   404
ingmarstein@2211
   405
	return [self registrationDictionaryByFillingInDictionary:registrationDictionary];
boredzo@1385
   406
}
boredzo@1385
   407
boredzo@1776
   408
#pragma mark -
boredzo@1776
   409
boredzo@1776
   410
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict {
boredzo@1789
   411
	return [self registrationDictionaryByFillingInDictionary:regDict restrictToKeys:nil];
boredzo@1776
   412
}
ingmarstein@1941
   413
boredzo@1776
   414
+ (NSDictionary *) registrationDictionaryByFillingInDictionary:(NSDictionary *)regDict restrictToKeys:(NSSet *)keys {
boredzo@1776
   415
	if (!regDict) return nil;
boredzo@1776
   416
boredzo@1776
   417
	NSMutableDictionary *mRegDict = [regDict mutableCopy];
boredzo@1776
   418
boredzo@1776
   419
	if ((!keys) || [keys containsObject:GROWL_APP_NAME]) {
boredzo@1776
   420
		if (![mRegDict objectForKey:GROWL_APP_NAME]) {
boredzo@1776
   421
			if (!appName)
boredzo@1789
   422
				appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:regDict] retain];
boredzo@1776
   423
boredzo@1776
   424
			[mRegDict setObject:appName
boredzo@1776
   425
			             forKey:GROWL_APP_NAME];
boredzo@1776
   426
		}
boredzo@1776
   427
	}
boredzo@1776
   428
boredzo@1776
   429
	if ((!keys) || [keys containsObject:GROWL_APP_ICON]) {
boredzo@1776
   430
		if (![mRegDict objectForKey:GROWL_APP_ICON]) {
boredzo@2239
   431
			if (!appIconData)
boredzo@2239
   432
				appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:regDict] retain];
ingmarstein@2306
   433
			if (appIconData)
ingmarstein@2306
   434
				[mRegDict setObject:appIconData forKey:GROWL_APP_ICON];
boredzo@1776
   435
		}
boredzo@1776
   436
	}
boredzo@1776
   437
boredzo@1776
   438
	if ((!keys) || [keys containsObject:GROWL_APP_LOCATION]) {
boredzo@1776
   439
		if (![mRegDict objectForKey:GROWL_APP_LOCATION]) {
boredzo@1776
   440
			NSURL *myURL = copyCurrentProcessURL();
boredzo@1776
   441
			if (myURL) {
ingmarstein@2639
   442
				NSDictionary *file_data = createDockDescriptionWithURL(myURL);
boredzo@1776
   443
				if (file_data) {
boredzo@1776
   444
					NSDictionary *location = [[NSDictionary alloc] initWithObjectsAndKeys:file_data, @"file-data", nil];
ingmarstein@2639
   445
					[file_data release];
ingmarstein@2639
   446
					[mRegDict setObject:location forKey:GROWL_APP_LOCATION];
boredzo@1776
   447
					[location release];
boredzo@1776
   448
				} else {
boredzo@1776
   449
					[mRegDict removeObjectForKey:GROWL_APP_LOCATION];
boredzo@1776
   450
				}
boredzo@1776
   451
				[myURL release];
boredzo@1776
   452
			}
boredzo@1776
   453
		}
boredzo@1776
   454
	}
boredzo@1776
   455
boredzo@1776
   456
	if ((!keys) || [keys containsObject:GROWL_NOTIFICATIONS_DEFAULT]) {
boredzo@1776
   457
		if (![mRegDict objectForKey:GROWL_NOTIFICATIONS_DEFAULT]) {
boredzo@1776
   458
			NSArray *all = [mRegDict objectForKey:GROWL_NOTIFICATIONS_ALL];
ingmarstein@2306
   459
			if (all)
ingmarstein@2306
   460
				[mRegDict setObject:all forKey:GROWL_NOTIFICATIONS_DEFAULT];
boredzo@1776
   461
		}
boredzo@1776
   462
	}
boredzo@1776
   463
ingmarstein@3035
   464
	if ((!keys) || [keys containsObject:GROWL_APP_ID])
ingmarstein@3035
   465
		if (![mRegDict objectForKey:GROWL_APP_ID])
ingmarstein@3035
   466
			[mRegDict setObject:(NSString *)CFBundleGetIdentifier(CFBundleGetMainBundle()) forKey:GROWL_APP_ID];
ingmarstein@3035
   467
evands@3959
   468
	return [mRegDict autorelease];
boredzo@1776
   469
}
boredzo@1776
   470
boredzo@3238
   471
+ (NSDictionary *) notificationDictionaryByFillingInDictionary:(NSDictionary *)notifDict {
boredzo@3238
   472
	NSMutableDictionary *mNotifDict = [notifDict mutableCopy];
boredzo@3238
   473
boredzo@3238
   474
	if (![mNotifDict objectForKey:GROWL_APP_NAME]) {
boredzo@3238
   475
		if (!appName)
boredzo@4030
   476
			appName = [[self _applicationNameForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
boredzo@3238
   477
boredzo@3238
   478
		if (appName) {
boredzo@3238
   479
			[mNotifDict setObject:appName
boredzo@3238
   480
			               forKey:GROWL_APP_NAME];
boredzo@3238
   481
		}
boredzo@3238
   482
	}
boredzo@3238
   483
boredzo@3238
   484
	if (![mNotifDict objectForKey:GROWL_APP_ICON]) {
boredzo@3238
   485
		if (!appIconData)
boredzo@4030
   486
			appIconData = [[self _applicationIconDataForGrowlSearchingRegistrationDictionary:cachedRegistrationDictionary] retain];
boredzo@3238
   487
boredzo@3238
   488
		if (appIconData) {
boredzo@3238
   489
			[mNotifDict setObject:appIconData
boredzo@3238
   490
			               forKey:GROWL_APP_ICON];
boredzo@3238
   491
		}
boredzo@3238
   492
	}
boredzo@3238
   493
boredzo@3473
   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.
boredzo@3473
   495
	if ([mNotifDict objectForKey:GROWL_NOTIFICATION_CLICK_CONTEXT] && ![mNotifDict objectForKey:GROWL_APP_PID]) {
boredzo@3473
   496
		NSNumber *pidNum = [[NSNumber alloc] initWithInt:[[NSProcessInfo processInfo] processIdentifier]];
boredzo@3473
   497
boredzo@3473
   498
		[mNotifDict setObject:pidNum
boredzo@3473
   499
		               forKey:GROWL_APP_PID];
boredzo@3473
   500
boredzo@3473
   501
		[pidNum release];
boredzo@3473
   502
	}
boredzo@3473
   503
boredzo@3238
   504
	return [mNotifDict autorelease];
boredzo@3238
   505
}
boredzo@3238
   506
ingmarstein@2368
   507
+ (NSDictionary *) frameworkInfoDictionary {
ingmarstein@2368
   508
#ifdef GROWL_WITH_INSTALLER
ingmarstein@3035
   509
	return (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetBundleWithIdentifier(CFSTR("com.growl.growlwithinstallerframework")));
ingmarstein@2368
   510
#else
ingmarstein@3035
   511
	return (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetBundleWithIdentifier(CFSTR("com.growl.growlframework")));
ingmarstein@2368
   512
#endif
ingmarstein@2368
   513
}
ingmarstein@2368
   514
boredzo@1776
   515
#pragma mark -
boredzo@1776
   516
#pragma mark Private methods
boredzo@1776
   517
boredzo@1776
   518
+ (NSString *) _applicationNameForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict {
boredzo@1776
   519
	NSString *applicationNameForGrowl = nil;
boredzo@1776
   520
boredzo@3656
   521
	if (delegate && [delegate respondsToSelector:@selector(applicationNameForGrowl)])
boredzo@1789
   522
		applicationNameForGrowl = [delegate applicationNameForGrowl];
boredzo@1789
   523
boredzo@3656
   524
	if (!applicationNameForGrowl) {
boredzo@3656
   525
		applicationNameForGrowl = [regDict objectForKey:GROWL_APP_NAME];
boredzo@3656
   526
boredzo@3656
   527
		if (!applicationNameForGrowl)
boredzo@3656
   528
			applicationNameForGrowl = [[NSProcessInfo processInfo] processName];
ingmarstein@3035
   529
	}
boredzo@1776
   530
boredzo@1385
   531
	return applicationNameForGrowl;
boredzo@1385
   532
}
boredzo@1776
   533
+ (NSData *) _applicationIconDataForGrowlSearchingRegistrationDictionary:(NSDictionary *)regDict {
boredzo@1776
   534
	NSData *iconData = nil;
boredzo@1776
   535
boredzo@2239
   536
	if (delegate) {
boredzo@2239
   537
		if ([delegate respondsToSelector:@selector(applicationIconForGrowl)])
boredzo@2239
   538
			iconData = (NSData *)[delegate applicationIconForGrowl];
boredzo@2239
   539
		else if ([delegate respondsToSelector:@selector(applicationIconDataForGrowl)])
boredzo@2239
   540
			iconData = [delegate applicationIconDataForGrowl];
boredzo@2239
   541
	}
boredzo@1789
   542
boredzo@1789
   543
	if (!iconData)
boredzo@1789
   544
		iconData = [regDict objectForKey:GROWL_APP_ICON];
boredzo@1776
   545
boredzo@2239
   546
	if (iconData && [iconData isKindOfClass:[NSImage class]])
boredzo@2239
   547
		iconData = [(NSImage *)iconData TIFFRepresentation];
boredzo@2239
   548
boredzo@1776
   549
	if (!iconData) {
boredzo@1776
   550
		NSURL *URL = copyCurrentProcessURL();
boredzo@1776
   551
		iconData = [copyIconDataForURL(URL) autorelease];
boredzo@1776
   552
		[URL release];
boredzo@1776
   553
	}
boredzo@1776
   554
boredzo@1776
   555
	return iconData;
boredzo@1776
   556
}
boredzo@1385
   557
boredzo@1385
   558
/*Selector called when a growl notification is clicked.  This should never be
boredzo@1385
   559
 *	called manually, and the calling observer should only be registered if the
boredzo@1385
   560
 *	delegate responds to growlNotificationWasClicked:.
boredzo@1385
   561
 */
ingmarstein@2397
   562
+ (void) growlNotificationWasClicked:(NSNotification *)notification {
evands@4016
   563
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ingmarstein@2397
   564
	[delegate growlNotificationWasClicked:
ingmarstein@2397
   565
		[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
Rudy@4276
   566
	[pool drain];
ingmarstein@2397
   567
}
ingmarstein@2397
   568
+ (void) growlNotificationTimedOut:(NSNotification *)notification {
evands@4016
   569
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ingmarstein@2397
   570
	[delegate growlNotificationTimedOut:
ingmarstein@2397
   571
		[[notification userInfo] objectForKey:GROWL_KEY_CLICKED_CONTEXT]];
Rudy@4276
   572
	[pool drain];
ingmarstein@1893
   573
}
boredzo@1385
   574
boredzo@1776
   575
#pragma mark -
boredzo@1776
   576
evands@3425
   577
//When a connection dies, release our reference to its proxy
evands@3425
   578
+ (void) connectionDidDie:(NSNotification *)notification {
evands@3425
   579
	[[NSNotificationCenter defaultCenter] removeObserver:self
evands@3425
   580
													name:NSConnectionDidDieNotification
evands@3425
   581
												  object:[notification object]];
evands@3425
   582
	[growlProxy release]; growlProxy = nil;
evands@3425
   583
}
evands@3425
   584
evands@3425
   585
+ (NSProxy<GrowlNotificationProtocol> *) growlProxy {
evands@3425
   586
	if (!growlProxy) {
evands@3425
   587
		NSConnection *connection = [NSConnection connectionWithRegisteredName:@"GrowlApplicationBridgePathway" host:nil];
evands@3425
   588
		if (connection) {
evands@3425
   589
			[[NSNotificationCenter defaultCenter] addObserver:self
evands@3425
   590
													 selector:@selector(connectionDidDie:)
evands@3425
   591
														 name:NSConnectionDidDieNotification
evands@3425
   592
													   object:connection];
evands@3425
   593
			
evands@3425
   594
			TRY
evands@3425
   595
			{
evands@3425
   596
				NSDistantObject *theProxy = [connection rootProxy];
evands@4073
   597
				if ([theProxy respondsToSelector:@selector(registerApplicationWithDictionary:)]) {
evands@4073
   598
					[theProxy setProtocolForProxy:@protocol(GrowlNotificationProtocol)];
evands@4073
   599
					growlProxy = [(NSProxy<GrowlNotificationProtocol> *)theProxy retain];
evands@4073
   600
				} else {
evands@4073
   601
					NSLog(@"Received a fake GrowlApplicationBridgePathway object. Some other application is interfering with Growl, or something went horribly wrong. Please file a bug report.");
evands@4073
   602
					growlProxy = nil;
evands@4073
   603
				}
evands@3425
   604
			}
evands@3425
   605
			ENDTRY
evands@3425
   606
				CATCH
evands@3425
   607
			{
evands@3425
   608
				NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
evands@3425
   609
				growlProxy = nil;
evands@3425
   610
			}
evands@3425
   611
			ENDCATCH
evands@3425
   612
		}
evands@3425
   613
	}
evands@3425
   614
	
evands@3425
   615
	return growlProxy;
evands@3425
   616
}
evands@3425
   617
boredzo@1385
   618
+ (void) _growlIsReady:(NSNotification *)notification {
ingmarstein@1865
   619
#pragma unused(notification)
evands@4016
   620
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ingmarstein@1905
   621
boredzo@1385
   622
	//Growl has now launched; we may get here with (growlLaunched == NO) when the user first installs
boredzo@1385
   623
	growlLaunched = YES;
ingmarstein@1905
   624
boredzo@1385
   625
	//Inform our delegate if it is interested
boredzo@2045
   626
	if ([delegate respondsToSelector:@selector(growlIsReady)])
boredzo@1385
   627
		[delegate growlIsReady];
ingmarstein@1905
   628
boredzo@1385
   629
	//Post a notification locally
evands@3425
   630
	[[NSNotificationCenter defaultCenter] postNotificationName:GROWL_IS_READY
evands@3425
   631
														object:nil
evands@3425
   632
													  userInfo:nil];
ingmarstein@2721
   633
boredzo@1385
   634
	//Stop observing for GROWL_IS_READY
evands@3425
   635
	[[NSDistributedNotificationCenter defaultCenter] removeObserver:self
evands@3425
   636
															   name:GROWL_IS_READY
evands@3425
   637
															 object:nil];
boredzo@1385
   638
boredzo@1470
   639
	//register (fixes #102: this is necessary if we got here by Growl having just been installed)
boredzo@1776
   640
	if (registerWhenGrowlIsReady) {
boredzo@1789
   641
		[self reregisterGrowlNotifications];
boredzo@1776
   642
		registerWhenGrowlIsReady = NO;
boredzo@1776
   643
	}
boredzo@1470
   644
boredzo@2046
   645
#ifdef GROWL_WITH_INSTALLER
boredzo@1385
   646
	//Perform any queued notifications
boredzo@2045
   647
	NSEnumerator *enumerator = [queuedGrowlNotifications objectEnumerator];
boredzo@1385
   648
	NSDictionary *noteDict;
ingmarstein@1905
   649
evands@3425
   650
	//Configure the growl proxy if it isn't currently configured
evands@3425
   651
	NSProxy<GrowlNotificationProtocol> *currentGrowlProxy = [self growlProxy];
evands@3425
   652
	
evands@3425
   653
	while ((noteDict = [enumerator nextObject])) {		
evands@3425
   654
		if (currentGrowlProxy) {
ingmarstein@1972
   655
			//Post to Growl via GrowlApplicationBridgePathway
ingmarstein@1941
   656
			NS_DURING
evands@3425
   657
				[currentGrowlProxy postNotificationWithDictionary:noteDict];
ingmarstein@1941
   658
			NS_HANDLER
ingmarstein@1941
   659
				NSLog(@"GrowlApplicationBridge: exception while sending notification: %@", localException);
ingmarstein@1941
   660
			NS_ENDHANDLER
ingmarstein@1941
   661
		} else {
ingmarstein@1941
   662
			//Post to Growl via NSDistributedNotificationCenter
boredzo@2156
   663
			NSLog(@"GrowlApplicationBridge: could not find local GrowlApplicationBridgePathway, falling back to NSDistributedNotificationCenter");
evands@3425
   664
			[[NSDistributedNotificationCenter defaultCenter] postNotificationName:GROWL_NOTIFICATION
evands@3425
   665
																		   object:NULL
evands@3425
   666
																		 userInfo:noteDict
evands@3425
   667
															   deliverImmediately:FALSE];
ingmarstein@1941
   668
		}
boredzo@1385
   669
	}
boredzo@1385
   670
boredzo@1385
   671
	[queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
boredzo@2046
   672
#endif
evands@4016
   673
	
Rudy@4276
   674
	[pool drain];
boredzo@1385
   675
}
boredzo@1385
   676
boredzo@1385
   677
#ifdef GROWL_WITH_INSTALLER
boredzo@1560
   678
/*Sent to us by GrowlInstallationPrompt if the user clicks Cancel so we can
boredzo@1560
   679
 *	avoid prompting again this session (or ever if they checked Don't Ask Again)
boredzo@1560
   680
 */
boredzo@1385
   681
+ (void) _userChoseNotToInstallGrowl {
boredzo@1385
   682
	//Note the user's action so we stop queueing notifications, etc.
boredzo@1385
   683
	userChoseNotToInstallGrowl = YES;
boredzo@1385
   684
boredzo@1385
   685
	//Clear our queued notifications; we won't be needing them
boredzo@1385
   686
	[queuedGrowlNotifications release]; queuedGrowlNotifications = nil;
boredzo@1385
   687
}
boredzo@1385
   688
boredzo@1385
   689
// Check against our current version number and ensure the installed Growl pane is the same or later
boredzo@1385
   690
+ (void) _checkForPackagedUpdateForGrowlPrefPaneBundle:(NSBundle *)growlPrefPaneBundle {
boredzo@1385
   691
	NSString *ourGrowlPrefPaneInfoPath;
boredzo@1385
   692
	NSDictionary *infoDictionary;
boredzo@1385
   693
	NSString *packagedVersion, *installedVersion;
boredzo@1385
   694
	BOOL upgradeIsAvailable;
ingmarstein@1905
   695
ingmarstein@2327
   696
	ourGrowlPrefPaneInfoPath = [[NSBundle bundleWithIdentifier:@"com.growl.growlwithinstallerframework"] pathForResource:@"GrowlPrefPaneInfo"
ingmarstein@2327
   697
																												  ofType:@"plist"];
boredzo@1385
   698
evands@3430
   699
	NSObject *infoPropertyList = createPropertyListFromURL([NSURL fileURLWithPath:ourGrowlPrefPaneInfoPath],
evands@3430
   700
														   kCFPropertyListImmutable,
evands@3430
   701
														   /* outFormat */ NULL, /* outErrorString */ NULL);
evands@3430
   702
	NSDictionary *infoDict = ([infoPropertyList isKindOfClass:[NSDictionary class]] ? (NSDictionary *)infoPropertyList : nil);
evands@3430
   703
ingmarstein@1637
   704
	packagedVersion = [infoDict objectForKey:(NSString *)kCFBundleVersionKey];
boredzo@1385
   705
boredzo@1385
   706
	infoDictionary = [growlPrefPaneBundle infoDictionary];
boredzo@1385
   707
	installedVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey];
boredzo@1385
   708
boredzo@1385
   709
	//If the installed version is earlier than our packaged version, we can offer an upgrade.
evands@1555
   710
	upgradeIsAvailable = (compareVersionStringsTranslating1_0To0_5(packagedVersion, installedVersion) == kCFCompareGreaterThan);
boredzo@1385
   711
	if (upgradeIsAvailable && !promptedToUpgradeGrowl) {
boredzo@2038
   712
		NSString	*lastDoNotPromptVersion = [[NSUserDefaults standardUserDefaults] objectForKey:@"Growl Update:Do Not Prompt Again:Last Version"];
ingmarstein@1905
   713
evands@1555
   714
		if (!lastDoNotPromptVersion ||
boredzo@2038
   715
			(compareVersionStringsTranslating1_0To0_5(packagedVersion, lastDoNotPromptVersion) == kCFCompareGreaterThan))
boredzo@2038
   716
		{
evands@1555
   717
			[GrowlInstallationPrompt showUpdatePromptForVersion:packagedVersion];
boredzo@1385
   718
			promptedToUpgradeGrowl = YES;
evands@1555
   719
		}
boredzo@1385
   720
	}
ingmarstein@1637
   721
	[infoDict release];
boredzo@1385
   722
}
boredzo@1385
   723
#endif
boredzo@1385
   724
boredzo@1776
   725
#pragma mark -
boredzo@1776
   726
boredzo@1776
   727
+ (BOOL) _launchGrowlIfInstalledWithRegistrationDictionary:(NSDictionary *)regDict {
evands@4151
   728
	BOOL success = NO;
evands@4151
   729
	NSBundle *growlPrefPaneBundle;
evands@4151
   730
	NSString *growlHelperAppPath;
evands@4151
   731
evands@4151
   732
#ifdef DEBUG
evands@4151
   733
	//For a debug build, first look for a running GHA. It might not actually be within a Growl prefpane bundle.
evands@4151
   734
	growlHelperAppPath = [[GrowlPathUtilities runningHelperAppBundle] bundlePath];
evands@4151
   735
	if (!growlHelperAppPath) {
evands@4151
   736
		growlPrefPaneBundle = [GrowlPathUtilities growlPrefPaneBundle];
evands@4151
   737
		growlHelperAppPath = [growlPrefPaneBundle pathForResource:@"GrowlHelperApp"
evands@4151
   738
														   ofType:@"app"];
evands@4151
   739
	}
evands@4151
   740
	NSLog(@"Will use GrowlHelperApp at %@", growlHelperAppPath);
evands@4151
   741
#else
boredzo@2677
   742
	growlPrefPaneBundle = [GrowlPathUtilities growlPrefPaneBundle];
evands@4151
   743
	growlHelperAppPath = [growlPrefPaneBundle pathForResource:@"GrowlHelperApp"
evands@4151
   744
													   ofType:@"app"];
evands@4151
   745
#endif
evands@4151
   746
evands@4151
   747
#ifdef GROWL_WITH_INSTALLER
boredzo@1776
   748
	if (growlPrefPaneBundle) {
boredzo@1776
   749
		/* Check against our current version number and ensure the installed Growl pane is the same or later */
boredzo@1789
   750
		[self _checkForPackagedUpdateForGrowlPrefPaneBundle:growlPrefPaneBundle];
evands@4151
   751
	}
evands@4151
   752
#endif
evands@4151
   753
evands@4151
   754
	//Houston, we are go for launch.
evands@4151
   755
	if (growlHelperAppPath) {
hg@4427
   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)
hg@4427
   757
		NSURL *appURL = [NSURL fileURLWithPath:growlHelperAppPath];
hg@4427
   758
		if (appURL) {
hg@4427
   759
			OSStatus err;
hg@4427
   760
hg@4427
   761
			//Find the PSN for GrowlHelperApp. (We'll need this later.)
hg@4427
   762
			struct ProcessSerialNumber appPSN = {
hg@4427
   763
				0, kNoProcess
hg@4427
   764
			};
hg@4427
   765
			while ((err = GetNextProcess(&appPSN)) == noErr) {
hg@4427
   766
				NSDictionary *dict = [(id)ProcessInformationCopyDictionary(&appPSN, kProcessDictionaryIncludeAllInformationMask) autorelease];
hg@4427
   767
				NSString *bundlePath = [dict objectForKey:@"BundlePath"];
hg@4427
   768
				if ([bundlePath isEqualToString:growlHelperAppPath]) {
hg@4427
   769
					//Match!
hg@4427
   770
					break;
hg@4427
   771
				}
hg@4427
   772
			}
hg@4427
   773
hg@4427
   774
			if (err == noErr) {
hg@4427
   775
				NSURL *regItemURL = nil;
hg@4427
   776
				BOOL passRegDict = NO;
hg@4427
   777
hg@4427
   778
				if (regDict) {
hg@4427
   779
					NSString *regDictFileName;
hg@4427
   780
					NSString *regDictPath;
hg@4427
   781
hg@4427
   782
					//Obtain a truly unique file name
hg@4427
   783
					CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
hg@4427
   784
					CFStringRef uuidString = CFUUIDCreateString(kCFAllocatorDefault, uuid);
hg@4427
   785
					CFRelease(uuid);
hg@4427
   786
					regDictFileName = [[NSString stringWithFormat:@"%@-%u-%@", [self _applicationNameForGrowlSearchingRegistrationDictionary:regDict], getpid(), (NSString *)uuidString] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
hg@4427
   787
					CFRelease(uuidString);
hg@4427
   788
					if ([regDictFileName length] > NAME_MAX)
hg@4427
   789
						regDictFileName = [[regDictFileName substringToIndex:(NAME_MAX - [GROWL_REG_DICT_EXTENSION length])] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
hg@4427
   790
hg@4427
   791
					//make sure it's within pathname length constraints
hg@4427
   792
					regDictPath = [NSTemporaryDirectory() stringByAppendingPathComponent:regDictFileName];
hg@4427
   793
					if ([regDictPath length] > PATH_MAX)
hg@4427
   794
						regDictPath = [[regDictPath substringToIndex:(PATH_MAX - [GROWL_REG_DICT_EXTENSION length])] stringByAppendingPathExtension:GROWL_REG_DICT_EXTENSION];
hg@4427
   795
hg@4427
   796
					//Write the registration dictionary out to the temporary directory
hg@4427
   797
					NSData *plistData;
hg@4427
   798
					NSString *error;
hg@4427
   799
					plistData = [NSPropertyListSerialization dataFromPropertyList:regDict
hg@4427
   800
																		   format:NSPropertyListBinaryFormat_v1_0
hg@4427
   801
																 errorDescription:&error];
hg@4427
   802
					if (plistData) {
hg@4427
   803
						if (![plistData writeToFile:regDictPath atomically:NO])
hg@4427
   804
							NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@", regDictPath);
hg@4427
   805
					} else {
hg@4427
   806
						NSLog(@"GrowlApplicationBridge: Error writing registration dictionary at %@: %@", regDictPath, error);
hg@4427
   807
						NSLog(@"GrowlApplicationBridge: Registration dictionary follows\n%@", regDict);
hg@4427
   808
						[error release];
hg@4427
   809
					}
hg@4427
   810
hg@4427
   811
					if ([[NSFileManager defaultManager] fileExistsAtPath:regDictPath]) {
hg@4427
   812
						regItemURL = [NSURL fileURLWithPath:regDictPath];
hg@4427
   813
						passRegDict = YES;
hg@4427
   814
					}
hg@4427
   815
				}
hg@4427
   816
hg@4427
   817
				AEStreamRef stream = AEStreamCreateEvent(kAECoreSuite, kAEOpen,
hg@4427
   818
					//Target application
hg@4427
   819
					typeProcessSerialNumber, &appPSN, sizeof(appPSN),
hg@4427
   820
					kAutoGenerateReturnID, kAnyTransactionID);
hg@4427
   821
				if (!stream) {
hg@4427
   822
					NSLog(@"%@: Could not create open-document event to register this application with Growl", [self class]);
evands@4151
   823
				} else {
hg@4427
   824
					if (passRegDict) {
hg@4427
   825
						NSString *regItemURLString = [regItemURL absoluteString];
hg@4427
   826
						NSData *regItemURLUTF8Data = [regItemURLString dataUsingEncoding:NSUTF8StringEncoding];
hg@4429
   827
						err = AEStreamWriteKeyDesc(stream, keyDirectObject, typeFileURL, [regItemURLUTF8Data bytes], [regItemURLUTF8Data length]);
hg@4427
   828
						if (err != noErr) {
hg@4427
   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));
hg@4427
   830
						}
hg@4427
   831
					}
hg@4427
   832
hg@4427
   833
					AppleEvent event;
hg@4427
   834
					err = AEStreamClose(stream, &event);
hg@4427
   835
					if (err != noErr) {
hg@4427
   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));
hg@4427
   837
					} else {
hg@4427
   838
						err = AESend(&event, /*reply*/ NULL, kAENoReply | kAEDontReconnect | kAENeverInteract | kAEDontRecord, kAENormalPriority, kAEDefaultTimeout, /*idleProc*/ NULL, /*filterProc*/ NULL);
hg@4427
   839
						if (err != noErr) {
hg@4427
   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));
hg@4427
   841
						}
hg@4427
   842
					}
hg@4427
   843
					
hg@4427
   844
					success = (err == noErr);
boredzo@1776
   845
				}
boredzo@1776
   846
			}
boredzo@1776
   847
		}
boredzo@1776
   848
	}
boredzo@1776
   849
boredzo@1776
   850
	return success;
boredzo@1776
   851
}
boredzo@1776
   852
boredzo@1776
   853
/*	+ (BOOL)launchGrowlIfInstalled
boredzo@1776
   854
 *
boredzo@1776
   855
 *Returns YES if the Growl helper app began launching or was already running.
boredzo@1776
   856
 *Returns NO and performs no other action if the Growl prefPane is not properly
boredzo@1776
   857
 *	installed.
boredzo@1776
   858
 *If Growl is installed but disabled, the application will be registered and
boredzo@1776
   859
 *	GrowlHelperApp will then quit.  This method will still return YES if Growl
boredzo@1776
   860
 *	is installed but disabled.
boredzo@1776
   861
 */
boredzo@1776
   862
+ (BOOL) launchGrowlIfInstalled {
boredzo@1789
   863
	return [self _launchGrowlIfInstalledWithRegistrationDictionary:nil];
boredzo@1776
   864
}
boredzo@1776
   865
boredzo@1385
   866
@end