Core/Source/GrowlPreferencesController.m
author Peter Hosey <hg@boredzo.org>
Sun Aug 02 13:03:53 2009 -0700 (2009-08-02)
changeset 4270 5c1d8f5e64c2
parent 4261 48b7c994f6c8
child 4273 b846a7b597a6
permissions -rw-r--r--
Burninating LoginItemsAE. We use LSSharedFileList now.

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