Fix hotkey timing accuracy by using system event timestamps instead of Date()
This commit is contained in:
parent
58577c3a7a
commit
6adbf538d9
@ -60,16 +60,17 @@ class HotkeyManager: ObservableObject {
|
|||||||
|
|
||||||
// Key state tracking
|
// Key state tracking
|
||||||
private var currentKeyState = false
|
private var currentKeyState = false
|
||||||
private var keyPressStartTime: Date?
|
private var keyPressEventTime: TimeInterval?
|
||||||
private let briefPressThreshold = 1.7
|
private let briefPressThreshold = 0.5
|
||||||
private var isHandsFreeMode = false
|
private var isHandsFreeMode = false
|
||||||
|
|
||||||
// Debounce for Fn key
|
// Debounce for Fn key
|
||||||
private var fnDebounceTask: Task<Void, Never>?
|
private var fnDebounceTask: Task<Void, Never>?
|
||||||
private var pendingFnKeyState: Bool? = nil
|
private var pendingFnKeyState: Bool? = nil
|
||||||
|
private var pendingFnEventTime: TimeInterval? = nil
|
||||||
|
|
||||||
// Keyboard shortcut state tracking
|
// Keyboard shortcut state tracking
|
||||||
private var shortcutKeyPressStartTime: Date?
|
private var shortcutKeyPressEventTime: TimeInterval?
|
||||||
private var isShortcutHandsFreeMode = false
|
private var isShortcutHandsFreeMode = false
|
||||||
private var shortcutCurrentKeyState = false
|
private var shortcutCurrentKeyState = false
|
||||||
private var lastShortcutTriggerTime: Date?
|
private var lastShortcutTriggerTime: Date?
|
||||||
@ -230,22 +231,24 @@ class HotkeyManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setupCustomShortcutMonitoring() {
|
private func setupCustomShortcutMonitoring() {
|
||||||
// Hotkey 1
|
|
||||||
if selectedHotkey1 == .custom {
|
if selectedHotkey1 == .custom {
|
||||||
KeyboardShortcuts.onKeyDown(for: .toggleMiniRecorder) { [weak self] in
|
KeyboardShortcuts.onKeyDown(for: .toggleMiniRecorder) { [weak self] in
|
||||||
Task { @MainActor in await self?.handleCustomShortcutKeyDown() }
|
let eventTime = ProcessInfo.processInfo.systemUptime
|
||||||
|
Task { @MainActor in await self?.handleCustomShortcutKeyDown(eventTime: eventTime) }
|
||||||
}
|
}
|
||||||
KeyboardShortcuts.onKeyUp(for: .toggleMiniRecorder) { [weak self] in
|
KeyboardShortcuts.onKeyUp(for: .toggleMiniRecorder) { [weak self] in
|
||||||
Task { @MainActor in await self?.handleCustomShortcutKeyUp() }
|
let eventTime = ProcessInfo.processInfo.systemUptime
|
||||||
|
Task { @MainActor in await self?.handleCustomShortcutKeyUp(eventTime: eventTime) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Hotkey 2
|
|
||||||
if selectedHotkey2 == .custom {
|
if selectedHotkey2 == .custom {
|
||||||
KeyboardShortcuts.onKeyDown(for: .toggleMiniRecorder2) { [weak self] in
|
KeyboardShortcuts.onKeyDown(for: .toggleMiniRecorder2) { [weak self] in
|
||||||
Task { @MainActor in await self?.handleCustomShortcutKeyDown() }
|
let eventTime = ProcessInfo.processInfo.systemUptime
|
||||||
|
Task { @MainActor in await self?.handleCustomShortcutKeyDown(eventTime: eventTime) }
|
||||||
}
|
}
|
||||||
KeyboardShortcuts.onKeyUp(for: .toggleMiniRecorder2) { [weak self] in
|
KeyboardShortcuts.onKeyUp(for: .toggleMiniRecorder2) { [weak self] in
|
||||||
Task { @MainActor in await self?.handleCustomShortcutKeyUp() }
|
let eventTime = ProcessInfo.processInfo.systemUptime
|
||||||
|
Task { @MainActor in await self?.handleCustomShortcutKeyUp(eventTime: eventTime) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,18 +277,18 @@ class HotkeyManager: ObservableObject {
|
|||||||
|
|
||||||
private func resetKeyStates() {
|
private func resetKeyStates() {
|
||||||
currentKeyState = false
|
currentKeyState = false
|
||||||
keyPressStartTime = nil
|
keyPressEventTime = nil
|
||||||
isHandsFreeMode = false
|
isHandsFreeMode = false
|
||||||
shortcutCurrentKeyState = false
|
shortcutCurrentKeyState = false
|
||||||
shortcutKeyPressStartTime = nil
|
shortcutKeyPressEventTime = nil
|
||||||
isShortcutHandsFreeMode = false
|
isShortcutHandsFreeMode = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleModifierKeyEvent(_ event: NSEvent) async {
|
private func handleModifierKeyEvent(_ event: NSEvent) async {
|
||||||
let keycode = event.keyCode
|
let keycode = event.keyCode
|
||||||
let flags = event.modifierFlags
|
let flags = event.modifierFlags
|
||||||
|
let eventTime = event.timestamp
|
||||||
// Determine which hotkey (if any) is being triggered
|
|
||||||
let activeHotkey: HotkeyOption?
|
let activeHotkey: HotkeyOption?
|
||||||
if selectedHotkey1.isModifierKey && selectedHotkey1.keyCode == keycode {
|
if selectedHotkey1.isModifierKey && selectedHotkey1.keyCode == keycode {
|
||||||
activeHotkey = selectedHotkey1
|
activeHotkey = selectedHotkey1
|
||||||
@ -294,11 +297,11 @@ class HotkeyManager: ObservableObject {
|
|||||||
} else {
|
} else {
|
||||||
activeHotkey = nil
|
activeHotkey = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let hotkey = activeHotkey else { return }
|
guard let hotkey = activeHotkey else { return }
|
||||||
|
|
||||||
var isKeyPressed = false
|
var isKeyPressed = false
|
||||||
|
|
||||||
switch hotkey {
|
switch hotkey {
|
||||||
case .rightOption, .leftOption:
|
case .rightOption, .leftOption:
|
||||||
isKeyPressed = flags.contains(.option)
|
isKeyPressed = flags.contains(.option)
|
||||||
@ -306,13 +309,13 @@ class HotkeyManager: ObservableObject {
|
|||||||
isKeyPressed = flags.contains(.control)
|
isKeyPressed = flags.contains(.control)
|
||||||
case .fn:
|
case .fn:
|
||||||
isKeyPressed = flags.contains(.function)
|
isKeyPressed = flags.contains(.function)
|
||||||
// Debounce Fn key
|
|
||||||
pendingFnKeyState = isKeyPressed
|
pendingFnKeyState = isKeyPressed
|
||||||
|
pendingFnEventTime = eventTime
|
||||||
fnDebounceTask?.cancel()
|
fnDebounceTask?.cancel()
|
||||||
fnDebounceTask = Task { [pendingState = isKeyPressed] in
|
fnDebounceTask = Task { [pendingState = isKeyPressed, pendingTime = eventTime] in
|
||||||
try? await Task.sleep(nanoseconds: 75_000_000) // 75ms
|
try? await Task.sleep(nanoseconds: 75_000_000) // 75ms
|
||||||
if pendingFnKeyState == pendingState {
|
if pendingFnKeyState == pendingState {
|
||||||
await self.processKeyPress(isKeyPressed: pendingState)
|
await self.processKeyPress(isKeyPressed: pendingState, eventTime: pendingTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -324,15 +327,15 @@ class HotkeyManager: ObservableObject {
|
|||||||
return // Should not reach here
|
return // Should not reach here
|
||||||
}
|
}
|
||||||
|
|
||||||
await processKeyPress(isKeyPressed: isKeyPressed)
|
await processKeyPress(isKeyPressed: isKeyPressed, eventTime: eventTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processKeyPress(isKeyPressed: Bool) async {
|
private func processKeyPress(isKeyPressed: Bool, eventTime: TimeInterval) async {
|
||||||
guard isKeyPressed != currentKeyState else { return }
|
guard isKeyPressed != currentKeyState else { return }
|
||||||
currentKeyState = isKeyPressed
|
currentKeyState = isKeyPressed
|
||||||
|
|
||||||
if isKeyPressed {
|
if isKeyPressed {
|
||||||
keyPressStartTime = Date()
|
keyPressEventTime = eventTime
|
||||||
|
|
||||||
if isHandsFreeMode {
|
if isHandsFreeMode {
|
||||||
isHandsFreeMode = false
|
isHandsFreeMode = false
|
||||||
@ -346,10 +349,8 @@ class HotkeyManager: ObservableObject {
|
|||||||
await whisperState.handleToggleMiniRecorder()
|
await whisperState.handleToggleMiniRecorder()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let now = Date()
|
if let startTime = keyPressEventTime {
|
||||||
|
let pressDuration = eventTime - startTime
|
||||||
if let startTime = keyPressStartTime {
|
|
||||||
let pressDuration = now.timeIntervalSince(startTime)
|
|
||||||
|
|
||||||
if pressDuration < briefPressThreshold {
|
if pressDuration < briefPressThreshold {
|
||||||
isHandsFreeMode = true
|
isHandsFreeMode = true
|
||||||
@ -359,43 +360,41 @@ class HotkeyManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyPressStartTime = nil
|
keyPressEventTime = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleCustomShortcutKeyDown() async {
|
private func handleCustomShortcutKeyDown(eventTime: TimeInterval) async {
|
||||||
if let lastTrigger = lastShortcutTriggerTime,
|
if let lastTrigger = lastShortcutTriggerTime,
|
||||||
Date().timeIntervalSince(lastTrigger) < shortcutCooldownInterval {
|
Date().timeIntervalSince(lastTrigger) < shortcutCooldownInterval {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !shortcutCurrentKeyState else { return }
|
guard !shortcutCurrentKeyState else { return }
|
||||||
shortcutCurrentKeyState = true
|
shortcutCurrentKeyState = true
|
||||||
lastShortcutTriggerTime = Date()
|
lastShortcutTriggerTime = Date()
|
||||||
shortcutKeyPressStartTime = Date()
|
shortcutKeyPressEventTime = eventTime
|
||||||
|
|
||||||
if isShortcutHandsFreeMode {
|
if isShortcutHandsFreeMode {
|
||||||
isShortcutHandsFreeMode = false
|
isShortcutHandsFreeMode = false
|
||||||
guard canProcessHotkeyAction else { return }
|
guard canProcessHotkeyAction else { return }
|
||||||
await whisperState.handleToggleMiniRecorder()
|
await whisperState.handleToggleMiniRecorder()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !whisperState.isMiniRecorderVisible {
|
if !whisperState.isMiniRecorderVisible {
|
||||||
guard canProcessHotkeyAction else { return }
|
guard canProcessHotkeyAction else { return }
|
||||||
await whisperState.handleToggleMiniRecorder()
|
await whisperState.handleToggleMiniRecorder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleCustomShortcutKeyUp() async {
|
private func handleCustomShortcutKeyUp(eventTime: TimeInterval) async {
|
||||||
guard shortcutCurrentKeyState else { return }
|
guard shortcutCurrentKeyState else { return }
|
||||||
shortcutCurrentKeyState = false
|
shortcutCurrentKeyState = false
|
||||||
|
|
||||||
let now = Date()
|
if let startTime = shortcutKeyPressEventTime {
|
||||||
|
let pressDuration = eventTime - startTime
|
||||||
if let startTime = shortcutKeyPressStartTime {
|
|
||||||
let pressDuration = now.timeIntervalSince(startTime)
|
|
||||||
|
|
||||||
if pressDuration < briefPressThreshold {
|
if pressDuration < briefPressThreshold {
|
||||||
isShortcutHandsFreeMode = true
|
isShortcutHandsFreeMode = true
|
||||||
} else {
|
} else {
|
||||||
@ -403,8 +402,8 @@ class HotkeyManager: ObservableObject {
|
|||||||
await whisperState.handleToggleMiniRecorder()
|
await whisperState.handleToggleMiniRecorder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcutKeyPressStartTime = nil
|
shortcutKeyPressEventTime = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computed property for backward compatibility with UI
|
// Computed property for backward compatibility with UI
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user