import AppKit import Carbon.HIToolbox /// Serializable representation of a keyboard shortcut (modifier flags + key code). /// Stored in UserDefaults as a JSON string. struct HotkeyBinding: Codable, Equatable { var modifiers: UInt // NSEvent.ModifierFlags.rawValue, masked to cmd/shift/ctrl/opt var keyCode: UInt16 /// Checks whether the given NSEvent matches this binding. func matches(_ event: NSEvent) -> Bool { let mask = event.modifierFlags.intersection(.deviceIndependentFlagsMask) let relevantFlags: NSEvent.ModifierFlags = [.command, .shift, .control, .option] return mask.intersection(relevantFlags).rawValue == modifiers && event.keyCode == keyCode } /// Human-readable label like "⌘⏎" or "⌘⇧T". var displayString: String { var parts: [String] = [] let flags = NSEvent.ModifierFlags(rawValue: modifiers) if flags.contains(.control) { parts.append("⌃") } if flags.contains(.option) { parts.append("⌥") } if flags.contains(.shift) { parts.append("⇧") } if flags.contains(.command) { parts.append("⌘") } parts.append(keyName) return parts.joined() } private var keyName: String { switch keyCode { case 36: return "⏎" case 48: return "⇥" case 49: return "Space" case 51: return "⌫" case 53: return "⎋" case 123: return "←" case 124: return "→" case 125: return "↓" case 126: return "↑" default: // Try to get the character from the key code let src = TISCopyCurrentKeyboardInputSource().takeRetainedValue() let layoutDataRef = TISGetInputSourceProperty(src, kTISPropertyUnicodeKeyLayoutData) if let layoutDataRef = layoutDataRef { let layoutData = unsafeBitCast(layoutDataRef, to: CFData.self) as Data var deadKeyState: UInt32 = 0 var length = 0 var chars = [UniChar](repeating: 0, count: 4) layoutData.withUnsafeBytes { ptr in let layoutPtr = ptr.baseAddress!.assumingMemoryBound(to: UCKeyboardLayout.self) UCKeyTranslate( layoutPtr, keyCode, UInt16(kUCKeyActionDisplay), 0, // no modifiers for the base character UInt32(LMGetKbdType()), UInt32(kUCKeyTranslateNoDeadKeysBit), &deadKeyState, 4, &length, &chars ) } if length > 0 { return String(utf16CodeUnits: chars, count: length).uppercased() } } return "Key\(keyCode)" } } // MARK: - Serialization func toJSON() -> String { (try? String(data: JSONEncoder().encode(self), encoding: .utf8)) ?? "{}" } static func fromJSON(_ string: String) -> HotkeyBinding? { guard let data = string.data(using: .utf8) else { return nil } return try? JSONDecoder().decode(HotkeyBinding.self, from: data) } // MARK: - Presets static let cmdReturn = HotkeyBinding(modifiers: NSEvent.ModifierFlags.command.rawValue, keyCode: 36) static let cmdT = HotkeyBinding(modifiers: NSEvent.ModifierFlags.command.rawValue, keyCode: 17) static let cmdW = HotkeyBinding(modifiers: NSEvent.ModifierFlags.command.rawValue, keyCode: 13) static let cmdShiftRB = HotkeyBinding(modifiers: NSEvent.ModifierFlags([.command, .shift]).rawValue, keyCode: 30) // ] static let cmdShiftLB = HotkeyBinding(modifiers: NSEvent.ModifierFlags([.command, .shift]).rawValue, keyCode: 33) // [ static let cmdD = HotkeyBinding(modifiers: NSEvent.ModifierFlags.command.rawValue, keyCode: 2) }