-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHotKeyManager.swift
More file actions
146 lines (125 loc) · 5.18 KB
/
HotKeyManager.swift
File metadata and controls
146 lines (125 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//
// HotKeyManager.swift
// Caffeinator
//
// Created by aaplmath on 12/30/19.
// Copyright © 2019 aaplmath. All rights reserved.
//
import CaffeineKit
import Cocoa
import HotKey
/// Manages all hotkeys (global and in-menu) as well as the persistence and retrieval of stored hotkey configurations.
class HotKeyManager: NSObject {
struct MenuAction {
let id: String
let item: NSMenuItem
let action: () -> Void
var hotKey: HotKey?
var key: Key?
var modifiers: NSEvent.ModifierFlags?
init(item: NSMenuItem, action: @escaping () -> Void) {
self.item = item
self.action = action
guard let id = item.identifier?.rawValue else {
Notifier.showErrorMessage(withTitle: txt("HKM.no-menu-id-title"), text: txt("HKM.no-menu-id-body"))
self.id = "ERROR"
return
}
self.id = id
self.hotKey = nil
self.key = nil
self.modifiers = nil
}
}
static let shared = HotKeyManager()
private override init() {
super.init()
}
var toggleHotKey: HotKey?
var actions: [MenuAction] = []
func registerMenuItem(_ item: NSMenuItem, withEquivalentAction action: @escaping () -> Void) {
let action = MenuAction(item: item, action: action)
actions.append(action)
if let dict = UserDefaults.standard.dictionary(forKey: "hotkeys") as? [String: [UInt32]],
let entry = dict[action.id],
let key = Key(carbonKeyCode: entry[0]) {
let modifiers = NSEvent.ModifierFlags(carbonFlags: entry[1])
setKeyEquivForMenu(withID: action.id, key: key, modifiers: modifiers, save: false)
}
}
/**
Sets a new key equivalent for a given menu item.
- Parameter id: the ID of the menu item whose key equivalent to set.
- Parameter key: the primary key in the key command.
- Parameter modifiers: any modifiers associated with the key command.
- Parameter save: whether to save the new key equivalent. Should only be set to false when doing initial load from storage on first registration.
*/
func setKeyEquivForMenu(withID id: String, key: Key, modifiers: NSEvent.ModifierFlags, save: Bool = true) {
guard let idx = findIndexOf(actionID: id) else {
Notifier.showErrorMessage(withTitle: txt("HKM.illegal-menu-id-title"), text: txt("HKM.illegal-menu-id-body"))
return
}
actions[idx].key = key
actions[idx].modifiers = modifiers
reassignHotKey(at: idx)
if save {
var dict: [String: [UInt32]] = UserDefaults.standard.dictionary(forKey: "hotkeys") as? [String: [UInt32]] ?? [:]
dict[actions[idx].id] = [key.carbonKeyCode, modifiers.carbonFlags]
UserDefaults.standard.set(dict, forKey: "hotkeys")
}
}
/**
Removes the specified shortcut from
*/
func clearKeyEquivForMenu(withID id: String) {
var dict: [String: [UInt32]] = UserDefaults.standard.dictionary(forKey: "hotkeys") as? [String: [UInt32]] ?? [:]
dict.removeValue(forKey: id)
UserDefaults.standard.set(dict, forKey: "hotkeys")
guard let idx = findIndexOf(actionID: id) else { return }
actions[idx].key = nil
actions[idx].modifiers = nil
reassignHotKey(at: idx)
}
private func unassignHotKeys() {
for (idx, _) in actions.enumerated() {
actions[idx].hotKey = nil
}
}
private func reassignHotKey(at index: Array<MenuAction>.Index) {
guard let key = actions[index].key, var modifiers = actions[index].modifiers else {
actions[index].hotKey = nil
actions[index].item.keyEquivalent = ""
actions[index].item.keyEquivalentModifierMask = []
return
}
// Global hotkey
actions[index].hotKey = HotKey(key: key, modifiers: modifiers)
actions[index].hotKey!.keyDownHandler = actions[index].action
// In-menu hotkey
if let alt = FunctionMap.map[key] {
// Even though using the description method for function keys works, it doesn't produce the right shortcut in the menu, so use this workaround instead
actions[index].item.keyEquivalent = String(Character(UnicodeScalar(alt)!))
modifiers.remove(.function)
} else {
actions[index].item.keyEquivalent = key.description.lowercased()
}
actions[index].item.keyEquivalentModifierMask = modifiers
}
private func reassignHotKeys() {
for (idx, _) in actions.enumerated() {
reassignHotKey(at: idx)
}
}
private func findIndexOf(actionID id: String) -> Array<MenuAction>.Index? {
return actions.firstIndex(where: { $0.id == id })
}
}
// HotKeyManager must be the delegate of the main menu so it can fall back on local hotkeys when the menu opens
extension HotKeyManager: NSMenuDelegate {
func menuWillOpen(_ menu: NSMenu) {
unassignHotKeys()
}
func menuDidClose(_ menu: NSMenu) {
reassignHotKeys()
}
}