feat: 快速双击 Fn 键切换语音输入/AI 助手模式
This commit is contained in:
parent
847ae72e49
commit
ad72907e89
@ -80,6 +80,18 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
keyMonitor.onFnUp = { [weak self] in
|
||||
self?.stopRecording()
|
||||
}
|
||||
keyMonitor.onFnDoubleClick = { [weak self] in
|
||||
self?.toggleModeViaDoubleClick()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Double-Click Mode Switch
|
||||
|
||||
private func toggleModeViaDoubleClick() {
|
||||
let newMode: TriggerMode = Config.shared.triggerMode == .input ? .assist : .input
|
||||
Config.shared.triggerMode = newMode
|
||||
menuBarController.refreshModeMenu()
|
||||
print("[App] Double-click: mode switched to \(newMode)")
|
||||
}
|
||||
|
||||
// MARK: - Recording
|
||||
|
||||
@ -201,6 +201,10 @@ final class MenuBarController: NSObject {
|
||||
return true
|
||||
}
|
||||
|
||||
func refreshModeMenu() {
|
||||
updateModeMenuState()
|
||||
}
|
||||
|
||||
private func updateInputLLMMenuState() {
|
||||
inputLLMEnabledItem.state = Config.shared.inputLLM.enabled ? .on : .off
|
||||
}
|
||||
|
||||
@ -1,18 +1,28 @@
|
||||
import Cocoa
|
||||
import CoreGraphics
|
||||
|
||||
/// 全局热键监听器:只监听 Fn 键
|
||||
/// 全局热键监听器:只监听 Fn 键(支持双击切换模式)
|
||||
final class KeyMonitor {
|
||||
|
||||
// Fn 键标志
|
||||
private static let fnMask: UInt64 = 0x00800000
|
||||
|
||||
// 双击检测
|
||||
private static let doubleClickWindow: TimeInterval = 0.40 // 两次点击的最大间隔
|
||||
private static let longPressThreshold: TimeInterval = 0.20 // 长按判定阈值
|
||||
|
||||
private var eventTap: CFMachPort?
|
||||
private var runLoopSource: CFRunLoopSource?
|
||||
private var isFnPressed = false
|
||||
|
||||
// 时间戳追踪
|
||||
private var lastFnUpTime: TimeInterval = 0
|
||||
private var fnDownTime: TimeInterval = 0
|
||||
private var longPressTimer: Timer?
|
||||
|
||||
var onFnDown: (() -> Void)?
|
||||
var onFnUp: (() -> Void)?
|
||||
var onFnDoubleClick: (() -> Void)? // 双击 Fn 切换模式
|
||||
|
||||
func start() -> Bool {
|
||||
guard eventTap == nil else { return true }
|
||||
@ -86,17 +96,51 @@ final class KeyMonitor {
|
||||
|
||||
if fnActive && !isFnPressed {
|
||||
isFnPressed = true
|
||||
print("[KeyMonitor] ⚡️ Fn DOWN")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onFnDown?()
|
||||
fnDownTime = ProcessInfo.processInfo.systemUptime
|
||||
|
||||
// 检测双击:上次松开距现在 < 400ms 且之前有单击
|
||||
let elapsed = fnDownTime - lastFnUpTime
|
||||
if elapsed > 0 && elapsed < Self.doubleClickWindow {
|
||||
// 双击!切换模式,取消长按 timer
|
||||
longPressTimer?.invalidate()
|
||||
longPressTimer = nil
|
||||
print("[KeyMonitor] ⚡️ Fn DOUBLE-CLICK → switch mode")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onFnDoubleClick?()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 延迟 200ms 判定是否为长按
|
||||
print("[KeyMonitor] ⚡️ Fn DOWN (waiting for long-press/double-click)")
|
||||
longPressTimer?.invalidate()
|
||||
longPressTimer = Timer.scheduledTimer(withTimeInterval: Self.longPressThreshold, repeats: false) { [weak self] _ in
|
||||
guard let self = self, self.isFnPressed else { return }
|
||||
print("[KeyMonitor] ⚡️ Fn LONG-PRESS → start recording")
|
||||
self.longPressTimer = nil
|
||||
DispatchQueue.main.async {
|
||||
self.onFnDown?()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !fnActive && isFnPressed {
|
||||
isFnPressed = false
|
||||
print("[KeyMonitor] ⚡️ Fn UP")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onFnUp?()
|
||||
lastFnUpTime = ProcessInfo.processInfo.systemUptime
|
||||
|
||||
// 取消长按 timer(说明是短按/单击)
|
||||
longPressTimer?.invalidate()
|
||||
longPressTimer = nil
|
||||
|
||||
print("[KeyMonitor] ⚡️ Fn UP (was pressed for \(String(format: "%.0f", (lastFnUpTime - fnDownTime) * 1000))ms)")
|
||||
|
||||
// 如果长按 timer 已被触发(已开始录音),正常触发 onFnUp
|
||||
// 否则这是短按(单击或双击的第一下),不触发录音
|
||||
if lastFnUpTime - fnDownTime >= Self.longPressThreshold {
|
||||
// 长按结束,正常触发 onFnUp
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onFnUp?()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user