From 1001035d611cb411df702a941f5eb40626fc5ea7 Mon Sep 17 00:00:00 2001 From: Alex Rodionov Date: Fri, 19 Jun 2020 19:56:02 -0400 Subject: [PATCH] Migrate hotkey to sindresorhus/KeyboardShortcuts Replaces the global hotkey library from combination of KeyHolder, Magnet and Sauce to KeyboardShortcuts. The package has few benefits: 1. More native-like UI. 2. Better non-US keyboard support (fixes #108). 3. Built-in storage via UserDefaults. The package has few limitations: 1. Storage key and type is hardcoded. This requires us to migrate from old "hotKey" string preference to "KeyboardShortcuts_popup" JSON preference. 2. There is no way to initialize KeyboardShortcuts.Key with character string. This is needed for migration. To workaround the limitation, Sauce package is left and is used to construct the key from string, retrieve key code from it and construct KeyboardShortcuts.Key with it. 3. There is no support for default shortcuts. This is addressed in sindresorhus/KeyboardShortcuts#13, which is why forked branch is used. 4. Recorder view cannot be previewed in Interface Builder: sindresorhus/KeyboardShortcuts#14. 5. It's impossible to register the same shortcut twice: sindresorhus/KeyboardShortcuts#15. The package is great even with all these limitations. The most important piece is of course better non-US keyboard support. DVORAK is supported as well. --- Maccy.xcodeproj/project.pbxproj | 14 +- Maccy/AppDelegate.swift | 42 + Maccy/FilterMenuItemView.swift | 6 +- Maccy/GlobalHotKey.swift | 63 +- Maccy/KeyboardShortcuts.Name+Popup.swift | 5 + Maccy/Keys.swift | 4 +- .../GeneralPreferenceViewController.xib | 91 +- .../GeneralPreferenceViewController.swift | 40 +- Maccy/UserDefaults+Configuration.swift | 7 - MaccyTests/UserDefaultsTests.swift | 6 - Podfile | 15 +- Podfile.lock | 26 +- Pods/KeyHolder/LICENSE | 21 - .../KeyHolder/Lib/KeyHolder/ClearButton.swift | 97 - Pods/KeyHolder/Lib/KeyHolder/RecordView.swift | 387 ---- Pods/KeyHolder/README.md | 70 - .../CarbonKeyboardShortcuts.swift | 181 ++ .../Sources/KeyboardShortcuts/Key.swift | 633 ++++++ .../KeyboardShortcuts/KeyboardShortcuts.swift | 226 +++ .../KeyboardShortcuts/NSMenuItem++.swift | 79 + .../Sources/KeyboardShortcuts/Name.swift | 47 + .../Sources/KeyboardShortcuts/Recorder.swift | 51 + .../KeyboardShortcuts/RecorderCocoa.swift | 217 +++ .../Sources/KeyboardShortcuts/Shortcut.swift | 254 +++ .../Sources/KeyboardShortcuts/util.swift | 348 ++++ Pods/KeyboardShortcuts/license | 9 + Pods/KeyboardShortcuts/readme.md | 163 ++ Pods/Local Podspecs/KeyHolder.podspec.json | 30 - .../KeyboardShortcuts.podspec.json | 21 + Pods/Magnet/LICENSE | 21 - .../Extensions/CollectionExtension.swift | 17 - .../Lib/Magnet/Extensions/IntExtension.swift | 36 - .../Lib/Magnet/Extensions/KeyExtension.swift | 75 - .../Magnet/Extensions/NSEventExtension.swift | 84 - Pods/Magnet/Lib/Magnet/HotKey.swift | 108 -- Pods/Magnet/Lib/Magnet/HotKeyCenter.swift | 170 -- Pods/Magnet/Lib/Magnet/KeyCombo.swift | 180 -- .../Lib/Magnet/ModifierEventHandler.swift | 72 - Pods/Magnet/README.md | 103 - Pods/Manifest.lock | 26 +- Pods/Pods.xcodeproj/project.pbxproj | 1718 +++++++---------- .../KeyHolder/KeyHolder-dummy.m | 5 - .../KeyHolder/KeyHolder.modulemap | 6 - .../KeyHolder/KeyHolder.xcconfig | 12 - .../Info.plist | 2 +- .../KeyboardShortcuts-dummy.m | 5 + .../KeyboardShortcuts-prefix.pch} | 0 .../KeyboardShortcuts-umbrella.h} | 4 +- .../KeyboardShortcuts.modulemap | 6 + .../KeyboardShortcuts.xcconfig} | 7 +- Pods/Target Support Files/Magnet/Info.plist | 26 - .../Magnet/Magnet-Info.plist | 26 - .../Magnet/Magnet-dummy.m | 5 - .../Magnet/Magnet-prefix.pch | 12 - .../Magnet/Magnet-umbrella.h | 16 - .../Magnet/Magnet.debug.xcconfig | 13 - .../Magnet/Magnet.modulemap | 6 - .../Magnet/Magnet.release.xcconfig | 13 - .../Pods-Maccy-acknowledgements.markdown | 49 +- .../Pods-Maccy-acknowledgements.plist | 55 +- .../Pods-Maccy/Pods-Maccy-frameworks.sh | 6 +- .../Pods-Maccy/Pods-Maccy.debug.xcconfig | 6 +- .../Pods-Maccy/Pods-Maccy.release.xcconfig | 6 +- .../Pods-MaccyTests.debug.xcconfig | 4 +- .../Pods-MaccyTests.release.xcconfig | 4 +- 65 files changed, 3142 insertions(+), 2915 deletions(-) create mode 100644 Maccy/KeyboardShortcuts.Name+Popup.swift delete mode 100644 Pods/KeyHolder/LICENSE delete mode 100644 Pods/KeyHolder/Lib/KeyHolder/ClearButton.swift delete mode 100644 Pods/KeyHolder/Lib/KeyHolder/RecordView.swift delete mode 100644 Pods/KeyHolder/README.md create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/CarbonKeyboardShortcuts.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/Key.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/KeyboardShortcuts.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/NSMenuItem++.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/Name.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/Recorder.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/RecorderCocoa.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/Shortcut.swift create mode 100644 Pods/KeyboardShortcuts/Sources/KeyboardShortcuts/util.swift create mode 100644 Pods/KeyboardShortcuts/license create mode 100644 Pods/KeyboardShortcuts/readme.md delete mode 100644 Pods/Local Podspecs/KeyHolder.podspec.json create mode 100644 Pods/Local Podspecs/KeyboardShortcuts.podspec.json delete mode 100644 Pods/Magnet/LICENSE delete mode 100644 Pods/Magnet/Lib/Magnet/Extensions/CollectionExtension.swift delete mode 100644 Pods/Magnet/Lib/Magnet/Extensions/IntExtension.swift delete mode 100644 Pods/Magnet/Lib/Magnet/Extensions/KeyExtension.swift delete mode 100644 Pods/Magnet/Lib/Magnet/Extensions/NSEventExtension.swift delete mode 100644 Pods/Magnet/Lib/Magnet/HotKey.swift delete mode 100644 Pods/Magnet/Lib/Magnet/HotKeyCenter.swift delete mode 100644 Pods/Magnet/Lib/Magnet/KeyCombo.swift delete mode 100644 Pods/Magnet/Lib/Magnet/ModifierEventHandler.swift delete mode 100644 Pods/Magnet/README.md delete mode 100644 Pods/Target Support Files/KeyHolder/KeyHolder-dummy.m delete mode 100644 Pods/Target Support Files/KeyHolder/KeyHolder.modulemap delete mode 100644 Pods/Target Support Files/KeyHolder/KeyHolder.xcconfig rename Pods/Target Support Files/{KeyHolder => KeyboardShortcuts}/Info.plist (96%) create mode 100644 Pods/Target Support Files/KeyboardShortcuts/KeyboardShortcuts-dummy.m rename Pods/Target Support Files/{KeyHolder/KeyHolder-prefix.pch => KeyboardShortcuts/KeyboardShortcuts-prefix.pch} (100%) rename Pods/Target Support Files/{KeyHolder/KeyHolder-umbrella.h => KeyboardShortcuts/KeyboardShortcuts-umbrella.h} (60%) create mode 100644 Pods/Target Support Files/KeyboardShortcuts/KeyboardShortcuts.modulemap rename Pods/Target Support Files/{Magnet/Magnet.xcconfig => KeyboardShortcuts/KeyboardShortcuts.xcconfig} (60%) delete mode 100644 Pods/Target Support Files/Magnet/Info.plist delete mode 100644 Pods/Target Support Files/Magnet/Magnet-Info.plist delete mode 100644 Pods/Target Support Files/Magnet/Magnet-dummy.m delete mode 100644 Pods/Target Support Files/Magnet/Magnet-prefix.pch delete mode 100644 Pods/Target Support Files/Magnet/Magnet-umbrella.h delete mode 100644 Pods/Target Support Files/Magnet/Magnet.debug.xcconfig delete mode 100644 Pods/Target Support Files/Magnet/Magnet.modulemap delete mode 100644 Pods/Target Support Files/Magnet/Magnet.release.xcconfig diff --git a/Maccy.xcodeproj/project.pbxproj b/Maccy.xcodeproj/project.pbxproj index 5c93064b..88ded3b0 100644 --- a/Maccy.xcodeproj/project.pbxproj +++ b/Maccy.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ DAEE38471E3DBEB100DD2966 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAEE38461E3DBEB100DD2966 /* AppDelegate.swift */; }; DAEE38541E3DD6F100DD2966 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAEE38531E3DD6F100DD2966 /* History.swift */; }; DAF176C6202AA56B00C5FD0D /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF176C5202AA56B00C5FD0D /* Keys.swift */; }; + DAFEF0B8249D7DEE006029E8 /* KeyboardShortcuts.Name+Popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAFEF0B7249D7DEE006029E8 /* KeyboardShortcuts.Name+Popup.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -151,6 +152,7 @@ DAEE384D1E3DBEB100DD2966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DAEE38531E3DD6F100DD2966 /* History.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = History.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; DAF176C5202AA56B00C5FD0D /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = ""; }; + DAFEF0B7249D7DEE006029E8 /* KeyboardShortcuts.Name+Popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyboardShortcuts.Name+Popup.swift"; sourceTree = ""; }; EE4F94E5429D67E354BC8011 /* Pods-MaccyTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MaccyTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MaccyTests/Pods-MaccyTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -285,6 +287,7 @@ DAB65E0C2440B0D4000AECA8 /* HistoryItemContent.swift */, DA0950EF244AA1A90008414C /* HistoryItemOld.swift */, DA573EAC1EDD410F00561FB2 /* HistoryMenuItem.swift */, + DAFEF0B7249D7DEE006029E8 /* KeyboardShortcuts.Name+Popup.swift */, DAF176C5202AA56B00C5FD0D /* Keys.swift */, DA8953B81E446A4E00884EAB /* Maccy.swift */, DA2C752B2029FE990090965D /* Menu.swift */, @@ -487,9 +490,8 @@ inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Maccy/Pods-Maccy-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Fuse/Fuse.framework", - "${BUILT_PRODUCTS_DIR}/KeyHolder/KeyHolder.framework", + "${BUILT_PRODUCTS_DIR}/KeyboardShortcuts/KeyboardShortcuts.framework", "${BUILT_PRODUCTS_DIR}/LoginServiceKit/LoginServiceKit.framework", - "${BUILT_PRODUCTS_DIR}/Magnet/Magnet.framework", "${BUILT_PRODUCTS_DIR}/Preferences/Preferences.framework", "${BUILT_PRODUCTS_DIR}/Sauce/Sauce.framework", "${PODS_ROOT}/Sparkle/Sparkle.framework", @@ -499,9 +501,8 @@ name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Fuse.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeyHolder.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeyboardShortcuts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LoginServiceKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Magnet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Preferences.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sauce.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sparkle.framework", @@ -518,12 +519,8 @@ buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( ); - outputFileListPaths = ( - ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -573,6 +570,7 @@ DAAEB196219694AE00A7883C /* About.swift in Sources */, DA0950F0244AA1A90008414C /* HistoryItemOld.swift in Sources */, DA6DA786236D605B0062CCC6 /* MenuTag.swift in Sources */, + DAFEF0B8249D7DEE006029E8 /* KeyboardShortcuts.Name+Popup.swift in Sources */, DAB65E0D2440B0D4000AECA8 /* HistoryItemContent.swift in Sources */, DA696BCE240177E800DE80CF /* Sorter.swift in Sources */, DA573EAD1EDD410F00561FB2 /* HistoryMenuItem.swift in Sources */, diff --git a/Maccy/AppDelegate.swift b/Maccy/AppDelegate.swift index d4d81229..1ba41172 100644 --- a/Maccy/AppDelegate.swift +++ b/Maccy/AppDelegate.swift @@ -1,4 +1,6 @@ import Cocoa +import KeyboardShortcuts +import Sauce import Sparkle @NSApplicationMain @@ -28,6 +30,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { CoreDataManager.shared.saveContext() } + // swiftlint:disable cyclomatic_complexity // swiftlint:disable function_body_length private func migrateUserDefaults() { if UserDefaults.standard.migrations["2020-02-22-introduce-history-item"] != true { @@ -89,6 +92,45 @@ class AppDelegate: NSObject, NSApplicationDelegate { ] UserDefaults.standard.migrations["2020-04-25-allow-custom-ignored-types"] = true } + + if UserDefaults.standard.migrations["2020-06-19-use-keyboardshortcuts"] != true { + if let keys = UserDefaults.standard.string(forKey: "hotKey") { + var keysList = keys.split(separator: "+") + + if let keyString = keysList.popLast() { + if let key = Key(character: String(keyString), virtualKeyCode: nil) { + var modifiers: NSEvent.ModifierFlags = [] + for keyString in keysList { + switch keyString { + case "command": + modifiers.insert(.command) + case "control": + modifiers.insert(.control) + case "option": + modifiers.insert(.option) + case "shift": + modifiers.insert(.shift) + default: () + } + } + + if let keyboardShortcutKey = KeyboardShortcuts.Key(rawValue: Int(key.QWERTYKeyCode)) { + let shortcut = KeyboardShortcuts.Shortcut(keyboardShortcutKey, modifiers: modifiers) + if let encoded = try? JSONEncoder().encode(shortcut) { + if let hotKeyString = String(data: encoded, encoding: .utf8) { + let preferenceKey = "KeyboardShortcuts_\(KeyboardShortcuts.Name.popup.rawValue)" + UserDefaults.standard.set(hotKeyString, forKey: preferenceKey) + } + } + } + } + } + } + + UserDefaults.standard.migrations["2020-06-19-use-keyboardshortcuts"] = true + } + } + // swiftlint:enable cyclomatic_complexity // swiftlint:enable function_body_length } diff --git a/Maccy/FilterMenuItemView.swift b/Maccy/FilterMenuItemView.swift index d65e3e2c..222d185a 100644 --- a/Maccy/FilterMenuItemView.swift +++ b/Maccy/FilterMenuItemView.swift @@ -1,8 +1,10 @@ import AppKit import Carbon -import Sauce +import KeyboardShortcuts class FilterMenuItemView: NSView, NSTextFieldDelegate { + typealias Key = KeyboardShortcuts.Key + @objc public var title: String { get { return titleField.stringValue } set { titleField.stringValue = newValue } @@ -178,7 +180,7 @@ class FilterMenuItemView: NSView, NSTextFieldDelegate { // swiftlint:disable cyclomatic_complexity // swiftlint:disable function_body_length private func processKeyDownEvent(_ event: NSEvent) -> Bool { - guard let key = Key(QWERTYKeyCode: Int(event.keyCode)) else { + guard let key = Key(rawValue: Int(event.keyCode)) else { return false } let modifierFlags = event.modifierFlags.intersection(.deviceIndependentFlagsMask) diff --git a/Maccy/GlobalHotKey.swift b/Maccy/GlobalHotKey.swift index 63e5e5ad..e7271d3b 100644 --- a/Maccy/GlobalHotKey.swift +++ b/Maccy/GlobalHotKey.swift @@ -1,69 +1,20 @@ -import Magnet -import Sauce +import KeyboardShortcuts class GlobalHotKey { typealias Handler = () -> Void - static public var key: Key? - static public var modifierFlags: NSEvent.ModifierFlags? + static public var key: KeyboardShortcuts.Key? { KeyboardShortcuts.Shortcut(name: .popup)?.key } + static public var modifierFlags: NSEvent.ModifierFlags? { KeyboardShortcuts.Shortcut(name: .popup)?.modifiers } - private var hotKey: HotKey! private var handler: Handler - private var hotKeyPrefObserver: NSKeyValueObservation? init(_ handler: @escaping Handler) { - UserDefaults.standard.register(defaults: [UserDefaults.Keys.hotKey: UserDefaults.Values.hotKey]) - self.handler = handler - hotKeyPrefObserver = UserDefaults.standard.observe(\.hotKey, options: [.initial, .new], changeHandler: { _, _ in - // Ensure old shortcut stops working. - self.hotKey?.unregister() - - if let (key, modifiers) = self.parseHotKey() { - if let keyCombo = KeyCombo(key: key, cocoaModifiers: modifiers) { - self.hotKey = HotKey(identifier: UserDefaults.standard.hotKey, keyCombo: keyCombo) { hotKey in - hotKey.unregister() - self.handler() - hotKey.register() - } - self.hotKey.register() - } - } - }) - } - - deinit { - hotKeyPrefObserver?.invalidate() - } - - private func parseHotKey() -> (Key, NSEvent.ModifierFlags)? { - var keysList = UserDefaults.standard.hotKey.split(separator: "+") - guard let keyString = keysList.popLast() else { - return nil + KeyboardShortcuts.onKeyDown(for: .popup) { + KeyboardShortcuts.disable(.popup) + handler() + KeyboardShortcuts.enable(.popup) } - guard let key = Key(character: String(keyString), virtualKeyCode: nil) else { - return nil - } - - var modifiers: NSEvent.ModifierFlags = [] - for keyString in keysList { - switch keyString { - case "command": - modifiers.insert(.command) - case "control": - modifiers.insert(.control) - case "option": - modifiers.insert(.option) - case "shift": - modifiers.insert(.shift) - default: () - } - } - - GlobalHotKey.key = key - GlobalHotKey.modifierFlags = modifiers - - return (key, modifiers) } } diff --git a/Maccy/KeyboardShortcuts.Name+Popup.swift b/Maccy/KeyboardShortcuts.Name+Popup.swift new file mode 100644 index 00000000..b51645ed --- /dev/null +++ b/Maccy/KeyboardShortcuts.Name+Popup.swift @@ -0,0 +1,5 @@ +import KeyboardShortcuts + +extension KeyboardShortcuts.Name { + static let popup = Name("popup", default: Shortcut(.c, modifiers: [.command, .shift])) +} diff --git a/Maccy/Keys.swift b/Maccy/Keys.swift index 7f2afccd..7cd78ac6 100644 --- a/Maccy/Keys.swift +++ b/Maccy/Keys.swift @@ -1,6 +1,8 @@ -import Sauce +import KeyboardShortcuts class Keys { + typealias Key = KeyboardShortcuts.Key + private static let keysToSkip = [ Key.home, Key.pageUp, diff --git a/Maccy/Preferences/Base.lproj/GeneralPreferenceViewController.xib b/Maccy/Preferences/Base.lproj/GeneralPreferenceViewController.xib index e8b0ea4e..0eb42aca 100644 --- a/Maccy/Preferences/Base.lproj/GeneralPreferenceViewController.xib +++ b/Maccy/Preferences/Base.lproj/GeneralPreferenceViewController.xib @@ -3,7 +3,6 @@ - @@ -12,7 +11,7 @@ - + @@ -22,13 +21,10 @@ - + - - - - + @@ -53,7 +49,7 @@ - + @@ -64,37 +60,19 @@ Hotkey: - - + + - - + - - - - - - - - - - - - - - - - - - + - + Global shortcut key to open application. Default: ⌘+⇧+C. @@ -105,7 +83,7 @@ Default: ⌘+⇧+C.