diff --git a/Shifty.xcodeproj/xcshareddata/xcschemes/Shifty.xcscheme b/Shifty.xcodeproj/xcshareddata/xcschemes/Shifty.xcscheme
index 45850ff..3950274 100644
--- a/Shifty.xcodeproj/xcshareddata/xcschemes/Shifty.xcscheme
+++ b/Shifty.xcodeproj/xcshareddata/xcschemes/Shifty.xcscheme
@@ -62,13 +62,13 @@
+ value = "true"
+ isEnabled = "NO">
+ value = "true"
+ isEnabled = "NO">
-
+
-
+
@@ -73,7 +73,7 @@
-
+
@@ -81,7 +81,7 @@
-
+
@@ -131,7 +131,7 @@ DQ
-
+
@@ -142,7 +142,7 @@ DQ
-
+
@@ -154,7 +154,7 @@ DQ
-
+
@@ -162,7 +162,7 @@ DQ
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
-
+
@@ -227,7 +227,7 @@ DQ
-
+
@@ -248,7 +248,7 @@ DQ
-
+
@@ -259,7 +259,7 @@ DQ
-
+
@@ -280,7 +280,7 @@ DQ
-
+
@@ -291,7 +291,7 @@ DQ
-
+
@@ -311,8 +311,23 @@ DQ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -343,8 +358,40 @@ DQ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -355,7 +402,7 @@ DQ
-
+
@@ -376,66 +423,53 @@ DQ
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
-
-
-
+
@@ -444,7 +478,7 @@ DQ
-
+
@@ -455,7 +489,7 @@ DQ
-
+
@@ -542,7 +576,7 @@ DQ
-
+
@@ -586,7 +620,7 @@ DQ
-
+
@@ -624,7 +658,7 @@ DQ
+
+
+
+
-
-
+
+
+
@@ -685,8 +724,4 @@ DQ
-
-
-
-
diff --git a/Shifty/BrowserManager.swift b/Shifty/BrowserManager.swift
index 8dfcf53..2202272 100644
--- a/Shifty/BrowserManager.swift
+++ b/Shifty/BrowserManager.swift
@@ -28,6 +28,9 @@ enum SupportedBrowserID: BundleIdentifier {
case operaBeta = "com.operasoftware.OperaNext"
case operaDeveloper = "com.operasoftware.OperaDeveloper"
+ case brave = "com.brave.Browser"
+ case braveBeta = "com.brave.Browser.beta"
+
case vivaldi = "com.vivaldi.Vivaldi"
init?(_ rawValue: String) {
@@ -135,9 +138,9 @@ class BrowserManager {
}
private func fireNightShiftEvent() {
- if RuleManager.shared.ruleForSubdomain == .enabled {
+ if RuleManager.shared.ruleForCurrentSubdomain == .enabled {
NightShiftManager.shared.respond(to: .nightShiftEnableRuleActivated)
- } else if RuleManager.shared.disabledForDomain || RuleManager.shared.ruleForSubdomain == .disabled {
+ } else if RuleManager.shared.isDisabledForDomain || RuleManager.shared.ruleForCurrentSubdomain == .disabled {
NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
} else {
NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
@@ -254,7 +257,10 @@ class BrowserManager {
let tab: Tab?
switch browserID {
- case .chrome, .chromeCanary, .chromium, .edge, .edgeBeta, .opera, .operaBeta, .operaDeveloper, .vivaldi:
+ case .chrome, .chromeCanary, .chromium,
+ .edge, .edgeBeta,
+ .opera, .operaBeta, .operaDeveloper,
+ .brave, .braveBeta, .vivaldi:
tab = window.activeTab
case .safari, .safariTechnologyPreview:
do {
diff --git a/Shifty/MainMenu.xib b/Shifty/MainMenu.xib
index 3c35868..058bd15 100644
--- a/Shifty/MainMenu.xib
+++ b/Shifty/MainMenu.xib
@@ -22,10 +22,11 @@
-
+
+
@@ -55,10 +56,10 @@
-
+
+
+
+
+
+
@@ -185,7 +192,7 @@
-
+
diff --git a/Shifty/NightShiftManager.swift b/Shifty/NightShiftManager.swift
index ad5020c..88b8496 100644
--- a/Shifty/NightShiftManager.swift
+++ b/Shifty/NightShiftManager.swift
@@ -12,6 +12,8 @@ import SwiftLog
class NightShiftManager {
static let shared = NightShiftManager()
let client = CBBlueLightClient.shared
+
+ var nightShiftChangeListeners = [() -> Void]()
var userSet: UserSet = .notSet
var userInitiatedShift = false
@@ -81,16 +83,11 @@ class NightShiftManager {
}
self.updateDarkMode()
-
- DispatchQueue.main.async {
- let appDelegate = NSApplication.shared.delegate as! AppDelegate
- appDelegate.updateMenuBarIcon()
-
- let prefWindow = appDelegate.preferenceWindowController
- let prefGeneral = prefWindow.viewControllers.compactMap { childViewController in
- return childViewController as? PrefGeneralViewController
- }.first
- prefGeneral?.updateSchedule?()
+
+ for listener in self.nightShiftChangeListeners {
+ DispatchQueue.main.async {
+ listener()
+ }
}
}
@@ -108,6 +105,10 @@ class NightShiftManager {
}
}
+ func onNightShiftChange(_ listener: @escaping () -> Void) {
+ nightShiftChangeListeners.append(listener)
+ }
+
func updateDarkMode() {
if UserDefaults.standard.bool(forKey: Keys.isDarkModeSyncEnabled) {
let scheduledState = client.scheduledState
diff --git a/Shifty/PrefGeneralViewController.swift b/Shifty/PrefGeneralViewController.swift
index 77f90ac..d369ff7 100644
--- a/Shifty/PrefGeneralViewController.swift
+++ b/Shifty/PrefGeneralViewController.swift
@@ -57,11 +57,6 @@ class PrefGeneralViewController: NSViewController, MASPreferencesViewController
@IBOutlet weak var toLabel: NSTextField!
@IBOutlet weak var customTimeStackView: NSStackView!
- let prefs = UserDefaults.standard
- var setStatusToggle: (() -> Void)?
- var updateSchedule: (() -> Void)?
- var updateDarkMode: (() -> Void)?
-
var appDelegate: AppDelegate!
var prefWindow: NSWindow!
@@ -72,6 +67,10 @@ class PrefGeneralViewController: NSViewController, MASPreferencesViewController
appDelegate = NSApplication.shared.delegate as? AppDelegate
prefWindow = appDelegate.preferenceWindowController.window
+
+ NightShiftManager.shared.onNightShiftChange {
+ self.updateSchedule()
+ }
//Hide True Tone settings on unsupported computers
if #available(macOS 10.14, *) {
@@ -86,31 +85,31 @@ class PrefGeneralViewController: NSViewController, MASPreferencesViewController
if !ProcessInfo().isOperatingSystemAtLeast(OperatingSystemVersion(majorVersion: 10, minorVersion: 13, patchVersion: 0)) {
view.wantsLayer = false
}
-
- updateSchedule = {
- switch NightShiftManager.shared.schedule {
- case .off:
- self.schedulePopup.select(self.offMenuItem)
- self.customTimeStackView.isHidden = true
- case .custom(start: let startTime, end: let endTime):
- self.schedulePopup.select(self.customMenuItem)
- let startDate = Date(startTime)
- let endDate = Date(endTime)
-
- self.fromTimePicker.dateValue = startDate
- self.toTimePicker.dateValue = endDate
- self.customTimeStackView.isHidden = false
- case .solar:
- self.schedulePopup.select(self.sunMenuItem)
- self.customTimeStackView.isHidden = true
- }
- }
}
override func viewWillAppear() {
super.viewWillAppear()
- updateSchedule?()
+ updateSchedule()
+ }
+
+ func updateSchedule() {
+ switch NightShiftManager.shared.schedule {
+ case .off:
+ self.schedulePopup.select(self.offMenuItem)
+ self.customTimeStackView.isHidden = true
+ case .custom(start: let startTime, end: let endTime):
+ self.schedulePopup.select(self.customMenuItem)
+ let startDate = Date(startTime)
+ let endDate = Date(endTime)
+
+ self.fromTimePicker.dateValue = startDate
+ self.toTimePicker.dateValue = endDate
+ self.customTimeStackView.isHidden = false
+ case .solar:
+ self.schedulePopup.select(self.sunMenuItem)
+ self.customTimeStackView.isHidden = true
+ }
}
//MARK: IBActions
@@ -149,7 +148,7 @@ class PrefGeneralViewController: NSViewController, MASPreferencesViewController
if !UIElement.isProcessTrusted() {
logw("Accessibility permissions alert shown")
- prefs.set(false, forKey: Keys.isWebsiteControlEnabled)
+ UserDefaults.standard.set(false, forKey: Keys.isWebsiteControlEnabled)
NSApp.runModal(for: AccessibilityWindow().window!)
}
} else {
diff --git a/Shifty/PrefManager.swift b/Shifty/PrefManager.swift
index 55f4ac1..2368e30 100644
--- a/Shifty/PrefManager.swift
+++ b/Shifty/PrefManager.swift
@@ -16,7 +16,8 @@ enum Keys {
static let isWebsiteControlEnabled = "isWebsiteControlEnabled"
static let trueToneControl = "trueToneControl"
static let analyticsPermission = "fabricCrashlyticsPermission"
- static let disabledApps = "disabledApps"
+ static let currentAppDisableRules = "disabledApps"
+ static let runningAppDisableRules = "disabledRunningApps"
static let browserRules = "browserRules"
static let toggleNightShiftShortcut = "toggleNightShiftShortcut"
@@ -55,40 +56,12 @@ class PrefManager {
Keys.isWebsiteControlEnabled: NSNumber(value: false),
Keys.trueToneControl: NSNumber(value: false),
Keys.analyticsPermission: NSNumber(value: false),
- Keys.disabledApps: NSData(),
+ Keys.currentAppDisableRules: NSData(),
+ Keys.runningAppDisableRules: NSData(),
Keys.browserRules: NSData(),
Keys.hasSetupWindowShown: NSNumber(value: false)
] as [String : Any]
userDefaults.register(defaults: factoryDefaults)
}
-
- func synchronize() {
- userDefaults.synchronize()
- }
-
- func reset() {
- userDefaults.removeObject(forKey: Keys.isAutoLaunchEnabled)
- userDefaults.removeObject(forKey: Keys.isStatusToggleEnabled)
- userDefaults.removeObject(forKey: Keys.isIconSwitchingEnabled)
- userDefaults.removeObject(forKey: Keys.isDarkModeSyncEnabled)
- userDefaults.removeObject(forKey: Keys.isWebsiteControlEnabled)
- userDefaults.removeObject(forKey: Keys.trueToneControl)
- userDefaults.removeObject(forKey: Keys.disabledApps)
- userDefaults.removeObject(forKey: Keys.browserRules)
- userDefaults.removeObject(forKey: Keys.toggleNightShiftShortcut)
- userDefaults.removeObject(forKey: Keys.incrementColorTempShortcut)
- userDefaults.removeObject(forKey: Keys.decrementColorTempShortcut)
- userDefaults.removeObject(forKey: Keys.disableAppShortcut)
- userDefaults.removeObject(forKey: Keys.disableDomainShortcut)
- userDefaults.removeObject(forKey: Keys.disableSubdomainShortcut)
- userDefaults.removeObject(forKey: Keys.disableHourShortcut)
- userDefaults.removeObject(forKey: Keys.disableCustomShortcut)
- userDefaults.removeObject(forKey: Keys.toggleTrueToneShortcut)
- userDefaults.removeObject(forKey: Keys.toggleDarkModeShortcut)
- userDefaults.removeObject(forKey: Keys.analyticsPermission)
- userDefaults.removeObject(forKey: Keys.hasSetupWindowShown)
-
- synchronize()
- }
}
diff --git a/Shifty/PrefShortcutsViewController.swift b/Shifty/PrefShortcutsViewController.swift
index 35b6340..93389ce 100644
--- a/Shifty/PrefShortcutsViewController.swift
+++ b/Shifty/PrefShortcutsViewController.swift
@@ -128,8 +128,8 @@ class PrefShortcutsViewController: NSViewController, MASPreferencesViewControlle
MASShortcutBinder.shared().bindShortcut(withDefaultsKey: Keys.disableAppShortcut) {
guard let menu = self.statusMenuController else { return }
- if !menu.disableAppMenuItem.isHidden && menu.disableAppMenuItem.isEnabled {
- self.statusMenuController?.disableForApp(self)
+ if !menu.disableCurrentAppMenuItem.isHidden && menu.disableCurrentAppMenuItem.isEnabled {
+ self.statusMenuController?.disableForCurrentApp(self)
} else {
NSSound.beep()
}
diff --git a/Shifty/RuleManager.swift b/Shifty/RuleManager.swift
index d9da9e1..944d68d 100644
--- a/Shifty/RuleManager.swift
+++ b/Shifty/RuleManager.swift
@@ -13,13 +13,67 @@ import ScriptingBridge
class RuleManager {
static var shared = RuleManager()
- var disabledApps = Set() {
- didSet {
- UserDefaults.standard.set(try? PropertyListEncoder().encode(disabledApps), forKey: Keys.disabledApps)
+ init() {
+ if let appData = UserDefaults.standard.value(forKey: Keys.currentAppDisableRules) as? Data {
+ do {
+ currentAppDisableRules = try PropertyListDecoder().decode(Set.self, from: appData)
+ } catch {
+ logw("Error: \(error.localizedDescription)")
+ }
+ }
+
+ if let appData = UserDefaults.standard.value(forKey: Keys.runningAppDisableRules) as? Data {
+ do {
+ runningAppDisableRules = try PropertyListDecoder().decode(Set.self, from: appData)
+ } catch let error {
+ logw("Error: \(error.localizedDescription)")
+ }
+ }
+
+ if let browserData = UserDefaults.standard.value(forKey: Keys.browserRules) as? Data {
+ do {
+ browserRules = try PropertyListDecoder().decode(Set.self, from: browserData)
+ } catch let error {
+ logw("Error: \(error.localizedDescription)")
+ }
+ }
+
+ NSWorkspace.shared.notificationCenter.addObserver(
+ forName: NSWorkspace.didActivateApplicationNotification,
+ object: nil,
+ queue: nil)
+ { notification in
+ self.appSwitched(notification: notification)
+ }
+
+ NSWorkspace.shared.notificationCenter.addObserver(
+ forName: NSWorkspace.didLaunchApplicationNotification,
+ object: nil,
+ queue: nil)
+ { notification in
+ self.appSwitched(notification: notification)
+ }
+
+ NSWorkspace.shared.notificationCenter.addObserver(
+ forName: NSWorkspace.didTerminateApplicationNotification,
+ object: nil,
+ queue: nil)
+ { notification in
+ self.appSwitched(notification: notification)
}
}
+ private var currentAppDisableRules = Set() {
+ didSet {
+ UserDefaults.standard.set(try? PropertyListEncoder().encode(currentAppDisableRules), forKey: Keys.currentAppDisableRules)
+ }
+ }
+ private var runningAppDisableRules = Set() {
+ didSet {
+ UserDefaults.standard.set(try? PropertyListEncoder().encode(runningAppDisableRules), forKey: Keys.runningAppDisableRules)
+ }
+ }
var browserRules = Set() {
didSet(newValue) {
@@ -27,177 +81,198 @@ class RuleManager {
}
}
-
-
var currentApp: NSRunningApplication? {
- return NSWorkspace.shared.menuBarOwningApplication
+ NSWorkspace.shared.menuBarOwningApplication
}
+ var runningApps: [NSRunningApplication] {
+ NSWorkspace.shared.runningApplications
+ }
- var disabledForApp: Bool {
- get {
- guard let bundleIdentifier = currentApp?.bundleIdentifier else {
- logw("Could not obtain bundle identifier of current application")
- return false
- }
- return disabledApps.filter {
- $0.bundleIdentifier == bundleIdentifier }.count > 0
- }
- set(newValue) {
- guard let bundleIdentifier = currentApp?.bundleIdentifier else {
- logw("Could not obtain bundle identifier of current application")
- return
- }
- let rule = AppRule(bundleIdentifier: bundleIdentifier, fullScreenOnly: false)
- if newValue {
- disabledApps.insert(rule)
- NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
- } else {
- guard let index = disabledApps.firstIndex(of: rule) else { return }
- disabledApps.remove(at: index)
- NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
- }
+ var isDisabledForCurrentApp: Bool {
+ guard let bundleIdentifier = currentApp?.bundleIdentifier else {
+ logw("Could not obtain bundle identifier of current application")
+ return false
}
+ return currentAppDisableRules.filter {
+ $0.bundleIdentifier == bundleIdentifier }.count > 0
+ }
+
+ func addCurrentAppDisableRule(forApp app: NSRunningApplication) {
+ guard let bundleID = app.bundleIdentifier else { return }
+ let rule = AppRule(bundleIdentifier: bundleID, fullScreenOnly: false)
+ currentAppDisableRules.insert(rule)
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
+ }
+
+ func removeCurrentAppDisableRule(forApp app: NSRunningApplication) {
+ guard let bundleID = app.bundleIdentifier,
+ let index = currentAppDisableRules.firstIndex(where: {
+ $0.bundleIdentifier == bundleID
+ }) else { return }
+
+ currentAppDisableRules.remove(at: index)
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
}
+ var isDisabledForRunningApp: Bool {
+ disabledCurrentlyRunningApps.count > 0
+ }
- var disabledForDomain: Bool {
- get {
- guard let currentDomain = BrowserManager.shared.currentDomain else { return false }
- let disabledDomain = browserRules.filter {
- $0.type == .domain && $0.host == currentDomain }.count > 0
- return disabledDomain
+ /// The currently running apps that Night Shift is disabled for
+ var disabledCurrentlyRunningApps: [NSRunningApplication] {
+ let disabledBundleIDs = Set(runningAppDisableRules.map { $0.bundleIdentifier })
+ return runningApps.filter {
+ guard let bundleID = $0.bundleIdentifier else { return false }
+ return disabledBundleIDs.contains(bundleID)
}
- set(newValue) {
- guard let currentDomain = BrowserManager.shared.currentDomain else { return }
- let rule = BrowserRule(type: .domain, host: currentDomain)
- if newValue {
- browserRules.insert(rule)
- NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
- } else {
- guard let index = browserRules.firstIndex(of: rule) else { return }
+ }
+
+ func isDisabledWhenRunningApp(_ app: NSRunningApplication) -> Bool {
+ guard let bundleID = app.bundleIdentifier else { return false }
+ return runningAppDisableRules.contains(where: { $0.bundleIdentifier == bundleID })
+ }
+
+ func addRunningAppDisableRule(forApp app: NSRunningApplication) {
+ guard let bundleID = app.bundleIdentifier else { return }
+ let rule = AppRule(bundleIdentifier: bundleID, fullScreenOnly: false)
+
+ runningAppDisableRules.insert(rule)
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
+ }
+
+ func removeRunningAppDisableRule(forApp app: NSRunningApplication) {
+ guard let bundleID = app.bundleIdentifier,
+ let index = runningAppDisableRules.firstIndex(where: {
+ $0.bundleIdentifier == bundleID
+ }) else { return }
- if ruleForSubdomain == .enabled {
- ruleForSubdomain = .none
- }
- browserRules.remove(at: index)
- NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
- }
+ runningAppDisableRules.remove(at: index)
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
+ }
+
+
+ var isDisabledForDomain: Bool {
+ guard let currentDomain = BrowserManager.shared.currentDomain else { return false }
+ let disabledDomain = browserRules.filter {
+ $0.type == .domain && $0.host == currentDomain }.count > 0
+ return disabledDomain
+ }
+
+ func addDomainDisableRule(forDomain domain: String) {
+ let rule = BrowserRule(type: .domain, host: domain)
+ browserRules.insert(rule)
+
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
+ }
+
+ func removeDomainDisableRule(forDomain domain: String) {
+ let rule = BrowserRule(type: .domain, host: domain)
+ guard let index = browserRules.firstIndex(of: rule) else { return }
+ browserRules.remove(at: index)
+
+ if let currentSubdomain = BrowserManager.shared.currentSubdomain,
+ getSubdomainRule(forSubdomain: currentSubdomain) == .enabled
+ {
+ setSubdomainRule(.none, forSubdomain: currentSubdomain)
}
+
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
}
+ func getSubdomainRule(forSubdomain subdomain: String) -> SubdomainRuleType {
+ if isDisabledForDomain {
+ let isEnabled = (browserRules.filter {
+ $0.type == .subdomainEnabled
+ && $0.host == subdomain
+ }.count > 0)
+ if isEnabled {
+ return .enabled
+ }
+ } else {
+ let isDisabled = (browserRules.filter {
+ $0.type == .subdomainDisabled
+ && $0.host == subdomain
+ }.count > 0)
+ if isDisabled {
+ return .disabled
+ }
+ }
+ return .none
+ }
- var ruleForSubdomain: SubdomainRuleType {
- get {
- guard let currentSubdomain = BrowserManager.shared.currentSubdomain else { return .none }
+ var ruleForCurrentSubdomain: SubdomainRuleType {
+ guard let currentSubdomain = BrowserManager.shared.currentSubdomain else { return .none }
+ return getSubdomainRule(forSubdomain: currentSubdomain)
+ }
+
+ func setSubdomainRule(_ ruleType: SubdomainRuleType, forSubdomain subdomain: String) {
+ switch ruleType {
+ case .disabled:
+ let rule = BrowserRule(type: .subdomainDisabled, host: subdomain)
+ browserRules.insert(rule)
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
+ case .enabled:
+ let rule = BrowserRule(type: .subdomainEnabled, host: subdomain)
+ browserRules.insert(rule)
+ NightShiftManager.shared.respond(to: .nightShiftEnableRuleActivated)
+ case .none:
+ var rule: BrowserRule
+ let prevValue = getSubdomainRule(forSubdomain: subdomain)
- if disabledForDomain {
- let isEnabled = (browserRules.filter {
- $0.type == .subdomainEnabled
- && $0.host == currentSubdomain
- }.count > 0)
- if isEnabled {
- return .enabled
- }
- } else {
- let isDisabled = (browserRules.filter {
- $0.type == .subdomainDisabled
- && $0.host == currentSubdomain
- }.count > 0)
- if isDisabled {
- return .disabled
- }
+ //Remove rule from set before triggering NightShiftEvent
+ switch prevValue {
+ case .disabled:
+ rule = BrowserRule(type: .subdomainDisabled, host: subdomain)
+ case .enabled:
+ rule = BrowserRule(type: .subdomainEnabled, host: subdomain)
+ case .none:
+ return
}
- return .none
- }
- set(newValue) {
- guard let currentSubdomain = BrowserManager.shared.currentSubdomain else { return }
+ guard let index = browserRules.firstIndex(of: rule) else { return }
+ browserRules.remove(at: index)
- switch newValue {
+ switch prevValue {
case .disabled:
- let rule = BrowserRule(type: .subdomainDisabled, host: currentSubdomain)
- browserRules.insert(rule)
- NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
+ NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
case .enabled:
- let rule = BrowserRule(type: .subdomainEnabled, host: currentSubdomain)
- browserRules.insert(rule)
- NightShiftManager.shared.respond(to: .nightShiftEnableRuleActivated)
+ NightShiftManager.shared.respond(to: .nightShiftEnableRuleDeactivated)
case .none:
- var rule: BrowserRule
- let prevValue = ruleForSubdomain
-
- //Remove rule from set before triggering NightShiftEvent
- switch prevValue {
- case .disabled:
- rule = BrowserRule(type: .subdomainDisabled, host: currentSubdomain)
- case .enabled:
- rule = BrowserRule(type: .subdomainEnabled, host: currentSubdomain)
- case .none:
- return
- }
- guard let index = browserRules.firstIndex(of: rule) else { return }
- browserRules.remove(at: index)
-
- switch prevValue {
- case .disabled:
- NightShiftManager.shared.respond(to: .nightShiftDisableRuleDeactivated)
- case .enabled:
- NightShiftManager.shared.respond(to: .nightShiftEnableRuleDeactivated)
- case .none:
- break
- }
+ break
}
}
}
-
var disableRuleIsActive: Bool {
- return disabledForApp || (disabledForDomain && ruleForSubdomain != .enabled) || ruleForSubdomain == .disabled
+ return isDisabledForCurrentApp || isDisabledForRunningApp ||
+ (isDisabledForDomain && ruleForCurrentSubdomain != .enabled) ||
+ ruleForCurrentSubdomain == .disabled
}
-
func removeRulesForCurrentState() {
- disabledForApp = false
- disabledForDomain = false
- ruleForSubdomain = .none
- }
-
-
-
- init() {
- NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didActivateApplicationNotification,
- object: nil,
- queue: nil) {
- self.appSwitched(notification: $0)
- }
-
- if let appData = UserDefaults.standard.value(forKey: Keys.disabledApps) as? Data {
- do {
- disabledApps = try PropertyListDecoder().decode(Set.self, from: appData)
- } catch let error {
- logw("Error: \(error.localizedDescription)")
+ if let currentApp = currentApp {
+ removeCurrentAppDisableRule(forApp: currentApp)
+ for app in disabledCurrentlyRunningApps {
+ removeRunningAppDisableRule(forApp: app)
}
}
-
- if let browserData = UserDefaults.standard.value(forKey: Keys.browserRules) as? Data {
- do {
- browserRules = try PropertyListDecoder().decode(Set.self, from: browserData)
- } catch let error {
- logw("Error: \(error.localizedDescription)")
- }
+ if let currentDomain = BrowserManager.shared.currentDomain {
+ removeDomainDisableRule(forDomain: currentDomain)
+ }
+ if let currentSubdomain = BrowserManager.shared.currentSubdomain {
+ setSubdomainRule(.none, forSubdomain: currentSubdomain)
}
}
-
private func appSwitched(notification: Notification) {
BrowserManager.shared.stopBrowserWatcher()
- if disabledForApp {
+ if isDisabledForCurrentApp || isDisabledForRunningApp {
NightShiftManager.shared.respond(to: .nightShiftDisableRuleActivated)
} else if BrowserManager.shared.currentAppIsSupportedBrowser {
BrowserManager.shared.updateForSupportedBrowser()
@@ -229,6 +304,7 @@ enum SubdomainRuleType: String, Codable {
struct AppRule: CustomStringConvertible, Hashable, Codable {
var bundleIdentifier: BundleIdentifier
+ // Currently unused
var fullScreenOnly: Bool
var description: String {
diff --git a/Shifty/StatusMenuController.swift b/Shifty/StatusMenuController.swift
index b2b6a3b..810512b 100644
--- a/Shifty/StatusMenuController.swift
+++ b/Shifty/StatusMenuController.swift
@@ -18,7 +18,8 @@ class StatusMenuController: NSObject, NSMenuDelegate {
@IBOutlet weak var trueToneMenuItem: NSMenuItem!
@IBOutlet weak var sliderMenuItem: NSMenuItem!
@IBOutlet weak var descriptionMenuItem: NSMenuItem!
- @IBOutlet weak var disableAppMenuItem: NSMenuItem!
+ @IBOutlet weak var disableCurrentAppMenuItem: NSMenuItem!
+ @IBOutlet weak var disableRunningAppMenuItem: NSMenuItem!
@IBOutlet weak var disableDomainMenuItem: NSMenuItem!
@IBOutlet weak var disableSubdomainMenuItem: NSMenuItem!
@IBOutlet weak var enableBrowserAutomationMenuItem: NSMenuItem!
@@ -125,7 +126,7 @@ class StatusMenuController: NSObject, NSMenuDelegate {
setDescriptionText()
assignKeyboardShortcutToMenuItem(powerMenuItem, userDefaultsKey: Keys.toggleNightShiftShortcut)
- assignKeyboardShortcutToMenuItem(disableAppMenuItem, userDefaultsKey: Keys.disableAppShortcut)
+ assignKeyboardShortcutToMenuItem(disableCurrentAppMenuItem, userDefaultsKey: Keys.disableAppShortcut)
assignKeyboardShortcutToMenuItem(disableDomainMenuItem, userDefaultsKey: Keys.disableDomainShortcut)
assignKeyboardShortcutToMenuItem(disableSubdomainMenuItem, userDefaultsKey: Keys.disableSubdomainShortcut)
assignKeyboardShortcutToMenuItem(disableHourMenuItem, userDefaultsKey: Keys.disableHourShortcut)
@@ -193,19 +194,33 @@ class StatusMenuController: NSObject, NSMenuDelegate {
//MARK: disable for app
- if RuleManager.shared.disabledForApp {
- disableAppMenuItem.state = .on
- disableAppMenuItem.title = String(format: NSLocalizedString("menu.disabled_for", comment: "Disabled for %@"), currentAppName)
+ if RuleManager.shared.isDisabledForCurrentApp {
+ disableCurrentAppMenuItem.state = .on
+ disableCurrentAppMenuItem.title = String(format: NSLocalizedString("menu.disabled_for", comment: "Disabled for %@"), currentAppName)
} else {
- disableAppMenuItem.state = .off
- disableAppMenuItem.title = String(format: NSLocalizedString("menu.disable_for", comment: "Disable for %@"), currentAppName)
+ disableCurrentAppMenuItem.state = .off
+ disableCurrentAppMenuItem.title = String(format: NSLocalizedString("menu.disable_for", comment: "Disable for %@"), currentAppName)
+ }
+
+ if let currentApp = RuleManager.shared.currentApp,
+ RuleManager.shared.isDisabledWhenRunningApp(currentApp)
+ {
+ disableRunningAppMenuItem.state = .on
+ disableRunningAppMenuItem.title = String(format: NSLocalizedString(
+ "menu.disabled_for_running_app",
+ comment: "Disabled when %@ is running"), currentAppName)
+ } else {
+ disableRunningAppMenuItem.state = .off
+ disableRunningAppMenuItem.title = String(format: NSLocalizedString(
+ "menu.disable_for_running_app",
+ comment: "Disable when %@ is running"), currentAppName)
}
// MARK: disable for domain
if BrowserManager.shared.hasValidDomain {
disableDomainMenuItem.isHidden = false
- if RuleManager.shared.disabledForDomain {
+ if RuleManager.shared.isDisabledForDomain {
disableDomainMenuItem.state = .on
disableDomainMenuItem.title = String(format: NSLocalizedString("menu.disabled_for", comment: "Disabled for %@"), currentDomain ?? "")
} else {
@@ -220,13 +235,13 @@ class StatusMenuController: NSObject, NSMenuDelegate {
// MARK: disable for subdomain
if BrowserManager.shared.hasValidSubdomain {
disableSubdomainMenuItem.isHidden = false
- if RuleManager.shared.ruleForSubdomain == .enabled {
+ if RuleManager.shared.ruleForCurrentSubdomain == .enabled {
disableSubdomainMenuItem.state = .on
disableSubdomainMenuItem.title = String(format: NSLocalizedString("menu.enabled_for", comment: "Enabled for %@"), currentSubdomain ?? "")
- } else if RuleManager.shared.ruleForSubdomain == .disabled {
+ } else if RuleManager.shared.ruleForCurrentSubdomain == .disabled {
disableSubdomainMenuItem.state = .on
disableSubdomainMenuItem.title = String(format: NSLocalizedString("menu.disabled_for", comment: "Disabled for %@"), currentSubdomain ?? "")
- } else if RuleManager.shared.disabledForDomain {
+ } else if RuleManager.shared.isDisabledForDomain {
disableSubdomainMenuItem.state = .off
disableSubdomainMenuItem.title = String(format: NSLocalizedString("menu.enable_for", comment: "Enable for %@"), currentSubdomain ?? "")
} else {
@@ -295,9 +310,9 @@ class StatusMenuController: NSObject, NSMenuDelegate {
case .disabled:
if NightShiftManager.shared.isDisableRuleActive {
trueToneMenuItem.isEnabled = false
- if RuleManager.shared.disabledForDomain {
+ if RuleManager.shared.isDisabledForDomain {
trueToneMenuItem.title = String(format: NSLocalizedString("menu.true_tone_disabled_for", comment: "True Tone is disabled for %@"), currentDomain ?? "")
- } else if RuleManager.shared.ruleForSubdomain == .disabled {
+ } else if RuleManager.shared.ruleForCurrentSubdomain == .disabled {
trueToneMenuItem.title = String(format: NSLocalizedString("menu.true_tone_disabled_for", comment: "True Tone is disabled for %@"), currentSubdomain ?? "")
} else {
trueToneMenuItem.title = String(format: NSLocalizedString("menu.true_tone_disabled_for", comment: "True Tone is disabled for %@"), currentAppName)
@@ -420,36 +435,50 @@ class StatusMenuController: NSObject, NSMenuDelegate {
- @IBAction func disableForApp(_ sender: Any) {
- if RuleManager.shared.disabledForApp {
- RuleManager.shared.disabledForApp = false
+ @IBAction func disableForCurrentApp(_ sender: Any) {
+ guard let currentApp = RuleManager.shared.currentApp else { return }
+
+ if RuleManager.shared.isDisabledForCurrentApp {
+ RuleManager.shared.removeCurrentAppDisableRule(forApp: currentApp)
} else {
- RuleManager.shared.disabledForApp = true
+ RuleManager.shared.addCurrentAppDisableRule(forApp: currentApp)
}
Event.disableForCurrentApp(state: (sender as? NSMenuItem)?.state == .on).record()
}
-
+ @IBAction func disableForRunningApp(_ sender: Any) {
+ guard let currentApp = RuleManager.shared.currentApp else { return }
+
+ if RuleManager.shared.isDisabledForRunningApp {
+ RuleManager.shared.removeRunningAppDisableRule(forApp: currentApp)
+ } else {
+ RuleManager.shared.addRunningAppDisableRule(forApp: currentApp)
+ }
+ }
@IBAction func disableForDomain(_ sender: Any) {
- if RuleManager.shared.disabledForDomain {
- RuleManager.shared.disabledForDomain = false
+ guard let currentDomain = BrowserManager.shared.currentDomain else { return }
+
+ if RuleManager.shared.isDisabledForDomain {
+ RuleManager.shared.removeDomainDisableRule(forDomain: currentDomain)
} else {
- RuleManager.shared.disabledForDomain = true
+ RuleManager.shared.addDomainDisableRule(forDomain: currentDomain)
}
}
@IBAction func disableForSubdomain(_ sender: Any) {
- if RuleManager.shared.ruleForSubdomain == .none {
- if RuleManager.shared.disabledForDomain {
- RuleManager.shared.ruleForSubdomain = .enabled
+ guard let currentSubdomain = BrowserManager.shared.currentSubdomain else { return }
+
+ if RuleManager.shared.ruleForCurrentSubdomain == .none {
+ if RuleManager.shared.isDisabledForDomain {
+ RuleManager.shared.setSubdomainRule(.enabled, forSubdomain: currentSubdomain)
} else {
- RuleManager.shared.ruleForSubdomain = .disabled
+ RuleManager.shared.setSubdomainRule(.disabled, forSubdomain: currentSubdomain)
}
} else {
- RuleManager.shared.ruleForSubdomain = .none
+ RuleManager.shared.setSubdomainRule(.none, forSubdomain: currentSubdomain)
}
}
diff --git a/Shifty/en.lproj/Localizable.strings b/Shifty/en.lproj/Localizable.strings
index e8ae230..3b256fe 100644
--- a/Shifty/en.lproj/Localizable.strings
+++ b/Shifty/en.lproj/Localizable.strings
@@ -13,6 +13,8 @@
"menu.toggle_off" = "Turn off Night Shift";
"menu.disable_for" = "Disable for %@";
"menu.disabled_for" = "Disabled for %@";
+"menu.disable_for_running_app" = "Disable when %@ is running";
+"menu.disabled_for_running_app" = "Disabled when %@ is running";
"menu.enable_for" = "Enable for %@";
"menu.enabled_for" = "Enabled for %@";
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index e9305f0..3ea4c2d 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -19,15 +19,15 @@ GIT
GEM
remote: https://rubygems.org/
specs:
- addressable (2.5.2)
- public_suffix (>= 2.0.2, < 4.0)
+ addressable (2.8.0)
+ public_suffix (>= 2.0.2, < 5.0)
colorator (1.1.0)
concurrent-ruby (1.0.5)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
eventmachine (1.2.5)
- ffi (1.9.23)
+ ffi (1.11.1)
forwardable-extended (2.6.0)
http_parser.rb (0.6.0)
i18n (0.9.5)
@@ -51,7 +51,7 @@ GEM
mercenary (0.3.6)
pathutil (0.16.1)
forwardable-extended (~> 2.6)
- public_suffix (3.0.2)
+ public_suffix (4.0.6)
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)