| // |
| // GTMLoginItems.m |
| // Based on AELoginItems from DTS. |
| // |
| // Copyright 2007-2008 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy |
| // of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| // |
| |
| #import "GTMLoginItems.h" |
| #import "GTMDefines.h" |
| |
| #include <Carbon/Carbon.h> |
| |
| // Exposed constants |
| NSString * const kGTMLoginItemsNameKey = @"Name"; |
| NSString * const kGTMLoginItemsPathKey = @"Path"; |
| NSString * const kGTMLoginItemsHiddenKey = @"Hide"; |
| |
| // kLSSharedFileListLoginItemHidden is supported on |
| // 10.5, but missing from the 10.5 headers. |
| // http://openradar.appspot.com/6482251 |
| #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 |
| static NSString * const kLSSharedFileListLoginItemHidden = |
| @"com.apple.loginitem.HideOnLaunch"; |
| #endif // MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 |
| |
| @interface GTMLoginItems (PrivateMethods) |
| + (NSInteger)indexOfLoginItemWithValue:(id)value |
| forKey:(NSString *)key |
| loginItems:(NSArray *)items; |
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| + (LSSharedFileListRef)loginItemsFileListRef; |
| + (NSArray *)loginItemsArrayForFileListRef:(LSSharedFileListRef)fileListRef; |
| #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| + (BOOL)compileAndRunScript:(NSString *)script |
| withError:(NSError **)errorInfo; |
| #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| @end |
| |
| @implementation GTMLoginItems (PrivateMethods) |
| |
| + (NSInteger)indexOfLoginItemWithValue:(id)value |
| forKey:(NSString *)key |
| loginItems:(NSArray *)items { |
| if (!value || !key || !items) return NSNotFound; |
| NSDictionary *item = nil; |
| NSInteger found = -1; |
| for (item in items) { |
| ++found; |
| id itemValue = [item objectForKey:key]; |
| if (itemValue && [itemValue isEqual:value]) { |
| return found; |
| } |
| } |
| return NSNotFound; |
| } |
| |
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| |
| + (LSSharedFileListRef)loginItemsFileListRef { |
| LSSharedFileListRef loginItemsRef = |
| LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); |
| return (LSSharedFileListRef)GTMCFAutorelease(loginItemsRef); |
| } |
| |
| + (NSArray *)loginItemsArrayForFileListRef:(LSSharedFileListRef)fileListRef { |
| UInt32 seedValue; |
| CFArrayRef filelistArrayRef = LSSharedFileListCopySnapshot(fileListRef, |
| &seedValue); |
| return GTMCFAutorelease(filelistArrayRef); |
| } |
| |
| #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| |
| + (BOOL)compileAndRunScript:(NSString *)script |
| withError:(NSError **)errorInfo { |
| if ([script length] == 0) { |
| // COV_NF_START - no real way to test this |
| if (errorInfo) |
| *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-90 userInfo:nil]; |
| return NO; |
| // COV_NF_END |
| } |
| NSAppleScript *query = [[[NSAppleScript alloc] initWithSource:script] autorelease]; |
| NSDictionary *errDict = nil; |
| if ( ![query compileAndReturnError:&errDict]) { |
| // COV_NF_START - no real way to test this |
| if (errorInfo) |
| *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-91 userInfo:errDict]; |
| return NO; |
| // COV_NF_END |
| } |
| NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict]; |
| if (!scriptResult) { |
| // COV_NF_START - no real way to test this |
| if (errorInfo) |
| *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-92 userInfo:errDict]; |
| return NO; |
| // COV_NF_END |
| } |
| // we don't process the result |
| return YES; |
| } |
| |
| #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| |
| @end |
| |
| @implementation GTMLoginItems |
| |
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| |
| + (NSArray*)loginItems:(NSError **)errorInfo { |
| // get the login items from LaunchServices |
| LSSharedFileListRef loginItemsRef = [self loginItemsFileListRef]; |
| if (!loginItemsRef) { |
| // COV_NF_START - no real way to test this |
| if (errorInfo) { |
| *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" |
| code:-1 |
| userInfo:nil]; |
| } |
| return nil; |
| // COV_NF_END |
| } |
| NSArray *fileList = [self loginItemsArrayForFileListRef:loginItemsRef]; |
| |
| // build our results |
| NSMutableArray *result = [NSMutableArray array]; |
| for (id fileItem in fileList) { |
| LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)fileItem; |
| // name |
| NSMutableDictionary *item = [NSMutableDictionary dictionary]; |
| CFStringRef nameRef = LSSharedFileListItemCopyDisplayName(itemRef); |
| if (nameRef) { |
| [item setObject:[(NSString *)nameRef stringByDeletingPathExtension] |
| forKey:kGTMLoginItemsNameKey]; |
| CFRelease(nameRef); |
| } |
| // path |
| CFURLRef urlRef = NULL; |
| if (LSSharedFileListItemResolve(itemRef, 0, &urlRef, NULL) == noErr) { |
| if (urlRef) { |
| NSString *path = [(NSURL *)urlRef path]; |
| if (path) { |
| [item setObject:path forKey:kGTMLoginItemsPathKey]; |
| } |
| CFRelease(urlRef); |
| } |
| } |
| // hidden |
| CFBooleanRef hiddenRef = LSSharedFileListItemCopyProperty(itemRef, |
| (CFStringRef)kLSSharedFileListLoginItemHidden); |
| if (hiddenRef) { |
| if (hiddenRef == kCFBooleanTrue) { |
| [item setObject:[NSNumber numberWithBool:YES] |
| forKey:kGTMLoginItemsHiddenKey]; |
| } |
| CFRelease(hiddenRef); |
| } |
| [result addObject:item]; |
| } |
| |
| return result; |
| } |
| |
| #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| |
| + (NSArray*)loginItems:(NSError **)errorInfo { |
| NSDictionary *errDict = nil; |
| // get the script compiled and saved off |
| static NSAppleScript *query = nil; |
| if (!query) { |
| NSString *querySource = @"tell application \"System Events\" to get properties of login items"; |
| query = [[NSAppleScript alloc] initWithSource:querySource]; |
| if ( ![query compileAndReturnError:&errDict]) { |
| // COV_NF_START - no real way to test this |
| if (errorInfo) |
| *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-1 userInfo:errDict]; |
| [query release]; |
| query = nil; |
| return nil; |
| // COV_NF_END |
| } |
| } |
| // run the script |
| NSAppleEventDescriptor *scriptResult = [query executeAndReturnError:&errDict]; |
| if (!scriptResult) { |
| // COV_NF_START - no real way to test this |
| if (errorInfo) |
| *errorInfo = [NSError errorWithDomain:@"GTMLoginItems" code:-2 userInfo:errDict]; |
| return nil; |
| // COV_NF_END |
| } |
| // build our results |
| NSMutableArray *result = [NSMutableArray array]; |
| NSInteger count = [scriptResult numberOfItems]; |
| for (NSInteger i = 0; i < count; ++i) { |
| NSAppleEventDescriptor *aeItem = [scriptResult descriptorAtIndex:i+1]; |
| NSAppleEventDescriptor *hidn = [aeItem descriptorForKeyword:kAEHidden]; |
| NSAppleEventDescriptor *nam = [aeItem descriptorForKeyword:pName]; |
| NSAppleEventDescriptor *ppth = [aeItem descriptorForKeyword:'ppth']; |
| NSMutableDictionary *item = [NSMutableDictionary dictionary]; |
| if (hidn && [hidn booleanValue]) { |
| [item setObject:[NSNumber numberWithBool:YES] forKey:kGTMLoginItemsHiddenKey]; |
| } |
| if (nam) { |
| NSString *name = [nam stringValue]; |
| if (name) { |
| [item setObject:name forKey:kGTMLoginItemsNameKey]; |
| } |
| } |
| if (ppth) { |
| NSString *path = [ppth stringValue]; |
| if (path) { |
| [item setObject:path forKey:kGTMLoginItemsPathKey]; |
| } |
| } |
| [result addObject:item]; |
| } |
| |
| return result; |
| } |
| |
| #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| |
| + (BOOL)pathInLoginItems:(NSString *)path { |
| NSArray *loginItems = [self loginItems:nil]; |
| NSInteger itemIndex = [self indexOfLoginItemWithValue:path |
| forKey:kGTMLoginItemsPathKey |
| loginItems:loginItems]; |
| return (itemIndex != NSNotFound) ? YES : NO; |
| } |
| |
| + (BOOL)itemWithNameInLoginItems:(NSString *)name { |
| NSArray *loginItems = [self loginItems:nil]; |
| NSInteger itemIndex = [self indexOfLoginItemWithValue:name |
| forKey:kGTMLoginItemsNameKey |
| loginItems:loginItems]; |
| return (itemIndex != NSNotFound) ? YES : NO; |
| } |
| |
| + (void)addPathToLoginItems:(NSString*)path hide:(BOOL)hide { |
| if (!path) return; |
| // make sure it isn't already there |
| if ([self pathInLoginItems:path]) return; |
| // now append it |
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| NSURL *url = [NSURL fileURLWithPath:path]; |
| if (url) { |
| LSSharedFileListRef loginItemsRef = [self loginItemsFileListRef]; |
| if (loginItemsRef) { |
| NSDictionary *setProperties = |
| [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:hide] |
| forKey:(id)kLSSharedFileListLoginItemHidden]; |
| LSSharedFileListItemRef itemRef = |
| LSSharedFileListInsertItemURL(loginItemsRef, |
| kLSSharedFileListItemLast, NULL, NULL, |
| (CFURLRef)url, |
| (CFDictionaryRef)setProperties, NULL); |
| if (itemRef) CFRelease(itemRef); |
| } |
| } |
| #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| NSString *scriptSource = |
| [NSString stringWithFormat: |
| @"tell application \"System Events\" to make new login item with properties { path:\"%s\", hidden:%s } at end", |
| [path UTF8String], |
| (hide ? "yes" : "no")]; |
| [self compileAndRunScript:scriptSource withError:nil]; |
| #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| } |
| |
| + (void)removePathFromLoginItems:(NSString*)path { |
| if ([path length] == 0) return; |
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| NSURL *url = [NSURL fileURLWithPath:path]; |
| LSSharedFileListRef loginItemsRef = [self loginItemsFileListRef]; |
| if (loginItemsRef) { |
| NSArray *fileList = [self loginItemsArrayForFileListRef:loginItemsRef]; |
| for (id item in fileList) { |
| LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)item; |
| CFURLRef urlRef = NULL; |
| if (LSSharedFileListItemResolve(itemRef, 0, &urlRef, NULL) == noErr) { |
| if (urlRef) { |
| if (CFEqual(urlRef, (CFURLRef)url)) { |
| LSSharedFileListItemRemove(loginItemsRef, itemRef); |
| } |
| CFRelease(urlRef); |
| } |
| } |
| } |
| } |
| #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| NSString *scriptSource = |
| [NSString stringWithFormat: |
| @"tell application \"System Events\" to delete (login items whose path is \"%s\")", |
| [path UTF8String]]; |
| [self compileAndRunScript:scriptSource withError:nil]; |
| #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| } |
| |
| + (void)removeItemWithNameFromLoginItems:(NSString *)name { |
| if ([name length] == 0) return; |
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| LSSharedFileListRef loginItemsRef = [self loginItemsFileListRef]; |
| if (loginItemsRef) { |
| NSArray *fileList = [self loginItemsArrayForFileListRef:loginItemsRef]; |
| for (id item in fileList) { |
| LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)item; |
| CFStringRef itemNameRef = LSSharedFileListItemCopyDisplayName(itemRef); |
| if (itemNameRef) { |
| NSString *itemName = |
| [(NSString *)itemNameRef stringByDeletingPathExtension]; |
| if ([itemName isEqual:name]) { |
| LSSharedFileListItemRemove(loginItemsRef, itemRef); |
| } |
| CFRelease(itemNameRef); |
| } |
| } |
| } |
| #else // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| NSString *scriptSource = |
| [NSString stringWithFormat: |
| @"tell application \"System Events\" to delete (login items whose name is \"%s\")", |
| [name UTF8String]]; |
| [self compileAndRunScript:scriptSource withError:nil]; |
| #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |
| } |
| |
| @end |