90 lines
2.4 KiB
Swift
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)
|
|
}
|
|
}
|