Burninating LoginItemsAE. We use LSSharedFileList now.
Also, first use of fast enumeration in Growl code! Woo!
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 "LoginItemsAE.h"
20 #include <Security/SecKeychain.h>
21 #include <Security/SecKeychainItem.h>
23 #define keychainServiceName "Growl"
24 #define keychainAccountName "Growl"
26 CFTypeRef GrowlPreferencesController_objectForKey(CFTypeRef key) {
27 return [[GrowlPreferencesController sharedController] objectForKey:(id)key];
30 CFIndex GrowlPreferencesController_integerForKey(CFTypeRef key) {
31 Boolean keyExistsAndHasValidFormat;
32 return CFPreferencesGetAppIntegerValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER, &keyExistsAndHasValidFormat);
35 Boolean GrowlPreferencesController_boolForKey(CFTypeRef key) {
36 Boolean keyExistsAndHasValidFormat;
37 return CFPreferencesGetAppBooleanValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER, &keyExistsAndHasValidFormat);
40 unsigned short GrowlPreferencesController_unsignedShortForKey(CFTypeRef key)
42 CFIndex theIndex = GrowlPreferencesController_integerForKey(key);
44 if (theIndex > USHRT_MAX)
46 else if (theIndex < 0)
48 return (unsigned short)index;
51 @implementation GrowlPreferencesController
53 + (GrowlPreferencesController *) sharedController {
54 return [self sharedInstance];
57 - (id) initSingleton {
58 if ((self = [super initSingleton])) {
59 [[NSDistributedNotificationCenter defaultCenter] addObserver:self
60 selector:@selector(growlPreferencesChanged:)
61 name:GrowlPreferencesChanged
63 loginItems = LSSharedFileListCreate(kCFAllocatorDefault, kLSSharedFileListSessionLoginItems, /*options*/ NULL);
69 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
70 CFRelease(loginItems);
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];
82 NSMutableDictionary *domain = [inDefaults mutableCopy];
83 [domain addEntriesFromDictionary:existing];
84 [helperAppDefaults setPersistentDomain:domain forName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
87 [helperAppDefaults setPersistentDomain:inDefaults forName:GROWL_HELPERAPP_BUNDLE_IDENTIFIER];
89 [helperAppDefaults release];
90 SYNCHRONIZE_GROWL_PREFS();
93 - (id) objectForKey:(NSString *)key {
94 id value = (id)CFPreferencesCopyAppValue((CFStringRef)key, (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER);
96 CFMakeCollectable(value);
97 return [value autorelease];
100 - (void) setObject:(id)object forKey:(NSString *)key {
101 CFPreferencesSetAppValue((CFStringRef)key,
102 (CFPropertyListRef)object,
103 (CFStringRef)GROWL_HELPERAPP_BUNDLE_IDENTIFIER);
105 SYNCHRONIZE_GROWL_PREFS();
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);
112 CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
113 (CFStringRef)GrowlPreferencesChanged,
115 /*userInfo*/ userInfo,
116 /*deliverImmediately*/ false);
120 - (BOOL) boolForKey:(NSString *)key {
121 return GrowlPreferencesController_boolForKey((CFTypeRef)key);
124 - (void) setBool:(BOOL)value forKey:(NSString *)key {
125 NSNumber *object = [[NSNumber alloc] initWithBool:value];
126 [self setObject:object forKey:key];
130 - (CFIndex) integerForKey:(NSString *)key {
131 return GrowlPreferencesController_integerForKey((CFTypeRef)key);
134 - (void) setInteger:(CFIndex)value forKey:(NSString *)key {
136 NSNumber *object = [[NSNumber alloc] initWithInteger:value];
138 NSNumber *object = [[NSNumber alloc] initWithInt:value];
140 [self setObject:object forKey:key];
144 - (unsigned short)unsignedShortForKey:(NSString *)key
146 return GrowlPreferencesController_unsignedShortForKey((CFTypeRef)key);
150 - (void)setUnsignedShort:(unsigned short)theShort forKey:(NSString *)key
152 [self setObject:[NSNumber numberWithUnsignedShort:theShort] forKey:key];
155 - (void) synchronize {
156 SYNCHRONIZE_GROWL_PREFS();
160 #pragma mark Start-at-login control
162 - (BOOL) shouldStartGrowlAtLogin {
163 Boolean foundIt = false;
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);
171 NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
172 for (id itemObject in currentLoginItems) {
173 LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
175 UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
177 OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
179 foundIt = CFEqual(URL, urlToGHA);
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];
198 - (void) setStartAtLogin:(NSString *)path enabled:(BOOL)enabled {
200 CFURLRef URLToToggle = (CFURLRef)[NSURL fileURLWithPath:path];
201 LSSharedFileListItemRef existingItem = NULL;
204 NSArray *currentLoginItems = [NSMakeCollectable(LSSharedFileListCopySnapshot(loginItems, &seed)) autorelease];
205 for (id itemObject in currentLoginItems) {
206 LSSharedFileListItemRef item = (LSSharedFileListItemRef)itemObject;
208 UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
210 OSStatus err = LSSharedFileListItemResolve(item, resolutionFlags, &URL, /*outRef*/ NULL);
212 Boolean foundIt = CFEqual(URL, URLToToggle);
222 if (enabled && (existingItem == NULL)) {
223 NSString *displayName = [[NSFileManager defaultManager] displayNameAtPath:path];
226 Boolean gotRef = CFURLGetFSRef(URLToToggle, &ref);
228 status = GetIconRefFromFileInfo(&ref,
229 /*fileNameLength*/ 0, /*fileName*/ NULL,
230 kFSCatInfoNone, /*catalogInfo*/ NULL,
231 kIconServicesNormalUsageFlag,
238 LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, (CFStringRef)displayName, icon, URLToToggle, /*propertiesToSet*/ NULL, /*propertiesToClear*/ NULL);
239 } else if (!enabled && (existingItem != NULL))
240 LSSharedFileListItemRemove(loginItems, existingItem);
244 #pragma mark GrowlMenu running state
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];
257 - (void) disableGrowlMenu {
258 // Ask GrowlMenu to shutdown via the DNC
259 CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
260 CFSTR("GrowlMenuShutdown"),
263 /*deliverImmediately*/ false);
267 #pragma mark Growl running state
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];
273 //now launch or terminate as appropriate.
275 [self launchGrowl:nmw];
277 [self terminateGrowl];
280 - (BOOL) isRunning:(NSString *)theBundleIdentifier {
282 ProcessSerialNumber PSN = { kNoProcess, kNoProcess };
284 while (GetNextProcess(&PSN) == noErr) {
285 NSDictionary *infoDict = (NSDictionary *)ProcessInformationCopyDictionary(&PSN, kProcessDictionaryIncludeAllInformationMask);
287 NSString *bundleID = [infoDict objectForKey:(NSString *)kCFBundleIdentifierKey];
288 isRunning = bundleID && [bundleID isEqualToString:theBundleIdentifier];
289 CFMakeCollectable(infoDict);
299 - (BOOL) isGrowlRunning {
300 return [self isRunning:@"com.Growl.GrowlHelperApp"];
303 - (void) launchGrowl:(BOOL)noMatterWhat {
304 NSString *helperPath = [[GrowlPathUtilities helperAppBundle] bundlePath];
305 NSURL *helperURL = [NSURL fileURLWithPath:helperPath];
307 unsigned options = NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAsync;
309 options |= NSWorkspaceLaunchNewInstance;
310 [[NSWorkspace sharedWorkspace] openURLs:[NSArray arrayWithObject:helperURL]
311 withAppBundleIdentifier:nil
313 additionalEventParamDescriptor:nil
314 launchIdentifiers:NULL];
317 - (void) terminateGrowl {
318 // Ask the Growl Helper App to shutdown via the DNC
319 CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(),
320 (CFStringRef)GROWL_SHUTDOWN,
323 /*deliverImmediately*/ false);
327 //Simplified accessors
331 - (CFIndex)selectedPosition {
332 return [self integerForKey:GROWL_POSITION_PREFERENCE_KEY];
335 - (BOOL) isBackgroundUpdateCheckEnabled {
336 return [self boolForKey:GrowlUpdateCheckKey];
338 - (void) setIsBackgroundUpdateCheckEnabled:(BOOL)flag {
339 [self setBool:flag forKey:GrowlUpdateCheckKey];
342 - (NSString *) defaultDisplayPluginName {
343 return [self objectForKey:GrowlDisplayPluginKey];
345 - (void) setDefaultDisplayPluginName:(NSString *)name {
346 [self setObject:name forKey:GrowlDisplayPluginKey];
349 - (BOOL) squelchMode {
350 return [self boolForKey:GrowlSquelchModeKey];
352 - (void) setSquelchMode:(BOOL)flag {
353 [self setBool:flag forKey:GrowlSquelchModeKey];
356 - (BOOL) stickyWhenAway {
357 return [self boolForKey:GrowlStickyWhenAwayKey];
359 - (void) setStickyWhenAway:(BOOL)flag {
360 [self setBool:flag forKey:GrowlStickyWhenAwayKey];
363 - (NSNumber*) idleThreshold {
365 return [NSNumber numberWithInteger:[self integerForKey:GrowlStickyIdleThresholdKey]];
367 return [NSNumber numberWithInt:[self integerForKey:GrowlStickyIdleThresholdKey]];
371 - (void) setIdleThreshold:(NSNumber*)value {
372 [self setInteger:[value intValue] forKey:GrowlStickyIdleThresholdKey];
374 #pragma mark Status Item
376 - (BOOL) isGrowlMenuEnabled {
377 return [self boolForKey:GrowlMenuExtraKey];
380 - (void) setGrowlMenuEnabled:(BOOL)state {
381 if (state != [self isGrowlMenuEnabled]) {
382 [self setBool:state forKey:GrowlMenuExtraKey];
384 [self enableGrowlMenu];
386 [self disableGrowlMenu];
392 - (BOOL) loggingEnabled {
393 return [self boolForKey:GrowlLoggingEnabledKey];
396 - (void) setLoggingEnabled:(BOOL)flag {
397 [self setBool:flag forKey:GrowlLoggingEnabledKey];
400 - (BOOL) isGrowlServerEnabled {
401 return [self boolForKey:GrowlStartServerKey];
404 - (void) setGrowlServerEnabled:(BOOL)enabled {
405 [self setBool:enabled forKey:GrowlStartServerKey];
408 #pragma mark Remote Growling
410 - (BOOL) isRemoteRegistrationAllowed {
411 return [self boolForKey:GrowlRemoteRegistrationKey];
414 - (void) setRemoteRegistrationAllowed:(BOOL)flag {
415 [self setBool:flag forKey:GrowlRemoteRegistrationKey];
418 - (NSString *) remotePassword {
419 unsigned char *password;
420 UInt32 passwordLength;
422 status = SecKeychainFindGenericPassword(NULL,
423 (UInt32)strlen(keychainServiceName), keychainServiceName,
424 (UInt32)strlen(keychainAccountName), keychainAccountName,
425 &passwordLength, (void **)&password, NULL);
427 NSString *passwordString;
428 if (status == noErr) {
429 passwordString = (NSString *)CFStringCreateWithBytes(kCFAllocatorDefault, password, passwordLength, kCFStringEncodingUTF8, false);
431 CFMakeCollectable(passwordString);
432 [passwordString autorelease];
433 SecKeychainItemFreeContent(NULL, password);
436 if (status != errSecItemNotFound)
437 NSLog(@"Failed to retrieve password from keychain. Error: %d", status);
438 passwordString = @"";
441 return passwordString;
444 - (void) setRemotePassword:(NSString *)value {
445 const char *password = value ? [value UTF8String] : "";
446 size_t length = strlen(password);
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) {
455 status = SecKeychainAddGenericPassword(NULL,
456 (UInt32)strlen(keychainServiceName), keychainServiceName,
457 (UInt32)strlen(keychainAccountName), keychainAccountName,
458 (UInt32)length, password, NULL);
460 NSLog(@"Failed to add password to keychain.");
462 // change existing password
463 SecKeychainAttribute attrs[] = {
464 { kSecAccountItemAttr, (UInt32)strlen(keychainAccountName), (char *)keychainAccountName },
465 { kSecServiceItemAttr, (UInt32)strlen(keychainServiceName), (char *)keychainServiceName }
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
476 NSLog(@"Failed to change password in keychain.");
480 - (unsigned short) UDPPort {
481 return [self unsignedShortForKey:GrowlUDPPortKey];
483 - (void) setUDPPort:(unsigned short)value {
484 [self setUnsignedShort:value forKey:GrowlUDPPortKey];
487 - (BOOL) isForwardingEnabled {
488 return [self boolForKey:GrowlEnableForwardKey];
490 - (void) setForwardingEnabled:(BOOL)enabled {
491 [self setBool:enabled forKey:GrowlEnableForwardKey];
496 * @brief Growl preferences changed
498 * Synchronize our NSUserDefaults to immediately get any changes from the disk
500 - (void) growlPreferencesChanged:(NSNotification *)notification {
501 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
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"];
510 if (!object || [object isEqualToString:GrowlSquelchModeKey]) {
511 [self willChangeValueForKey:@"squelchMode"];
512 [self didChangeValueForKey:@"squelchMode"];
514 if (!object || [object isEqualToString:GrowlMenuExtraKey]) {
515 [self willChangeValueForKey:@"growlMenuEnabled"];
516 [self didChangeValueForKey:@"growlMenuEnabled"];
518 if (!object || [object isEqualToString:GrowlEnableForwardKey]) {
519 [self willChangeValueForKey:@"forwardingEnabled"];
520 [self didChangeValueForKey:@"forwardingEnabled"];
522 if (!object || [object isEqualToString:GrowlUpdateCheckKey]) {
523 [self willChangeValueForKey:@"backgroundUpdateCheckEnabled"];
524 [self didChangeValueForKey:@"backgroundUpdateCheckEnabled"];
526 if (!object || [object isEqualToString:GrowlStickyWhenAwayKey]) {
527 [self willChangeValueForKey:@"stickyWhenAway"];
528 [self didChangeValueForKey:@"stickyWhenAway"];
530 if (!object || [object isEqualToString:GrowlStickyIdleThresholdKey]) {
531 [self willChangeValueForKey:@"idleThreshold"];
532 [self didChangeValueForKey:@"idleThreshold"];
534 if (!object || [object isEqualToString:GrowlRemoteRegistrationKey]) {
535 [self willChangeValueForKey:@"remoteRegistrationAllowed"];
536 [self didChangeValueForKey:@"remoteRegistrationAllowed"];