diff --git a/alt-tab-macos/logic/DebugProfile.swift b/alt-tab-macos/logic/DebugProfile.swift index 564a02e2a..242114409 100644 --- a/alt-tab-macos/logic/DebugProfile.swift +++ b/alt-tab-macos/logic/DebugProfile.swift @@ -42,9 +42,9 @@ class DebugProfile { } private static func appPreferences() -> String { - return nestedSeparator + Preferences.rawValues + return nestedSeparator + Preferences.all .sorted { $0.0 < $1.0 } - .map { $0 + intraSeparator + $1 } + .map { $0.key + intraSeparator + Preferences.getAsString($0.key)! } .joined(separator: nestedSeparator) } diff --git a/alt-tab-macos/logic/Preferences.swift b/alt-tab-macos/logic/Preferences.swift index f4e7dc4c4..31698ab44 100644 --- a/alt-tab-macos/logic/Preferences.swift +++ b/alt-tab-macos/logic/Preferences.swift @@ -1,193 +1,113 @@ import Cocoa import Carbon.HIToolbox.Events -class Preferences { - // the following constant are not exposed as preferences but may be in the future, probably through macro preferences - static let windowMaterial = NSVisualEffectView.Material.dark - static let fontColor = NSColor.white - static let windowPadding = CGFloat(23) - static let interCellPadding = CGFloat(5) - static let intraCellPadding = CGFloat(5) - static let fontIconSize = CGFloat(20) - - static let themeMacro = MacroPreferenceHelper<(CGFloat, CGFloat, CGFloat, NSColor, NSColor)>([ - MacroPreference(" macOS", (0, 5, 20, .clear, NSColor(red: 0, green: 0, blue: 0, alpha: 0.4))), - MacroPreference("❖ Windows 10", (2, 0, 0, .white, .clear)) - ]) - static let metaKeyMacro = MacroPreferenceHelper<([Int], NSEvent.ModifierFlags)>([ - MacroPreference("⌥ option", ([kVK_Option, kVK_RightOption], .option)), - MacroPreference("⌃ control", ([kVK_Control, kVK_RightControl], .control)), - MacroPreference("⌘ command", ([kVK_Command, kVK_RightCommand], .command)) - ]) - static let showOnScreenMacro = MacroPreferenceHelper([ - MacroPreference("Main screen", ShowOnScreenPreference.main), - MacroPreference("Screen including mouse", ShowOnScreenPreference.mouse), - ]) +let defaults = UserDefaults.standard - static var defaults: [String: String] = [ - "maxScreenUsage": "80", - "minCellsPerRow": "5", - "maxCellsPerRow": "10", - "minRows": "3", - "iconSize": "32", - "fontHeight": "15", - "tabKeyCode": String(kVK_Tab), - "metaKey": metaKeyMacro.macros[0].label, - "windowDisplayDelay": "0", - "theme": themeMacro.macros[0].label, - "showOnScreen": showOnScreenMacro.macros[0].label, - "hideSpaceNumberLabels": String(false), +class Preferences { + // default values + static var defaultValues: [String : Any] = [ + "maxScreenUsage": Float(80), + "minCellsPerRow": Float(5), + "maxCellsPerRow": Float(10), + "minRows": Float(3), + "iconSize": Float(32), + "fontHeight": Float(15), + "tabKeyCode": kVK_Tab, + "windowDisplayDelay": 0, + "metaKey": MacroPreferences.metaKeyList.keys.first!, + "theme": MacroPreferences.themeList.keys.first!, + "showOnScreen": MacroPreferences.showOnScreenList.keys.first!, + "hideSpaceNumberLabels": false, ] - static var rawValues = [String: String]() - - static var cellBorderWidth: CGFloat! - static var cellCornerRadius: CGFloat! - static var tabKeyCode: UInt16! - static var highlightBorderColor: NSColor! - static var highlightBackgroundColor: NSColor! - static var metaKeyCodes: [UInt16]! - static var metaModifierFlag: NSEvent.ModifierFlags! - static var windowDisplayDelay: DispatchTimeInterval! - static var windowCornerRadius: CGFloat! - static var showOnScreen: ShowOnScreenPreference! - static var hideSpaceNumberLabels: Bool! - static var maxScreenUsage: CGFloat! - static var iconSize: CGFloat! - static var fontHeight: CGFloat! - static var font: NSFont! - static var minCellsPerRow: CGFloat! - static var maxCellsPerRow: CGFloat! - static var minRows: CGFloat! - - private static let defaultsFile = fileFromPreferencesFolder("alt-tab-macos-defaults.json") - private static let userFile = fileFromPreferencesFolder("alt-tab-macos.json") - - static func loadFromDiskAndUpdateValues() { - do { - try saveDefaultsToDisk() - let preferencesExist = FileManager.default.fileExists(atPath: userFile.path) - if !preferencesExist { - try FileManager.default.copyItem(at: defaultsFile, to: userFile) - } - rawValues = try loadFromDisk(userFile) - if preferencesExist { - let compatiblePreferences = rawValues.filter { defaults[$0.key] != nil } - rawValues = defaults.merging(compatiblePreferences) { (_, new) in new } - } - try rawValues.forEach { try updateAndValidateFromString($0.key, $0.value) } - if preferencesExist { - try saveRawToDisk() - } - } catch { - debugPrint("Error loading preferences", error) - if (FileManager.default.fileExists(atPath: userFile.path)) { - try! FileManager.default.removeItem(at: userFile) - } - loadFromDiskAndUpdateValues() - } - } - - static func updateAndValidateFromString(_ valueName: String, _ value: String) throws { - switch valueName { - case "maxScreenUsage": - maxScreenUsage = try CGFloat(CGFloat(value).orThrow() / 100) - case "minCellsPerRow": - minCellsPerRow = try CGFloat(value).orThrow() - case "maxCellsPerRow": - maxCellsPerRow = try CGFloat(value).orThrow() - case "minRows": - minRows = try CGFloat(value).orThrow() - case "iconSize": - iconSize = try CGFloat(value).orThrow() - case "fontHeight": - fontHeight = try CGFloat(value).orThrow() - font = NSFont.systemFont(ofSize: fontHeight) - case "tabKeyCode": - tabKeyCode = try UInt16(value).orThrow() - case "metaKey": - let p = try metaKeyMacro.labelToMacro[value].orThrow() - metaKeyCodes = p.preferences.0.map { UInt16($0) } - metaModifierFlag = p.preferences.1 - case "theme": - let p = try themeMacro.labelToMacro[value].orThrow() - cellBorderWidth = p.preferences.0 - cellCornerRadius = p.preferences.1 - windowCornerRadius = p.preferences.2 - highlightBorderColor = p.preferences.3 - highlightBackgroundColor = p.preferences.4 - case "windowDisplayDelay": - windowDisplayDelay = DispatchTimeInterval.milliseconds(try Int(value).orThrow()) - case "showOnScreen": - let p = try showOnScreenMacro.labelToMacro[value].orThrow() - showOnScreen = p.preferences - case "hideSpaceNumberLabels": - hideSpaceNumberLabels = try Bool(value).orThrow() - default: - throw NSError.make(domain: "Preferences", message: "Tried to update an unknown preference: '\(valueName)' = '\(value)'") - } - rawValues[valueName] = value - } - - static func saveRawToDisk() throws { - ProcessInfo.processInfo.disableSuddenTermination() - try saveToDisk(rawValues, userFile) - ProcessInfo.processInfo.enableSuddenTermination() - } - private static func preferencesVersion(_ url: URL) throws -> Int { - return try Int(loadFromDisk(url)["version"] ?? "0").orThrow() + // constant values + // not exposed as preferences now but may be in the future, probably through macro preferences + static var windowMaterial: NSVisualEffectView.Material { .dark } + static var fontColor: NSColor { .white } + static var windowPadding: CGFloat { 23 } + static var interCellPadding: CGFloat { 5 } + static var intraCellPadding: CGFloat { 5 } + static var fontIconSize: CGFloat { 20 } + + // persisted values + static var maxScreenUsage: CGFloat { CGFloat(defaults.float(forKey: "maxScreenUsage")) } + static var minCellsPerRow: CGFloat { CGFloat(defaults.float(forKey: "minCellsPerRow")) } + static var maxCellsPerRow: CGFloat { CGFloat(defaults.float(forKey: "maxCellsPerRow")) } + static var minRows: CGFloat { CGFloat(defaults.float(forKey: "minRows")) } + static var iconSize: CGFloat { CGFloat(defaults.float(forKey: "iconSize")) } + static var fontHeight: CGFloat { CGFloat(defaults.float(forKey: "fontHeight")) } + static var tabKeyCode: UInt16 { UInt16(defaults.integer(forKey: "tabKeyCode")) } + static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(defaults.integer(forKey: "windowDisplayDelay")) } + static var hideSpaceNumberLabels: Bool { defaults.bool(forKey: "hideSpaceNumberLabels") } + + // macro values + static var theme: Theme { MacroPreferences.themeList[defaults.string(forKey: "theme")!]! } + static var metaKey: MetaKey { MacroPreferences.metaKeyList[defaults.string(forKey: "metaKey")!]! } + static var showOnScreen: ShowOnScreenPreference { MacroPreferences.showOnScreenList[defaults.string(forKey: "showOnScreen")!]! } + + // derived values + static var cellBorderWidth: CGFloat { theme.cellBorderWidth } + static var cellCornerRadius: CGFloat { theme.cellCornerRadius } + static var windowCornerRadius: CGFloat { theme.windowCornerRadius } + static var highlightBorderColor: NSColor { theme.highlightBorderColor } + static var highlightBackgroundColor: NSColor { theme.highlightBackgroundColor } + static var metaKeyCodes: [UInt16] { metaKey.keyCodes.map { UInt16($0) } } + static var metaModifierFlag: NSEvent.ModifierFlags { metaKey.modifierFlag } + static var font: NSFont { NSFont.systemFont(ofSize: fontHeight) } + + static func registerDefaults() { + defaults.register(defaults: defaultValues) } - private static func loadFromDisk(_ url: URL) throws -> [String: String] { - return try JSONDecoder().decode([String: String].self, from: Data(contentsOf: url)) + static func get(_ key: String) -> Any? { + defaults.object(forKey: key) } - private static func saveDefaultsToDisk() throws { - try saveToDisk(defaults, defaultsFile) + static func getAsString(_ key: String) -> String? { + defaults.string(forKey: key) } - private static func saveToDisk(_ values: [String: String], _ path: URL) throws { - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - try encoder - .encode(values) - .write(to: path) + static func set(_ key: String, _ value: Any?) { + defaults.set(value, forKey: key) } - private static func fileFromPreferencesFolder(_ fileName: String) -> URL { - return FileManager.default - .urls(for: .libraryDirectory, in: .userDomainMask) - .first! - .appendingPathComponent("Preferences", isDirectory: true) - .appendingPathComponent(fileName) - } + static var all: [String: Any] { defaults.dictionaryRepresentation() } } -struct MacroPreference { +struct Theme { let label: String - let preferences: T - - init(_ label: String, _ preferences: T) { - self.label = label - self.preferences = preferences - } + let cellBorderWidth: CGFloat + let cellCornerRadius: CGFloat + let windowCornerRadius: CGFloat + let highlightBorderColor: NSColor + let highlightBackgroundColor: NSColor } -class MacroPreferenceHelper { - let macros: [MacroPreference] - var labels = [String]() - var labelToMacro = [String: MacroPreference]() - - init(_ array: [MacroPreference]) { - self.macros = array - array.forEach { - labelToMacro[$0.label] = $0 - labels.append($0.label) - } - } +struct MetaKey { + let label: String + let keyCodes: [Int] + let modifierFlag: NSEvent.ModifierFlags } enum ShowOnScreenPreference { case main case mouse } + +// macros are collection of values derived from a single key +// we don't want to store every value in UserDefaults as the user could change them and contradict the macro +class MacroPreferences { + static let themeList = [ + " macOS": Theme(label: " macOS", cellBorderWidth: 0, cellCornerRadius: 5, windowCornerRadius: 20, highlightBorderColor: .clear, highlightBackgroundColor: NSColor(red: 0, green: 0, blue: 0, alpha: 0.4)), + "❖ Windows 10": Theme(label: "❖ Windows 10", cellBorderWidth: 2, cellCornerRadius: 0, windowCornerRadius: 0, highlightBorderColor: .white, highlightBackgroundColor: .clear), + ] + static let metaKeyList = [ + "⌥ option": MetaKey(label: "⌥ option", keyCodes: [kVK_Option, kVK_RightOption], modifierFlag: .option), + "⌃ control": MetaKey(label: "⌃ control", keyCodes: [kVK_Control, kVK_RightControl], modifierFlag: .control), + "⌘ command": MetaKey(label: "⌘ command", keyCodes: [kVK_Command, kVK_RightCommand], modifierFlag: .command) + ] + static let showOnScreenList = [ + "Main screen": ShowOnScreenPreference.main, + "Screen including mouse": ShowOnScreenPreference.mouse, + ] +} diff --git a/alt-tab-macos/logic/Screen.swift b/alt-tab-macos/logic/Screen.swift index a3ab74669..a0f72d4e8 100644 --- a/alt-tab-macos/logic/Screen.swift +++ b/alt-tab-macos/logic/Screen.swift @@ -2,7 +2,7 @@ import Cocoa class Screen { static func preferred() -> NSScreen { - switch Preferences.showOnScreen! { + switch Preferences.showOnScreen { case .mouse: return withMouse() ?? NSScreen.main!; // .main as fall-back case .main: return NSScreen.main!; } diff --git a/alt-tab-macos/ui/App.swift b/alt-tab-macos/ui/App.swift index fa5b89898..7339a9eba 100644 --- a/alt-tab-macos/ui/App.swift +++ b/alt-tab-macos/ui/App.swift @@ -32,7 +32,7 @@ class App: NSApplication, NSApplicationDelegate, NSWindowDelegate { #endif SystemPermissions.ensureAccessibilityCheckboxIsChecked() SystemPermissions.ensureScreenRecordingCheckboxIsChecked() - Preferences.loadFromDiskAndUpdateValues() + Preferences.registerDefaults() statusItem = Menubar.make(self) loadMainMenuXib() initPreferencesDependentComponents() diff --git a/alt-tab-macos/ui/preferences-window/LabelAndControl.swift b/alt-tab-macos/ui/preferences-window/LabelAndControl.swift index 2cb460750..ed71bb54c 100644 --- a/alt-tab-macos/ui/preferences-window/LabelAndControl.swift +++ b/alt-tab-macos/ui/preferences-window/LabelAndControl.swift @@ -4,7 +4,7 @@ class LabelAndControl: NSObject { static var callbackTarget: PreferencesWindow! static func makeLabelWithInput(_ labelText: String, _ rawName: String, _ width: CGFloat, _ suffixText: String? = nil, _ suffixUrl: String? = nil, _ validator: ((String) -> Bool)? = nil) -> [NSView] { - let input = TextField(Preferences.rawValues[rawName]!) + let input = TextField(Preferences.getAsString(rawName)!) input.validationHandler = validator input.delegate = input input.visualizeValidationState() @@ -15,19 +15,19 @@ class LabelAndControl: NSObject { static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String) -> [NSView] { let checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil) - setControlValue(checkbox, Preferences.rawValues[rawName]!) + setControlValue(checkbox, Preferences.getAsString(rawName)!) return makeLabelWithProvidedControl(labelText, rawName, checkbox) } static func makeLabelWithDropdown(_ labelText: String, _ rawName: String, _ values: [String], _ suffixText: String? = nil) -> [NSView] { let popUp = NSPopUpButton() popUp.addItems(withTitles: values) - popUp.selectItem(withTitle: Preferences.rawValues[rawName]!) + popUp.selectItem(withTitle: Preferences.getAsString(rawName)!) return makeLabelWithProvidedControl(labelText, rawName, popUp, suffixText) } static func makeLabelWithSlider(_ labelText: String, _ rawName: String, _ minValue: Double, _ maxValue: Double, _ numberOfTickMarks: Int, _ allowsTickMarkValuesOnly: Bool, _ unitText: String = "") -> [NSView] { - let value = Preferences.rawValues[rawName]! + let value = Preferences.getAsString(rawName)! let suffixText = value + " " + unitText let slider = NSSlider() slider.minValue = minValue @@ -74,8 +74,6 @@ class LabelAndControl: NSObject { return suffix } - - static func getControlValue(_ control: NSControl) -> String { if control is NSPopUpButton { return (control as! NSPopUpButton).titleOfSelectedItem! @@ -107,13 +105,10 @@ class LabelAndControl: NSObject { } } - - private static func updateSuffixWithValue(_ control: NSControl, _ value: String) { let suffixIdentifierPredicate = { (view: NSView) -> Bool in view.identifier?.rawValue == control.identifier!.rawValue + ControlIdentifierDiscriminator.SUFFIX.rawValue } - if let suffixView: NSTextField = control.superview?.subviews.first(where: suffixIdentifierPredicate) as? NSTextField { let regex = try! NSRegularExpression(pattern: "^[0-9]+") // first decimal let range = NSMakeRange(0, suffixView.stringValue.count) diff --git a/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift b/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift index 969e75f7e..fdab18b29 100644 --- a/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift +++ b/alt-tab-macos/ui/preferences-window/PreferencesWindow.swift @@ -1,7 +1,6 @@ import Cocoa class PreferencesWindow: NSWindow { - var windowCloseRequested = false let tabViewController = TabViewController() override init(contentRect: NSRect, styleMask style: StyleMask, backing backingStoreType: BackingStoreType, defer flag: Bool) { @@ -17,29 +16,10 @@ class PreferencesWindow: NSWindow { } func controlWasChanged(_ senderControl: NSControl) { - let key: String = senderControl.identifier!.rawValue - let previousValue: String = Preferences.rawValues[key]! - let newValue: String = LabelAndControl.getControlValue(senderControl) - let invalidTextField = senderControl is TextField && !(senderControl as! TextField).isValid() - - if (invalidTextField && !windowCloseRequested) || (newValue == previousValue && !invalidTextField) { - return - } - + let newValue = LabelAndControl.getControlValue(senderControl) LabelAndControl.updateControlExtras(senderControl, newValue) - - do { - // TODO: remove conditional as soon a Preference does validation on its own - if invalidTextField && windowCloseRequested { - throw NSError.make(domain: "Preferences", message: "Please enter a valid value for '" + key + "'") - } - try Preferences.updateAndValidateFromString(key, newValue) - try Preferences.saveRawToDisk() - (App.shared as! App).initPreferencesDependentComponents() - } catch let error { - debugPrint("PreferencesWindow: save: error", key, newValue, error) - showSaveErrorSheetModal(error as NSError, senderControl, key, previousValue) // allows recursive call by user choice - } + Preferences.set(senderControl.identifier!.rawValue, newValue) + (App.shared as! App).initPreferencesDependentComponents() } private func setupWindow() { @@ -56,47 +36,4 @@ class PreferencesWindow: NSWindow { tabViewController.addTabViewItem(UpdatesTab.make()) tabViewController.addTabViewItem(AboutTab.make()) } - - private func challengeNextInvalidEditableTextField() { - let invalidFields = (contentView? - .findNestedViews(subclassOf: TextField.self) - .filter({ !$0.isValid() }) - ) - let focusedField = invalidFields?.filter({ $0.currentEditor() != nil }).first - let fieldToNotify = focusedField ?? invalidFields?.first - fieldToNotify?.delegate?.controlTextDidChange?(Notification(name: NSControl.textDidChangeNotification, object: fieldToNotify)) - - if fieldToNotify != focusedField { - makeFirstResponder(fieldToNotify) - } - } - - private func showSaveErrorSheetModal(_ nsError: NSError, _ control: NSControl, _ key: String, _ previousValue: String) { - let alert = NSAlert() - alert.messageText = NSLocalizedString("Could not save Preference", comment: "") - alert.informativeText = nsError.localizedDescription + "\n" - alert.addButton(withTitle: NSLocalizedString("Edit", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Check again", comment: "")) - - alert.beginSheetModal(for: self, completionHandler: { (modalResponse: NSApplication.ModalResponse) -> Void in - if modalResponse == NSApplication.ModalResponse.alertFirstButtonReturn { - debugPrint("PreferencesWindow: save: error: user choice: edit") - self.windowCloseRequested = false - } - if modalResponse == NSApplication.ModalResponse.alertSecondButtonReturn { - debugPrint("PreferencesWindow: save: error: user choice: cancel -> revert value and eventually close window") - try! Preferences.updateAndValidateFromString(key, previousValue) - LabelAndControl.setControlValue(control, previousValue) - LabelAndControl.updateControlExtras(control, previousValue) - if self.windowCloseRequested { - self.close() - } - } - if modalResponse == NSApplication.ModalResponse.alertThirdButtonReturn { - debugPrint("PreferencesWindow: save: error: user choice: check again") - self.controlWasChanged(control) - } - }) - } } diff --git a/alt-tab-macos/ui/preferences-window/tabs/AppearanceTab.swift b/alt-tab-macos/ui/preferences-window/tabs/AppearanceTab.swift index 132bbc28b..69596095e 100644 --- a/alt-tab-macos/ui/preferences-window/tabs/AppearanceTab.swift +++ b/alt-tab-macos/ui/preferences-window/tabs/AppearanceTab.swift @@ -9,14 +9,14 @@ class AppearanceTab { private static func makeView() -> NSGridView { let view = GridView.make([ - LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Theme", comment: ""), "theme", Preferences.themeMacro.labels), + LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Theme", comment: ""), "theme", MacroPreferences.themeList.values.map { $0.label }), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Max size on screen", comment: ""), "maxScreenUsage", 10, 100, 10, true, "%"), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Min windows per row", comment: ""), "minCellsPerRow", 1, 20, 20, true), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Max windows per row", comment: ""), "maxCellsPerRow", 1, 40, 20, true), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Min rows of windows", comment: ""), "minRows", 1, 20, 20, true), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window app icon size", comment: ""), "iconSize", 0, 64, 11, false, "px"), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Window title font size", comment: ""), "fontHeight", 0, 64, 11, false, "px"), - LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show on", comment: ""), "showOnScreen", Preferences.showOnScreenMacro.labels), + LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Show on", comment: ""), "showOnScreen", Array(MacroPreferences.showOnScreenList.keys)), LabelAndControl.makeLabelWithSlider(NSLocalizedString("Apparition delay", comment: ""), "windowDisplayDelay", 0, 2000, 11, false, "ms"), LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hide space number labels", comment: ""), "hideSpaceNumberLabels"), ]) diff --git a/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift b/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift index 0d4586636..2a016322e 100644 --- a/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift +++ b/alt-tab-macos/ui/preferences-window/tabs/ShortcutsTab.swift @@ -22,7 +22,7 @@ class ShortcutsTab { } let view = GridView.make([ - LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Alt key", comment: ""), "metaKey", Preferences.metaKeyMacro.labels), + LabelAndControl.makeLabelWithDropdown(NSLocalizedString("Alt key", comment: ""), "metaKey", MacroPreferences.metaKeyList.values.map { $0.label }), LabelAndControl.makeLabelWithInput(NSLocalizedString("Tab key", comment: ""), "tabKeyCode", 33, NSLocalizedString("KeyCodes Reference", comment: ""), "https://eastmanreference.com/complete-list-of-applescript-key-codes", tabKeyCodeValidator), ]) view.column(at: 0).xPlacement = .trailing diff --git a/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift b/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift index 1982c9d68..4c3b3c854 100644 --- a/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift +++ b/alt-tab-macos/ui/preferences-window/tabs/UpdatesTab.swift @@ -6,6 +6,8 @@ class UpdatesTab: NSObject { static var periodicallyCheck: NSButton! static var periodicallyInstall: NSButton! static var policyObserver = PolicyObserver() + // this helps prevent double-dipping (i.e. user updates the UI > changes the preference > updates the UI) + static var policyLock = false static func make() -> NSTabViewItem { return TabViewItem.make(NSLocalizedString("Updates", comment: ""), NSImage.refreshTemplateName, makeView()) @@ -49,13 +51,16 @@ class UpdatesTab: NSObject { @objc static func updatePolicyCallback() { + policyLock = true SUUpdater.shared().automaticallyDownloadsUpdates = periodicallyInstall.state == .on SUUpdater.shared().automaticallyChecksForUpdates = periodicallyInstall.state == .on || periodicallyCheck.state == .on + policyLock = false } } class PolicyObserver: NSObject { override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { + guard !UpdatesTab.policyLock else { return } if SUUpdater.shared().automaticallyDownloadsUpdates { UpdatesTab.periodicallyInstall.state = .on // Sparkle UI "Automatically download and install updates in the future" doesn't activate periodical checks; we do it manually