blob: cd45418a6c45fd048072ed0de8f64e6e32ea9d01 [file] [log] [blame]
//
// 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