Files
Snaggle/GreenshotMac/Preferences/HotkeyRecorder.swift
T
2026-09-17 15:15:45 -05:00

90 lines
2.4 KiB
Swift

// 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)
}
}