diff --git a/LIFX Menu.xcodeproj/project.pbxproj b/LIFX Menu.xcodeproj/project.pbxproj index 7440b4c..b664cc3 100644 --- a/LIFX Menu.xcodeproj/project.pbxproj +++ b/LIFX Menu.xcodeproj/project.pbxproj @@ -13,11 +13,12 @@ C721971119E5F79700FCA5CB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C721970F19E5F79700FCA5CB /* MainMenu.xib */; }; C721971D19E5F79700FCA5CB /* LIFX_MenuTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C721971C19E5F79700FCA5CB /* LIFX_MenuTests.m */; }; C721973719E5FA1200FCA5CB /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C721973519E5FA1200FCA5CB /* libz.dylib */; }; - C721973819E5FA1200FCA5CB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C721973619E5FA1200FCA5CB /* SystemConfiguration.framework */; }; C721974219E607BD00FCA5CB /* lifx-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = C721974019E607BD00FCA5CB /* lifx-icon.png */; }; C721974319E607BD00FCA5CB /* lifx-icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C721974119E607BD00FCA5CB /* lifx-icon@2x.png */; }; C721974819E622D400FCA5CB /* LIFXKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C721974619E622B200FCA5CB /* LIFXKit.framework */; }; C721974A19E622FD00FCA5CB /* LIFXKit.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = C721974619E622B200FCA5CB /* LIFXKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C721975319E631D400FCA5CB /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = C721975219E631D400FCA5CB /* LaunchAtLoginController.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + C721975419E631FD00FCA5CB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C721973619E5FA1200FCA5CB /* SystemConfiguration.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -59,6 +60,9 @@ C721974019E607BD00FCA5CB /* lifx-icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "lifx-icon.png"; sourceTree = ""; }; C721974119E607BD00FCA5CB /* lifx-icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "lifx-icon@2x.png"; sourceTree = ""; }; C721974619E622B200FCA5CB /* LIFXKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = LIFXKit.framework; sourceTree = ""; }; + C721974F19E62DC800FCA5CB /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; + C721975119E631D400FCA5CB /* LaunchAtLoginController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = ""; }; + C721975219E631D400FCA5CB /* LaunchAtLoginController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -66,8 +70,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + C721975419E631FD00FCA5CB /* SystemConfiguration.framework in Frameworks */, C721973719E5FA1200FCA5CB /* libz.dylib in Frameworks */, - C721973819E5FA1200FCA5CB /* SystemConfiguration.framework in Frameworks */, C721974819E622D400FCA5CB /* LIFXKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -105,6 +109,7 @@ children = ( C721970A19E5F79600FCA5CB /* AppDelegate.h */, C721970B19E5F79600FCA5CB /* AppDelegate.m */, + C721974E19E6292A00FCA5CB /* Launch at login */, C721970D19E5F79600FCA5CB /* Images.xcassets */, C721970F19E5F79700FCA5CB /* MainMenu.xib */, C721970619E5F79600FCA5CB /* Supporting Files */, @@ -145,12 +150,22 @@ isa = PBXGroup; children = ( C721974619E622B200FCA5CB /* LIFXKit.framework */, + C721974F19E62DC800FCA5CB /* ServiceManagement.framework */, C721973619E5FA1200FCA5CB /* SystemConfiguration.framework */, C721973519E5FA1200FCA5CB /* libz.dylib */, ); name = Frameworks; sourceTree = ""; }; + C721974E19E6292A00FCA5CB /* Launch at login */ = { + isa = PBXGroup; + children = ( + C721975119E631D400FCA5CB /* LaunchAtLoginController.h */, + C721975219E631D400FCA5CB /* LaunchAtLoginController.m */, + ); + name = "Launch at login"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -254,6 +269,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C721975319E631D400FCA5CB /* LaunchAtLoginController.m in Sources */, C721970C19E5F79600FCA5CB /* AppDelegate.m in Sources */, C721970919E5F79600FCA5CB /* main.m in Sources */, ); diff --git a/LIFX Menu/AppDelegate.m b/LIFX Menu/AppDelegate.m index d85de11..ab34c72 100644 --- a/LIFX Menu/AppDelegate.m +++ b/LIFX Menu/AppDelegate.m @@ -6,8 +6,9 @@ // Copyright (c) 2014 Kyle Howells. All rights reserved. // -#import "AppDelegate.h" #import +#import "AppDelegate.h" +#import "LaunchAtLoginController.h" @interface AppDelegate () @@ -23,10 +24,22 @@ @interface AppDelegate () -@implementation AppDelegate +@implementation AppDelegate{ + LaunchAtLoginController *loginController; + NSMenuItem *autorunItem; +} + +#pragma mark - Application Delegate methods -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { + // User defaults + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"AutoLaunch" : @YES }]; + + + // Variable setup self.lightItems = [NSMutableArray array]; + loginController = [[LaunchAtLoginController alloc] init]; + // Status bar item self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; @@ -42,11 +55,18 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { self.menu = [[NSMenu alloc] init]; // Always there buttons - [self.menu addItemWithTitle:@"Turn lights ON" action:@selector(allLightsOn) keyEquivalent:@""]; - [self.menu addItemWithTitle:@"Turn lights OFF" action:@selector(allLightsOff) keyEquivalent:@""]; + [self.menu addItemWithTitle:@"Turn all lights on" action:@selector(allLightsOn) keyEquivalent:@""]; + [self.menu addItemWithTitle:@"Turn all lights off" action:@selector(allLightsOff) keyEquivalent:@""]; // Separator to the section with the individual lights [self.menu addItem:[NSMenuItem separatorItem]]; + + + [self.menu addItem:[NSMenuItem separatorItem]]; + autorunItem = [[NSMenuItem alloc] initWithTitle:@"Launch at login" action:@selector(autoLaunchPressed) keyEquivalent:@""]; + [self.menu addItem:autorunItem]; + [self updateAutoLaunch]; + self.statusItem.menu = self.menu; @@ -54,6 +74,12 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { [[[LFXClient sharedClient] localNetworkContext].allLightsCollection addLightCollectionObserver:self]; } +-(void)applicationWillTerminate:(NSNotification *)aNotification { + // Insert code here to tear down your application +} + + + @@ -96,7 +122,7 @@ -(void)addLight:(LFXLight*)light{ [item setRepresentedObject:light]; [self updateLightMenuItem:item]; - [self.menu addItem:item]; + [self.menu insertItem:item atIndex:(self.menu.numberOfItems - 2)]; [self.lightItems addObject:item]; [light addLightObserver:self]; @@ -143,6 +169,34 @@ -(void)updateLightMenuItem:(NSMenuItem*)item{ + + + +#pragma mark - LFXLightCollectionObserver + +-(void)lightCollection:(LFXLightCollection *)lightCollection didAddLight:(LFXLight *)light{ + [self addLight:light]; +} +-(void)lightCollection:(LFXLightCollection *)lightCollection didRemoveLight:(LFXLight *)light{ + [self removeLight:light]; +} + + +#pragma mark - LFXLightObserver + +-(void)light:(LFXLight *)light didChangeLabel:(NSString *)label{ + [self updateLight:light]; +} +-(void)light:(LFXLight *)light didChangePowerState:(LFXPowerState)powerState{ + [self updateLight:light]; +} + + + + + + + #pragma mark - Helper methods -(NSMenuItem*)menuItemForLight:(LFXLight*)light{ @@ -166,34 +220,42 @@ -(NSString*)titleForLight:(LFXLight*)light{ +#pragma mark - Auto launch methods - - -#pragma mark - LFXLightCollectionObserver - --(void)lightCollection:(LFXLightCollection *)lightCollection didAddLight:(LFXLight *)light{ - [self addLight:light]; +-(BOOL)autoLaunch{ + id object = [[NSUserDefaults standardUserDefaults] objectForKey:@"AutoLaunch"]; + return (object ? [object boolValue] : YES); } --(void)lightCollection:(LFXLightCollection *)lightCollection didRemoveLight:(LFXLight *)light{ - [self removeLight:light]; +-(void)setAutoLaunch:(BOOL)autoLaunch{ + [[NSUserDefaults standardUserDefaults] setBool:autoLaunch forKey:@"AutoLaunch"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [self updateAutoLaunch]; } - -#pragma mark - LFXLightObserver - --(void)light:(LFXLight *)light didChangeLabel:(NSString *)label{ - [self updateLight:light]; -} --(void)light:(LFXLight *)light didChangePowerState:(LFXPowerState)powerState{ - [self updateLight:light]; +-(void)updateAutoLaunch{ + if ([self autoLaunch]) { + if (![loginController launchAtLogin]) { + [loginController setLaunchAtLogin:YES]; + [autorunItem setState:NSOnState]; + } + } + else { + if ([loginController launchAtLogin]) { + [loginController setLaunchAtLogin:NO]; + [autorunItem setState:NSOffState]; + } + } } - - - - --(void)applicationWillTerminate:(NSNotification *)aNotification { - // Insert code here to tear down your application +-(void)autoLaunchPressed{ + if (autorunItem.state == NSOnState) { + [self setAutoLaunch:NO]; + } + else { + [self setAutoLaunch:YES]; + } } + @end diff --git a/LIFX Menu/LaunchAtLoginController.h b/LIFX Menu/LaunchAtLoginController.h new file mode 100755 index 0000000..0f71dc2 --- /dev/null +++ b/LIFX Menu/LaunchAtLoginController.h @@ -0,0 +1,34 @@ +// +// LaunchAtLoginController.h +// +// Copyright 2011 Tomáš Znamenáček +// Copyright 2010 Ben Clark-Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the ‘Software’), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +@import Foundation; + +@interface LaunchAtLoginController : NSObject {} + +@property(assign) BOOL launchAtLogin; + +- (BOOL) willLaunchAtLogin: (NSURL*) itemURL; +- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL; + +@end diff --git a/LIFX Menu/LaunchAtLoginController.m b/LIFX Menu/LaunchAtLoginController.m new file mode 100755 index 0000000..69f7809 --- /dev/null +++ b/LIFX Menu/LaunchAtLoginController.m @@ -0,0 +1,127 @@ +// +// LaunchAtLoginController.m +// +// Copyright 2011 Tomáš Znamenáček +// Copyright 2010 Ben Clark-Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the ‘Software’), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import "LaunchAtLoginController.h" + +static NSString *const StartAtLoginKey = @"launchAtLogin"; + +@interface LaunchAtLoginController () +@property(assign) LSSharedFileListRef loginItems; +@end + +@implementation LaunchAtLoginController +@synthesize loginItems; + +#pragma mark Change Observing + +void sharedFileListDidChange(LSSharedFileListRef inList, void *context) +{ + LaunchAtLoginController *self = (id) context; + [self willChangeValueForKey:StartAtLoginKey]; + [self didChangeValueForKey:StartAtLoginKey]; +} + +#pragma mark Initialization + +- (id) init +{ + self = [super init]; + if (self) + { + loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); + LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(), + (CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, self); + } + + return self; +} + +- (void) dealloc +{ + LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(), + (CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, self); + CFRelease(loginItems); + + [super dealloc]; +} + +#pragma mark Launch List Control + +- (LSSharedFileListItemRef) findItemWithURL: (NSURL*) wantedURL inFileList: (LSSharedFileListRef) fileList +{ + if (wantedURL == NULL || fileList == NULL) + return NULL; + + NSArray *listSnapshot = [NSMakeCollectable(LSSharedFileListCopySnapshot(fileList, NULL)) autorelease]; + for (id itemObject in listSnapshot) { + LSSharedFileListItemRef item = (LSSharedFileListItemRef) itemObject; + UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; + CFURLRef currentItemURL = NULL; + LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL); + if (currentItemURL && CFEqual(currentItemURL, wantedURL)) { + CFRelease(currentItemURL); + return item; + } + if (currentItemURL) + CFRelease(currentItemURL); + } + + return NULL; +} + +- (BOOL) willLaunchAtLogin: (NSURL*) itemURL +{ + return !![self findItemWithURL:itemURL inFileList:loginItems]; +} + +- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL +{ + LSSharedFileListItemRef appItem = [self findItemWithURL:itemURL inFileList:loginItems]; + if (enabled && !appItem) { + LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, + NULL, NULL, (CFURLRef)itemURL, NULL, NULL); + } else if (!enabled && appItem) + LSSharedFileListItemRemove(loginItems, appItem); +} + +#pragma mark Basic Interface + +- (NSURL*) appURL +{ + return [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; +} + +- (void) setLaunchAtLogin: (BOOL) enabled +{ + [self willChangeValueForKey:StartAtLoginKey]; + [self setLaunchAtLogin:enabled forURL:[self appURL]]; + [self didChangeValueForKey:StartAtLoginKey]; +} + +- (BOOL) launchAtLogin +{ + return [self willLaunchAtLogin:[self appURL]]; +} + +@end