diff --git a/RocketFuel.xcodeproj/project.pbxproj b/RocketFuel.xcodeproj/project.pbxproj index ac0d718..66c0e63 100644 --- a/RocketFuel.xcodeproj/project.pbxproj +++ b/RocketFuel.xcodeproj/project.pbxproj @@ -7,8 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 6A72FC1C1BDA9B6400690D05 /* RFAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A72FC1A1BDA9B6400690D05 /* RFAboutWindow.m */; settings = {ASSET_TAGS = (); }; }; - 6A72FC1D1BDA9B6400690D05 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A72FC1B1BDA9B6400690D05 /* About.xib */; settings = {ASSET_TAGS = (); }; }; + 6A0578B11BDE178E0028AAB4 /* RFMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A0578B01BDE178E0028AAB4 /* RFMenu.m */; }; + 6A72FC1C1BDA9B6400690D05 /* RFAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A72FC1A1BDA9B6400690D05 /* RFAboutWindow.m */; }; + 6A72FC1D1BDA9B6400690D05 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6A72FC1B1BDA9B6400690D05 /* About.xib */; }; 6A80E57A1BD980E80092CC55 /* RFStatusItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A80E5791BD980E80092CC55 /* RFStatusItemView.m */; }; 6A80E57D1BD981FF0092CC55 /* RFStatusItemController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A80E57C1BD981FF0092CC55 /* RFStatusItemController.m */; }; 6AC219351BD8E3090093E846 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AC219341BD8E3090093E846 /* AppDelegate.m */; }; @@ -16,12 +17,14 @@ 6AC2193A1BD8E3090093E846 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6AC219391BD8E3090093E846 /* Assets.xcassets */; }; 6AC2193D1BD8E3090093E846 /* Main.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AC2193B1BD8E3090093E846 /* Main.xib */; }; 6AC2194F1BD8F07F0093E846 /* RocketFuel.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AC2194E1BD8F07F0093E846 /* RocketFuel.m */; }; - 6ACD6F191BDAAA8C00B15623 /* RocketFuel.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 6ACD6F181BDAAA8C00B15623 /* RocketFuel.sdef */; settings = {ASSET_TAGS = (); }; }; - 6ACD6F1F1BDAB9CE00B15623 /* RFAppleScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACD6F1E1BDAB9CE00B15623 /* RFAppleScript.m */; settings = {ASSET_TAGS = (); }; }; - 6ACD6F221BDACE9400B15623 /* RFApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACD6F211BDACE9400B15623 /* RFApplication.m */; settings = {ASSET_TAGS = (); }; }; + 6ACD6F191BDAAA8C00B15623 /* RocketFuel.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 6ACD6F181BDAAA8C00B15623 /* RocketFuel.sdef */; }; + 6ACD6F1F1BDAB9CE00B15623 /* RFAppleScript.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACD6F1E1BDAB9CE00B15623 /* RFAppleScript.m */; }; + 6ACD6F221BDACE9400B15623 /* RFApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ACD6F211BDACE9400B15623 /* RFApplication.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 6A0578AF1BDE178E0028AAB4 /* RFMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFMenu.h; sourceTree = ""; }; + 6A0578B01BDE178E0028AAB4 /* RFMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFMenu.m; sourceTree = ""; }; 6A72FC191BDA9B6400690D05 /* RFAboutWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RFAboutWindow.h; sourceTree = ""; }; 6A72FC1A1BDA9B6400690D05 /* RFAboutWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RFAboutWindow.m; sourceTree = ""; }; 6A72FC1B1BDA9B6400690D05 /* About.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = About.xib; sourceTree = ""; }; @@ -84,6 +87,8 @@ 6A80E57C1BD981FF0092CC55 /* RFStatusItemController.m */, 6A80E5781BD980E80092CC55 /* RFStatusItemView.h */, 6A80E5791BD980E80092CC55 /* RFStatusItemView.m */, + 6A0578AF1BDE178E0028AAB4 /* RFMenu.h */, + 6A0578B01BDE178E0028AAB4 /* RFMenu.m */, 6AC2194D1BD8F07F0093E846 /* RocketFuel.h */, 6AC2194E1BD8F07F0093E846 /* RocketFuel.m */, 6A72FC191BDA9B6400690D05 /* RFAboutWindow.h */, @@ -209,6 +214,7 @@ 6AC2194F1BD8F07F0093E846 /* RocketFuel.m in Sources */, 6AC219351BD8E3090093E846 /* AppDelegate.m in Sources */, 6ACD6F1F1BDAB9CE00B15623 /* RFAppleScript.m in Sources */, + 6A0578B11BDE178E0028AAB4 /* RFMenu.m in Sources */, 6A72FC1C1BDA9B6400690D05 /* RFAboutWindow.m in Sources */, 6A80E57D1BD981FF0092CC55 /* RFStatusItemController.m in Sources */, 6ACD6F221BDACE9400B15623 /* RFApplication.m in Sources */, diff --git a/RocketFuel/Info.plist b/RocketFuel/Info.plist index 3a77a9d..da4dd66 100644 --- a/RocketFuel/Info.plist +++ b/RocketFuel/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.3 + 1.3.1 CFBundleSignature RkFl CFBundleVersion - 0197 + 01AA LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement diff --git a/RocketFuel/RFMenu.h b/RocketFuel/RFMenu.h new file mode 100644 index 0000000..3a6e1f9 --- /dev/null +++ b/RocketFuel/RFMenu.h @@ -0,0 +1,23 @@ +/*! + * @class RFMenu + * @brief A Custom Menu. + * @version 1.0.0 + * @author Ardalan Samimi + * @copyright Saturn Five + */ +@interface RFMenu : NSMenu + +@property (nonatomic, strong) NSMenuItem *autoStartMenu; +@property (nonatomic, strong) NSMenuItem *aboutAppMenu; +@property (nonatomic, strong) NSMenuItem *durationMenu; +@property (nonatomic, strong) NSMenuItem *shutdownMenu; + +- (instancetype)initMainMenuWithTitle:(NSString *)title; +- (instancetype)initSubMenuWithTitle:(NSString *)title; +- (void)addItemWithTitle:(NSString *)title + tag:(int)tag + selector:(SEL)selector + target:(id)sender; +- (void)resetStateForMenuItems; + +@end diff --git a/RocketFuel/RFMenu.m b/RocketFuel/RFMenu.m new file mode 100644 index 0000000..eaa9f54 --- /dev/null +++ b/RocketFuel/RFMenu.m @@ -0,0 +1,72 @@ +/*! + * RFMenu.h + * RocketFuel + * + * Created by Ardalan Samimi on 26/10/15. + * Copyright © 2015 Saturn Five. All rights reserved. + */ + +#import "RFMenu.h" + +@implementation RFMenu + +- (instancetype)initMainMenuWithTitle:(NSString *)title { + self = [super initWithTitle:title]; + + if (self) { + [self addItemsToMainMenu]; + } + + return self; +} + +- (instancetype)initSubMenuWithTitle:(NSString *)title { + self = [super initWithTitle:title]; + + if (self) { + + } + + return self; +} + +- (void)addItemsToMainMenu { + self.autoStartMenu = [[NSMenuItem alloc] initWithTitle:@"Launch at login" + action:nil + keyEquivalent:@""]; + self.durationMenu = [[NSMenuItem alloc] initWithTitle:@"Deactivate after" + action:nil + keyEquivalent:@""]; + self.aboutAppMenu = [[NSMenuItem alloc] initWithTitle:@"About" + action:nil + keyEquivalent:@""]; + self.shutdownMenu = [[NSMenuItem alloc] initWithTitle:@"Quit" + action:@selector(terminate:) + keyEquivalent:@""]; + [self addItem:self.autoStartMenu]; + [self addItem:self.durationMenu]; + [self addItem:[NSMenuItem separatorItem]]; + [self addItem:self.aboutAppMenu]; + [self addItem:[NSMenuItem separatorItem]]; + [self addItem:self.shutdownMenu]; +} + +- (void)addItemWithTitle:(NSString *)title + tag:(int)tag + selector:(SEL)selector + target:(id)sender { + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title + action:selector + keyEquivalent:@""]; + item.tag = tag; + item.target = sender; + [self addItem:item]; +} + +- (void)resetStateForMenuItems { + for (NSMenuItem *item in self.itemArray) { + item.state = NSOffState; + } +} + +@end diff --git a/RocketFuel/RFStatusItemController.h b/RocketFuel/RFStatusItemController.h index 640ca7d..9b0a94d 100644 --- a/RocketFuel/RFStatusItemController.h +++ b/RocketFuel/RFStatusItemController.h @@ -1,7 +1,7 @@ /*! * @class Status Item Controller * @brief Manages the status item and the menu. - * @version 2.1.0 + * @version 2.2.0 * @author Ardalan Samimi * @copyright Saturn Five */ diff --git a/RocketFuel/RFStatusItemController.m b/RocketFuel/RFStatusItemController.m index d22bb26..a689967 100644 --- a/RocketFuel/RFStatusItemController.m +++ b/RocketFuel/RFStatusItemController.m @@ -8,14 +8,16 @@ #import "RFStatusItemController.h" #import "RFStatusItemView.h" +#import "RFMenu.h" #import "RocketFuel.h" #import "RFAboutWindow.h" @interface RFStatusItemController () -@property (nonatomic, strong) NSMenu *menu; @property (nonatomic, strong) NSStatusItem *statusItem; @property (nonatomic, strong) RFStatusItemView *statusItemView; +@property (nonatomic, strong) RFMenu *menu; +@property (nonatomic, strong) RFMenu *subMenu; @property (nonatomic, strong) RocketFuel *rocketFuel; @property (nonatomic, strong) RFAboutWindow *aboutWindow; @property (nonatomic) BOOL applicationWillLaunchAtLogin; @@ -71,31 +73,53 @@ - (void)statusItemView:(RFStatusItemView *)view #pragma mark MENU METHODS -- (NSMenu *)menu { +- (RFMenu *)menu { if (!_menu) { - _menu = [[NSMenu alloc] initWithTitle:@"RocketFuel"]; + _menu = [[RFMenu alloc] initMainMenuWithTitle:@"RocketFuel"]; + + _menu.autoStartMenu.action = @selector(toggleLaunchAtLogin:); + _menu.autoStartMenu.target = self; + _menu.autoStartMenu.state = self.applicationWillLaunchAtLogin; + + _menu.durationMenu.submenu = self.subMenu; + + _menu.aboutAppMenu.action = @selector(openAboutWindow:); + _menu.aboutAppMenu.target = self; + _menu.delegate = self; - NSMenuItem *loginMenu = [[NSMenuItem alloc] initWithTitle:@"Launch at login" - action:@selector(toggleLaunchAtLogin:) - keyEquivalent:@""]; - NSMenuItem *aboutMenu = [[NSMenuItem alloc] initWithTitle:@"About" - action:@selector(openAboutWindow:) - keyEquivalent:@""]; - loginMenu.target = self; - aboutMenu.target = self; - [_menu addItem:loginMenu]; - [_menu addItem:aboutMenu]; - [_menu addItem:[NSMenuItem separatorItem]]; - [_menu addItemWithTitle:@"Quit" - action:@selector(terminate:) - keyEquivalent:@""]; - // Set application will launch at login state - loginMenu.state = self.applicationWillLaunchAtLogin; } return _menu; } +- (RFMenu *)subMenu { + if (!_subMenu) { + _subMenu = [[RFMenu alloc] initSubMenuWithTitle:@"RocketFuel Submenu"]; + [_subMenu addItemWithTitle:@"5 minutes" + tag:300 + selector:@selector(deactivateAfterDuration:) + target:self]; + [_subMenu addItemWithTitle:@"15 minutes" + tag:900 + selector:@selector(deactivateAfterDuration:) + target:self]; + [_subMenu addItemWithTitle:@"30 minutes" + tag:1800 + selector:@selector(deactivateAfterDuration:) + target:self]; + [_subMenu addItemWithTitle:@"1 hour" + tag:3600 + selector:@selector(deactivateAfterDuration:) + target:self]; + [_subMenu addItemWithTitle:@"never" + tag:0 + selector:@selector(deactivateAfterDuration:) + target:self]; + } + + return _subMenu; +} + - (void)menuWillOpen:(NSMenu *)menu { self.statusItemView.image = [self imageForPushedState]; self.statusItemView.highlightMode = YES; @@ -106,6 +130,34 @@ - (void)menuDidClose:(NSMenu *)menu { self.statusItemView.highlightMode = NO; } +- (void)toggleLaunchAtLogin:(NSMenuItem *)sender { + self.applicationWillLaunchAtLogin = !self.applicationWillLaunchAtLogin; + sender.state = self.applicationWillLaunchAtLogin; +} + +- (void)openAboutWindow:(id)sender { + self.aboutWindow = [[RFAboutWindow alloc] init]; + [self.aboutWindow showWindow:self]; + [self.aboutWindow.window makeKeyAndOrderFront:self]; + [self.aboutWindow.window setLevel:NSFloatingWindowLevel]; +} + +- (void)deactivateAfterDuration:(NSMenuItem *)sender { + if (sender.state == NSOffState) { + NSInteger duration = sender.tag; + + if (self.isActive) { + [self.rocketFuel terminate]; + } + + self.rocketFuel.duration = duration; + [self.rocketFuel toggleSleepMode]; + + [self.subMenu resetStateForMenuItems]; + sender.state = NSOnState; + } +} + #pragma mark IMAGE METHODS - (NSImage *)imageForCurrentState { @@ -156,18 +208,6 @@ - (void)requestActivation { #pragma mark MISC METHODS -- (void)toggleLaunchAtLogin:(NSMenuItem *)sender { - self.applicationWillLaunchAtLogin = !self.applicationWillLaunchAtLogin; - sender.state = self.applicationWillLaunchAtLogin; -} - -- (void)openAboutWindow:(id)sender { - self.aboutWindow = [[RFAboutWindow alloc] init]; - [self.aboutWindow showWindow:self]; - [self.aboutWindow.window makeKeyAndOrderFront:self]; - [self.aboutWindow.window setLevel:NSFloatingWindowLevel]; -} - - (BOOL)isMenuDark { return [NSAppearance.currentAppearance.name hasPrefix:@"NSAppearanceNameVibrantDark"]; } diff --git a/RocketFuel/RocketFuel.h b/RocketFuel/RocketFuel.h index 816dd04..0ceb0dd 100644 --- a/RocketFuel/RocketFuel.h +++ b/RocketFuel/RocketFuel.h @@ -9,7 +9,7 @@ /*! * @class RocketFuel * @brief The RocketFuel Object. - * @version 1.1.0 + * @version 1.2.0 * @author Ardalan Samimi * @copyright Saturn Five */ @@ -19,6 +19,10 @@ * @return YES if caffeinate is running, otherwise NO. */ @property (nonatomic, readonly) BOOL active; +/*! + * @brief The duration in seconds for which RocketFuel is to be active. + */ +@property (nonatomic) NSInteger duration; /*! * @brief The delegate adopting the Rocket Fuel protocol. */ @@ -32,5 +36,9 @@ * @discussion To check whether sleep mode is one, use property isSleepModeOn. */ - (void)toggleSleepMode; +/*! + * @brief Terminate sleep prevention. + */ +- (void)terminate; @end diff --git a/RocketFuel/RocketFuel.m b/RocketFuel/RocketFuel.m index e6c0301..9672aeb 100644 --- a/RocketFuel/RocketFuel.m +++ b/RocketFuel/RocketFuel.m @@ -36,21 +36,21 @@ - (void)toggleSleepMode { if (_task) { [self terminate]; } else { - [self launch]; + [self task]; + __weak typeof(self) weakSelf = self; + [_task setTerminationHandler:^(NSTask * _Nonnull task) { + weakSelf.active = weakSelf.isActive; + }]; } } -- (void)launch { - [self.arguments addObject:@"-di"]; - [self task]; -} - - (void)terminate { if ([self.task isRunning]) { [_task terminate]; } _task = nil; + _arguments = nil; self.active = self.isActive; } @@ -79,7 +79,14 @@ - (NSTask *)task { - (NSMutableArray *)arguments { if (!_arguments) { - _arguments = [NSMutableArray array]; + NSString *argument; + if (_duration) { + argument = [NSString stringWithFormat:@"-dit %li", _duration]; + } else { + argument = @"-di"; + } + + _arguments = [NSMutableArray arrayWithObject:argument]; } return _arguments;