I forgot to remove this import of LoginItemsAE.h. How did this build before?!
2 // GrowlPreferencesController.m
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.
9 // This file is under the BSD License, refer to License.txt for details
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>
22 #define keychainServiceName "Growl"
23 #define keychainAccountName "Growl"
25 CFTypeRef GrowlPreferencesController_objectForKey(CFTypeRef key) {
26 return [[GrowlPreferencesController sharedController] objectForKey:(id)key];
29 CFIndex GrowlPreferencesController_integerForKey(CFTypeRef key) {
30 Boolean keyExistsAndHasValidFormat;
31 return CFPreferencesGetAppIntegerValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER, &keyExistsAndHasValidFormat);
34 Boolean GrowlPreferencesController_boolForKey(CFTypeRef key) {
35 Boolean keyExistsAndHasValidFormat;
36 return CFPreferencesGetAppBooleanValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER, &keyExistsAndHasValidFormat);
39 unsigned short GrowlPreferencesController_unsignedShortForKey(CFTypeRef key)
41 CFIndex theIndex = GrowlPreferencesController_integerForKey(key);
43 if (theIndex > USHRT_MAX)
45 else if (theIndex < 0)
47 return (unsigned short)index;
50 @implementation GrowlPreferencesController
52 + (GrowlPreferencesController *) sharedController {
53 return [self sharedInstance];
56 - (id) initSingleton {
57 if ((self = [super initSingleton])) {
58 [[NSDistributedNotificationCenter defaultCenter] addObserver:self
59 selector:@selector(growlPreferencesChanged:)
60 name:GrowlPreferencesChanged
62 loginItems = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, /*options*/ NULL);
68 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
69 CFRelease(loginItems);
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];
81 NSMutableDictionary *domain = [inDefaults mutableCopy];
82 [domain addEntriesFromDictionary:existing];
83 [helperAppDefaults setPersistentDomain:domain forName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
86 [helperAppDefaults setPersistentDomain:inDefaults forName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
88 [helperAppDefaults release];
89 SYNCHRONIZE_GROWL_PREFS();
92 - (id) objectForKey:(NSString *)key {
93 id value = (id)CFPreferencesCopyAppValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER);
95 CFMakeCollectable(value);
96 return [value autorelease];
99 - (void) setObject:(id)object forKey:(NSString *)key {
100 CFPreferencesSetAppValue((CFStringRef)key,
101 (CFPropertyListRef)object,
102 (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER);
104 SYNCHRONIZE_GROWL_PREFS();
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);
111 CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
112 (CFStringRef)GrowlPreferencesChanged,
114 /*userInfo*/ userInfo,
115 /*deliverImmediately*/ false);
119 - (BOOL) boolForKey:(NSString *)key {
120 return GrowlPreferencesController_boolForKey((CFTypeRef)key);
123 - (void) setBool:(BOOL)value forKey:(NSString *)key {
124 NSNumber *object = [[NSNumber alloc] initWithBool:value];
125 [self setObject:object forKey:key];
129 - (CFIndex) integerForKey:(NSString *)key {
130 return GrowlPreferencesController_integerForKey((CFTypeRef)key);
133 - (void) setInteger:(CFIndex)value forKey:(NSString *)key {
135 NSNumber *object = [[NSNumber alloc] initWithInteger:value];
137 NSNumber *object = [[NSNumber alloc] initWithInt:value];
139 [self setObject:object forKey:key];
143 - (unsigned short)unsignedShortForKey:(NSString *)key
145 return GrowlPreferencesController_unsignedShortForKey((CFTypeRef)key);
149 - (void)setUnsignedShort:(unsigned short)theShort forKey:(NSString *)key
151 [self setObject:[NSNumber numberWithUnsignedShort:theShort] forKey:key];
154 - (void) synchronize {
155 SYNCHRONIZE_GROWL_PREFS();
159 #pragma mark Start-at-login control
161 - (BOOL) shouldStartGrowlAtLogin {
162 Boolean foundIt = false;
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);
170 NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
171 for (id itemObject in currentLoginItems) {
172 LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
174 UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
176 OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
178 foundIt = CFEqual(URL, urlToGHA);
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];
197 - (void) setStartAtLogin:(NSString *)path enabled:(BOOL)enabled {
199 CFURLRef URLToToggle = (CFURLRef)[NSURL fileURLWithPath:path];
200 LSSharedFileListItemRef existingItem = NULL;
203 NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
204 for (id itemObject in currentLoginItems) {
205 LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
207 UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
209 OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
211 Boolean foundIt = CFEqual(URL, URLToToggle);
221 if (enabled && (existingItem == NULL)) {
222 NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:path];
225 Boolean gotRef = CFURLGetFSRef(URLToToggle, &ref);
227 status = GetIconRefFromFileInfo(&ref,
228 /*fileNameLength*/ 0, /*fileName*/ NULL,
229 kFSCatInfoNone, /*catalogInfo*/ NULL,
230 kIconServicesNormalUsageFlag,
237 LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, (CFStringRef)displayName, icon, URLToToggle, /*propertiesToSet*/ NULL, /*propertiesToClear*/ NULL);
238 } else if (!enabled && (existingItem != NULL))
239 LSSharedFileListItemRemove(loginItems, existingItem);
243 #pragma mark GrowlMenu running state
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];
256 - (void) disableGrowlMenu {
257 // Ask GrowlMenu to shutdown via the DNC
258 CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
259 CFSTR("GrowlMenuShutdown"),
262 /*deliverImmediately*/ false);
266 #pragma mark Growl running state
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];
272 //now launch or terminate as appropriate.
274 [self launchGrowl:nmw];
276 [self terminateGrowl];
279 - (BOOL) isRunning:(NSString *)theBundleIdentifier {
281 ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
283 while (GetNextProcess(&PSN) == noErr) {
284 NSDictionary *infoDict = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
286 NSString *bundleID = [infoDict objectForKey:(NSString *)kCFBundleIdentifierKey];
287 isRunning = bundleID && [bundleID isEqualToString:theBundleIdentifier];
288 CFMakeCollectable(infoDict);
298 - (BOOL) isGrowlRunning {
299 return [self isRunning:@"com.Growl.GrowlHelperApp"];
302 - (void) launchGrowl:(BOOL)noMatterWhat {
303 NSString *helperPath = [[GrowlPathUtilities helperAppBundle] bundlePath];
304 NSURL *helperURL = [NSURL fileURLWithPath:helperPath];
306 unsigned options = NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAsync;
308 options |= NSWorkspaceLaunchNewInstance;
309 [[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:helperURL]
310 withAppBundleIdentifier:nil
312 additionalEventParamDescriptor:nil
313 launchIdentifiers:NULL];
316 - (void) terminateGrowl {
317 // Ask the Growl Helper App to shutdown via the DNC
318 CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
319 (CFStringRef)GROWL_SHUTDOWN,
322 /*deliverImmediately*/ false);
326 //Simplified accessors
330 - (CFIndex)selectedPosition {
331 return [self integerForKey:GROWL_POSITION_PREFERENCE_KEY];
334 - (BOOL) isBackgroundUpdateCheckEnabled {
335 return [self boolForKey:GrowlUpdateCheckKey];
337 - (void) setIsBackgroundUpdateCheckEnabled:(BOOL)flag {
338 [self setBool:flag forKey:GrowlUpdateCheckKey];
341 - (NSString *) defaultDisplayPluginName {
342 return [self objectForKey:GrowlDisplayPluginKey];
344 - (void) setDefaultDisplayPluginName:(NSString *)name {
345 [self setObject:name forKey:GrowlDisplayPluginKey];
348 - (BOOL) squelchMode {
349 return [self boolForKey:GrowlSquelchModeKey];
351 - (void) setSquelchMode:(BOOL)flag {
352 [self setBool:flag forKey:GrowlSquelchModeKey];
355 - (BOOL) stickyWhenAway {
356 return [self boolForKey:GrowlStickyWhenAwayKey];
358 - (void) setStickyWhenAway:(BOOL)flag {
359 [self setBool:flag forKey:GrowlStickyWhenAwayKey];
362 - (NSNumber*) idleThreshold {
364 return [NSNumber numberWithInteger:[self integerForKey:GrowlStickyIdleThresholdKey]];
366 return [NSNumber numberWithInt:[self integerForKey:GrowlStickyIdleThresholdKey]];
370 - (void) setIdleThreshold:(NSNumber*)value {
371 [self setInteger:[value intValue] forKey:GrowlStickyIdleThresholdKey];
373 #pragma mark Status Item
375 - (BOOL) isGrowlMenuEnabled {
376 return [self boolForKey:GrowlMenuExtraKey];
379 - (void) setGrowlMenuEnabled:(BOOL)state {
380 if (state != [self isGrowlMenuEnabled]) {
381 [self setBool:state forKey:GrowlMenuExtraKey];
383 [self enableGrowlMenu];
385 [self disableGrowlMenu];
391 - (BOOL) loggingEnabled {
392 return [self boolForKey:GrowlLoggingEnabledKey];
395 - (void) setLoggingEnabled:(BOOL)flag {
396 [self setBool:flag forKey:GrowlLoggingEnabledKey];
399 - (BOOL) isGrowlServerEnabled {
400 return [self boolForKey:GrowlStartServerKey];
403 - (void) setGrowlServerEnabled:(BOOL)enabled {
404 [self setBool:enabled forKey:GrowlStartServerKey];
407 #pragma mark Remote Growling
409 - (BOOL) isRemoteRegistrationAllowed {
410 return [self boolForKey:GrowlRemoteRegistrationKey];
413 - (void) setRemoteRegistrationAllowed:(BOOL)flag {
414 [self setBool:flag forKey:GrowlRemoteRegistrationKey];
417 - (NSString *) remotePassword {
418 unsigned char *password;
419 UInt32 passwordLength;
421 status = SecKeychainFindGenericPassword(NULL,
422 (UInt32)strlen(keychainServiceName), keychainServiceName,
423 (UInt32)strlen(keychainAccountName), keychainAccountName,
424 &passwordLength, (void **)&password, NULL);
426 NSString *passwordString;
427 if (status == noErr) {
428 passwordString = (NSString *)CFStringCreateWithBytes(kCFAllocatorDefault, password, passwordLength, kCFStringEncodingUTF8, false);
430 CFMakeCollectable(passwordString);
431 [passwordString autorelease];
432 SecKeychainItemFreeContent(NULL, password);
435 if (status != errSecItemNotFound)
436 NSLog(@"Failed to retrieve password from keychain. Error: %d", status);
437 passwordString = @"";
440 return passwordString;
443 - (void) setRemotePassword:(NSString *)value {
444 const char *password = value ? [value UTF8String] : "";
445 size_t length = strlen(password);
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) {
454 status = SecKeychainAddGenericPassword(NULL,
455 (UInt32)strlen(keychainServiceName), keychainServiceName,
456 (UInt32)strlen(keychainAccountName), keychainAccountName,
457 (UInt32)length, password, NULL);
459 NSLog(@"Failed to add password to keychain.");
461 // change existing password
462 SecKeychainAttribute attrs[] = {
463 { kSecAccountItemAttr, (UInt32)strlen(keychainAccountName), (char *)keychainAccountName },
464 { kSecServiceItemAttr, (UInt32)strlen(keychainServiceName), (char *)keychainServiceName }
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
475 NSLog(@"Failed to change password in keychain.");
479 - (unsigned short) UDPPort {
480 return [self unsignedShortForKey:GrowlUDPPortKey];
482 - (void) setUDPPort:(unsigned short)value {
483 [self setUnsignedShort:value forKey:GrowlUDPPortKey];
486 - (BOOL) isForwardingEnabled {
487 return [self boolForKey:GrowlEnableForwardKey];
489 - (void) setForwardingEnabled:(BOOL)enabled {
490 [self setBool:enabled forKey:GrowlEnableForwardKey];
495 * @brief Growl preferences changed
497 * Synchronize our NSUserDefaults to immediately get any changes from the disk
499 - (void) growlPreferencesChanged:(NSNotification *)notification {
500 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
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"];
509 if (!object || [object isEqualToString:GrowlSquelchModeKey]) {
510 [self willChangeValueForKey:@"squelchMode"];
511 [self didChangeValueForKey:@"squelchMode"];
513 if (!object || [object isEqualToString:GrowlMenuExtraKey]) {
514 [self willChangeValueForKey:@"growlMenuEnabled"];
515 [self didChangeValueForKey:@"growlMenuEnabled"];
517 if (!object || [object isEqualToString:GrowlEnableForwardKey]) {
518 [self willChangeValueForKey:@"forwardingEnabled"];
519 [self didChangeValueForKey:@"forwardingEnabled"];
521 if (!object || [object isEqualToString:GrowlUpdateCheckKey]) {
522 [self willChangeValueForKey:@"backgroundUpdateCheckEnabled"];
523 [self didChangeValueForKey:@"backgroundUpdateCheckEnabled"];
525 if (!object || [object isEqualToString:GrowlStickyWhenAwayKey]) {
526 [self willChangeValueForKey:@"stickyWhenAway"];
527 [self didChangeValueForKey:@"stickyWhenAway"];
529 if (!object || [object isEqualToString:GrowlStickyIdleThresholdKey]) {
530 [self willChangeValueForKey:@"idleThreshold"];
531 [self didChangeValueForKey:@"idleThreshold"];
533 if (!object || [object isEqualToString:GrowlRemoteRegistrationKey]) {
534 [self willChangeValueForKey:@"remoteRegistrationAllowed"];
535 [self didChangeValueForKey:@"remoteRegistrationAllowed"];