Core/Source/GrowlPreferencesController.m
author Peter Hosey <hg@boredzo.org>
Mon Aug 03 11:55:33 2009 -0700 (2009-08-03)
changeset 4273 b846a7b597a6
parent 4270 5c1d8f5e64c2
child 4329 7d3e76457212
permissions -rw-r--r--
I forgot to remove this import of LoginItemsAE.h. How did this build before?!
     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)index;
    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 	NSString *pathToGHA      = [[NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER] pathForResource:@"GrowlHelperApp" ofType:@"app"];
   166 	//get the file url to GHA.
   167 	CFURLRef urlToGHA = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)pathToGHA, kCFURLPOSIXPathStyle, true);
   168 
   169 	UInt32 seed = 0U;
   170 	NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
   171 	for (id itemObject in currentLoginItems) {
   172 		LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
   173 
   174 		UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
   175 		CFURLRef URL = NULL;
   176 		OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
   177 		if (err == noErr) {
   178 			foundIt = CFEqual(URL, urlToGHA);
   179 			CFRelease(URL);
   180 
   181 			if (foundIt)
   182 				break;
   183 		}
   184 	}
   185 
   186 	CFRelease(urlToGHA);
   187 
   188 	return foundIt;
   189 }
   190 
   191 - (void) setShouldStartGrowlAtLogin:(BOOL)flag {
   192 	//get the prefpane bundle and find GHA within it.
   193 	NSString *pathToGHA = [[NSBundle bundleWithIdentifier:GROWL_PREFPANE_BUNDLE_IDENTIFIER] pathForResource:@"GrowlHelperApp" ofType:@"app"];
   194 	[self setStartAtLogin:pathToGHA enabled:flag];
   195 }
   196 
   197 - (void) setStartAtLogin:(NSString *)path enabled:(BOOL)enabled {
   198 	OSStatus status;
   199 	CFURLRef URLToToggle = (CFURLRef)[NSURL fileURLWithPath:path];
   200 	LSSharedFileListItemRef existingItem = NULL;
   201 
   202 	UInt32 seed = 0U;
   203 	NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
   204 	for (id itemObject in currentLoginItems) {
   205 		LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
   206 
   207 		UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
   208 		CFURLRef URL = NULL;
   209 		OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
   210 		if (err == noErr) {
   211 			Boolean foundIt = CFEqual(URL, URLToToggle);
   212 			CFRelease(URL);
   213 
   214 			if (foundIt) {
   215 				existingItem = item;
   216 				break;
   217 			}
   218 		}
   219 	}
   220 
   221 	if (enabled && (existingItem == NULL)) {
   222 		NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:path];
   223 		IconRef icon = NULL;
   224 		FSRef ref;
   225 		Boolean gotRef = CFURLGetFSRef(URLToToggle, &ref);
   226 		if (gotRef) {
   227 			status = GetIconRefFromFileInfo(&ref,
   228 											/*fileNameLength*/ 0, /*fileName*/ NULL,
   229 											kFSCatInfoNone, /*catalogInfo*/ NULL,
   230 											kIconServicesNormalUsageFlag,
   231 											&icon,
   232 											/*outLabel*/ NULL);
   233 			if (status != noErr)
   234 				icon = NULL;
   235 		}
   236 
   237 		LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, (CFStringRef)displayName, icon, URLToToggle, /*propertiesToSet*/ NULL, /*propertiesToClear*/ NULL);
   238 	} else if (!enabled && (existingItem != NULL))
   239 		LSSharedFileListItemRemove(loginItems, existingItem);
   240 }
   241 
   242 #pragma mark -
   243 #pragma mark GrowlMenu running state
   244 
   245 - (void) enableGrowlMenu {
   246 	NSBundle *bundle = [NSBundle bundleForClass:[GrowlPreferencesController class]];
   247 	NSString *growlMenuPath = [bundle pathForResource:@"GrowlMenu" ofType:@"app"];
   248 	NSURL *growlMenuURL = [NSURL fileURLWithPath:growlMenuPath];
   249 	[[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:growlMenuURL]
   250 	                withAppBundleIdentifier:nil
   251 	                                options:NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAsync
   252 	         additionalEventParamDescriptor:nil
   253 	                      launchIdentifiers:NULL];
   254 }
   255 
   256 - (void) disableGrowlMenu {
   257 	// Ask GrowlMenu to shutdown via the DNC
   258 	CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
   259 										 CFSTR("GrowlMenuShutdown"),
   260 										 /*object*/ NULL,
   261 										 /*userInfo*/ NULL,
   262 										 /*deliverImmediately*/ false);
   263 }
   264 
   265 #pragma mark -
   266 #pragma mark Growl running state
   267 
   268 - (void) setGrowlRunning:(BOOL)flag noMatterWhat:(BOOL)nmw {
   269 	// Store the desired running-state of the helper app for use by GHA.
   270 	[self setBool:flag forKey:GrowlEnabledKey];
   271 
   272 	//now launch or terminate as appropriate.
   273 	if (flag)
   274 		[self launchGrowl:nmw];
   275 	else
   276 		[self terminateGrowl];
   277 }
   278 
   279 - (BOOL) isRunning:(NSString *)theBundleIdentifier {
   280 	BOOL isRunning = NO;
   281 	ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
   282 
   283 	while (GetNextProcess(&PSN) == noErr) {
   284 		NSDictionary *infoDict = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
   285 		if(infoDict) {
   286 			NSString *bundleID = [infoDict objectForKey:(NSString *)kCFBundleIdentifierKey];
   287 			isRunning = bundleID && [bundleID isEqualToString:theBundleIdentifier];
   288 			CFMakeCollectable(infoDict);
   289 			[infoDict release];
   290 		}
   291 		if (isRunning)
   292 			break;
   293 	}
   294 
   295 	return isRunning;
   296 }
   297 
   298 - (BOOL) isGrowlRunning {
   299 	return [self isRunning:@"com.Growl.GrowlHelperApp"];
   300 }
   301 
   302 - (void) launchGrowl:(BOOL)noMatterWhat {
   303 	NSString *helperPath = [[GrowlPathUtilities helperAppBundle] bundlePath];
   304 	NSURL *helperURL = [NSURL fileURLWithPath:helperPath];
   305 
   306 	unsigned options = NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAsync;
   307 	if (noMatterWhat)
   308 		options |= NSWorkspaceLaunchNewInstance;
   309 	[[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:helperURL]
   310 	                withAppBundleIdentifier:nil
   311 	                                options:options
   312 	         additionalEventParamDescriptor:nil
   313 	                      launchIdentifiers:NULL];
   314 }
   315 
   316 - (void) terminateGrowl {
   317 	// Ask the Growl Helper App to shutdown via the DNC
   318 	CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
   319 										 (CFStringRef)GROWL_SHUTDOWN,
   320 										 /*object*/ NULL,
   321 										 /*userInfo*/ NULL,
   322 										 /*deliverImmediately*/ false);
   323 }
   324 
   325 #pragma mark -
   326 //Simplified accessors
   327 
   328 #pragma mark UI
   329 
   330 - (CFIndex)selectedPosition {
   331 	return [self integerForKey:GROWL_POSITION_PREFERENCE_KEY];
   332 }
   333 
   334 - (BOOL) isBackgroundUpdateCheckEnabled {
   335 	return [self boolForKey:GrowlUpdateCheckKey];
   336 }
   337 - (void) setIsBackgroundUpdateCheckEnabled:(BOOL)flag {
   338 	[self setBool:flag forKey:GrowlUpdateCheckKey];
   339 }
   340 
   341 - (NSString *) defaultDisplayPluginName {
   342 	return [self objectForKey:GrowlDisplayPluginKey];
   343 }
   344 - (void) setDefaultDisplayPluginName:(NSString *)name {
   345 	[self setObject:name forKey:GrowlDisplayPluginKey];
   346 }
   347 
   348 - (BOOL) squelchMode {
   349 	return [self boolForKey:GrowlSquelchModeKey];
   350 }
   351 - (void) setSquelchMode:(BOOL)flag {
   352 	[self setBool:flag forKey:GrowlSquelchModeKey];
   353 }
   354 
   355 - (BOOL) stickyWhenAway {
   356 	return [self boolForKey:GrowlStickyWhenAwayKey];
   357 }
   358 - (void) setStickyWhenAway:(BOOL)flag {
   359 	[self setBool:flag forKey:GrowlStickyWhenAwayKey];
   360 }
   361 
   362 - (NSNumber*) idleThreshold {
   363 #ifdef __LP64__
   364 	return [NSNumber numberWithInteger:[self integerForKey:GrowlStickyIdleThresholdKey]];
   365 #else
   366 	return [NSNumber numberWithInt:[self integerForKey:GrowlStickyIdleThresholdKey]];
   367 #endif
   368 }
   369 
   370 - (void) setIdleThreshold:(NSNumber*)value {
   371 	[self setInteger:[value intValue] forKey:GrowlStickyIdleThresholdKey];
   372 }
   373 #pragma mark Status Item
   374 
   375 - (BOOL) isGrowlMenuEnabled {
   376 	return [self boolForKey:GrowlMenuExtraKey];
   377 }
   378 
   379 - (void) setGrowlMenuEnabled:(BOOL)state {
   380 	if (state != [self isGrowlMenuEnabled]) {
   381 		[self setBool:state forKey:GrowlMenuExtraKey];
   382 		if (state)
   383 			[self enableGrowlMenu];
   384 		else
   385 			[self disableGrowlMenu];
   386 	}
   387 }
   388 
   389 #pragma mark Logging
   390 
   391 - (BOOL) loggingEnabled {
   392 	return [self boolForKey:GrowlLoggingEnabledKey];
   393 }
   394 
   395 - (void) setLoggingEnabled:(BOOL)flag {
   396 	[self setBool:flag forKey:GrowlLoggingEnabledKey];
   397 }
   398 
   399 - (BOOL) isGrowlServerEnabled {
   400 	return [self boolForKey:GrowlStartServerKey];
   401 }
   402 
   403 - (void) setGrowlServerEnabled:(BOOL)enabled {
   404 	[self setBool:enabled forKey:GrowlStartServerKey];
   405 }
   406 
   407 #pragma mark Remote Growling
   408 
   409 - (BOOL) isRemoteRegistrationAllowed {
   410 	return [self boolForKey:GrowlRemoteRegistrationKey];
   411 }
   412 
   413 - (void) setRemoteRegistrationAllowed:(BOOL)flag {
   414 	[self setBool:flag forKey:GrowlRemoteRegistrationKey];
   415 }
   416 
   417 - (NSString *) remotePassword {
   418 	unsigned char *password;
   419 	UInt32 passwordLength;
   420 	OSStatus status;
   421 	status = SecKeychainFindGenericPassword(NULL,
   422 											(UInt32)strlen(keychainServiceName), keychainServiceName,
   423 											(UInt32)strlen(keychainAccountName), keychainAccountName,
   424 											&passwordLength, (void **)&password, NULL);
   425 
   426 	NSString *passwordString;
   427 	if (status == noErr) {
   428 		passwordString = (NSString *)CFStringCreateWithBytes(kCFAllocatorDefault, password, passwordLength, kCFStringEncodingUTF8, false);
   429 		if(passwordString) {
   430 			CFMakeCollectable(passwordString);
   431 			[passwordString autorelease];
   432 			SecKeychainItemFreeContent(NULL, password);
   433 		}
   434 	} else {
   435 		if (status != errSecItemNotFound)
   436 			NSLog(@"Failed to retrieve password from keychain. Error: %d", status);
   437 		passwordString = @"";
   438 	}
   439 
   440 	return passwordString;
   441 }
   442 
   443 - (void) setRemotePassword:(NSString *)value {
   444 	const char *password = value ? [value UTF8String] : "";
   445 	size_t length = strlen(password);
   446 	OSStatus status;
   447 	SecKeychainItemRef itemRef = nil;
   448 	status = SecKeychainFindGenericPassword(NULL,
   449 											(UInt32)strlen(keychainServiceName), keychainServiceName,
   450 											(UInt32)strlen(keychainAccountName), keychainAccountName,
   451 											NULL, NULL, &itemRef);
   452 	if (status == errSecItemNotFound) {
   453 		// add new item
   454 		status = SecKeychainAddGenericPassword(NULL,
   455 											   (UInt32)strlen(keychainServiceName), keychainServiceName,
   456 											   (UInt32)strlen(keychainAccountName), keychainAccountName,
   457 											   (UInt32)length, password, NULL);
   458 		if (status)
   459 			NSLog(@"Failed to add password to keychain.");
   460 	} else {
   461 		// change existing password
   462 		SecKeychainAttribute attrs[] = {
   463 			{ kSecAccountItemAttr, (UInt32)strlen(keychainAccountName), (char *)keychainAccountName },
   464 			{ kSecServiceItemAttr, (UInt32)strlen(keychainServiceName), (char *)keychainServiceName }
   465 		};
   466 		const SecKeychainAttributeList attributes = { (UInt32)sizeof(attrs) / (UInt32)sizeof(attrs[0]), attrs };
   467 		status = SecKeychainItemModifyAttributesAndData(itemRef,		// the item reference
   468 														&attributes,	// no change to attributes
   469 														(UInt32)length,			// length of password
   470 														password		// pointer to password data
   471 														);
   472 		if (itemRef)
   473 			CFRelease(itemRef);
   474 		if (status)
   475 			NSLog(@"Failed to change password in keychain.");
   476 	}
   477 }
   478 
   479 - (unsigned short) UDPPort {
   480 	return [self unsignedShortForKey:GrowlUDPPortKey];
   481 }
   482 - (void) setUDPPort:(unsigned short)value {
   483 	[self setUnsignedShort:value forKey:GrowlUDPPortKey];
   484 }
   485 
   486 - (BOOL) isForwardingEnabled {
   487 	return [self boolForKey:GrowlEnableForwardKey];
   488 }
   489 - (void) setForwardingEnabled:(BOOL)enabled {
   490 	[self setBool:enabled forKey:GrowlEnableForwardKey];
   491 }
   492 
   493 #pragma mark -
   494 /*
   495  * @brief Growl preferences changed
   496  *
   497  * Synchronize our NSUserDefaults to immediately get any changes from the disk
   498  */
   499 - (void) growlPreferencesChanged:(NSNotification *)notification {
   500 	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   501 
   502 	NSString *object = [notification object];
   503 //	NSLog(@"%s: %@\n", __func__, object);
   504 	SYNCHRONIZE_GROWL_PREFS();
   505 	if (!object || [object isEqualToString:GrowlDisplayPluginKey]) {
   506 		[self willChangeValueForKey:@"defaultDisplayPluginName"];
   507 		[self didChangeValueForKey:@"defaultDisplayPluginName"];
   508 	}
   509 	if (!object || [object isEqualToString:GrowlSquelchModeKey]) {
   510 		[self willChangeValueForKey:@"squelchMode"];
   511 		[self didChangeValueForKey:@"squelchMode"];
   512 	}
   513 	if (!object || [object isEqualToString:GrowlMenuExtraKey]) {
   514 		[self willChangeValueForKey:@"growlMenuEnabled"];
   515 		[self didChangeValueForKey:@"growlMenuEnabled"];
   516 	}
   517 	if (!object || [object isEqualToString:GrowlEnableForwardKey]) {
   518 		[self willChangeValueForKey:@"forwardingEnabled"];
   519 		[self didChangeValueForKey:@"forwardingEnabled"];
   520 	}
   521 	if (!object || [object isEqualToString:GrowlUpdateCheckKey]) {
   522 		[self willChangeValueForKey:@"backgroundUpdateCheckEnabled"];
   523 		[self didChangeValueForKey:@"backgroundUpdateCheckEnabled"];
   524 	}
   525 	if (!object || [object isEqualToString:GrowlStickyWhenAwayKey]) {
   526 		[self willChangeValueForKey:@"stickyWhenAway"];
   527 		[self didChangeValueForKey:@"stickyWhenAway"];
   528 	}
   529 	if (!object || [object isEqualToString:GrowlStickyIdleThresholdKey]) {
   530 		[self willChangeValueForKey:@"idleThreshold"];
   531 		[self didChangeValueForKey:@"idleThreshold"];
   532 	}
   533 	if (!object || [object isEqualToString:GrowlRemoteRegistrationKey]) {
   534 		[self willChangeValueForKey:@"remoteRegistrationAllowed"];
   535 		[self didChangeValueForKey:@"remoteRegistrationAllowed"];
   536 	}
   537 	
   538 	[pool release];
   539 }
   540 
   541 @end