152 lines
5.5 KiB
Swift
152 lines
5.5 KiB
Swift
import AppKit
|
|
import Carbon.HIToolbox
|
|
import Foundation
|
|
|
|
/// User-chosen capture hotkey. `modifiers` are Carbon bits (`cmdKey`, `optionKey`,
|
|
/// `shiftKey`, `controlKey`), matching `HotkeyCenter.register`.
|
|
struct HotkeyPreference: Codable, Equatable, Sendable {
|
|
var keyCode: UInt32
|
|
var modifiers: UInt32
|
|
|
|
static let defaultsKey = "ai.flowmaster.shotdeck.hotkey"
|
|
|
|
/// ⌥⇧2 — kVK_ANSI_2 (19) with optionKey|shiftKey.
|
|
static let `default` = HotkeyPreference(
|
|
keyCode: 19,
|
|
modifiers: UInt32(optionKey) | UInt32(shiftKey)
|
|
)
|
|
|
|
static func load(defaults: UserDefaults = .standard) -> HotkeyPreference {
|
|
guard let data = defaults.data(forKey: defaultsKey),
|
|
let decoded = try? JSONDecoder().decode(HotkeyPreference.self, from: data),
|
|
decoded.modifiers != 0
|
|
else { return .default }
|
|
return decoded
|
|
}
|
|
|
|
func save(defaults: UserDefaults = .standard) {
|
|
guard let data = try? JSONEncoder().encode(self) else { return }
|
|
defaults.set(data, forKey: Self.defaultsKey)
|
|
}
|
|
|
|
/// ⌘⌥⇧⌃ in that order, then a name for common keycodes.
|
|
var displayString: String {
|
|
var s = ""
|
|
if modifiers & UInt32(cmdKey) != 0 { s += "⌘" }
|
|
if modifiers & UInt32(optionKey) != 0 { s += "⌥" }
|
|
if modifiers & UInt32(shiftKey) != 0 { s += "⇧" }
|
|
if modifiers & UInt32(controlKey) != 0 { s += "⌃" }
|
|
s += Self.keyName(for: keyCode)
|
|
return s
|
|
}
|
|
|
|
/// `nil` when the event is modifier-only or has no ⌘⌥⇧⌃ flags.
|
|
static func fromKeyEvent(keyCode: UInt16, modifierFlags: NSEvent.ModifierFlags) -> HotkeyPreference? {
|
|
switch Int(keyCode) {
|
|
case kVK_Shift, kVK_RightShift,
|
|
kVK_Command, kVK_RightCommand,
|
|
kVK_Option, kVK_RightOption,
|
|
kVK_Control, kVK_RightControl,
|
|
kVK_CapsLock, kVK_Function:
|
|
return nil
|
|
default:
|
|
break
|
|
}
|
|
var carbon: UInt32 = 0
|
|
if modifierFlags.contains(.command) { carbon |= UInt32(cmdKey) }
|
|
if modifierFlags.contains(.option) { carbon |= UInt32(optionKey) }
|
|
if modifierFlags.contains(.shift) { carbon |= UInt32(shiftKey) }
|
|
if modifierFlags.contains(.control) { carbon |= UInt32(controlKey) }
|
|
guard carbon != 0 else { return nil }
|
|
return HotkeyPreference(keyCode: UInt32(keyCode), modifiers: carbon)
|
|
}
|
|
|
|
private static func keyName(for keyCode: UInt32) -> String {
|
|
switch Int(keyCode) {
|
|
case kVK_ANSI_A: return "A"
|
|
case kVK_ANSI_B: return "B"
|
|
case kVK_ANSI_C: return "C"
|
|
case kVK_ANSI_D: return "D"
|
|
case kVK_ANSI_E: return "E"
|
|
case kVK_ANSI_F: return "F"
|
|
case kVK_ANSI_G: return "G"
|
|
case kVK_ANSI_H: return "H"
|
|
case kVK_ANSI_I: return "I"
|
|
case kVK_ANSI_J: return "J"
|
|
case kVK_ANSI_K: return "K"
|
|
case kVK_ANSI_L: return "L"
|
|
case kVK_ANSI_M: return "M"
|
|
case kVK_ANSI_N: return "N"
|
|
case kVK_ANSI_O: return "O"
|
|
case kVK_ANSI_P: return "P"
|
|
case kVK_ANSI_Q: return "Q"
|
|
case kVK_ANSI_R: return "R"
|
|
case kVK_ANSI_S: return "S"
|
|
case kVK_ANSI_T: return "T"
|
|
case kVK_ANSI_U: return "U"
|
|
case kVK_ANSI_V: return "V"
|
|
case kVK_ANSI_W: return "W"
|
|
case kVK_ANSI_X: return "X"
|
|
case kVK_ANSI_Y: return "Y"
|
|
case kVK_ANSI_Z: return "Z"
|
|
case kVK_ANSI_0: return "0"
|
|
case kVK_ANSI_1: return "1"
|
|
case kVK_ANSI_2: return "2"
|
|
case kVK_ANSI_3: return "3"
|
|
case kVK_ANSI_4: return "4"
|
|
case kVK_ANSI_5: return "5"
|
|
case kVK_ANSI_6: return "6"
|
|
case kVK_ANSI_7: return "7"
|
|
case kVK_ANSI_8: return "8"
|
|
case kVK_ANSI_9: return "9"
|
|
case kVK_ANSI_Equal: return "="
|
|
case kVK_ANSI_Minus: return "-"
|
|
case kVK_ANSI_RightBracket: return "]"
|
|
case kVK_ANSI_LeftBracket: return "["
|
|
case kVK_ANSI_Quote: return "'"
|
|
case kVK_ANSI_Semicolon: return ";"
|
|
case kVK_ANSI_Backslash: return "\\"
|
|
case kVK_ANSI_Comma: return ","
|
|
case kVK_ANSI_Slash: return "/"
|
|
case kVK_ANSI_Period: return "."
|
|
case kVK_ANSI_Grave: return "`"
|
|
case kVK_Space: return "Space"
|
|
case kVK_Return: return "Return"
|
|
case kVK_Tab: return "Tab"
|
|
case kVK_Delete: return "Delete"
|
|
case kVK_ForwardDelete: return "Fwd Delete"
|
|
case kVK_Escape: return "Esc"
|
|
case kVK_Home: return "Home"
|
|
case kVK_End: return "End"
|
|
case kVK_PageUp: return "Page Up"
|
|
case kVK_PageDown: return "Page Down"
|
|
case kVK_Help: return "Help"
|
|
case kVK_LeftArrow: return "←"
|
|
case kVK_RightArrow: return "→"
|
|
case kVK_DownArrow: return "↓"
|
|
case kVK_UpArrow: return "↑"
|
|
case kVK_F1: return "F1"
|
|
case kVK_F2: return "F2"
|
|
case kVK_F3: return "F3"
|
|
case kVK_F4: return "F4"
|
|
case kVK_F5: return "F5"
|
|
case kVK_F6: return "F6"
|
|
case kVK_F7: return "F7"
|
|
case kVK_F8: return "F8"
|
|
case kVK_F9: return "F9"
|
|
case kVK_F10: return "F10"
|
|
case kVK_F11: return "F11"
|
|
case kVK_F12: return "F12"
|
|
case kVK_F13: return "F13"
|
|
case kVK_F14: return "F14"
|
|
case kVK_F15: return "F15"
|
|
case kVK_F16: return "F16"
|
|
case kVK_F17: return "F17"
|
|
case kVK_F18: return "F18"
|
|
case kVK_F19: return "F19"
|
|
case kVK_F20: return "F20"
|
|
default: return "Key \(keyCode)"
|
|
}
|
|
}
|
|
}
|