Skip to content

Commit

Permalink
Migrate hotkey to sindresorhus/KeyboardShortcuts
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
p0deje committed Jun 20, 2020
1 parent 08eec02 commit 1001035
Show file tree
Hide file tree
Showing 65 changed files with 3,142 additions and 2,915 deletions.
14 changes: 6 additions & 8 deletions Maccy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -151,6 +152,7 @@
DAEE384D1E3DBEB100DD2966 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DAEE38531E3DD6F100DD2966 /* History.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = History.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
DAF176C5202AA56B00C5FD0D /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
DAFEF0B7249D7DEE006029E8 /* KeyboardShortcuts.Name+Popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyboardShortcuts.Name+Popup.swift"; sourceTree = "<group>"; };
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 = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -518,12 +519,8 @@
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -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 */,
Expand Down
42 changes: 42 additions & 0 deletions Maccy/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Cocoa
import KeyboardShortcuts
import Sauce
import Sparkle

@NSApplicationMain
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
6 changes: 4 additions & 2 deletions Maccy/FilterMenuItemView.swift
Original file line number Diff line number Diff line change
@@ -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 }
Expand Down Expand Up @@ -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)
Expand Down
63 changes: 7 additions & 56 deletions Maccy/GlobalHotKey.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
5 changes: 5 additions & 0 deletions Maccy/KeyboardShortcuts.Name+Popup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import KeyboardShortcuts

extension KeyboardShortcuts.Name {
static let popup = Name("popup", default: Shortcut(.c, modifiers: [.command, .shift]))
}
4 changes: 3 additions & 1 deletion Maccy/Keys.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Sauce
import KeyboardShortcuts

class Keys {
typealias Key = KeyboardShortcuts.Key

private static let keysToSkip = [
Key.home,
Key.pageUp,
Expand Down
Loading

0 comments on commit 1001035

Please sign in to comment.