feat: improve performance by caching preferences better
This commit is contained in:
1 parent
c2ccccd093
commit
d653a54fc3
6 files changed
+365
-346
No files matched your search
@@ -80,6 +80,7 @@
|
||||
BF0C8052AE41B1B10E42BFCE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C875C983226CB16DBD90F /* InfoPlist.strings */; };
|
||||
BF0C8053A58DFB383A3E3351 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E237569FA2A8FE20707 /* InfoPlist.strings */; };
|
||||
BF0C807D26E465F467B066F7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C849EBA5A547912BD8BB9 /* Localizable.strings */; };
|
||||
BF0C80C69EFEF93C18539925 /* PreferencesMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8F58B38F29B2662E6C2D /* PreferencesMigrations.swift */; };
|
||||
BF0C80FCECC1386EB73B66E5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8E9B9EF1485A14AE3316 /* Localizable.strings */; };
|
||||
BF0C812C068CD751CE9BFB67 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8D5993267A74D4875C1A /* Localizable.strings */; };
|
||||
BF0C81480366BAD71BC9A158 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8ABA423E72218605CA4F /* InfoPlist.strings */; };
|
||||
@@ -475,6 +476,7 @@
|
||||
BF0C8EAF02309B9C489E65F4 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
BF0C8EC400A560FDF99F8C35 /* el */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = el; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
BF0C8EE17F3ABD7A6D44BFFE /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
BF0C8F58B38F29B2662E6C2D /* PreferencesMigrations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesMigrations.swift; sourceTree = "<group>"; };
|
||||
BF0C8FC083A2443F3EAF5DB7 /* preferences-blacklist.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "preferences-blacklist.jpg"; sourceTree = "<group>"; };
|
||||
C0712B3BEA2B3780398C0999 /* Pods_alt_tab_macos.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_alt_tab_macos.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C7E6F409CA442F83B7BE71AD /* Pods_unit_tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_unit_tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -1198,6 +1200,7 @@
|
||||
BF0C8D606B53D315F2D9927B /* KeyRepeatTimer.swift */,
|
||||
BF0C8D57C86AF492D43A576D /* AppearanceTestable.swift */,
|
||||
BF0C8998862E0E04D60CACBC /* MacroPreferences.swift */,
|
||||
BF0C8F58B38F29B2662E6C2D /* PreferencesMigrations.swift */,
|
||||
);
|
||||
path = logic;
|
||||
sourceTree = "<group>";
|
||||
@@ -2321,6 +2324,7 @@
|
||||
BF0C8A49891371E4027E0641 /* MacroPreferences.swift in Sources */,
|
||||
BF0C8E366BC25171C0A3881C /* ApplicationServices.HIServices.framework.swift in Sources */,
|
||||
BF0C803A13C409E7F01FBCAB /* SkyLight.framework.swift in Sources */,
|
||||
BF0C80C69EFEF93C18539925 /* PreferencesMigrations.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -33,8 +33,8 @@ class KeyRepeatTimer {
|
||||
|
||||
private static func activateTimerForRepeatingKey(_ atShortcut: ATShortcut, _ block: @escaping () -> Void) {
|
||||
if ((timer == nil || !timer!.isValid) && atShortcut.state != .up) {
|
||||
let repeatRate = ticksToSeconds(UserDefaults.standard.string(forKey: "KeyRepeat") ?? "6")
|
||||
let initialDelay = ticksToSeconds(UserDefaults.standard.string(forKey: "InitialKeyRepeat") ?? "25")
|
||||
let repeatRate = ticksToSeconds(CachedUserDefaults.globalString("KeyRepeat") ?? "6")
|
||||
let initialDelay = ticksToSeconds(CachedUserDefaults.globalString("InitialKeyRepeat") ?? "25")
|
||||
timer = Timer(fire: Date(timeIntervalSinceNow: initialDelay), interval: repeatRate, repeats: true) { _ in
|
||||
handleEvent(atShortcut, block)
|
||||
}
|
||||
|
||||
+100
-328
@@ -90,80 +90,71 @@ class Preferences {
|
||||
static var finderShowsQuitMenuItem: Bool { UserDefaults(suiteName: "com.apple.Finder")?.bool(forKey: "QuitMenuItem") ?? false }
|
||||
|
||||
// persisted values
|
||||
static var holdShortcut: [String] { ["holdShortcut", "holdShortcut2", "holdShortcut3"].map { UserDefaults.standard.string($0) } }
|
||||
static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2", "nextWindowShortcut3"].map { UserDefaults.standard.string($0) } }
|
||||
static var nextWindowGesture: GesturePreference { UserDefaults.standard.macroPref("nextWindowGesture", GesturePreference.allCases) }
|
||||
static var focusWindowShortcut: String { UserDefaults.standard.string("focusWindowShortcut") }
|
||||
static var previousWindowShortcut: String { UserDefaults.standard.string("previousWindowShortcut") }
|
||||
static var cancelShortcut: String { UserDefaults.standard.string("cancelShortcut") }
|
||||
static var closeWindowShortcut: String { UserDefaults.standard.string("closeWindowShortcut") }
|
||||
static var minDeminWindowShortcut: String { UserDefaults.standard.string("minDeminWindowShortcut") }
|
||||
static var toggleFullscreenWindowShortcut: String { UserDefaults.standard.string("toggleFullscreenWindowShortcut") }
|
||||
static var quitAppShortcut: String { UserDefaults.standard.string("quitAppShortcut") }
|
||||
static var hideShowAppShortcut: String { UserDefaults.standard.string("hideShowAppShortcut") }
|
||||
static var holdShortcut: [String] { ["holdShortcut", "holdShortcut2", "holdShortcut3"].map { CachedUserDefaults.string($0) } }
|
||||
static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2", "nextWindowShortcut3"].map { CachedUserDefaults.string($0) } }
|
||||
static var nextWindowGesture: GesturePreference { CachedUserDefaults.macroPref("nextWindowGesture", GesturePreference.allCases) }
|
||||
static var focusWindowShortcut: String { CachedUserDefaults.string("focusWindowShortcut") }
|
||||
static var previousWindowShortcut: String { CachedUserDefaults.string("previousWindowShortcut") }
|
||||
static var cancelShortcut: String { CachedUserDefaults.string("cancelShortcut") }
|
||||
static var closeWindowShortcut: String { CachedUserDefaults.string("closeWindowShortcut") }
|
||||
static var minDeminWindowShortcut: String { CachedUserDefaults.string("minDeminWindowShortcut") }
|
||||
static var toggleFullscreenWindowShortcut: String { CachedUserDefaults.string("toggleFullscreenWindowShortcut") }
|
||||
static var quitAppShortcut: String { CachedUserDefaults.string("quitAppShortcut") }
|
||||
static var hideShowAppShortcut: String { CachedUserDefaults.string("hideShowAppShortcut") }
|
||||
// periphery:ignore
|
||||
static var arrowKeysEnabled: Bool { UserDefaults.standard.bool("arrowKeysEnabled") }
|
||||
static var arrowKeysEnabled: Bool { CachedUserDefaults.bool("arrowKeysEnabled") }
|
||||
// periphery:ignore
|
||||
static var vimKeysEnabled: Bool { UserDefaults.standard.bool("vimKeysEnabled") }
|
||||
static var mouseHoverEnabled: Bool { UserDefaults.standard.bool("mouseHoverEnabled") }
|
||||
static var cursorFollowFocusEnabled: Bool { UserDefaults.standard.bool("cursorFollowFocusEnabled") }
|
||||
static var showTabsAsWindows: Bool { UserDefaults.standard.bool("showTabsAsWindows") }
|
||||
static var hideColoredCircles: Bool { UserDefaults.standard.bool("hideColoredCircles") }
|
||||
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(UserDefaults.standard.int("windowDisplayDelay")) }
|
||||
static var fadeOutAnimation: Bool { UserDefaults.standard.bool("fadeOutAnimation") }
|
||||
static var hideSpaceNumberLabels: Bool { UserDefaults.standard.bool("hideSpaceNumberLabels") }
|
||||
static var hideStatusIcons: Bool { UserDefaults.standard.bool("hideStatusIcons") }
|
||||
static var hideAppBadges: Bool { UserDefaults.standard.bool("hideAppBadges") }
|
||||
static var hideWindowlessApps: Bool { UserDefaults.standard.bool("hideWindowlessApps") }
|
||||
static var vimKeysEnabled: Bool { CachedUserDefaults.bool("vimKeysEnabled") }
|
||||
static var mouseHoverEnabled: Bool { CachedUserDefaults.bool("mouseHoverEnabled") }
|
||||
static var cursorFollowFocusEnabled: Bool { CachedUserDefaults.bool("cursorFollowFocusEnabled") }
|
||||
static var showTabsAsWindows: Bool { CachedUserDefaults.bool("showTabsAsWindows") }
|
||||
static var hideColoredCircles: Bool { CachedUserDefaults.bool("hideColoredCircles") }
|
||||
static var windowDisplayDelay: DispatchTimeInterval { DispatchTimeInterval.milliseconds(CachedUserDefaults.int("windowDisplayDelay")) }
|
||||
static var fadeOutAnimation: Bool { CachedUserDefaults.bool("fadeOutAnimation") }
|
||||
static var hideSpaceNumberLabels: Bool { CachedUserDefaults.bool("hideSpaceNumberLabels") }
|
||||
static var hideStatusIcons: Bool { CachedUserDefaults.bool("hideStatusIcons") }
|
||||
static var hideAppBadges: Bool { CachedUserDefaults.bool("hideAppBadges") }
|
||||
static var hideWindowlessApps: Bool { CachedUserDefaults.bool("hideWindowlessApps") }
|
||||
// periphery:ignore
|
||||
static var startAtLogin: Bool { UserDefaults.standard.bool("startAtLogin") }
|
||||
static var blacklist: [BlacklistEntry] { UserDefaults.standard.json("blacklist", [BlacklistEntry].self) }
|
||||
static var previewFocusedWindow: Bool { UserDefaults.standard.bool("previewFocusedWindow") }
|
||||
static var screenRecordingPermissionSkipped: Bool { UserDefaults.standard.bool("screenRecordingPermissionSkipped") }
|
||||
static var startAtLogin: Bool { CachedUserDefaults.bool("startAtLogin") }
|
||||
static var blacklist: [BlacklistEntry] { CachedUserDefaults.json("blacklist", [BlacklistEntry].self) }
|
||||
static var previewFocusedWindow: Bool { CachedUserDefaults.bool("previewFocusedWindow") }
|
||||
static var screenRecordingPermissionSkipped: Bool { CachedUserDefaults.bool("screenRecordingPermissionSkipped") }
|
||||
|
||||
// macro values
|
||||
static var appearanceStyle: AppearanceStylePreference { UserDefaults.standard.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
|
||||
static var appearanceSize: AppearanceSizePreference { UserDefaults.standard.macroPref("appearanceSize", AppearanceSizePreference.allCases) }
|
||||
static var appearanceTheme: AppearanceThemePreference { UserDefaults.standard.macroPref("appearanceTheme", AppearanceThemePreference.allCases) }
|
||||
static var appearanceVisibility: AppearanceVisibilityPreference { UserDefaults.standard.macroPref("appearanceVisibility", AppearanceVisibilityPreference.allCases) }
|
||||
static var appearanceStyle: AppearanceStylePreference { CachedUserDefaults.macroPref("appearanceStyle", AppearanceStylePreference.allCases) }
|
||||
static var appearanceSize: AppearanceSizePreference { CachedUserDefaults.macroPref("appearanceSize", AppearanceSizePreference.allCases) }
|
||||
static var appearanceTheme: AppearanceThemePreference { CachedUserDefaults.macroPref("appearanceTheme", AppearanceThemePreference.allCases) }
|
||||
static var appearanceVisibility: AppearanceVisibilityPreference { CachedUserDefaults.macroPref("appearanceVisibility", AppearanceVisibilityPreference.allCases) }
|
||||
// periphery:ignore
|
||||
static var theme: ThemePreference { ThemePreference.macOs/*UserDefaults.standard.macroPref("theme", ThemePreference.allCases)*/ }
|
||||
static var showOnScreen: ShowOnScreenPreference { UserDefaults.standard.macroPref("showOnScreen", ShowOnScreenPreference.allCases) }
|
||||
static var titleTruncation: TitleTruncationPreference { UserDefaults.standard.macroPref("titleTruncation", TitleTruncationPreference.allCases) }
|
||||
static var alignThumbnails: AlignThumbnailsPreference { UserDefaults.standard.macroPref("alignThumbnails", AlignThumbnailsPreference.allCases) }
|
||||
static var showAppsOrWindows: ShowAppsOrWindowsPreference { UserDefaults.standard.macroPref("showAppsOrWindows", ShowAppsOrWindowsPreference.allCases) }
|
||||
static var showTitles: ShowTitlesPreference { UserDefaults.standard.macroPref("showTitles", ShowTitlesPreference.allCases) }
|
||||
static var updatePolicy: UpdatePolicyPreference { UserDefaults.standard.macroPref("updatePolicy", UpdatePolicyPreference.allCases) }
|
||||
static var crashPolicy: CrashPolicyPreference { UserDefaults.standard.macroPref("crashPolicy", CrashPolicyPreference.allCases) }
|
||||
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2", "appsToShow3", "appsToShow4"].map { UserDefaults.standard.macroPref($0, AppsToShowPreference.allCases) } }
|
||||
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2", "spacesToShow3", "spacesToShow4"].map { UserDefaults.standard.macroPref($0, SpacesToShowPreference.allCases) } }
|
||||
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2", "screensToShow3", "screensToShow4"].map { UserDefaults.standard.macroPref($0, ScreensToShowPreference.allCases) } }
|
||||
static var showMinimizedWindows: [ShowHowPreference] { ["showMinimizedWindows", "showMinimizedWindows2", "showMinimizedWindows3", "showMinimizedWindows4"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showHiddenWindows: [ShowHowPreference] { ["showHiddenWindows", "showHiddenWindows2", "showHiddenWindows3", "showHiddenWindows4"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4"].map { UserDefaults.standard.macroPref($0, WindowOrderPreference.allCases) } }
|
||||
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4"].map { UserDefaults.standard.macroPref($0, ShortcutStylePreference.allCases) } }
|
||||
static var menubarIcon: MenubarIconPreference { UserDefaults.standard.macroPref("menubarIcon", MenubarIconPreference.allCases) }
|
||||
static var menubarIconShown: Bool { UserDefaults.standard.bool("menubarIconShown") }
|
||||
static var language: LanguagePreference { UserDefaults.standard.macroPref("language", LanguagePreference.allCases) }
|
||||
static var theme: ThemePreference { ThemePreference.macOs/*CachedUserDefaults.macroPref("theme", ThemePreference.allCases)*/ }
|
||||
static var showOnScreen: ShowOnScreenPreference { CachedUserDefaults.macroPref("showOnScreen", ShowOnScreenPreference.allCases) }
|
||||
static var titleTruncation: TitleTruncationPreference { CachedUserDefaults.macroPref("titleTruncation", TitleTruncationPreference.allCases) }
|
||||
static var alignThumbnails: AlignThumbnailsPreference { CachedUserDefaults.macroPref("alignThumbnails", AlignThumbnailsPreference.allCases) }
|
||||
static var showAppsOrWindows: ShowAppsOrWindowsPreference { CachedUserDefaults.macroPref("showAppsOrWindows", ShowAppsOrWindowsPreference.allCases) }
|
||||
static var showTitles: ShowTitlesPreference { CachedUserDefaults.macroPref("showTitles", ShowTitlesPreference.allCases) }
|
||||
static var updatePolicy: UpdatePolicyPreference { CachedUserDefaults.macroPref("updatePolicy", UpdatePolicyPreference.allCases) }
|
||||
static var crashPolicy: CrashPolicyPreference { CachedUserDefaults.macroPref("crashPolicy", CrashPolicyPreference.allCases) }
|
||||
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2", "appsToShow3", "appsToShow4"].map { CachedUserDefaults.macroPref($0, AppsToShowPreference.allCases) } }
|
||||
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2", "spacesToShow3", "spacesToShow4"].map { CachedUserDefaults.macroPref($0, SpacesToShowPreference.allCases) } }
|
||||
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2", "screensToShow3", "screensToShow4"].map { CachedUserDefaults.macroPref($0, ScreensToShowPreference.allCases) } }
|
||||
static var showMinimizedWindows: [ShowHowPreference] { ["showMinimizedWindows", "showMinimizedWindows2", "showMinimizedWindows3", "showMinimizedWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showHiddenWindows: [ShowHowPreference] { ["showHiddenWindows", "showHiddenWindows2", "showHiddenWindows3", "showHiddenWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4"].map { CachedUserDefaults.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4"].map { CachedUserDefaults.macroPref($0, WindowOrderPreference.allCases) } }
|
||||
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4"].map { CachedUserDefaults.macroPref($0, ShortcutStylePreference.allCases) } }
|
||||
static var menubarIcon: MenubarIconPreference { CachedUserDefaults.macroPref("menubarIcon", MenubarIconPreference.allCases) }
|
||||
static var menubarIconShown: Bool { CachedUserDefaults.bool("menubarIconShown") }
|
||||
static var language: LanguagePreference { CachedUserDefaults.macroPref("language", LanguagePreference.allCases) }
|
||||
|
||||
static let gestureIndex = 3
|
||||
|
||||
static func initialize() {
|
||||
removeCorruptedPreferences()
|
||||
migratePreferences()
|
||||
PreferencesMigrations.removeCorruptedPreferences()
|
||||
PreferencesMigrations.migratePreferences()
|
||||
registerDefaults()
|
||||
}
|
||||
|
||||
static func removeCorruptedPreferences() {
|
||||
// from v5.1.0+, there are crash reports of users somehow having their hold shortcuts set to ""
|
||||
["holdShortcut", "holdShortcut2", "holdShortcut3", "holdShortcut4", "holdShortcut5"].forEach {
|
||||
if let s = UserDefaults.standard.string(forKey: $0), s == "" {
|
||||
UserDefaults.standard.removeObject(forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func resetAll() {
|
||||
UserDefaults.standard.removePersistentDomain(forName: App.id)
|
||||
}
|
||||
@@ -172,249 +163,18 @@ class Preferences {
|
||||
UserDefaults.standard.register(defaults: defaultValues)
|
||||
}
|
||||
|
||||
static func getString(_ key: String) -> String? {
|
||||
UserDefaults.standard.string(forKey: key)
|
||||
}
|
||||
|
||||
static func set<T>(_ key: String, _ value: T) where T: Encodable {
|
||||
UserDefaults.standard.set(key == "blacklist" ? jsonEncode(value) : value, forKey: key)
|
||||
UserDefaults.cache.removeValue(forKey: key)
|
||||
CachedUserDefaults.cache.removeValue(forKey: key)
|
||||
}
|
||||
|
||||
static func remove(_ key: String) {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
UserDefaults.cache.removeValue(forKey: key)
|
||||
CachedUserDefaults.cache.removeValue(forKey: key)
|
||||
}
|
||||
|
||||
static var all: [String: Any] { UserDefaults.standard.persistentDomain(forName: App.id)! }
|
||||
|
||||
static func migratePreferences() {
|
||||
let preferencesKey = "preferencesVersion"
|
||||
if let versionInPlist = UserDefaults.standard.string(forKey: preferencesKey) {
|
||||
if versionInPlist != "#VERSION#" && versionInPlist.compare(App.version, options: .numeric) != .orderedDescending {
|
||||
updateToNewPreferences(versionInPlist)
|
||||
}
|
||||
}
|
||||
UserDefaults.standard.set(App.version, forKey: preferencesKey)
|
||||
}
|
||||
|
||||
private static func updateToNewPreferences(_ versionInPlist: String) {
|
||||
// x.compare(y) is .orderedDescending if x > y
|
||||
if versionInPlist.compare("7.13.1", options: .numeric) != .orderedDescending {
|
||||
migrateGestures()
|
||||
if versionInPlist.compare("7.8.0", options: .numeric) != .orderedDescending {
|
||||
migrateMenubarIconWithNewShownToggle()
|
||||
if versionInPlist.compare("7.0.0", options: .numeric) != .orderedDescending {
|
||||
migratePreferencesIndexes()
|
||||
if versionInPlist.compare("6.43.0", options: .numeric) != .orderedDescending {
|
||||
migrateBlacklists()
|
||||
if versionInPlist.compare("6.28.1", options: .numeric) != .orderedDescending {
|
||||
migrateMinMaxWindowsWidthInRow()
|
||||
if versionInPlist.compare("6.27.1", options: .numeric) != .orderedDescending {
|
||||
// "Start at login" new implem doesn't use Login Items; we remove the entry from previous versions
|
||||
(Preferences.self as AvoidDeprecationWarnings.Type).migrateLoginItem()
|
||||
if versionInPlist.compare("6.23.0", options: .numeric) != .orderedDescending {
|
||||
// "Show windows from:" got the "Active Space" option removed
|
||||
migrateShowWindowsFrom()
|
||||
if versionInPlist.compare("6.18.1", options: .numeric) != .orderedDescending {
|
||||
// nextWindowShortcut used to be able to have modifiers already present in holdShortcut; we remove these
|
||||
migrateNextWindowShortcuts()
|
||||
// dropdowns preferences used to store English text; now they store indexes
|
||||
migrateDropdownsFromTextToIndexes()
|
||||
// the "Hide menubar icon" checkbox was replaced with a dropdown of: icon1, icon2, hidden
|
||||
migrateMenubarIconFromCheckboxToDropdown()
|
||||
// "Show minimized/hidden/fullscreen windows" checkboxes were replaced with dropdowns
|
||||
migrateShowWindowsCheckboxToDropdown()
|
||||
// "Max size on screen" was split into max width and max height
|
||||
migrateMaxSizeOnScreenToWidthAndHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we split gestures from disabled, 3-finger, 4-finger to: disabled, 3-finger-horizontal, 3-finger-vertical, 4-finger-horizontal, 4-finger-vertical
|
||||
// no need to map 0 -> 0 (disabled -> disabled)
|
||||
// no need to map 1 -> 1 (3-finger -> 3-finger-horizontal)
|
||||
// we need to map 2 -> 3 (4-finger -> 4-finger-horizontal)
|
||||
private static func migrateGestures() {
|
||||
if let old = UserDefaults.standard.string(forKey: "nextWindowGesture") {
|
||||
if old == "2" { // 2 (4-finger) -> 3 (4-finger-horizontal)
|
||||
UserDefaults.standard.set("3", forKey: "nextWindowGesture")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// we added the new menubarIconShown toggle. It replaces menubarIcon having value "3" which would hide the icon
|
||||
/// there are now 2 preferences : menubarIconShown is a boolean, and menubarIcon has values 0, 1, 2
|
||||
private static func migrateMenubarIconWithNewShownToggle() {
|
||||
if let old = UserDefaults.standard.string(forKey: "menubarIcon") {
|
||||
if old == "3" {
|
||||
UserDefaults.standard.set("0", forKey: "menubarIcon")
|
||||
UserDefaults.standard.set("false", forKey: "menubarIconShown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we want to rely on preferences numbers to match the enum indexes. This migration realigns existing desyncs
|
||||
private static func migratePreferencesIndexes() {
|
||||
// migrate spacesToShow from 1 to 2. 1 was removed a while ago. 1=active => 2=>visible
|
||||
["", "2", "3", "4", "5"].forEach { suffix in
|
||||
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
|
||||
if spacesToShow == "1" {
|
||||
UserDefaults.standard.set("2", forKey: "spacesToShow" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
// migrate spacesToShow from 0 to 2 and 2 to 0. 0 used to be end, 2 used to be start; they got switch for the UI order
|
||||
["", "2", "3", "4", "5"].forEach { suffix in
|
||||
if let spacesToShow = UserDefaults.standard.string(forKey: "titleTruncation" + suffix) {
|
||||
if spacesToShow == "0" {
|
||||
UserDefaults.standard.set("2", forKey: "titleTruncation" + suffix)
|
||||
}
|
||||
if spacesToShow == "2" {
|
||||
UserDefaults.standard.set("0", forKey: "titleTruncation" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateBlacklists() {
|
||||
var entries = [BlacklistEntry]()
|
||||
if let old = UserDefaults.standard.string(forKey: "dontShowBlacklist") {
|
||||
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .always, .none))
|
||||
}
|
||||
if let old = UserDefaults.standard.string(forKey: "disableShortcutsBlacklist") {
|
||||
let onlyFullscreen = UserDefaults.standard.bool(forKey: "disableShortcutsBlacklistOnlyFullscreen")
|
||||
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .none, onlyFullscreen ? .whenFullscreen : .always))
|
||||
}
|
||||
if entries.count > 0 {
|
||||
UserDefaults.standard.set(Preferences.jsonEncode(entries), forKey: "blacklist")
|
||||
["dontShowBlacklist", "disableShortcutsBlacklist", "disableShortcutsBlacklistOnlyFullscreen"].forEach {
|
||||
UserDefaults.standard.removeObject(forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func oldBlacklistEntriesToNewOnes(_ old: String, _ hide: BlacklistHidePreference, _ ignore: BlacklistIgnorePreference) -> [BlacklistEntry] {
|
||||
old.split(separator: "\n").compactMap { (e) -> BlacklistEntry? in
|
||||
let line = e.trimmingCharacters(in: .whitespaces)
|
||||
if line.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return BlacklistEntry(bundleIdentifier: line, hide: hide, ignore: ignore)
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateMinMaxWindowsWidthInRow() {
|
||||
["windowMinWidthInRow", "windowMaxWidthInRow"].forEach {
|
||||
if let old = UserDefaults.standard.string(forKey: $0) {
|
||||
if old == "0" {
|
||||
UserDefaults.standard.set("1", forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX, deprecated: 10.11)
|
||||
static func migrateLoginItem() {
|
||||
do {
|
||||
if let loginItemsWrapped = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil) {
|
||||
let loginItems = loginItemsWrapped.takeRetainedValue()
|
||||
if let loginItemsSnapshotWrapped = LSSharedFileListCopySnapshot(loginItems, nil) {
|
||||
let loginItemsSnapshot = loginItemsSnapshotWrapped.takeRetainedValue() as! [LSSharedFileListItem]
|
||||
let itemName = Bundle.main.bundleURL.lastPathComponent as CFString
|
||||
let itemUrl = URL(fileURLWithPath: Bundle.main.bundlePath) as CFURL
|
||||
loginItemsSnapshot.forEach {
|
||||
if (LSSharedFileListItemCopyDisplayName($0).takeRetainedValue() == itemName) ||
|
||||
(LSSharedFileListItemCopyResolvedURL($0, 0, nil)?.takeRetainedValue() == itemUrl) {
|
||||
LSSharedFileListItemRemove(loginItems, $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw AxError.runtimeError // remove compiler warning
|
||||
} catch {
|
||||
// the LSSharedFile API is deprecated, and has a runtime crash on M1 Monterey
|
||||
// we catch any exception to void the app crashing
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateShowWindowsFrom() {
|
||||
["", "2"].forEach { suffix in
|
||||
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
|
||||
if spacesToShow == "2" {
|
||||
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
|
||||
UserDefaults.standard.set("1", forKey: "spacesToShow" + suffix)
|
||||
} else if spacesToShow == "1" {
|
||||
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateNextWindowShortcuts() {
|
||||
["", "2"].forEach { suffix in
|
||||
if let oldHoldShortcut = UserDefaults.standard.string(forKey: "holdShortcut" + suffix),
|
||||
let oldNextWindowShortcut = UserDefaults.standard.string(forKey: "nextWindowShortcut" + suffix) {
|
||||
let nextWindowShortcutCleanedUp = oldHoldShortcut.reduce(oldNextWindowShortcut, { $0.replacingOccurrences(of: String($1), with: "") })
|
||||
if oldNextWindowShortcut != nextWindowShortcutCleanedUp {
|
||||
UserDefaults.standard.set(nextWindowShortcutCleanedUp, forKey: "nextWindowShortcut" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateMaxSizeOnScreenToWidthAndHeight() {
|
||||
if let old = UserDefaults.standard.string(forKey: "maxScreenUsage") {
|
||||
UserDefaults.standard.set(old, forKey: "maxWidthOnScreen")
|
||||
UserDefaults.standard.set(old, forKey: "maxHeightOnScreen")
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateShowWindowsCheckboxToDropdown() {
|
||||
["showMinimizedWindows", "showHiddenWindows", "showFullscreenWindows"]
|
||||
.flatMap { [$0, $0 + "2"] }
|
||||
.forEach {
|
||||
if let old = UserDefaults.standard.string(forKey: $0) {
|
||||
if old == "true" {
|
||||
UserDefaults.standard.set(ShowHowPreference.show.indexAsString, forKey: $0)
|
||||
} else if old == "false" {
|
||||
UserDefaults.standard.set(ShowHowPreference.hide.indexAsString, forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateDropdownsFromTextToIndexes() {
|
||||
migratePreferenceValue("theme", [" macOS": "0", "❖ Windows 10": "1"])
|
||||
// "Main screen" was renamed to "Active screen"
|
||||
migratePreferenceValue("showOnScreen", ["Main screen": "0", "Active screen": "0", "Screen including mouse": "1"])
|
||||
migratePreferenceValue("alignThumbnails", ["Left": "0", "Center": "1"])
|
||||
migratePreferenceValue("appsToShow", ["All apps": "0", "Active app": "1"])
|
||||
migratePreferenceValue("spacesToShow", ["All spaces": "0", "Active space": "1"])
|
||||
migratePreferenceValue("screensToShow", ["All screens": "0", "Screen showing AltTab": "1"])
|
||||
}
|
||||
|
||||
private static func migrateMenubarIconFromCheckboxToDropdown() {
|
||||
if let old = UserDefaults.standard.string(forKey: "hideMenubarIcon") {
|
||||
if old == "true" {
|
||||
UserDefaults.standard.set("3", forKey: "menubarIcon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func migratePreferenceValue(_ preference: String, _ oldAndNew: [String: String]) {
|
||||
if let old = UserDefaults.standard.string(forKey: preference),
|
||||
let new = oldAndNew[old] {
|
||||
UserDefaults.standard.set(new, forKey: preference)
|
||||
}
|
||||
}
|
||||
|
||||
static func onlyShowApplications() -> Bool {
|
||||
return Preferences.showAppsOrWindows == .applications && Preferences.appearanceStyle != .thumbnails
|
||||
}
|
||||
@@ -458,66 +218,78 @@ class Preferences {
|
||||
}
|
||||
}
|
||||
|
||||
extension UserDefaults {
|
||||
static var cache = [String: String]()
|
||||
class CachedUserDefaults {
|
||||
static var cache = [String: Any]()
|
||||
|
||||
func getFromCacheOrFetchAndCache(_ key: String) -> String {
|
||||
if let c = UserDefaults.cache[key] {
|
||||
return c
|
||||
/// retrieve strings in the globalDomain (e.g. defaults read -g KeyRepeat)
|
||||
/// these may be nil since we they don't have default values from AltTab
|
||||
static func globalString(_ key: String) -> String? {
|
||||
if let cached = CachedUserDefaults.cache[key] {
|
||||
return cached as? String
|
||||
}
|
||||
let v = UserDefaults.standard.string(forKey: key)!
|
||||
UserDefaults.cache[key] = v
|
||||
return v
|
||||
}
|
||||
|
||||
func getThenConvertOrReset<T>(_ key: String, _ getterFn: (String) -> T?) -> T {
|
||||
let stringValue = getFromCacheOrFetchAndCache(key)
|
||||
if let v = getterFn(stringValue) {
|
||||
return v
|
||||
if let string = UserDefaults.standard.string(forKey: key) {
|
||||
CachedUserDefaults.cache[key] = string
|
||||
}
|
||||
removeObject(forKey: key)
|
||||
UserDefaults.cache.removeValue(forKey: key)
|
||||
let stringValue2 = getFromCacheOrFetchAndCache(key)
|
||||
let v = getterFn(stringValue2)!
|
||||
return v
|
||||
return nil
|
||||
}
|
||||
|
||||
func string(_ key: String) -> String {
|
||||
return getFromCacheOrFetchAndCache(key)
|
||||
static func string(_ key: String) -> String {
|
||||
if let cachedFinalValue = CachedUserDefaults.cache[key] {
|
||||
return cachedFinalValue as! String
|
||||
}
|
||||
let finalValue = UserDefaults.standard.string(forKey: key)!
|
||||
CachedUserDefaults.cache[key] = finalValue
|
||||
return finalValue
|
||||
}
|
||||
|
||||
func int(_ key: String) -> Int {
|
||||
static func int(_ key: String) -> Int {
|
||||
return getThenConvertOrReset(key, { s in Int(s) })
|
||||
}
|
||||
|
||||
func bool(_ key: String) -> Bool {
|
||||
static func bool(_ key: String) -> Bool {
|
||||
return getThenConvertOrReset(key, { s in Bool(s) })
|
||||
}
|
||||
|
||||
func double(_ key: String) -> Double {
|
||||
static func double(_ key: String) -> Double {
|
||||
return getThenConvertOrReset(key, { s in Double(s) })
|
||||
}
|
||||
|
||||
func macroPref<A>(_ key: String, _ macroPreferences: [A]) -> A {
|
||||
static func macroPref<A>(_ key: String, _ macroPreferences: [A]) -> A {
|
||||
return getThenConvertOrReset(key, { s in Int(s).flatMap { macroPreferences[safe: $0] } })
|
||||
}
|
||||
|
||||
func json<T>(_ key: String, _ type: T.Type) -> T where T: Decodable {
|
||||
/// some UI elements (e.g. dropdown, radios) need an int. We find the right int from the MacroPreference index
|
||||
static func intFromMacroPref(_ key: String, _ macroPreferences: [MacroPreference]) -> Int {
|
||||
let macroPref = macroPref(key, macroPreferences)
|
||||
return macroPreferences.firstIndex { $0.localizedString == macroPref.localizedString }!
|
||||
}
|
||||
|
||||
static func json<T>(_ key: String, _ type: T.Type) -> T where T: Decodable {
|
||||
return getThenConvertOrReset(key, { s in jsonDecode(s, type) })
|
||||
}
|
||||
|
||||
private func jsonDecode<T>(_ value: String, _ type: T.Type) -> T? where T: Decodable {
|
||||
private static func getThenConvertOrReset<T>(_ key: String, _ getterFn: (String) -> T?) -> T {
|
||||
if let cachedFinalValue = CachedUserDefaults.cache[key] {
|
||||
return cachedFinalValue as! T
|
||||
}
|
||||
let stringValue = UserDefaults.standard.string(forKey: key)!
|
||||
if let finalValue = getterFn(stringValue) {
|
||||
CachedUserDefaults.cache[key] = finalValue
|
||||
return finalValue
|
||||
}
|
||||
// value couldn't be read properly; we remove it and work with the default
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
let defaultStringValue = UserDefaults.standard.string(forKey: key)!
|
||||
let defaultFinalValue = getterFn(defaultStringValue)!
|
||||
CachedUserDefaults.cache[key] = defaultFinalValue
|
||||
return defaultFinalValue
|
||||
}
|
||||
|
||||
private static func jsonDecode<T>(_ value: String, _ type: T.Type) -> T? where T: Decodable {
|
||||
return value.data(using: .utf8).flatMap { try? JSONDecoder().decode(type, from: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
/// workaround to silence compiler warning
|
||||
private protocol AvoidDeprecationWarnings {
|
||||
static func migrateLoginItem()
|
||||
}
|
||||
|
||||
extension Preferences: AvoidDeprecationWarnings {}
|
||||
|
||||
struct BlacklistEntry: Codable {
|
||||
var bundleIdentifier: String
|
||||
var hide: BlacklistHidePreference
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
class PreferencesMigrations {
|
||||
static func removeCorruptedPreferences() {
|
||||
// from v5.1.0+, there are crash reports of users somehow having their hold shortcuts set to ""
|
||||
["holdShortcut", "holdShortcut2", "holdShortcut3", "holdShortcut4", "holdShortcut5"].forEach {
|
||||
if let s = UserDefaults.standard.string(forKey: $0), s == "" {
|
||||
UserDefaults.standard.removeObject(forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func migratePreferences() {
|
||||
let preferencesKey = "preferencesVersion"
|
||||
if let versionInPlist = UserDefaults.standard.string(forKey: preferencesKey) {
|
||||
if versionInPlist != "#VERSION#" && versionInPlist.compare(App.version, options: .numeric) != .orderedDescending {
|
||||
updateToNewPreferences(versionInPlist)
|
||||
}
|
||||
}
|
||||
UserDefaults.standard.set(App.version, forKey: preferencesKey)
|
||||
}
|
||||
|
||||
private static func updateToNewPreferences(_ versionInPlist: String) {
|
||||
// x.compare(y) is .orderedDescending if x > y
|
||||
if versionInPlist.compare("7.13.1", options: .numeric) != .orderedDescending {
|
||||
migrateGestures()
|
||||
if versionInPlist.compare("7.8.0", options: .numeric) != .orderedDescending {
|
||||
migrateMenubarIconWithNewShownToggle()
|
||||
if versionInPlist.compare("7.0.0", options: .numeric) != .orderedDescending {
|
||||
migratePreferencesIndexes()
|
||||
if versionInPlist.compare("6.43.0", options: .numeric) != .orderedDescending {
|
||||
migrateBlacklists()
|
||||
if versionInPlist.compare("6.28.1", options: .numeric) != .orderedDescending {
|
||||
migrateMinMaxWindowsWidthInRow()
|
||||
if versionInPlist.compare("6.27.1", options: .numeric) != .orderedDescending {
|
||||
// "Start at login" new implem doesn't use Login Items; we remove the entry from previous versions
|
||||
(PreferencesMigrations.self as AvoidDeprecationWarnings.Type).migrateLoginItem()
|
||||
if versionInPlist.compare("6.23.0", options: .numeric) != .orderedDescending {
|
||||
// "Show windows from:" got the "Active Space" option removed
|
||||
migrateShowWindowsFrom()
|
||||
if versionInPlist.compare("6.18.1", options: .numeric) != .orderedDescending {
|
||||
// nextWindowShortcut used to be able to have modifiers already present in holdShortcut; we remove these
|
||||
migrateNextWindowShortcuts()
|
||||
// dropdowns preferences used to store English text; now they store indexes
|
||||
migrateDropdownsFromTextToIndexes()
|
||||
// the "Hide menubar icon" checkbox was replaced with a dropdown of: icon1, icon2, hidden
|
||||
migrateMenubarIconFromCheckboxToDropdown()
|
||||
// "Show minimized/hidden/fullscreen windows" checkboxes were replaced with dropdowns
|
||||
migrateShowWindowsCheckboxToDropdown()
|
||||
// "Max size on screen" was split into max width and max height
|
||||
migrateMaxSizeOnScreenToWidthAndHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we split gestures from disabled, 3-finger, 4-finger to: disabled, 3-finger-horizontal, 3-finger-vertical, 4-finger-horizontal, 4-finger-vertical
|
||||
// no need to map 0 -> 0 (disabled -> disabled)
|
||||
// no need to map 1 -> 1 (3-finger -> 3-finger-horizontal)
|
||||
// we need to map 2 -> 3 (4-finger -> 4-finger-horizontal)
|
||||
private static func migrateGestures() {
|
||||
if let old = UserDefaults.standard.string(forKey: "nextWindowGesture") {
|
||||
if old == "2" { // 2 (4-finger) -> 3 (4-finger-horizontal)
|
||||
UserDefaults.standard.set("3", forKey: "nextWindowGesture")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// we added the new menubarIconShown toggle. It replaces menubarIcon having value "3" which would hide the icon
|
||||
/// there are now 2 preferences : menubarIconShown is a boolean, and menubarIcon has values 0, 1, 2
|
||||
private static func migrateMenubarIconWithNewShownToggle() {
|
||||
if let old = UserDefaults.standard.string(forKey: "menubarIcon") {
|
||||
if old == "3" {
|
||||
UserDefaults.standard.set("0", forKey: "menubarIcon")
|
||||
UserDefaults.standard.set("false", forKey: "menubarIconShown")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we want to rely on preferences numbers to match the enum indexes. This migration realigns existing desyncs
|
||||
private static func migratePreferencesIndexes() {
|
||||
// migrate spacesToShow from 1 to 2. 1 was removed a while ago. 1=active => 2=>visible
|
||||
["", "2", "3", "4", "5"].forEach { suffix in
|
||||
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
|
||||
if spacesToShow == "1" {
|
||||
UserDefaults.standard.set("2", forKey: "spacesToShow" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
// migrate spacesToShow from 0 to 2 and 2 to 0. 0 used to be end, 2 used to be start; they got switch for the UI order
|
||||
["", "2", "3", "4", "5"].forEach { suffix in
|
||||
if let spacesToShow = UserDefaults.standard.string(forKey: "titleTruncation" + suffix) {
|
||||
if spacesToShow == "0" {
|
||||
UserDefaults.standard.set("2", forKey: "titleTruncation" + suffix)
|
||||
}
|
||||
if spacesToShow == "2" {
|
||||
UserDefaults.standard.set("0", forKey: "titleTruncation" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateBlacklists() {
|
||||
var entries = [BlacklistEntry]()
|
||||
if let old = UserDefaults.standard.string(forKey: "dontShowBlacklist") {
|
||||
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .always, .none))
|
||||
}
|
||||
if let old = UserDefaults.standard.string(forKey: "disableShortcutsBlacklist") {
|
||||
let onlyFullscreen = UserDefaults.standard.bool(forKey: "disableShortcutsBlacklistOnlyFullscreen")
|
||||
entries.append(contentsOf: oldBlacklistEntriesToNewOnes(old, .none, onlyFullscreen ? .whenFullscreen : .always))
|
||||
}
|
||||
if entries.count > 0 {
|
||||
UserDefaults.standard.set(Preferences.jsonEncode(entries), forKey: "blacklist")
|
||||
["dontShowBlacklist", "disableShortcutsBlacklist", "disableShortcutsBlacklistOnlyFullscreen"].forEach {
|
||||
UserDefaults.standard.removeObject(forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func oldBlacklistEntriesToNewOnes(_ old: String, _ hide: BlacklistHidePreference, _ ignore: BlacklistIgnorePreference) -> [BlacklistEntry] {
|
||||
old.split(separator: "\n").compactMap { (e) -> BlacklistEntry? in
|
||||
let line = e.trimmingCharacters(in: .whitespaces)
|
||||
if line.isEmpty {
|
||||
return nil
|
||||
}
|
||||
return BlacklistEntry(bundleIdentifier: line, hide: hide, ignore: ignore)
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateMinMaxWindowsWidthInRow() {
|
||||
["windowMinWidthInRow", "windowMaxWidthInRow"].forEach {
|
||||
if let old = UserDefaults.standard.string(forKey: $0) {
|
||||
if old == "0" {
|
||||
UserDefaults.standard.set("1", forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX, deprecated: 10.11)
|
||||
static func migrateLoginItem() {
|
||||
do {
|
||||
if let loginItemsWrapped = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil) {
|
||||
let loginItems = loginItemsWrapped.takeRetainedValue()
|
||||
if let loginItemsSnapshotWrapped = LSSharedFileListCopySnapshot(loginItems, nil) {
|
||||
let loginItemsSnapshot = loginItemsSnapshotWrapped.takeRetainedValue() as! [LSSharedFileListItem]
|
||||
let itemName = Bundle.main.bundleURL.lastPathComponent as CFString
|
||||
let itemUrl = URL(fileURLWithPath: Bundle.main.bundlePath) as CFURL
|
||||
loginItemsSnapshot.forEach {
|
||||
if (LSSharedFileListItemCopyDisplayName($0).takeRetainedValue() == itemName) ||
|
||||
(LSSharedFileListItemCopyResolvedURL($0, 0, nil)?.takeRetainedValue() == itemUrl) {
|
||||
LSSharedFileListItemRemove(loginItems, $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw AxError.runtimeError // remove compiler warning
|
||||
} catch {
|
||||
// the LSSharedFile API is deprecated, and has a runtime crash on M1 Monterey
|
||||
// we catch any exception to void the app crashing
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateShowWindowsFrom() {
|
||||
["", "2"].forEach { suffix in
|
||||
if let spacesToShow = UserDefaults.standard.string(forKey: "spacesToShow" + suffix) {
|
||||
if spacesToShow == "2" {
|
||||
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
|
||||
UserDefaults.standard.set("1", forKey: "spacesToShow" + suffix)
|
||||
} else if spacesToShow == "1" {
|
||||
UserDefaults.standard.set("1", forKey: "screensToShow" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateNextWindowShortcuts() {
|
||||
["", "2"].forEach { suffix in
|
||||
if let oldHoldShortcut = UserDefaults.standard.string(forKey: "holdShortcut" + suffix),
|
||||
let oldNextWindowShortcut = UserDefaults.standard.string(forKey: "nextWindowShortcut" + suffix) {
|
||||
let nextWindowShortcutCleanedUp = oldHoldShortcut.reduce(oldNextWindowShortcut, { $0.replacingOccurrences(of: String($1), with: "") })
|
||||
if oldNextWindowShortcut != nextWindowShortcutCleanedUp {
|
||||
UserDefaults.standard.set(nextWindowShortcutCleanedUp, forKey: "nextWindowShortcut" + suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateMaxSizeOnScreenToWidthAndHeight() {
|
||||
if let old = UserDefaults.standard.string(forKey: "maxScreenUsage") {
|
||||
UserDefaults.standard.set(old, forKey: "maxWidthOnScreen")
|
||||
UserDefaults.standard.set(old, forKey: "maxHeightOnScreen")
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateShowWindowsCheckboxToDropdown() {
|
||||
["showMinimizedWindows", "showHiddenWindows", "showFullscreenWindows"]
|
||||
.flatMap { [$0, $0 + "2"] }
|
||||
.forEach {
|
||||
if let old = UserDefaults.standard.string(forKey: $0) {
|
||||
if old == "true" {
|
||||
UserDefaults.standard.set(ShowHowPreference.show.indexAsString, forKey: $0)
|
||||
} else if old == "false" {
|
||||
UserDefaults.standard.set(ShowHowPreference.hide.indexAsString, forKey: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateDropdownsFromTextToIndexes() {
|
||||
migratePreferenceValue("theme", [" macOS": "0", "❖ Windows 10": "1"])
|
||||
// "Main screen" was renamed to "Active screen"
|
||||
migratePreferenceValue("showOnScreen", ["Main screen": "0", "Active screen": "0", "Screen including mouse": "1"])
|
||||
migratePreferenceValue("alignThumbnails", ["Left": "0", "Center": "1"])
|
||||
migratePreferenceValue("appsToShow", ["All apps": "0", "Active app": "1"])
|
||||
migratePreferenceValue("spacesToShow", ["All spaces": "0", "Active space": "1"])
|
||||
migratePreferenceValue("screensToShow", ["All screens": "0", "Screen showing AltTab": "1"])
|
||||
}
|
||||
|
||||
private static func migrateMenubarIconFromCheckboxToDropdown() {
|
||||
if let old = UserDefaults.standard.string(forKey: "hideMenubarIcon") {
|
||||
if old == "true" {
|
||||
UserDefaults.standard.set("3", forKey: "menubarIcon")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func migratePreferenceValue(_ preference: String, _ oldAndNew: [String: String]) {
|
||||
if let old = UserDefaults.standard.string(forKey: preference),
|
||||
let new = oldAndNew[old] {
|
||||
UserDefaults.standard.set(new, forKey: preference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// workaround to silence compiler warning
|
||||
private protocol AvoidDeprecationWarnings {
|
||||
static func migrateLoginItem()
|
||||
}
|
||||
|
||||
extension PreferencesMigrations: AvoidDeprecationWarnings {}
|
||||
@@ -208,7 +208,6 @@ class App: AppCenterApplication {
|
||||
guard appIsBeingUsed else { return }
|
||||
Windows.updateFocusedWindowIndex()
|
||||
guard appIsBeingUsed else { return }
|
||||
print("updateItemsAndLayout")
|
||||
thumbnailsPanel.thumbnailsView.updateItemsAndLayout()
|
||||
guard appIsBeingUsed else { return }
|
||||
thumbnailsPanel.setContentSize(thumbnailsPanel.thumbnailsView.frame.size)
|
||||
|
||||
@@ -63,20 +63,20 @@ class LabelAndControl: NSObject {
|
||||
// periphery:ignore
|
||||
static func makeLabelWithImageRadioButtons(_ labelText: String,
|
||||
_ rawName: String,
|
||||
_ values: [ImageMacroPreference],
|
||||
_ macroPreferences: [ImageMacroPreference],
|
||||
extraAction: ActionClosure? = nil,
|
||||
buttonSpacing: CGFloat = 15) -> [NSView] {
|
||||
let view = makeImageRadioButtons(rawName, values, extraAction: extraAction, buttonSpacing: buttonSpacing)
|
||||
let view = makeImageRadioButtons(rawName, macroPreferences, extraAction: extraAction, buttonSpacing: buttonSpacing)
|
||||
return [makeLabel(labelText), view]
|
||||
}
|
||||
|
||||
static func makeImageRadioButtons(_ rawName: String,
|
||||
_ values: [ImageMacroPreference],
|
||||
_ macroPreferences: [ImageMacroPreference],
|
||||
extraAction: ActionClosure? = nil,
|
||||
buttonSpacing: CGFloat = 15) -> NSStackView {
|
||||
var buttons = [ImageTextButtonView]()
|
||||
let buttonViews = values.enumerated().map { (index, preference) -> ImageTextButtonView in
|
||||
let state: NSControl.StateValue = UserDefaults.standard.int(rawName) == index ? .on : .off
|
||||
let buttonViews = macroPreferences.enumerated().map { (index, preference) -> ImageTextButtonView in
|
||||
let state: NSControl.StateValue = CachedUserDefaults.intFromMacroPref(rawName, macroPreferences) == index ? .on : .off
|
||||
let buttonView = ImageTextButtonView(title: preference.localizedString, rawName: rawName, image: preference.image, state: state)
|
||||
buttons.append(buttonView)
|
||||
buttonView.onClick = { control in
|
||||
@@ -109,13 +109,13 @@ class LabelAndControl: NSObject {
|
||||
static func makeLabelWithCheckbox(_ labelText: String, _ rawName: String, extraAction: ActionClosure? = nil, labelPosition: LabelPosition = .leftWithSeparator) -> [NSView] {
|
||||
let checkbox = NSButton(checkboxWithTitle: labelPosition == .right ? labelText : " ", target: nil, action: nil)
|
||||
checkbox.translatesAutoresizingMaskIntoConstraints = false
|
||||
checkbox.state = UserDefaults.standard.bool(rawName) ? .on : .off
|
||||
checkbox.state = CachedUserDefaults.bool(rawName) ? .on : .off
|
||||
let views = makeLabelWithProvidedControl(labelText, rawName, checkbox, labelPosition: labelPosition, extraAction: extraAction)
|
||||
return views
|
||||
}
|
||||
|
||||
static func makeSwitch(_ rawName: String, extraAction: ActionClosure? = nil) -> Switch {
|
||||
let button = Switch(UserDefaults.standard.bool(rawName))
|
||||
let button = Switch(CachedUserDefaults.bool(rawName))
|
||||
_ = setupControl(button, rawName, extraAction: extraAction)
|
||||
return button
|
||||
}
|
||||
@@ -124,7 +124,7 @@ class LabelAndControl: NSObject {
|
||||
static func makeCheckbox(_ rawName: String, extraAction: ActionClosure? = nil) -> NSButton {
|
||||
let checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil)
|
||||
checkbox.translatesAutoresizingMaskIntoConstraints = false
|
||||
checkbox.state = UserDefaults.standard.bool(rawName) ? .on : .off
|
||||
checkbox.state = CachedUserDefaults.bool(rawName) ? .on : .off
|
||||
_ = setupControl(checkbox, rawName, extraAction: extraAction)
|
||||
return checkbox
|
||||
}
|
||||
@@ -179,7 +179,7 @@ class LabelAndControl: NSObject {
|
||||
extraAction?(textArea)
|
||||
}
|
||||
textArea.identifier = NSUserInterfaceItemIdentifier(rawName)
|
||||
textArea.stringValue = UserDefaults.standard.string(rawName)
|
||||
textArea.stringValue = CachedUserDefaults.string(rawName)
|
||||
return [textArea]
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ class LabelAndControl: NSObject {
|
||||
popUp.addItems(withTitles: macroPreferences.map {
|
||||
$0.localizedString
|
||||
})
|
||||
popUp.selectItem(at: UserDefaults.standard.int(rawName))
|
||||
popUp.selectItem(at: CachedUserDefaults.intFromMacroPref(rawName, macroPreferences))
|
||||
return popUp
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ class LabelAndControl: NSObject {
|
||||
return macroPreferences.map {
|
||||
let button = NSButton(radioButtonWithTitle: $0.localizedString, target: nil, action: nil)
|
||||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
button.state = UserDefaults.standard.int(rawName) == i ? .on : .off
|
||||
button.state = CachedUserDefaults.intFromMacroPref(rawName, macroPreferences) == i ? .on : .off
|
||||
_ = setupControl(button, rawName, String(i), extraAction: extraAction)
|
||||
i += 1
|
||||
return button
|
||||
@@ -242,7 +242,7 @@ class LabelAndControl: NSObject {
|
||||
button.setImage(symbolImage, forSegment: i)
|
||||
}
|
||||
}
|
||||
let selectedSegment = UserDefaults.standard.int(rawName)
|
||||
let selectedSegment = CachedUserDefaults.intFromMacroPref(rawName, macroPreferences)
|
||||
if selectedSegment >= 0 && selectedSegment < macroPreferences.count {
|
||||
button.selectedSegment = selectedSegment
|
||||
}
|
||||
@@ -254,10 +254,10 @@ class LabelAndControl: NSObject {
|
||||
static func makeLabelWithSlider(_ labelText: String, _ rawName: String, _ minValue: Double, _ maxValue: Double,
|
||||
_ numberOfTickMarks: Int = 0, _ allowsTickMarkValuesOnly: Bool = false,
|
||||
_ unitText: String = "", width: CGFloat = 200, extraAction: ActionClosure? = nil) -> [NSView] {
|
||||
let value = UserDefaults.standard.double(rawName)
|
||||
let value = CachedUserDefaults.int(rawName)
|
||||
let formatter = MeasurementFormatter()
|
||||
formatter.numberFormatter = NumberFormatter()
|
||||
let suffixText = formatter.string(from: Measurement(value: value, unit: Unit(symbol: unitText)))
|
||||
let suffixText = formatter.string(from: Measurement(value: Double(value), unit: Unit(symbol: unitText)))
|
||||
let slider = NSSlider()
|
||||
slider.minValue = minValue
|
||||
slider.maxValue = maxValue
|
||||
@@ -304,7 +304,7 @@ class LabelAndControl: NSObject {
|
||||
|
||||
static func controlWasChanged(_ senderControl: NSControl, _ controlId: String?) {
|
||||
if let newValue = LabelAndControl.getControlValue(senderControl, controlId) {
|
||||
if let oldValue = Preferences.getString(senderControl.identifier!.rawValue), newValue == oldValue {
|
||||
if let oldValue = UserDefaults.standard.string(forKey: senderControl.identifier!.rawValue), newValue == oldValue {
|
||||
return
|
||||
}
|
||||
if senderControl is NSSlider {
|
||||
|
||||
Reference in new issue
Block a user