// HotkeyRecorder.swift // GreenshotMac // // A small AppKit control that records the next key combination, wrapped for SwiftUI. import AppKit import SwiftUI import Carbon.HIToolbox final class HotkeyRecorderControl: NSButton { var onRecord: ((HotkeySpec) -> Void)? var spec: HotkeySpec = .none { didSet { refreshTitle() } } private var isRecording = false { didSet { refreshTitle() } } init() { super.init(frame: .zero) bezelStyle = .rounded setButtonType(.momentaryPushIn) target = self action = #selector(startRecording) refreshTitle() } required init?(coder: NSCoder) { fatalError("init(coder:) is not used") } private func refreshTitle() { title = isRecording ? "Press a key…" : HotkeyManager.description(of: spec) } @objc private func startRecording() { isRecording = true window?.makeFirstResponder(self) } override var acceptsFirstResponder: Bool { true } override func keyDown(with event: NSEvent) { guard isRecording else { return super.keyDown(with: event) } if event.keyCode == UInt16(kVK_Escape) { isRecording = false return } if event.keyCode == UInt16(kVK_Delete) { isRecording = false spec = .none onRecord?(.none) return } let modifiers = HotkeyManager.carbonModifiers(from: event.modifierFlags) guard modifiers != 0 else { NSSound.beep() // a global hotkey without modifiers would swallow normal typing return } let recorded = HotkeySpec(keyCode: UInt32(event.keyCode), modifiers: modifiers) isRecording = false spec = recorded onRecord?(recorded) } override func resignFirstResponder() -> Bool { isRecording = false return super.resignFirstResponder() } } struct HotkeyRecorder: NSViewRepresentable { let action: HotkeyAction func makeNSView(context: Context) -> HotkeyRecorderControl { let control = HotkeyRecorderControl() control.spec = HotkeyBindings.shared.spec(for: action) control.onRecord = { spec in HotkeyBindings.shared.set(spec, for: action) } return control } func updateNSView(_ nsView: HotkeyRecorderControl, context: Context) { nsView.spec = HotkeyBindings.shared.spec(for: action) } }