fix: issues when using multiple keyboard languages (closes #2125)
This commit is contained in:
1 parent
37ba9662f1
commit
ea7284b387
9 files changed
+274
-130
No files matched your search
@@ -215,6 +215,7 @@
|
||||
D04BA03B560B681AF96E4610 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA88ED495C54CFE0B7B87 /* InfoPlist.strings */; };
|
||||
D04BA084CD1236EC78D90A01 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BACCBE5F97BE9B6CA645B /* Localizable.strings */; };
|
||||
D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BA5D9B4EBEB9E4333AE39 /* UserDefaultsEvents.swift */; };
|
||||
D04BB101A1B2C3D4E5F60011 /* InputSourceEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB102A1B2C3D4E5F60011 /* InputSourceEvents.swift */; };
|
||||
D04BA0DD6819010987B578D1 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BA07BBF2237CC6C72D264 /* Localizable.strings */; };
|
||||
D04BA100BD0F47828EB649FF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BAAEC2847830A3991F8D1 /* InfoPlist.strings */; };
|
||||
D04BA11E56383D082D7BE5A5 /* TilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BAA998119CAA8B70A2B67 /* TilesView.swift */; };
|
||||
@@ -642,6 +643,7 @@
|
||||
D04BA5C8373D1480280B71A6 /* tr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = tr; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D04BA5D2FD6E26D82721C574 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D04BA5D9B4EBEB9E4333AE39 /* UserDefaultsEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsEvents.swift; sourceTree = "<group>"; };
|
||||
D04BB102A1B2C3D4E5F60011 /* InputSourceEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputSourceEvents.swift; sourceTree = "<group>"; };
|
||||
D04BA5DB4FB71B583A28EF4F /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
D04BA5E208CBB8E28EE3FAFF /* site.webmanifest */ = {isa = PBXFileReference; lastKnownFileType = file.webmanifest; path = site.webmanifest; sourceTree = "<group>"; };
|
||||
D04BA60A6D08C041F5BF8CC9 /* SF-Pro-Text-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file.otf; path = "SF-Pro-Text-Regular.otf"; sourceTree = "<group>"; };
|
||||
@@ -2002,6 +2004,7 @@
|
||||
D04BA4A621A8A15660F973C1 /* KeyboardEvents.swift */,
|
||||
D04BA76AEE37D6656EB80126 /* RunningApplicationsEvents.swift */,
|
||||
D04BA5D9B4EBEB9E4333AE39 /* UserDefaultsEvents.swift */,
|
||||
D04BB102A1B2C3D4E5F60011 /* InputSourceEvents.swift */,
|
||||
5FA1E0B12F50000100A1A1A1 /* PreferencesEvents.swift */,
|
||||
5883F0A729A5182C0071DB65 /* TrackpadEvents.swift */,
|
||||
BF0C870C14C20C936BA4AF40 /* DockEvents.swift */,
|
||||
@@ -2569,6 +2572,7 @@
|
||||
D04BA1766FBCB2E941D081A5 /* RunningApplicationsEvents.swift in Sources */,
|
||||
5F906A342F16A18500B99924 /* Screens.swift in Sources */,
|
||||
D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */,
|
||||
D04BB101A1B2C3D4E5F60011 /* InputSourceEvents.swift in Sources */,
|
||||
5FA1E0B22F50000100A1A1A1 /* PreferencesEvents.swift in Sources */,
|
||||
D04BA4A11F821548EE3C5E95 /* Bash.swift in Sources */,
|
||||
5FBB24D32F389A0400BF7E14 /* Benchmark.swift in Sources */,
|
||||
|
||||
@@ -107,12 +107,7 @@ class DebugProfile {
|
||||
|
||||
static func inputSource() -> String {
|
||||
let r = DispatchQueue.main.sync {
|
||||
if let inputSource = TISCopyCurrentKeyboardInputSource()?.takeUnretainedValue(),
|
||||
let localizedNamePointer = TISGetInputSourceProperty(inputSource, kTISPropertyLocalizedName) {
|
||||
let localizedName = Unmanaged<AnyObject>.fromOpaque(localizedNamePointer).takeUnretainedValue()
|
||||
return localizedName as? String ?? ""
|
||||
}
|
||||
return ""
|
||||
InputSourceEvents.currentInputSource()
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
+146
-84
@@ -3,80 +3,57 @@ import Carbon.HIToolbox.Events
|
||||
import ShortcutRecorder
|
||||
|
||||
class Preferences {
|
||||
// default values
|
||||
static var defaultValues: [String: String] = {
|
||||
var values: [String: String] = [
|
||||
"shortcutCount": "2",
|
||||
"holdShortcut": "⌥",
|
||||
"holdShortcut2": "⌥",
|
||||
"nextWindowShortcut": "⇥",
|
||||
"nextWindowShortcut2": keyAboveTabDependingOnInputSource(),
|
||||
"nextWindowGesture": GesturePreference.disabled.indexAsString,
|
||||
"focusWindowShortcut": returnKeyEquivalent(),
|
||||
"previousWindowShortcut": "⇧",
|
||||
"cancelShortcut": "⎋",
|
||||
"lockSearchShortcut": "Space",
|
||||
"closeWindowShortcut": "W",
|
||||
"minDeminWindowShortcut": "M",
|
||||
"toggleFullscreenWindowShortcut": "F",
|
||||
"quitAppShortcut": "Q",
|
||||
"hideShowAppShortcut": "H",
|
||||
"searchShortcut": "S",
|
||||
"arrowKeysEnabled": "true",
|
||||
"vimKeysEnabled": "false",
|
||||
"mouseHoverEnabled": "false",
|
||||
"cursorFollowFocus": CursorFollowFocus.never.indexAsString,
|
||||
"showMinimizedWindows": ShowHowPreference.show.indexAsString,
|
||||
"showMinimizedWindows2": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows2": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows2": ShowHowPreference.show.indexAsString,
|
||||
"showWindowlessApps": ShowHowPreference.showAtTheEnd.indexAsString,
|
||||
"showWindowlessApps2": ShowHowPreference.showAtTheEnd.indexAsString,
|
||||
"windowOrder": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"windowOrder2": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"showTabsAsWindows": "false",
|
||||
"hideColoredCircles": "false",
|
||||
"windowDisplayDelay": "100",
|
||||
"appearanceStyle": AppearanceStylePreference.thumbnails.indexAsString,
|
||||
"appearanceSize": AppearanceSizePreference.auto.indexAsString,
|
||||
"appearanceTheme": AppearanceThemePreference.system.indexAsString,
|
||||
"theme": ThemePreference.macOs.indexAsString,
|
||||
"showOnScreen": ShowOnScreenPreference.active.indexAsString,
|
||||
"titleTruncation": TitleTruncationPreference.end.indexAsString,
|
||||
"alignThumbnails": AlignThumbnailsPreference.center.indexAsString,
|
||||
"showAppsOrWindows": ShowAppsOrWindowsPreference.windows.indexAsString,
|
||||
"showTitles": ShowTitlesPreference.windowTitle.indexAsString,
|
||||
"appsToShow": AppsToShowPreference.all.indexAsString,
|
||||
"appsToShow2": AppsToShowPreference.active.indexAsString,
|
||||
"spacesToShow": SpacesToShowPreference.all.indexAsString,
|
||||
"spacesToShow2": SpacesToShowPreference.all.indexAsString,
|
||||
"screensToShow": ScreensToShowPreference.all.indexAsString,
|
||||
"screensToShow2": ScreensToShowPreference.all.indexAsString,
|
||||
"fadeOutAnimation": "false",
|
||||
"previewFadeInAnimation": "true",
|
||||
"hideSpaceNumberLabels": "false",
|
||||
"hideStatusIcons": "false",
|
||||
"startAtLogin": "true",
|
||||
"menubarIcon": MenubarIconPreference.outlined.indexAsString,
|
||||
"menubarIconShown": "true",
|
||||
"language": LanguagePreference.systemDefault.indexAsString,
|
||||
"exceptions": defaultExceptions(),
|
||||
"updatePolicy": UpdatePolicyPreference.autoCheck.indexAsString,
|
||||
"crashPolicy": CrashPolicyPreference.ask.indexAsString,
|
||||
"shortcutStyle": ShortcutStylePreference.focusOnRelease.indexAsString,
|
||||
"shortcutStyle2": ShortcutStylePreference.focusOnRelease.indexAsString,
|
||||
"hideAppBadges": "false",
|
||||
"hideThumbnails": "false",
|
||||
"previewFocusedWindow": "false",
|
||||
"screenRecordingPermissionSkipped": "false",
|
||||
"trackpadHapticFeedbackEnabled": "true",
|
||||
"settingsWindowShownOnFirstLaunch": "false",
|
||||
static var defaultValues: [String: Any] = {
|
||||
var values: [String: Any] = [
|
||||
"shortcutCount": "2",
|
||||
"nextWindowGesture": GesturePreference.disabled.indexAsString,
|
||||
"focusWindowShortcut": defaultShortcut(returnKeyEquivalent()),
|
||||
"previousWindowShortcut": defaultShortcut("⇧"),
|
||||
"cancelShortcut": defaultShortcut("⎋"),
|
||||
"lockSearchShortcut": defaultShortcut("Space"),
|
||||
"closeWindowShortcut": defaultShortcut("W"),
|
||||
"minDeminWindowShortcut": defaultShortcut("M"),
|
||||
"toggleFullscreenWindowShortcut": defaultShortcut("F"),
|
||||
"quitAppShortcut": defaultShortcut("Q"),
|
||||
"hideShowAppShortcut": defaultShortcut("H"),
|
||||
"searchShortcut": defaultShortcut("S"),
|
||||
"arrowKeysEnabled": "true",
|
||||
"vimKeysEnabled": "false",
|
||||
"mouseHoverEnabled": "false",
|
||||
"cursorFollowFocus": CursorFollowFocus.never.indexAsString,
|
||||
"showTabsAsWindows": "false",
|
||||
"hideColoredCircles": "false",
|
||||
"windowDisplayDelay": "100",
|
||||
"appearanceStyle": AppearanceStylePreference.thumbnails.indexAsString,
|
||||
"appearanceSize": AppearanceSizePreference.auto.indexAsString,
|
||||
"appearanceTheme": AppearanceThemePreference.system.indexAsString,
|
||||
"theme": ThemePreference.macOs.indexAsString,
|
||||
"showOnScreen": ShowOnScreenPreference.active.indexAsString,
|
||||
"titleTruncation": TitleTruncationPreference.end.indexAsString,
|
||||
"alignThumbnails": AlignThumbnailsPreference.center.indexAsString,
|
||||
"showAppsOrWindows": ShowAppsOrWindowsPreference.windows.indexAsString,
|
||||
"showTitles": ShowTitlesPreference.windowTitle.indexAsString,
|
||||
"fadeOutAnimation": "false",
|
||||
"previewFadeInAnimation": "true",
|
||||
"startAtLogin": "true",
|
||||
"menubarIcon": MenubarIconPreference.outlined.indexAsString,
|
||||
"menubarIconShown": "true",
|
||||
"language": LanguagePreference.systemDefault.indexAsString,
|
||||
"exceptions": defaultExceptions(),
|
||||
"updatePolicy": UpdatePolicyPreference.autoCheck.indexAsString,
|
||||
"crashPolicy": CrashPolicyPreference.ask.indexAsString,
|
||||
"hideAppBadges": "false",
|
||||
"hideThumbnails": "false",
|
||||
"hideSpaceNumberLabels": "false",
|
||||
"hideStatusIcons": "false",
|
||||
"previewFocusedWindow": "false",
|
||||
"screenRecordingPermissionSkipped": "false",
|
||||
"trackpadHapticFeedbackEnabled": "true",
|
||||
"settingsWindowShownOnFirstLaunch": "false",
|
||||
]
|
||||
(0..<maxShortcutCount).forEach { index in
|
||||
values[indexToName("holdShortcut", index)] = "⌥"
|
||||
values[indexToName("nextWindowShortcut", index)] = index == 0 ? "⇥" : (index == 1 ? keyAboveTabDependingOnInputSource() : "")
|
||||
values[indexToName("holdShortcut", index)] = defaultShortcut("⌥")
|
||||
values[indexToName("nextWindowShortcut", index)] = defaultShortcut(index == 0 ? "⇥" : (index == 1 ? keyAboveTabDependingOnInputSource() : ""))
|
||||
}
|
||||
(0...maxShortcutCount).forEach { index in
|
||||
values[indexToName("appsToShow", index)] = index == 1 ? AppsToShowPreference.active.indexAsString : (index == 2 ? AppsToShowPreference.nonActive.indexAsString : AppsToShowPreference.all.indexAsString)
|
||||
@@ -94,21 +71,31 @@ class Preferences {
|
||||
|
||||
// system preferences
|
||||
static var finderShowsQuitMenuItem: Bool { UserDefaults(suiteName: "com.apple.Finder")?.bool(forKey: "QuitMenuItem") ?? false }
|
||||
static let staticShortcutKeys = [
|
||||
"focusWindowShortcut", "previousWindowShortcut", "cancelShortcut", "lockSearchShortcut", "closeWindowShortcut",
|
||||
"minDeminWindowShortcut", "toggleFullscreenWindowShortcut", "quitAppShortcut", "hideShowAppShortcut", "searchShortcut",
|
||||
]
|
||||
static var allShortcutPreferenceKeys: [String] {
|
||||
staticShortcutKeys + (0..<maxShortcutCount).flatMap { [indexToName("holdShortcut", $0), indexToName("nextWindowShortcut", $0)] }
|
||||
}
|
||||
static let emptyShortcut = Shortcut(code: .none, modifierFlags: [], characters: nil, charactersIgnoringModifiers: nil)
|
||||
private static let shortcutStorageStringField = "string"
|
||||
private static let shortcutStorageDataField = "secureData"
|
||||
|
||||
// persisted values
|
||||
static var holdShortcut: [String] { (0..<shortcutCount).map { CachedUserDefaults.string(indexToName("holdShortcut", $0)) } }
|
||||
static var nextWindowShortcut: [String] { (0..<shortcutCount).map { CachedUserDefaults.string(indexToName("nextWindowShortcut", $0)) } }
|
||||
static var holdShortcut: [Shortcut?] { (0..<shortcutCount).map { CachedUserDefaults.shortcut(indexToName("holdShortcut", $0)) } }
|
||||
static var nextWindowShortcut: [Shortcut?] { (0..<shortcutCount).map { CachedUserDefaults.shortcut(indexToName("nextWindowShortcut", $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 lockSearchShortcut: String { CachedUserDefaults.string("lockSearchShortcut") }
|
||||
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") }
|
||||
static var searchShortcut: String { CachedUserDefaults.string("searchShortcut") }
|
||||
static var focusWindowShortcut: Shortcut? { CachedUserDefaults.shortcut("focusWindowShortcut") }
|
||||
static var previousWindowShortcut: Shortcut? { CachedUserDefaults.shortcut("previousWindowShortcut") }
|
||||
static var cancelShortcut: Shortcut? { CachedUserDefaults.shortcut("cancelShortcut") }
|
||||
static var lockSearchShortcut: Shortcut? { CachedUserDefaults.shortcut("lockSearchShortcut") }
|
||||
static var closeWindowShortcut: Shortcut? { CachedUserDefaults.shortcut("closeWindowShortcut") }
|
||||
static var minDeminWindowShortcut: Shortcut? { CachedUserDefaults.shortcut("minDeminWindowShortcut") }
|
||||
static var toggleFullscreenWindowShortcut: Shortcut? { CachedUserDefaults.shortcut("toggleFullscreenWindowShortcut") }
|
||||
static var quitAppShortcut: Shortcut? { CachedUserDefaults.shortcut("quitAppShortcut") }
|
||||
static var hideShowAppShortcut: Shortcut? { CachedUserDefaults.shortcut("hideShowAppShortcut") }
|
||||
static var searchShortcut: Shortcut? { CachedUserDefaults.shortcut("searchShortcut") }
|
||||
// periphery:ignore
|
||||
static var arrowKeysEnabled: Bool { CachedUserDefaults.bool("arrowKeysEnabled") }
|
||||
// periphery:ignore
|
||||
@@ -183,6 +170,30 @@ class Preferences {
|
||||
set("settingsWindowShownOnFirstLaunch", "true", false)
|
||||
}
|
||||
|
||||
static func defaultShortcut(_ keyEquivalent: String) -> [String: Any] {
|
||||
shortcutStorage(shortcutFromKeyEquivalent(keyEquivalent), keyEquivalent)
|
||||
}
|
||||
|
||||
static func setShortcut(_ key: String, _ shortcut: Shortcut?, _ notify: Bool = true) {
|
||||
setShortcut(key, shortcut, stringRepresentation: nil, notify)
|
||||
}
|
||||
|
||||
static func setShortcut(_ key: String, _ shortcut: Shortcut?, stringRepresentation: String?, _ notify: Bool = true) {
|
||||
UserDefaults.standard.set(shortcutStorage(shortcut, stringRepresentation), forKey: key)
|
||||
CachedUserDefaults.removeFromCache(key)
|
||||
if notify {
|
||||
PreferencesEvents.preferenceChanged(key)
|
||||
}
|
||||
}
|
||||
|
||||
static func setShortcut(_ key: String, keyEquivalent: String, _ notify: Bool = true) {
|
||||
setShortcut(key, shortcutFromKeyEquivalent(keyEquivalent), stringRepresentation: keyEquivalent, notify)
|
||||
}
|
||||
|
||||
static func shortcut(_ key: String) -> Shortcut? {
|
||||
CachedUserDefaults.shortcut(key)
|
||||
}
|
||||
|
||||
static func set<T>(_ key: String, _ value: T, _ notify: Bool = true) where T: Encodable {
|
||||
UserDefaults.standard.set(key == "exceptions" ? jsonEncode(value) : value, forKey: key)
|
||||
CachedUserDefaults.removeFromCache(key)
|
||||
@@ -238,6 +249,40 @@ class Preferences {
|
||||
return String(data: try! JSONEncoder().encode(value), encoding: .utf8)!
|
||||
}
|
||||
|
||||
static func archiveShortcut(_ shortcut: Shortcut?) -> Data {
|
||||
if #available(macOS 10.13, *) {
|
||||
return try! NSKeyedArchiver.archivedData(withRootObject: shortcut ?? emptyShortcut, requiringSecureCoding: true)
|
||||
}
|
||||
return NSKeyedArchiver.archivedData(withRootObject: shortcut ?? emptyShortcut)
|
||||
}
|
||||
|
||||
static func shortcutStorage(_ shortcut: Shortcut?, _ stringRepresentation: String?) -> [String: Any] {
|
||||
[
|
||||
shortcutStorageStringField: stringRepresentation ?? shortcut?.readableStringRepresentation(isASCII: true) ?? "",
|
||||
shortcutStorageDataField: archiveShortcut(shortcut),
|
||||
]
|
||||
}
|
||||
|
||||
static func decodeShortcutStorage(_ value: Any) -> (Bool, Shortcut?) {
|
||||
guard let storage = value as? [String: Any], let data = storage[shortcutStorageDataField] as? Data else { return (false, nil) }
|
||||
return unarchiveShortcut(data)
|
||||
}
|
||||
|
||||
static func unarchiveShortcut(_ data: Data) -> (Bool, Shortcut?) {
|
||||
let shortcut: Shortcut?
|
||||
if #available(macOS 10.13, *) {
|
||||
shortcut = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Shortcut.self, from: data)
|
||||
} else {
|
||||
shortcut = NSKeyedUnarchiver.unarchiveObject(with: data) as? Shortcut
|
||||
}
|
||||
guard let shortcut else { return (false, nil) }
|
||||
return (true, shortcut.keyCode == .none && shortcut.modifierFlags == [] ? nil : shortcut)
|
||||
}
|
||||
|
||||
static func shortcutFromKeyEquivalent(_ keyEquivalent: String) -> Shortcut? {
|
||||
keyEquivalent.isEmpty ? nil : Shortcut(keyEquivalent: keyEquivalent)
|
||||
}
|
||||
|
||||
static func indexToName(_ baseName: String, _ index: Int) -> String {
|
||||
return baseName + (index == 0 ? "" : String(index + 1))
|
||||
}
|
||||
@@ -277,6 +322,23 @@ class CachedUserDefaults {
|
||||
return finalValue
|
||||
}
|
||||
|
||||
static func shortcut(_ key: String) -> Shortcut? {
|
||||
if let cachedFinalValue = cache.withLock({ $0[key] }) {
|
||||
return cachedFinalValue as? Shortcut
|
||||
}
|
||||
guard let objectValue = UserDefaults.standard.object(forKey: key) else {
|
||||
cache.withLock { $0[key] = NSNull() }
|
||||
return nil
|
||||
}
|
||||
let (isValid, finalValue) = Preferences.decodeShortcutStorage(objectValue)
|
||||
if isValid {
|
||||
cache.withLock { $0[key] = finalValue ?? NSNull() }
|
||||
return finalValue
|
||||
}
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
return shortcut(key)
|
||||
}
|
||||
|
||||
static func int(_ key: String) -> Int {
|
||||
return getThenConvertOrReset(key, { s in Int(s) })
|
||||
}
|
||||
|
||||
@@ -295,6 +295,44 @@ class PreferencesMigrations {
|
||||
UserDefaults.standard.set(new, forKey: preference)
|
||||
}
|
||||
}
|
||||
|
||||
private static func migrateShortcutPreferencesToSecureCoding() {
|
||||
Preferences.allShortcutPreferenceKeys.forEach {
|
||||
let key = $0
|
||||
guard let oldValue = UserDefaults.standard.object(forKey: key) else { return }
|
||||
if let oldStorage = oldValue as? [String: Any] {
|
||||
let (isValid, shortcut) = Preferences.decodeShortcutStorage(oldStorage)
|
||||
guard isValid else {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
UserDefaults.standard.set(Preferences.shortcutStorage(shortcut, oldStorage["string"] as? String), forKey: key)
|
||||
return
|
||||
}
|
||||
if let oldDataValue = oldValue as? Data {
|
||||
let (isValid, shortcut) = Preferences.unarchiveShortcut(oldDataValue)
|
||||
guard isValid else {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
UserDefaults.standard.set(Preferences.shortcutStorage(shortcut, nil), forKey: key)
|
||||
return
|
||||
}
|
||||
guard let oldStringValue = oldValue as? String else {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
if oldStringValue.isEmpty {
|
||||
UserDefaults.standard.set(Preferences.shortcutStorage(nil, ""), forKey: key)
|
||||
return
|
||||
}
|
||||
guard let migratedShortcut = Preferences.shortcutFromKeyEquivalent(oldStringValue) else {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
UserDefaults.standard.set(Preferences.shortcutStorage(migratedShortcut, oldStringValue), forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// workaround to silence compiler warning
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import Cocoa
|
||||
import Carbon.HIToolbox.TextInputSources
|
||||
|
||||
class InputSourceEvents: NSObject {
|
||||
private static let observer = InputSourceEvents()
|
||||
private static var isObserving = false
|
||||
|
||||
static func observe() {
|
||||
guard !isObserving else { return }
|
||||
isObserving = true
|
||||
let name = NSNotification.Name(rawValue: kTISNotifySelectedKeyboardInputSourceChanged as String)
|
||||
DistributedNotificationCenter.default().addObserver(observer, selector: #selector(handleEvent), name: name, object: nil, suspensionBehavior: .deliverImmediately)
|
||||
}
|
||||
|
||||
@objc private func handleEvent(_ notification: Notification) {
|
||||
Logger.debug { InputSourceEvents.currentInputSource() }
|
||||
ControlsTab.inputSourceChanged()
|
||||
}
|
||||
|
||||
static func currentInputSource() -> String {
|
||||
guard let inputSource = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue(),
|
||||
let localizedNamePointer = TISGetInputSourceProperty(inputSource, kTISPropertyLocalizedName) else { return "unknown" }
|
||||
let localizedName = Unmanaged<AnyObject>.fromOpaque(localizedNamePointer).takeUnretainedValue()
|
||||
return localizedName as? String ?? "unknown"
|
||||
}
|
||||
}
|
||||
@@ -387,6 +387,7 @@ class App: AppCenterApplication {
|
||||
ScreensEvents.observe()
|
||||
SystemAppearanceEvents.observe()
|
||||
SystemScrollerStyleEvents.observe()
|
||||
InputSourceEvents.observe()
|
||||
Applications.initialDiscovery()
|
||||
KeyboardEvents.addEventHandlers()
|
||||
CursorEvents.observe()
|
||||
|
||||
@@ -8,6 +8,10 @@ class CustomRecorderControl: RecorderControl {
|
||||
var id: String!
|
||||
|
||||
convenience init(_ shortcutString: String, _ clearable: Bool, _ id: String) {
|
||||
self.init(Shortcut(keyEquivalent: shortcutString), clearable, id)
|
||||
}
|
||||
|
||||
convenience init(_ shortcut: Shortcut?, _ clearable: Bool, _ id: String) {
|
||||
self.init(frame: .zero)
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
self.clearable = clearable
|
||||
@@ -17,7 +21,7 @@ class CustomRecorderControl: RecorderControl {
|
||||
allowsDeleteToClearShortcutAndEndRecording = false
|
||||
allowsModifierFlagsOnlyShortcut = true
|
||||
restrictModifiers([])
|
||||
objectValue = Shortcut(keyEquivalent: shortcutString)
|
||||
objectValue = shortcut
|
||||
addOrUpdateConstraint(widthAnchor, 100)
|
||||
}
|
||||
|
||||
@@ -134,4 +138,3 @@ extension CustomRecorderControl: RecorderControlDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,8 +106,8 @@ class LabelAndControl: NSObject {
|
||||
return view
|
||||
}
|
||||
|
||||
static func makeLabelWithRecorder(_ labelText: String, _ rawName: String, _ shortcutString: String, _ clearable: Bool = true, labelPosition: LabelPosition = .leftWithSeparator) -> [NSView] {
|
||||
let input = CustomRecorderControl(shortcutString, clearable, rawName)
|
||||
static func makeLabelWithRecorder(_ labelText: String, _ rawName: String, _ shortcut: Shortcut?, _ clearable: Bool = true, labelPosition: LabelPosition = .leftWithSeparator) -> [NSView] {
|
||||
let input = CustomRecorderControl(shortcut, clearable, rawName)
|
||||
let views = makeLabelWithProvidedControl(labelText, rawName, input, labelPosition: labelPosition, extraAction: { _ in ControlsTab.shortcutChangedCallback(input) })
|
||||
ControlsTab.shortcutChangedCallback(input)
|
||||
ControlsTab.shortcutControls[rawName] = (input, labelText)
|
||||
@@ -325,6 +325,15 @@ class LabelAndControl: NSObject {
|
||||
}
|
||||
|
||||
static func controlWasChanged(_ senderControl: NSControl, _ controlId: String?) {
|
||||
if let recorderControl = senderControl as? RecorderControl {
|
||||
let key = senderControl.identifier!.rawValue
|
||||
let oldValue = Preferences.shortcut(key)
|
||||
let newValue = recorderControl.objectValue
|
||||
if oldValue == nil && newValue == nil { return }
|
||||
if let oldValue, let newValue, oldValue.isEqual(newValue) { return }
|
||||
Preferences.setShortcut(key, newValue, stringRepresentation: recorderControl.stringValue)
|
||||
return
|
||||
}
|
||||
if let newValue = LabelAndControl.getControlValue(senderControl, controlId) {
|
||||
if let oldValue = UserDefaults.standard.string(forKey: senderControl.identifier!.rawValue), newValue == oldValue {
|
||||
return
|
||||
|
||||
@@ -222,6 +222,11 @@ class ControlsTab {
|
||||
}
|
||||
}
|
||||
|
||||
static func inputSourceChanged() {
|
||||
refreshShortcutRows()
|
||||
refreshShortcutControlsDisplay()
|
||||
}
|
||||
|
||||
static func initTab() -> NSView {
|
||||
shortcutEditorViews = (0..<Preferences.maxShortcutCount).map { shortcutTab($0) }
|
||||
gestureEditorView = gestureTab(Preferences.gestureIndex)
|
||||
@@ -369,12 +374,10 @@ class ControlsTab {
|
||||
|
||||
private static func shortcutTab(_ index: Int) -> TableGroupView {
|
||||
let holdName = Preferences.indexToName("holdShortcut", index)
|
||||
let holdValue = UserDefaults.standard.string(forKey: holdName) ?? ""
|
||||
var holdShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Hold", comment: ""), holdName, holdValue, false, labelPosition: .leftWithoutSeparator)
|
||||
var holdShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Hold", comment: ""), holdName, Preferences.shortcut(holdName), false, labelPosition: .leftWithoutSeparator)
|
||||
holdShortcut.append(LabelAndControl.makeLabel(NSLocalizedString("and press", comment: "")))
|
||||
let nextName = Preferences.indexToName("nextWindowShortcut", index)
|
||||
let nextValue = UserDefaults.standard.string(forKey: nextName) ?? ""
|
||||
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), nextName, nextValue, labelPosition: .right)
|
||||
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), nextName, Preferences.shortcut(nextName), labelPosition: .right)
|
||||
return controlTab(index, holdShortcut + [nextWindowShortcut[0]], shortcutEditorContentWidth)
|
||||
}
|
||||
|
||||
@@ -419,6 +422,13 @@ class ControlsTab {
|
||||
return table
|
||||
}
|
||||
|
||||
private static func refreshShortcutControlsDisplay() {
|
||||
shortcutControls.values.forEach {
|
||||
$0.0.needsDisplay = true
|
||||
$0.0.invalidateIntrinsicContentSize()
|
||||
}
|
||||
}
|
||||
|
||||
private static func refreshShortcutUi() {
|
||||
if selectedShortcutIndex != gestureSelectionIndex {
|
||||
selectedShortcutIndex = max(0, min(selectedShortcutIndex, Preferences.shortcutCount - 1))
|
||||
@@ -524,8 +534,8 @@ class ControlsTab {
|
||||
}
|
||||
|
||||
private static func setAddedShortcutTriggerDefaults(_ index: Int) {
|
||||
Preferences.set(Preferences.indexToName("holdShortcut", index), "⌥", false)
|
||||
Preferences.set(Preferences.indexToName("nextWindowShortcut", index), "", false)
|
||||
Preferences.setShortcut(Preferences.indexToName("holdShortcut", index), keyEquivalent: "⌥", false)
|
||||
Preferences.setShortcut(Preferences.indexToName("nextWindowShortcut", index), keyEquivalent: "", false)
|
||||
Preferences.set(Preferences.indexToName("appsToShow", index), AppsToShowPreference.all.indexAsString, false)
|
||||
}
|
||||
|
||||
@@ -533,8 +543,9 @@ class ControlsTab {
|
||||
removableShortcutPreferences.forEach { baseName in
|
||||
let fromKey = Preferences.indexToName(baseName, fromIndex)
|
||||
let toKey = Preferences.indexToName(baseName, toIndex)
|
||||
if let value = UserDefaults.standard.string(forKey: fromKey) {
|
||||
Preferences.set(toKey, value, false)
|
||||
if let value = UserDefaults.standard.object(forKey: fromKey) {
|
||||
UserDefaults.standard.set(value, forKey: toKey)
|
||||
CachedUserDefaults.removeFromCache(toKey)
|
||||
} else {
|
||||
Preferences.remove(toKey, false)
|
||||
}
|
||||
@@ -558,8 +569,7 @@ class ControlsTab {
|
||||
|
||||
private static func syncShortcutRecorderControlValue(_ controlId: String) {
|
||||
guard let control = shortcutControls[controlId]?.0 else { return }
|
||||
let value = UserDefaults.standard.string(forKey: controlId) ?? ""
|
||||
control.objectValue = value.isEmpty ? nil : Shortcut(keyEquivalent: value)
|
||||
control.objectValue = Preferences.shortcut(controlId)
|
||||
}
|
||||
|
||||
private static func syncShortcutDropdownControlValue(_ controlId: String) {
|
||||
@@ -588,8 +598,8 @@ class ControlsTab {
|
||||
}
|
||||
|
||||
private static func shortcutSummary(_ index: Int) -> String {
|
||||
let holdShortcut = UserDefaults.standard.string(forKey: Preferences.indexToName("holdShortcut", index)) ?? ""
|
||||
let nextWindowShortcut = UserDefaults.standard.string(forKey: Preferences.indexToName("nextWindowShortcut", index)) ?? ""
|
||||
let holdShortcut = Preferences.shortcut(Preferences.indexToName("holdShortcut", index))?.keyEquivalent ?? ""
|
||||
let nextWindowShortcut = Preferences.shortcut(Preferences.indexToName("nextWindowShortcut", index))?.keyEquivalent ?? ""
|
||||
if nextWindowShortcut.isEmpty {
|
||||
return holdShortcut
|
||||
}
|
||||
@@ -717,8 +727,7 @@ class ControlsTab {
|
||||
}
|
||||
if controlId.hasPrefix("holdShortcut") {
|
||||
let i = Preferences.nameToIndex(controlId)
|
||||
let holdShortcut = UserDefaults.standard.string(forKey: controlId) ?? ""
|
||||
guard let shortcut = Shortcut(keyEquivalent: holdShortcut) else {
|
||||
guard let shortcut = Preferences.shortcut(controlId) else {
|
||||
removeShortcutIfExists(controlId)
|
||||
return
|
||||
}
|
||||
@@ -728,9 +737,8 @@ class ControlsTab {
|
||||
shortcutChangedCallback(nextWindowShortcut)
|
||||
}
|
||||
} else {
|
||||
let newValue = combineHoldAndNextWindow(controlId, sender)
|
||||
let newShortcut = Shortcut(keyEquivalent: newValue)
|
||||
if newValue.isEmpty || newShortcut == nil {
|
||||
let newShortcut = combineHoldAndNextWindow(controlId, sender)
|
||||
if newShortcut == nil {
|
||||
removeShortcutIfExists(controlId)
|
||||
restrictModifiersOfHoldShortcut(controlId, [])
|
||||
(sender as! CustomRecorderControl).objectValue = nil
|
||||
@@ -750,16 +758,13 @@ class ControlsTab {
|
||||
}
|
||||
}
|
||||
|
||||
static func combineHoldAndNextWindow(_ controlId: String, _ sender: NSControl) -> String {
|
||||
let baseValue = (sender as! RecorderControl).stringValue
|
||||
if baseValue == "" {
|
||||
return ""
|
||||
}
|
||||
static func combineHoldAndNextWindow(_ controlId: String, _ sender: NSControl) -> Shortcut? {
|
||||
guard let baseShortcut = (sender as! RecorderControl).objectValue else { return nil }
|
||||
if controlId.starts(with: "nextWindowShortcut") {
|
||||
let holdShortcut = UserDefaults.standard.string(forKey: Preferences.indexToName("holdShortcut", Preferences.nameToIndex(controlId))) ?? ""
|
||||
return holdShortcut + baseValue
|
||||
let holdShortcut = Preferences.shortcut(Preferences.indexToName("holdShortcut", Preferences.nameToIndex(controlId)))
|
||||
return combineShortcuts(holdShortcut, baseShortcut)
|
||||
}
|
||||
return baseValue
|
||||
return baseShortcut
|
||||
}
|
||||
|
||||
@objc static func arrowKeysEnabledCallback(_ sender: NSControl) {
|
||||
@@ -856,8 +861,7 @@ class ControlsTab {
|
||||
applyShortcutPreference(Preferences.indexToName("nextWindowShortcut", Preferences.nameToIndex(controlId)))
|
||||
return
|
||||
}
|
||||
let shortcutString = combinedShortcutString(controlId)
|
||||
guard !shortcutString.isEmpty, let shortcut = Shortcut(keyEquivalent: shortcutString) else {
|
||||
guard let shortcut = combinedShortcut(controlId) else {
|
||||
removeShortcutIfExists(controlId)
|
||||
restrictModifiersOfHoldShortcut(controlId, [])
|
||||
return
|
||||
@@ -868,23 +872,25 @@ class ControlsTab {
|
||||
|
||||
private static func applyHoldShortcutPreference(_ controlId: String) {
|
||||
let i = Preferences.nameToIndex(controlId)
|
||||
let holdShortcut = UserDefaults.standard.string(forKey: controlId) ?? ""
|
||||
guard let shortcut = Shortcut(keyEquivalent: holdShortcut) else {
|
||||
guard let shortcut = Preferences.shortcut(controlId) else {
|
||||
removeShortcutIfExists(controlId)
|
||||
return
|
||||
}
|
||||
addShortcut(.up, .global, shortcut, controlId, i)
|
||||
}
|
||||
|
||||
private static func combinedShortcutString(_ controlId: String) -> String {
|
||||
guard let baseValue = UserDefaults.standard.string(forKey: controlId), !baseValue.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
private static func combinedShortcut(_ controlId: String) -> Shortcut? {
|
||||
guard let baseShortcut = Preferences.shortcut(controlId) else { return nil }
|
||||
if controlId.starts(with: "nextWindowShortcut") {
|
||||
let holdShortcut = UserDefaults.standard.string(forKey: Preferences.indexToName("holdShortcut", Preferences.nameToIndex(controlId))) ?? ""
|
||||
return holdShortcut + baseValue
|
||||
let holdShortcut = Preferences.shortcut(Preferences.indexToName("holdShortcut", Preferences.nameToIndex(controlId)))
|
||||
return combineShortcuts(holdShortcut, baseShortcut)
|
||||
}
|
||||
return baseValue
|
||||
return baseShortcut
|
||||
}
|
||||
|
||||
private static func combineShortcuts(_ holdShortcut: Shortcut?, _ baseShortcut: Shortcut) -> Shortcut {
|
||||
guard let holdShortcut else { return baseShortcut }
|
||||
return Shortcut(code: baseShortcut.keyCode, modifierFlags: [holdShortcut.modifierFlags, baseShortcut.modifierFlags], characters: baseShortcut.characters, charactersIgnoringModifiers: baseShortcut.charactersIgnoringModifiers)
|
||||
}
|
||||
|
||||
private static func applyArrowKeysPreference() {
|
||||
|
||||
Reference in new issue
Block a user