From c4798d5e0d9b0e1c33619dfb2c600a0eb4fb7f38 Mon Sep 17 00:00:00 2001 From: Pyroh Date: Mon, 5 Sep 2016 16:04:38 +0200 Subject: [PATCH] Started implementation of rules editor window --- Fluor.xcodeproj/project.pbxproj | 58 ++++++- Fluor/AboutWindowController.swift | 21 +++ Fluor/AboutWindowController.xib | 40 +++++ Fluor/Base.lproj/MainMenu.xib | 78 +++++---- Fluor/BehaviorManager.swift | 75 +++++++-- Fluor/CurrentAppView.swift | 9 +- Fluor/Fluor-Bridging-Header.h | 1 + Fluor/LaunchAtLoginController.h | 35 +++++ Fluor/LaunchAtLoginController.m | 122 +++++++++++++++ Fluor/RulesEditorWindowController.swift | 65 ++++++++ Fluor/RulesEditorWindowController.xib | 200 ++++++++++++++++++++++++ Fluor/StatusMenuController.swift | 57 +++++-- 12 files changed, 706 insertions(+), 55 deletions(-) create mode 100644 Fluor/AboutWindowController.swift create mode 100644 Fluor/AboutWindowController.xib create mode 100755 Fluor/LaunchAtLoginController.h create mode 100755 Fluor/LaunchAtLoginController.m create mode 100644 Fluor/RulesEditorWindowController.swift create mode 100644 Fluor/RulesEditorWindowController.xib diff --git a/Fluor.xcodeproj/project.pbxproj b/Fluor.xcodeproj/project.pbxproj index d97ed63..6157e45 100644 --- a/Fluor.xcodeproj/project.pbxproj +++ b/Fluor.xcodeproj/project.pbxproj @@ -15,6 +15,11 @@ 3FCD16A71D79793600C57B22 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3FCD16A61D79793600C57B22 /* Assets.xcassets */; }; 3FCD16AA1D79793600C57B22 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3FCD16A81D79793600C57B22 /* MainMenu.xib */; }; 3FCD16B41D79797600C57B22 /* utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FCD16B31D79797600C57B22 /* utils.m */; }; + 3FE7E0E01D7CA14600DC754D /* LaunchAtLoginController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FE7E0DF1D7CA14600DC754D /* LaunchAtLoginController.m */; }; + 3FE7E0E31D7CBE1700DC754D /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE7E0E11D7CBE1700DC754D /* AboutWindowController.swift */; }; + 3FE7E0E41D7CBE1700DC754D /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3FE7E0E21D7CBE1700DC754D /* AboutWindowController.xib */; }; + 3FE7E0EA1D7CC01B00DC754D /* RulesEditorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE7E0E81D7CC01B00DC754D /* RulesEditorWindowController.swift */; }; + 3FE7E0EB1D7CC01B00DC754D /* RulesEditorWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3FE7E0E91D7CC01B00DC754D /* RulesEditorWindowController.xib */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +35,12 @@ 3FCD16B11D79797600C57B22 /* Fluor-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Fluor-Bridging-Header.h"; sourceTree = ""; }; 3FCD16B21D79797600C57B22 /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = ""; }; 3FCD16B31D79797600C57B22 /* utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = utils.m; sourceTree = ""; }; + 3FE7E0DE1D7CA14600DC754D /* LaunchAtLoginController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LaunchAtLoginController.h; sourceTree = ""; }; + 3FE7E0DF1D7CA14600DC754D /* LaunchAtLoginController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LaunchAtLoginController.m; sourceTree = ""; }; + 3FE7E0E11D7CBE1700DC754D /* AboutWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; + 3FE7E0E21D7CBE1700DC754D /* AboutWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindowController.xib; sourceTree = ""; }; + 3FE7E0E81D7CC01B00DC754D /* RulesEditorWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RulesEditorWindowController.swift; sourceTree = ""; }; + 3FE7E0E91D7CC01B00DC754D /* RulesEditorWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RulesEditorWindowController.xib; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -62,21 +73,51 @@ 3FCD16A31D79793600C57B22 /* Fluor */ = { isa = PBXGroup; children = ( + 3FCD16B11D79797600C57B22 /* Fluor-Bridging-Header.h */, 3FCD16A41D79793600C57B22 /* AppDelegate.swift */, - 3FCD16A81D79793600C57B22 /* MainMenu.xib */, - 3F2FA4021D7A059F003E2AD8 /* CurrentAppView.swift */, - 3F2FA4001D79D66D003E2AD8 /* StateView.swift */, - 3F2FA3FE1D79D094003E2AD8 /* StatusMenuController.swift */, 3F2FA4041D7A1F87003E2AD8 /* BehaviorManager.swift */, + 3FE7E0E51D7CBEAE00DC754D /* Controllers */, + 3FE7E0E61D7CBEB900DC754D /* Views */, + 3FE7E0E71D7CBEC700DC754D /* XIBs */, 3FCD16A61D79793600C57B22 /* Assets.xcassets */, 3FCD16AB1D79793600C57B22 /* Info.plist */, 3FCD16B21D79797600C57B22 /* utils.h */, 3FCD16B31D79797600C57B22 /* utils.m */, - 3FCD16B11D79797600C57B22 /* Fluor-Bridging-Header.h */, ); path = Fluor; sourceTree = ""; }; + 3FE7E0E51D7CBEAE00DC754D /* Controllers */ = { + isa = PBXGroup; + children = ( + 3FE7E0DE1D7CA14600DC754D /* LaunchAtLoginController.h */, + 3FE7E0DF1D7CA14600DC754D /* LaunchAtLoginController.m */, + 3F2FA3FE1D79D094003E2AD8 /* StatusMenuController.swift */, + 3FE7E0E11D7CBE1700DC754D /* AboutWindowController.swift */, + 3FE7E0E81D7CC01B00DC754D /* RulesEditorWindowController.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + 3FE7E0E61D7CBEB900DC754D /* Views */ = { + isa = PBXGroup; + children = ( + 3F2FA4021D7A059F003E2AD8 /* CurrentAppView.swift */, + 3F2FA4001D79D66D003E2AD8 /* StateView.swift */, + ); + name = Views; + sourceTree = ""; + }; + 3FE7E0E71D7CBEC700DC754D /* XIBs */ = { + isa = PBXGroup; + children = ( + 3FCD16A81D79793600C57B22 /* MainMenu.xib */, + 3FE7E0E21D7CBE1700DC754D /* AboutWindowController.xib */, + 3FE7E0E91D7CC01B00DC754D /* RulesEditorWindowController.xib */, + ); + name = XIBs; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -143,7 +184,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3FE7E0E41D7CBE1700DC754D /* AboutWindowController.xib in Resources */, 3FCD16A71D79793600C57B22 /* Assets.xcassets in Resources */, + 3FE7E0EB1D7CC01B00DC754D /* RulesEditorWindowController.xib in Resources */, 3FCD16AA1D79793600C57B22 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -160,6 +203,9 @@ 3FCD16A51D79793600C57B22 /* AppDelegate.swift in Sources */, 3F2FA4011D79D66D003E2AD8 /* StateView.swift in Sources */, 3FCD16B41D79797600C57B22 /* utils.m in Sources */, + 3FE7E0EA1D7CC01B00DC754D /* RulesEditorWindowController.swift in Sources */, + 3FE7E0E31D7CBE1700DC754D /* AboutWindowController.swift in Sources */, + 3FE7E0E01D7CA14600DC754D /* LaunchAtLoginController.m in Sources */, 3F2FA4051D7A1F87003E2AD8 /* BehaviorManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -273,6 +319,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Q2E884V952; INFOPLIST_FILE = Fluor/Info.plist; @@ -290,6 +337,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = Q2E884V952; INFOPLIST_FILE = Fluor/Info.plist; diff --git a/Fluor/AboutWindowController.swift b/Fluor/AboutWindowController.swift new file mode 100644 index 0000000..456f211 --- /dev/null +++ b/Fluor/AboutWindowController.swift @@ -0,0 +1,21 @@ +// +// AboutWindowController.swift +// Fluor +// +// Created by Pierre TACCHI on 04/09/16. +// Copyright © 2016 Pyrolyse. All rights reserved. +// + +import Cocoa + +class AboutWindowController: NSWindowController { + + override func windowDidLoad() { + super.windowDidLoad() + + window?.styleMask.formUnion(.fullSizeContentView) + window?.titleVisibility = .hidden + window?.setFrameAutosaveName("AboutWindowAutosaveName") + } + +} diff --git a/Fluor/AboutWindowController.xib b/Fluor/AboutWindowController.xib new file mode 100644 index 0000000..7a4d03b --- /dev/null +++ b/Fluor/AboutWindowController.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fluor/Base.lproj/MainMenu.xib b/Fluor/Base.lproj/MainMenu.xib index b6fef6b..22f16dc 100644 --- a/Fluor/Base.lproj/MainMenu.xib +++ b/Fluor/Base.lproj/MainMenu.xib @@ -1,8 +1,9 @@ - + + @@ -13,6 +14,7 @@ + @@ -30,12 +32,21 @@ - + + + + + + + + + + @@ -46,32 +57,43 @@ - + - + + + + + + + + + + + + - - - + + + + + - - - - + @@ -86,10 +108,10 @@ - + - + @@ -100,9 +122,9 @@ - - - + + + @@ -129,7 +151,7 @@ - + @@ -147,7 +169,7 @@ - + diff --git a/Fluor/BehaviorManager.swift b/Fluor/BehaviorManager.swift index 44941f5..63f59f7 100644 --- a/Fluor/BehaviorManager.swift +++ b/Fluor/BehaviorManager.swift @@ -6,14 +6,21 @@ // Copyright © 2016 Pyrolyse. All rights reserved. // -import Foundation +import Cocoa -enum KeyboardState { +enum KeyboardState: Int { case error case apple case other } +struct DefaultsKeys { + static let appleIsDefaultBehavior = "AppleBehaviorIsDefault" + static let appRules = "AppRules" + static let resetStateOnQuit = "ResetBehaviorOnQuit" + static let onQuitState = "OnQuitBehavior" +} + class BehaviorManager { static let `default`: BehaviorManager = BehaviorManager() @@ -39,8 +46,13 @@ class BehaviorManager { } } - private var isAppleDefaultBehavior: Bool - private var behaviorDict: [String: AppBehavior] + private var isAppleDefaultBehavior: Bool { + didSet { + defaults.set(isAppleDefaultBehavior, forKey: DefaultsKeys.appleIsDefaultBehavior) + } + } + private var behaviorDict: [String: (behavior: AppBehavior, url: URL)] + private let defaults = UserDefaults.standard private init() { isAppleDefaultBehavior = true @@ -49,29 +61,50 @@ class BehaviorManager { loadPrefs() } + func retrieveRules() -> [RulesTableItem] { + guard let rawRules = defaults.array(forKey: DefaultsKeys.appRules) as? [[String: Any]] else { return [] } + var rules = [RulesTableItem]() + rawRules.forEach({ (dict) in + let appId = dict["id"] as! String + let appBehavior = dict["behavior"] as! Int - 1 + let appPath = dict["path"] as! String + let appUrl = URL(fileURLWithPath: appPath) + let appIcon = NSWorkspace.shared().icon(forFile: appPath) + let appName: String + if let name = Bundle(path: appPath)?.localizedInfoDictionary?["CFBundleName"] as? String { + appName = name + } else { + appName = appUrl.deletingPathExtension().lastPathComponent + } + let item = RulesTableItem(id: appId, url: appUrl, icon: appIcon, name: appName, behavior: appBehavior) + rules.append(item) + }) + return rules + } + func defaultKeyBoardState() -> KeyboardState { return isAppleDefaultBehavior ? .apple : .other } func behaviorForApp(id: String) -> AppBehavior { - return behaviorDict[id] ?? .infered + return behaviorDict[id]?.behavior ?? .infered } - func setBehaviorForApp(id: String, behavior: AppBehavior) { + func setBehaviorForApp(id: String, behavior: AppBehavior, url: URL) { var change = false if behavior == .infered { behaviorDict.removeValue(forKey: id) change = true - } else if let previousBehavior = behaviorDict[id] { + } else if let previousBehavior = behaviorDict[id]?.behavior { if previousBehavior != behavior { - behaviorDict[id] = behavior + behaviorDict[id]?.behavior = behavior change = true } } else { - behaviorDict[id] = behavior + behaviorDict[id] = (behavior, url) change = true } - if change { synchronizePrefs() } + if change { synchronizeRules() } } func getActualStateAccordingToPreferences() -> KeyboardState { @@ -86,10 +119,28 @@ class BehaviorManager { } private func loadPrefs() { + let factoryDefaults: [String: Any] = [DefaultsKeys.appleIsDefaultBehavior: true, DefaultsKeys.appRules: [Any](), DefaultsKeys.resetStateOnQuit: false, DefaultsKeys.onQuitState: KeyboardState.apple.rawValue] + defaults.register(defaults: factoryDefaults) + + isAppleDefaultBehavior = defaults.bool(forKey: DefaultsKeys.appleIsDefaultBehavior) + guard let arr = defaults.array(forKey: DefaultsKeys.appRules) else { return } + for item in arr { + let dict = item as! [String: Any] + let key = dict["id"] as! String + let behavior = AppBehavior(rawValue: dict["behavior"] as! Int)! + let path = dict["path"] as! String + let url = URL(fileURLWithPath: path) + behaviorDict[key] = (behavior, url) + } } - private func synchronizePrefs() { - + private func synchronizeRules() { + var arr = [Any]() + behaviorDict.forEach { (key: String, value: (behavior: AppBehavior, url: URL)) in + let dict: [String: Any] = ["id": key, "behavior": value.behavior.rawValue, "path": value.url.path] + arr.append(dict) + } + defaults.set(arr, forKey: DefaultsKeys.appRules) } } diff --git a/Fluor/CurrentAppView.swift b/Fluor/CurrentAppView.swift index 683e34d..c398aea 100644 --- a/Fluor/CurrentAppView.swift +++ b/Fluor/CurrentAppView.swift @@ -20,7 +20,10 @@ class CurrentAppView: NSView { @IBOutlet weak var appNameLabel: NSTextField! @IBOutlet weak var behaviorSegment: NSSegmentedControl! + private var currentApp: NSRunningApplication? + func setCurrent(app: NSRunningApplication, behavior: AppBehavior) { + currentApp = app behaviorSegment.setSelected(true, forSegment: behavior.rawValue) appIconView.image = app.icon if let name = app.localizedName { @@ -30,6 +33,10 @@ class CurrentAppView: NSView { } } + func updateBehaviorForCurrentApp(_ behavior: AppBehavior) { + behaviorSegment.setSelected(true, forSegment: behavior.rawValue) + } + func enabled(_ flag: Bool) { let controls = [appIconView, appNameLabel, behaviorSegment] as [NSControl] controls.forEach { @@ -39,7 +46,7 @@ class CurrentAppView: NSView { @IBAction func behaviorChanged(_ sender: NSSegmentedControl) { if let behavior = AppBehavior(rawValue: sender.selectedSegment) { - let userInfo = ["behavior": behavior] + let userInfo = StatusMenuController.behaviorDidChangeUserInfoConstructor(id: currentApp!.bundleIdentifier!, url: currentApp!.bundleURL!, behavior: behavior) let not = Notification(name: Notification.Name.BehaviorDidChangeForApp, object: self, userInfo: userInfo) NotificationCenter.default.post(not) } diff --git a/Fluor/Fluor-Bridging-Header.h b/Fluor/Fluor-Bridging-Header.h index eed2ad1..a80afc2 100644 --- a/Fluor/Fluor-Bridging-Header.h +++ b/Fluor/Fluor-Bridging-Header.h @@ -3,3 +3,4 @@ // #include "utils.h" +#include "LaunchAtLoginController.h" diff --git a/Fluor/LaunchAtLoginController.h b/Fluor/LaunchAtLoginController.h new file mode 100755 index 0000000..bed7dda --- /dev/null +++ b/Fluor/LaunchAtLoginController.h @@ -0,0 +1,35 @@ +// +// 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; +@import CoreServices; + +@interface LaunchAtLoginController : NSObject {} + +@property(assign) BOOL launchAtLogin; + +- (BOOL) willLaunchAtLogin: (NSURL*) itemURL; +- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL; + +@end diff --git a/Fluor/LaunchAtLoginController.m b/Fluor/LaunchAtLoginController.m new file mode 100755 index 0000000..f9f4c12 --- /dev/null +++ b/Fluor/LaunchAtLoginController.m @@ -0,0 +1,122 @@ +// +// 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 = (__bridge id) context; + [self willChangeValueForKey:StartAtLoginKey]; + [self didChangeValueForKey:StartAtLoginKey]; +} + +#pragma mark Initialization + +- (id) init +{ + self = [super init]; + loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); + LSSharedFileListAddObserver(loginItems, CFRunLoopGetMain(), + (CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (voidPtr)CFBridgingRetain(self)); + return self; +} + +- (void) dealloc +{ + LSSharedFileListRemoveObserver(loginItems, CFRunLoopGetMain(), + (CFStringRef)NSDefaultRunLoopMode, sharedFileListDidChange, (__bridge void *)(self)); + CFRelease(loginItems); +} + +#pragma mark Launch List Control + +LSSharedFileListItemRef copyItemWithURLinFileList(NSURL* wantedURL, LSSharedFileListRef fileList) { + if (wantedURL == NULL || fileList == NULL) + return NULL; + + NSArray *listSnapshot = (__bridge_transfer NSArray *)LSSharedFileListCopySnapshot(fileList, NULL); + for(NSUInteger i = 0; i< [listSnapshot count]; i++) { + LSSharedFileListItemRef item = (__bridge LSSharedFileListItemRef)[listSnapshot objectAtIndex:i]; + UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; + CFURLRef currentItemURL = NULL; + LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL); + if (currentItemURL && [(__bridge_transfer NSURL*)currentItemURL isEqual:wantedURL]) { + CFRetain(item); + return item; + } + } + + return NULL; +} + +- (BOOL) willLaunchAtLogin: (NSURL*) itemURL +{ + return !!copyItemWithURLinFileList(itemURL, loginItems); +} + +- (void) setLaunchAtLogin: (BOOL) enabled forURL: (NSURL*) itemURL +{ + LSSharedFileListItemRef appItem = copyItemWithURLinFileList(itemURL, loginItems); + if (enabled && !appItem) { + LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, + NULL, NULL, (__bridge CFURLRef)itemURL, NULL, NULL); + } else if (!enabled && appItem) { + LSSharedFileListItemRemove(loginItems, appItem); + } + if (appItem) { + CFRelease(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 diff --git a/Fluor/RulesEditorWindowController.swift b/Fluor/RulesEditorWindowController.swift new file mode 100644 index 0000000..10b86d4 --- /dev/null +++ b/Fluor/RulesEditorWindowController.swift @@ -0,0 +1,65 @@ +// +// RulesEditorWindowController.swift +// Fluor +// +// Created by Pierre TACCHI on 04/09/16. +// Copyright © 2016 Pyrolyse. All rights reserved. +// + +import Cocoa + +class RulesTableItem: NSObject { + let id: String + let url: URL + let icon: NSImage + let name: String + var behavior: Int { + didSet { + let info = StatusMenuController.behaviorDidChangeUserInfoConstructor(id: id, url: url, behavior: AppBehavior(rawValue: behavior + 1)!) + let not = Notification(name: Notification.Name.BehaviorDidChangeForApp, object: self, userInfo: info) + NotificationCenter.default.post(not) + } + } + + init(id: String, url: URL, icon: NSImage, name: String, behavior: Int) { + self.id = id + self.url = url + self.icon = icon + self.name = name + self.behavior = behavior + } +} + +class RulesEditorWindowController: NSWindowController, NSTableViewDataSource { + @IBOutlet weak var tableView: NSTableView! + + private var rulesArray = [RulesTableItem]() + + override func windowDidLoad() { + super.windowDidLoad() + window?.setFrameAutosaveName("EditRulesWindowAutosaveName") + } + + func loadData() { + loadRules() + tableView.reloadData() + } + + private func loadRules() { + var rules = BehaviorManager.default.retrieveRules() + rules.sort { $0.name < $1.name } + rulesArray = rules + } + + func numberOfRows(in tableView: NSTableView) -> Int { + return rulesArray.count + } + + func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? { + return rulesArray[row] + } + + @IBAction func behaviorChange(_ sender: NSSegmentedControl) { + tableView.row(for: sender) + } +} diff --git a/Fluor/RulesEditorWindowController.xib b/Fluor/RulesEditorWindowController.xib new file mode 100644 index 0000000..5224823 --- /dev/null +++ b/Fluor/RulesEditorWindowController.xib @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fluor/StatusMenuController.swift b/Fluor/StatusMenuController.swift index e2e5fb2..4e95e07 100644 --- a/Fluor/StatusMenuController.swift +++ b/Fluor/StatusMenuController.swift @@ -18,6 +18,8 @@ class StatusMenuController: NSObject { @IBOutlet weak var stateView: StateView! @IBOutlet weak var currentAppView: CurrentAppView! + private var rulesController: RulesEditorWindowController? + private var currentKeyboardState: KeyboardState = .error private var currentID: String = "" private var currentBehavior: AppBehavior = .infered @@ -49,9 +51,25 @@ class StatusMenuController: NSObject { print(passedState) } - @objc private func appDidChangeBehavior(notification: NSNotification) { - guard let behavior = notification.userInfo?["behavior"] as? AppBehavior else { return } - setBehaviorForApp(id: currentID, behavior: behavior) + @objc private func behaviorDidChangeForApp(notification: NSNotification) { + guard let userInfo = notification.userInfo, + let info = StatusMenuController.behaviorDidChangeUserInfoFor(dict: userInfo) else { return } + setBehaviorForApp(id: info.id, behavior: info.behavior, url: info.url) + switch notification.object! { + case is CurrentAppView: + rulesController?.loadData() + adaptBehaviorForApp(id: info.id) + default: + if info.id == currentID { + adaptBehaviorForApp(id: info.id) + currentAppView.updateBehaviorForCurrentApp(info.behavior) + } + } + } + + @objc private func rulesEditorWindowWillClose(notification: Notification) { + NotificationCenter.default.removeObserver(self, name: Notification.Name.NSWindowWillClose, object: rulesController?.window) + rulesController = nil } // MARK: Private functions @@ -66,10 +84,9 @@ class StatusMenuController: NSObject { currentPlaceHolder?.view = currentAppView } - private func applyAsObserver() { NotificationCenter.default.addObserver(self, selector: #selector(stateViewDidChangeState(notification:)), name: Notification.Name.StateViewDidChangeState, object: stateView) - NotificationCenter.default.addObserver(self, selector: #selector(appDidChangeBehavior(notification:)), name: Notification.Name.BehaviorDidChangeForApp, object: currentAppView) + NotificationCenter.default.addObserver(self, selector: #selector(behaviorDidChangeForApp(notification:)), name: Notification.Name.BehaviorDidChangeForApp, object: nil) NSWorkspace.shared().notificationCenter.addObserver(self, selector: #selector(activeAppDidChange(notification:)), name: NSNotification.Name.NSWorkspaceDidActivateApplication, object: nil) } @@ -83,9 +100,8 @@ class StatusMenuController: NSObject { currentAppView.setCurrent(app: app, behavior: BehaviorManager.default.behaviorForApp(id: id)) } - private func setBehaviorForApp(id: String, behavior: AppBehavior) { - BehaviorManager.default.setBehaviorForApp(id: id, behavior: behavior) - adaptBehaviorForApp(id: id) + private func setBehaviorForApp(id: String, behavior: AppBehavior, url: URL) { + BehaviorManager.default.setBehaviorForApp(id: id, behavior: behavior, url: url) } private func adaptBehaviorForApp(id: String) { @@ -95,8 +111,10 @@ class StatusMenuController: NSObject { currentKeyboardState = state switch state { case .apple: + NSLog("Switch to Apple Mode for %@", id) setFnKeysToAppleMode() case .other: + NSLog("Switch to Other Mode for %@", id) setFnKeysToOtherMode() default: // Soneone should handle this, no ? @@ -104,9 +122,30 @@ class StatusMenuController: NSObject { } } + static func behaviorDidChangeUserInfoConstructor(id: String, url: URL, behavior: AppBehavior) -> [String: Any] { + return ["id": id, "url": url, "behavior": behavior] + } + + static func behaviorDidChangeUserInfoFor(dict: [AnyHashable: Any]) -> (id: String, url: URL, behavior: AppBehavior)? { + guard let behavior = dict["behavior"] as? AppBehavior, + let url = dict["url"] as? URL, + let id = dict["id"] as? String else { return nil } + return (id, url, behavior) + } + // MARK: IBActions @IBAction func editRules(_ sender: AnyObject) { - NSLog("Should edit rules...") +// if rulesController == nil { + rulesController = RulesEditorWindowController(windowNibName: "RulesEditorWindowController") + rulesController?.window?.becomeMain() + rulesController?.loadData() +// } else { +// rulesController?.showWindow(self) +// rulesController?.window?.becomeMain() +// rulesController?.loadData() +// } + NotificationCenter.default.addObserver(self, selector: #selector(rulesEditorWindowWillClose(notification:)), name: Notification.Name.NSWindowWillClose, object: rulesController?.window) + rulesController?.window?.orderFrontRegardless() } @IBAction func quitApplication(_ sender: AnyObject) {