fix(windows): hotkey toggle 二次按键失效 - 串行化 Pressed/Released edge (#489)
根因: hotkey_bridge_loop 和 combo_hotkey_bridge_loop 把每条 Pressed/Released 边沿独立 async_runtime::spawn 成两个 task,被 tokio work-stealing 调度器 并行执行。Windows WH_KEYBOARD_LL 边沿间隔微秒级,Released task 经常先 swap(true→false),导致下一次 Pressed 看到 was_held=true → toggle 被静默吞掉。 macOS 上 CGEventTap 边沿间隔几十毫秒,race window 极窄,肉眼不可见, 所以仅 Windows 必现。ESC 走 HotkeyEvent::Cancelled 不经过 latch, 是用户唯一能逃出 stuck-latch 的路径。 修复: 用 tauri::async_runtime::block_on 顺序 await 两个 handler, bridge 线程的 mpsc::Receiver::recv() FIFO 保证 swap 顺序正确。 bridge 在独立 OS 线程而非 tokio worker,block_on 安全。 代价: Hold 模式短按 stop 多 ≤500ms 延迟,被 request_stop_during_starting 覆盖,可接受。qa / translation bridge 不走 handle_*_edge 不受影响。 附带清理 coordinator.rs unreachable_code warning。 Refs #468 Co-authored-by: baiqing <lbx12309@icloud.com>
This commit is contained in:
1 parent
cb2c233ebe
commit
f30a787227
1 file changed
+28
-5
@@ -1305,11 +1305,17 @@ fn combo_hotkey_bridge_loop(inner: Arc<Inner>, rx: mpsc::Receiver<ComboHotkeyEve
|
||||
}
|
||||
let inner_cloned = Arc::clone(&inner);
|
||||
match evt {
|
||||
// P0 #468/#475: 同 hotkey_bridge_loop —— Pressed/Released 必须串行 await,
|
||||
// 否则 latch 竞态导致 combo 快捷键二次按键失效。
|
||||
ComboHotkeyEvent::Pressed => {
|
||||
async_runtime::spawn(async move { handle_pressed_edge(&inner_cloned).await });
|
||||
async_runtime::block_on(async {
|
||||
handle_pressed_edge(&inner_cloned).await;
|
||||
});
|
||||
}
|
||||
ComboHotkeyEvent::Released => {
|
||||
async_runtime::spawn(async move { handle_released_edge(&inner_cloned).await });
|
||||
async_runtime::block_on(async {
|
||||
handle_released_edge(&inner_cloned).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1697,11 +1703,25 @@ fn hotkey_bridge_loop(inner: Arc<Inner>, rx: mpsc::Receiver<HotkeyEvent>) {
|
||||
}
|
||||
let inner_cloned = Arc::clone(&inner);
|
||||
match evt {
|
||||
// P0 #468/#475: Pressed/Released 必须串行处理,否则在 Windows 上 WH_KEYBOARD_LL
|
||||
// 边沿间隔微秒级 → 两个独立 spawn 的 task 被 work-stealing 调度器并行执行 →
|
||||
// `hotkey_trigger_held` latch 翻转顺序错乱 → 下次按键被静默吞掉
|
||||
// (UI 关不掉 / 录音停不下来)。改为 bridge 线程内 block_on 顺序 await,
|
||||
// recv 的 FIFO 顺序就是 handler 执行顺序。
|
||||
// 注意:handle_pressed_edge / handle_released_edge 内部走 .await(含网络
|
||||
// 握手),会暂时阻塞本 bridge 线程;Hold 模式短按时 Released 会排队在 channel
|
||||
// 里直到 begin_session 完成,但 SessionPhase::Starting 已经有
|
||||
// request_stop_during_starting 兜底,begin_session 完成进 Listening 后
|
||||
// bridge 立刻 recv Released → end_session,行为正确,仅有短暂 stop 延迟。
|
||||
HotkeyEvent::Pressed => {
|
||||
async_runtime::spawn(async move { handle_pressed_edge(&inner_cloned).await });
|
||||
async_runtime::block_on(async {
|
||||
handle_pressed_edge(&inner_cloned).await;
|
||||
});
|
||||
}
|
||||
HotkeyEvent::Released => {
|
||||
async_runtime::spawn(async move { handle_released_edge(&inner_cloned).await });
|
||||
async_runtime::block_on(async {
|
||||
handle_released_edge(&inner_cloned).await;
|
||||
});
|
||||
}
|
||||
HotkeyEvent::Cancelled => {
|
||||
cancel_session(&inner_cloned);
|
||||
@@ -2091,7 +2111,10 @@ fn ensure_asr_credentials() -> Result<(), String> {
|
||||
{
|
||||
return Err("Foundry Local Whisper 当前仅支持 Windows".to_string());
|
||||
}
|
||||
return Ok(());
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if is_whisper_compatible_provider(&active_asr) || is_bailian_provider(&active_asr) {
|
||||
|
||||
Reference in new issue
Block a user