Core/Source/GrowlPreferencesController.m
author Rudy Richter
Wed Sep 09 22:11:15 2009 -0400 (2009-09-09)
changeset 4387 0e19238de90f
parent 4329 7d3e76457212
child 4666 59b81a267426
child 4825 417a5ca92879
permissions -rw-r--r--
if the user has a corrupt install and as a result pathForResource:ofType: returns nil for GHA, CFURLCreateWithFileSystemPath will crash, so we guard the call and the rest of the block since it depends on that path and log a message to console indicating that the user will need to reinstall Growl, as well as the returned value of prefPaneBundle and pathToGHA.
     1 //
     2 //  GrowlPreferencesController.m
     3 //  Growl
     4 //
     5 //  Created by Nelson Elhage on 8/24/04.
     6 //  Renamed from GrowlPreferences.m by Mac-arena the Bored Zo on 2005-06-27.
     7 //  Copyright 2004-2006 The Growl Project. All rights reserved.
     8 //
     9 // This file is under the BSD License, refer to License.txt for details
    10 
    11 
    12 #import "GrowlPreferencesController.h"
    13 #import "GrowlDefinesInternal.h"
    14 #import "GrowlDefines.h"
    15 #import "GrowlPathUtilities.h"
    16 #import "NSStringAdditions.h"
    17 #include "CFURLAdditions.h"
    18 #include "CFDictionaryAdditions.h"
    19 #include <Security/SecKeychain.h>
    20 #include <Security/SecKeychainItem.h>
    21 
    22 #define keychainServiceName "Growl"
    23 #define keychainAccountName "Growl"
    24 
    25 CFTypeRef GrowlPreferencesController_objectForKey(CFTypeRef key) {
    26 	return [[GrowlPreferencesController sharedController] objectForKey:(id)key];
    27 }
    28 
    29 CFIndex GrowlPreferencesController_integerForKey(CFTypeRef key) {
    30 	Boolean keyExistsAndHasValidFormat;
    31 	return CFPreferencesGetAppIntegerValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER, &keyExistsAndHasValidFormat);
    32 }
    33 
    34 Boolean GrowlPreferencesController_boolForKey(CFTypeRef key) {
    35 	Boolean keyExistsAndHasValidFormat;
    36 	return CFPreferencesGetAppBooleanValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER, &keyExistsAndHasValidFormat);
    37 }
    38 
    39 unsigned short GrowlPreferencesController_unsignedShortForKey(CFTypeRef key)
    40 {
    41 	CFIndex theIndex = GrowlPreferencesController_integerForKey(key);
    42 	
    43 	if (theIndex > USHRT_MAX)
    44 		return USHRT_MAX;
    45 	else if (theIndex < 0)
    46 		return 0;
    47 	return (unsigned short)theIndex;
    48 }
    49 
    50 @implementation GrowlPreferencesController
    51 
    52 + (GrowlPreferencesController *) sharedController {
    53 	return [self sharedInstance];
    54 }
    55 
    56 - (id) initSingleton {
    57 	if ((self = [super initSingleton])) {
    58 		[[NSDistributedNotificationCenter defaultCenter] addObserver:self
    59 															selector:@selector(growlPreferencesChanged:)
    60 																name:GrowlPreferencesChanged
    61 															  object:nil];
    62 		loginItems = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, /*options*/ NULL);
    63 	}
    64 	return self;
    65 }
    66 
    67 - (void) destroy {
    68 	[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
    69 	CFRelease(loginItems);
    70 
    71 	[super destroy];
    72 }
    73 
    74 #pragma mark -
    75 
    76 - (void) registerDefaults:(NSDictionary *)inDefaults {
    77 	NSUserDefaults *helperAppDefaults = [[NSUserDefaults alloc] init];
    78 	[helperAppDefaults addSuiteNamed:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
    79 	NSDictionary *existing = [helperAppDefaults persistentDomainForName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
    80 	if (existing) {
    81 		NSMutableDictionary *domain = [inDefaults mutableCopy];
    82 		[domain addEntriesFromDictionary:existing];
    83 		[helperAppDefaults setPersistentDomain:domain forName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
    84 		[domain release];
    85 	} else {
    86 		[helperAppDefaults setPersistentDomain:inDefaults forName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
    87 	}
    88 	[helperAppDefaults release];
    89 	SYNCHRONIZE_GROWL_PREFS();
    90 }
    91 
    92 - (id) objectForKey:(NSString *)key {
    93 	id value = (id)CFPreferencesCopyAppValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER);
    94 	if(value)
    95 		CFMakeCollectable(value);
    96 	return [value autorelease];
    97 }
    98 
    99 - (void) setObject:(id)object forKey:(NSString *)key {
   100 	CFPreferencesSetAppValue((CFStringRef)key,
   101 							 (CFPropertyListRef)object,
   102 							 (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER);
   103 
   104 	SYNCHRONIZE_GROWL_PREFS();
   105 
   106 	int pid = getpid();
   107 	CFNumberRef pidValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &pid);
   108 	CFStringRef pidKey = CFSTR("pid");
   109 	CFDictionaryRef userInfo = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&pidKey, (const void **)&pidValue, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
   110 	CFRelease(pidValue);
   111 	CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
   112 										 (CFStringRef)GrowlPreferencesChanged,
   113 										 /*object*/ key,
   114 										 /*userInfo*/ userInfo,
   115 										 /*deliverImmediately*/ false);
   116 	CFRelease(userInfo);
   117 }
   118 
   119 - (BOOL) boolForKey:(NSString *)key {
   120 	return GrowlPreferencesController_boolForKey((CFTypeRef)key);
   121 }
   122 
   123 - (void) setBool:(BOOL)value forKey:(NSString *)key {
   124 	NSNumber *object = [[NSNumber alloc] initWithBool:value];
   125 	[self setObject:object forKey:key];
   126 	[object release];
   127 }
   128 
   129 - (CFIndex) integerForKey:(NSString *)key {
   130 	return GrowlPreferencesController_integerForKey((CFTypeRef)key);
   131 }
   132 
   133 - (void) setInteger:(CFIndex)value forKey:(NSString *)key {
   134 #ifdef __LP64__
   135 	NSNumber *object = [[NSNumber alloc] initWithInteger:value];
   136 #else
   137 	NSNumber *object = [[NSNumber alloc] initWithInt:value];
   138 #endif
   139 	[self setObject:object forKey:key];
   140 	[object release];
   141 }
   142 
   143 - (unsigned short)unsignedShortForKey:(NSString *)key
   144 {
   145 	return GrowlPreferencesController_unsignedShortForKey((CFTypeRef)key);
   146 }
   147 
   148 
   149 - (void)setUnsignedShort:(unsigned short)theShort forKey:(NSString *)key
   150 {
   151 	[self setObject:[NSNumber numberWithUnsignedShort:theShort] forKey:key];
   152 }
   153 
   154 - (void) synchronize {
   155 	SYNCHRONIZE_GROWL_PREFS();
   156 }
   157 
   158 #pragma mark -
   159 #pragma mark Start-at-login control
   160 
   161 - (BOOL) shouldStartGrowlAtLogin {
   162 	Boolean    foundIt = false;
   163 
   164 	//get the prefpane bundle and find GHA within it.
   165 	NSBundle *prefPaneBundle = [NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER];
   166 	NSString *pathToGHA      = [prefPaneBundle pathForResource:@"GrowlHelperApp" ofType:@"app"];
   167 	if(pathToGHA) {
   168 		//get the file url to GHA.
   169 		CFURLRef urlToGHA = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)pathToGHA, kCFURLPOSIXPathStyle, true);
   170 		
   171 		UInt32 seed = 0U;
   172 		NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
   173 		for (id itemObject in currentLoginItems) {
   174 			LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
   175 			
   176 			UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
   177 			CFURLRef URL = NULL;
   178 			OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
   179 			if (err == noErr) {
   180 				foundIt = CFEqual(URL, urlToGHA);
   181 				CFRelease(URL);
   182 				
   183 				if (foundIt)
   184 					break;
   185 			}
   186 		}
   187 		
   188 		CFRelease(urlToGHA);
   189 	}
   190 	else {
   191 		NSLog(@"Growl: your install is corrupt, you will need to reinstall\nyour prefpane bundle is:%@\n your pathToGHA is:%@", prefPaneBundle, pathToGHA);
   192 	}
   193 	
   194 	return foundIt;
   195 }
   196 
   197 - (void) setShouldStartGrowlAtLogin:(BOOL)flag {
   198 	//get the prefpane bundle and find GHA within it.
   199 	NSString *pathToGHA = [[NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER] pathForResource:@"GrowlHelperApp" ofType:@"app"];
   200 	[self setStartAtLogin:pathToGHA enabled:flag];
   201 }
   202 
   203 - (void) setStartAtLogin:(NSString *)path enabled:(BOOL)enabled {
   204 	OSStatus status;
   205 	CFURLRef URLToToggle = (CFURLRef)[NSURL fileURLWithPath:path];
   206 	LSSharedFileListItemRef existingItem = NULL;
   207 
   208 	UInt32 seed = 0U;
   209 	NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
   210 	for (id itemObject in currentLoginItems) {
   211 		LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
   212 
   213 		UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
   214 		CFURLRef URL = NULL;
   215 		OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
   216 		if (err == noErr) {
   217 			Boolean foundIt = CFEqual(URL, URLToToggle);
   218 			CFRelease(URL);
   219 
   220 			if (foundIt) {
   221 				existingItem = item;
   222 				break;
   223 			}
   224 		}
   225 	}
   226 
   227 	if (enabled && (existingItem == NULL)) {
   228 		NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:path];
   229 		IconRef icon = NULL;
   230 		FSRef ref;
   231 		Boolean gotRef = CFURLGetFSRef(URLToToggle, &ref);
   232 		if (gotRef) {
   233 			status = GetIconRefFromFileInfo(&ref,
   234 											/*fileNameLength*/ 0, /*fileName*/ NULL,
   235 											kFSCatInfoNone, /*catalogInfo*/ NULL,
   236 											kIconServicesNormalUsageFlag,
   237 											&icon,
   238 											/*outLabel*/ NULL);
   239 			if (status != noErr)
   240 				icon = NULL;
   241 		}
   242 
   243 		LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, (CFStringRef)displayName, icon, URLToToggle, /*propertiesToSet*/ NULL, /*propertiesToClear*/ NULL);
   244 	} else if (!enabled && (existingItem != NULL))
   245 		LSSharedFileListItemRemove(loginItems, existingItem);
   246 }
   247 
   248 #pragma mark -
   249 #pragma mark GrowlMenu running state
   250 
   251 - (void) enableGrowlMenu {
   252 	NSBundle *bundle = [NSBundle bundleForClass:[GrowlPreferencesController class]];
   253 	NSString *growlMenuPath = [bundle pathForResource:@"GrowlMenu" ofType:@"app"];
   254 	NSURL *growlMenuURL = [NSURL fileURLWithPath:growlMenuPath];
   255 	[[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:growlMenuURL]
   256 	                withAppBundleIdentifier:nil
   257 	                                options:NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAsync
   258 	         additionalEventParamDescriptor:nil
   259 	                      launchIdentifiers:NULL];
   260 }
   261 
   262 - (void) disableGrowlMenu {
   263 	// Ask GrowlMenu to shutdown via the DNC
   264 	CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
   265 										 CFSTR("GrowlMenuShutdown"),
   266 										 /*object*/ NULL,
   267 										 /*userInfo*/ NULL,
   268 										 /*deliverImmediately*/ false);
   269 }
   270 
   271 #pragma mark -
   272 #pragma mark Growl running state
   273 
   274 - (void) setGrowlRunning:(BOOL)flag noMatterWhat:(BOOL)nmw {
   275 	// Store the desired running-state of the helper app for use by GHA.
   276 	[self setBool:flag forKey:GrowlEnabledKey];
   277 
   278 	//now launch or terminate as appropriate.
   279 	if (flag)
   280 		[self launchGrowl:nmw];
   281 	else
   282 		[self terminateGrowl];
   283 }
   284 
   285 - (BOOL) isRunning:(NSString *)theBundleIdentifier {
   286 	BOOL isRunning = NO;
   287 	ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
   288 
   289 	while (GetNextProcess(&PSN) == noErr) {
   290 		NSDictionary *infoDict = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
   291 		if(infoDict) {
   292 			NSString *bundleID = [infoDict objectForKey:(NSString *)kCFBundleIdentifierKey];
   293 			isRunning = bundleID && [bundleID isEqualToString:theBundleIdentifier];
   294 			CFMakeCollectable(infoDict);
   295 			[infoDict release];
   296 		}
   297 		if (isRunning)
   298 			break;
   299 	}
   300 
   301 	return isRunning;
   302 }
   303 
   304 - (BOOL) isGrowlRunning {
   305 	return [self isRunning:@"com.Growl.GrowlHelperApp"];
   306 }
   307 
   308 - (void) launchGrowl:(BOOL)noMatterWhat {
   309 	NSString *helperPath = [[GrowlPathUtilities helperAppBundle] bundlePath];
   310 	NSURL *helperURL = [NSURL fileURLWithPath:helperPath];
   311 
   312 	unsigned options = NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAsync;
   313 	if (noMatterWhat)
   314 		options |= NSWorkspaceLaunchNewInstance;
   315 	[[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:helperURL]
   316 	                withAppBundleIdentifier:nil
   317 	                                options:options
   318 	         additionalEventParamDescriptor:nil
   319 	                      launchIdentifiers:NULL];
   320 }
   321 
   322 - (void) terminateGrowl {
   323 	// Ask the Growl Helper App to shutdown via the DNC
   324 	CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
   325 										 (CFStringRef)GROWL_SHUTDOWN,
   326 										 /*object*/ NULL,
   327 										 /*userInfo*/ NULL,
   328 										 /*deliverImmediately*/ false);
   329 }
   330 
   331 #pragma mark -
   332 //Simplified accessors
   333 
   334 #pragma mark UI
   335 
   336 - (CFIndex)selectedPosition {
   337 	return [self integerForKey:GROWL_POSITION_PREFERENCE_KEY];
   338 }
   339 
   340 - (BOOL) isBackgroundUpdateCheckEnabled {
   341 	return [self boolForKey:GrowlUpdateCheckKey];
   342 }
   343 - (void) setIsBackgroundUpdateCheckEnabled:(BOOL)flag {
   344 	[self setBool:flag forKey:GrowlUpdateCheckKey];
   345 }
   346 
   347 - (NSString *) defaultDisplayPluginName {
   348 	return [self objectForKey:GrowlDisplayPluginKey];
   349 }
   350 - (void) setDefaultDisplayPluginName:(NSString *)name {
   351 	[self setObject:name forKey:GrowlDisplayPluginKey];
   352 }
   353 
   354 - (BOOL) squelchMode {
   355 	return [self boolForKey:GrowlSquelchModeKey];
   356 }
   357 - (void) setSquelchMode:(BOOL)flag {
   358 	[self setBool:flag forKey:GrowlSquelchModeKey];
   359 }
   360 
   361 - (BOOL) stickyWhenAway {
   362 	return [self boolForKey:GrowlStickyWhenAwayKey];
   363 }
   364 - (void) setStickyWhenAway:(BOOL)flag {
   365 	[self setBool:flag forKey:GrowlStickyWhenAwayKey];
   366 }
   367 
   368 - (NSNumber*) idleThreshold {
   369 #ifdef __LP64__
   370 	return [NSNumber numberWithInteger:[self integerForKey:GrowlStickyIdleThresholdKey]];
   371 #else
   372 	return [NSNumber numberWithInt:[self integerForKey:GrowlStickyIdleThresholdKey]];
   373 #endif
   374 }
   375 
   376 - (void) setIdleThreshold:(NSNumber*)value {
   377 	[self setInteger:[value intValue] forKey:GrowlStickyIdleThresholdKey];
   378 }
   379 #pragma mark Status Item
   380 
   381 - (BOOL) isGrowlMenuEnabled {
   382 	return [self boolForKey:GrowlMenuExtraKey];
   383 }
   384 
   385 - (void) setGrowlMenuEnabled:(BOOL)state {
   386 	if (state != [self isGrowlMenuEnabled]) {
   387 		[self setBool:state forKey:GrowlMenuExtraKey];
   388 		if (state)
   389 			[self enableGrowlMenu];
   390 		else
   391 			[self disableGrowlMenu];
   392 	}
   393 }
   394 
   395 #pragma mark Logging
   396 
   397 - (BOOL) loggingEnabled {
   398 	return [self boolForKey:GrowlLoggingEnabledKey];
   399 }
   400 
   401 - (void) setLoggingEnabled:(BOOL)flag {
   402 	[self setBool:flag forKey:GrowlLoggingEnabledKey];
   403 }
   404 
   405 - (BOOL) isGrowlServerEnabled {
   406 	return [self boolForKey:GrowlStartServerKey];
   407 }
   408 
   409 - (void) setGrowlServerEnabled:(BOOL)enabled {
   410 	[self setBool:enabled forKey:GrowlStartServerKey];
   411 }
   412 
   413 #pragma mark Remote Growling
   414 
   415 - (BOOL) isRemoteRegistrationAllowed {
   416 	return [self boolForKey:GrowlRemoteRegistrationKey];
   417 }
   418 
   419 - (void) setRemoteRegistrationAllowed:(BOOL)flag {
   420 	[self setBool:flag forKey:GrowlRemoteRegistrationKey];
   421 }
   422 
   423 - (NSString *) remotePassword {
   424 	unsigned char *password;
   425 	UInt32 passwordLength;
   426 	OSStatus status;
   427 	status = SecKeychainFindGenericPassword(NULL,
   428 											(UInt32)strlen(keychainServiceName), keychainServiceName,
   429 											(UInt32)strlen(keychainAccountName), keychainAccountName,
   430 											&passwordLength, (void **)&password, NULL);
   431 
   432 	NSString *passwordString;
   433 	if (status == noErr) {
   434 		passwordString = (NSString *)CFStringCreateWithBytes(kCFAllocatorDefault, password, passwordLength, kCFStringEncodingUTF8, false);
   435 		if(passwordString) {
   436 			CFMakeCollectable(passwordString);
   437 			[passwordString autorelease];
   438 			SecKeychainItemFreeContent(NULL, password);
   439 		}
   440 	} else {
   441 		if (status != errSecItemNotFound)
   442 			NSLog(@"Failed to retrieve password from keychain. Error: %d", status);
   443 		passwordString = @"";
   444 	}
   445 
   446 	return passwordString;
   447 }
   448 
   449 - (void) setRemotePassword:(NSString *)value {
   450 	const char *password = value ? [value UTF8String] : "";
   451 	size_t length = strlen(password);
   452 	OSStatus status;
   453 	SecKeychainItemRef itemRef = nil;
   454 	status = SecKeychainFindGenericPassword(NULL,
   455 											(UInt32)strlen(keychainServiceName), keychainServiceName,
   456 											(UInt32)strlen(keychainAccountName), keychainAccountName,
   457 											NULL, NULL, &itemRef);
   458 	if (status == errSecItemNotFound) {
   459 		// add new item
   460 		status = SecKeychainAddGenericPassword(NULL,
   461 											   (UInt32)strlen(keychainServiceName), keychainServiceName,
   462 											   (UInt32)strlen(keychainAccountName), keychainAccountName,
   463 											   (UInt32)length, password, NULL);
   464 		if (status)
   465 			NSLog(@"Failed to add password to keychain.");
   466 	} else {
   467 		// change existing password
   468 		SecKeychainAttribute attrs[] = {
   469 			{ kSecAccountItemAttr, (UInt32)strlen(keychainAccountName), (char *)keychainAccountName },
   470 			{ kSecServiceItemAttr, (UInt32)strlen(keychainServiceName), (char *)keychainServiceName }
   471 		};
   472 		const SecKeychainAttributeList attributes = { (UInt32)sizeof(attrs) / (UInt32)sizeof(attrs[0]), attrs };
   473 		status = SecKeychainItemModifyAttributesAndData(itemRef,		// the item reference
   474 														&attributes,	// no change to attributes
   475 														(UInt32)length,			// length of password
   476 														password		// pointer to password data
   477 														);
   478 		if (itemRef)
   479 			CFRelease(itemRef);
   480 		if (status)
   481 			NSLog(@"Failed to change password in keychain.");
   482 	}
   483 }
   484 
   485 - (unsigned short) UDPPort {
   486 	return [self unsignedShortForKey:GrowlUDPPortKey];
   487 }
   488 - (void) setUDPPort:(unsigned short)value {
   489 	[self setUnsignedShort:value forKey:GrowlUDPPortKey];
   490 }
   491 
   492 - (BOOL) isForwardingEnabled {
   493 	return [self boolForKey:GrowlEnableForwardKey];
   494 }
   495 - (void) setForwardingEnabled:(BOOL)enabled {
   496 	[self setBool:enabled forKey:GrowlEnableForwardKey];
   497 }
   498 
   499 #pragma mark -
   500 /*
   501  * @brief Growl preferences changed
   502  *
   503  * Synchronize our NSUserDefaults to immediately get any changes from the disk
   504  */
   505 - (void) growlPreferencesChanged:(NSNotification *)notification {
   506 	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   507 
   508 	NSString *object = [notification object];
   509 //	NSLog(@"%s: %@\n", __func__, object);
   510 	SYNCHRONIZE_GROWL_PREFS();
   511 	if (!object || [object isEqualToString:GrowlDisplayPluginKey]) {
   512 		[self willChangeValueForKey:@"defaultDisplayPluginName"];
   513 		[self didChangeValueForKey:@"defaultDisplayPluginName"];
   514 	}
   515 	if (!object || [object isEqualToString:GrowlSquelchModeKey]) {
   516 		[self willChangeValueForKey:@"squelchMode"];
   517 		[self didChangeValueForKey:@"squelchMode"];
   518 	}
   519 	if (!object || [object isEqualToString:GrowlMenuExtraKey]) {
   520 		[self willChangeValueForKey:@"growlMenuEnabled"];
   521 		[self didChangeValueForKey:@"growlMenuEnabled"];
   522 	}
   523 	if (!object || [object isEqualToString:GrowlEnableForwardKey]) {
   524 		[self willChangeValueForKey:@"forwardingEnabled"];
   525 		[self didChangeValueForKey:@"forwardingEnabled"];
   526 	}
   527 	if (!object || [object isEqualToString:GrowlUpdateCheckKey]) {
   528 		[self willChangeValueForKey:@"backgroundUpdateCheckEnabled"];
   529 		[self didChangeValueForKey:@"backgroundUpdateCheckEnabled"];
   530 	}
   531 	if (!object || [object isEqualToString:GrowlStickyWhenAwayKey]) {
   532 		[self willChangeValueForKey:@"stickyWhenAway"];
   533 		[self didChangeValueForKey:@"stickyWhenAway"];
   534 	}
   535 	if (!object || [object isEqualToString:GrowlStickyIdleThresholdKey]) {
   536 		[self willChangeValueForKey:@"idleThreshold"];
   537 		[self didChangeValueForKey:@"idleThreshold"];
   538 	}
   539 	if (!object || [object isEqualToString:GrowlRemoteRegistrationKey]) {
   540 		[self willChangeValueForKey:@"remoteRegistrationAllowed"];
   541 		[self didChangeValueForKey:@"remoteRegistrationAllowed"];
   542 	}
   543 	
   544 	[pool release];
   545 }
   546 
   547 @end