Skip to content

fix(windows): 快捷键二次按键失效 - 串行化 Pressed/Released edge#489

Merged
appergb merged 1 commit into
betafrom
fix/windows-hotkey-toggle-race-468
May 18, 2026
Merged

fix(windows): 快捷键二次按键失效 - 串行化 Pressed/Released edge#489
appergb merged 1 commit into
betafrom
fix/windows-hotkey-toggle-race-468

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented May 18, 2026

Note: 此 PR 不是 #468 (silent-startup) 的修复。它修的是用户在 1.3.4-4 Beta 上单独反馈的另一个 Windows bug —— 快捷键二次按键失效。两者只是同一个 Beta 版本中并发出现,无代码关联。autostart 的修复在 #488

问题

Windows Beta 1.3.4-4 上快捷键二次按键失效:

  1. 按快捷键打开 capsule UI 后,再按一次同样的快捷键无法关闭
  2. 按快捷键开始录音后,再按一次无法停止,只能按 ESC

macOS 正常。

根因

hotkey_bridge_loopcombo_hotkey_bridge_loop 把每条 HotkeyEvent::Pressed / Released 边沿独立 async_runtime::spawn 成两个 task:

HotkeyEvent::Pressed  => { async_runtime::spawn(async move { handle_pressed_edge(...).await }); }
HotkeyEvent::Released => { async_runtime::spawn(async move { handle_released_edge(...).await }); }

两个 task 被 tokio work-stealing 调度器并行执行:

  • macOS:CGEventTap 边沿间隔几十毫秒,Pressed 大概率先完成 swap(false→true),race window 极窄
  • WindowsWH_KEYBOARD_LL 是 kernel 注入回调,KEYDOWN/KEYUP 间隔微秒级 → Released task 经常先 swap(true→false)hotkey_trigger_held 卡在 true → 下一次 Pressed 看到 was_held=true → toggle 静默吞掉

ESC 走 HotkeyEvent::Cancelled 直接调 cancel_session,不经过 latch,所以唯一逃脱路径。

修复

把 bridge loop 内的两次独立 spawn 改为 tauri::async_runtime::block_on 顺序 await。bridge 线程是独立 OS 线程(std::thread,非 tokio worker),block_on 安全。mpsc::Receiver::recv() FIFO 保证 Pressed/Released 严格按到达顺序处理。

combo_hotkey_bridge_loop 同模式替换。qa_hotkey_bridge_loop 只有 Pressed 单事件、translation_hotkey_bridge_loop 不走 handle_*_edge,本地审查已确认都无需修。

附带清理 coordinator.rs:2094unreachable_code warning。

取舍

Hold 模式下 bridge 线程会被 block_on 短暂占用,相邻 Released 事件最多延迟一个 handle_pressed_edge 完成时间(≤500ms,主要是 ASR OpenSession 握手)。request_stop_during_starting 已覆盖 Starting 阶段松手语义,本地审查已验证路径正确。三平台同源,无 cfg 分叉。

本地审查结论

  • rust-reviewer: APPROVE — 5 个审查点全部验证通过
  • cargo check --all-targets:零 error,无新增 warning
  • 唯一存量 cargo fmt 问题在 coordinator.rs:1662,与本 PR 无关

Test plan

  • Windows 实机:按快捷键开 capsule → 再按一次关 capsule
  • Windows 实机:按快捷键开始录音 → 再按一次停止录音
  • Windows Hold 模式短按松手 stop 行为
  • macOS 回归测试 toggle / hold 双模式
  • Linux fcitx5 路径 hotkey 行为

🤖 Generated with Claude Code

根因: 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
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

468 - Partially compliant

Compliant requirements:

Non-compliant requirements:

  • The diff does not change the Windows startup/silent-launch code path.
  • There is no visible fix for suppressing the main window on Win11 startup.
  • The tray-only startup behavior requested by the ticket is not implemented in this PR.
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ No major issues detected

@appergb appergb changed the title fix(windows): 快捷键二次按键失效 - 串行化 Pressed/Released edge (#468) fix(windows): 快捷键二次按键失效 - 串行化 Pressed/Released edge May 18, 2026
@appergb
Copy link
Copy Markdown
Collaborator Author

appergb commented May 18, 2026

@claude 请审核此 PR。如有问题请逐条列出(CRITICAL / HIGH / MEDIUM);如全部通过请明确说 LGTM。

@claude
Copy link
Copy Markdown

claude Bot commented May 18, 2026

Claude finished @appergb's task in 2m 2s —— View job


PR #489 代码审核

  • 读取仓库规范
  • 获取 PR diff
  • 阅读 coordinator.rs 相关上下文
  • 分析 hotkey_bridge_loop 修复逻辑
  • 分析 combo_hotkey_bridge_loop 修复逻辑
  • 分析 unreachable_code 清理
  • 确认其余 bridge loop 无需修改
  • 输出审核结论

LGTM ✓

三处改动全部通过审核,无 CRITICAL / HIGH / MEDIUM 级别问题。


审核详情

1. 竞态根因分析(正确)

hotkey_trigger_held 是一个充当 latch 的 AtomicBool

// handle_pressed_edge: swap(true) → was_held=false 时才处理
// handle_released_edge: swap(false) → was_held=true 时才处理

原来两次独立 spawn 产生两个并行 task,在 Windows 上 WH_KEYBOARD_LL 边沿微秒级到达,work-stealing 调度器能让 Released task 先 swap(false) — latch 永远不归零 — 下次 Pressed 拿到 was_held=true 被静默丢弃。根因分析完全正确。

2. hotkey_bridge_loopcombo_hotkey_bridge_loop 修复(正确)

tauri::async_runtime::block_on 在这里调用安全:bridge 线程是 std::thread::Builder::new().spawn(...) 启动的普通 OS 线程,不在 tokio worker 上下文里,不会触发 "Cannot block on async runtime from async context" panic。mpsc::Receiver::recv() FIFO 保证了 Pressed 先完成才 recv Released,latch 翻转顺序恢复正确。

3. 其余 bridge loop 正确地未修改(正确)

loop 原因
qa_hotkey_bridge_loop 只有 Pressed 单事件,无 latch,spawn 无害
translation_hotkey_bridge_loop 只调同步 mark_translation_modifier_seen,不走 handle_*_edge
action_hotkey_bridge_loop 只调同步函数,不走 handle_*_edge

hotkey_bridge_loop 里的 QaShortcutPressed 分支继续使用 spawn 也正确,handle_qa_hotkey_pressed 不读写 hotkey_trigger_held,无竞态。

4. Hold 模式短按延迟(可接受)

block_on(handle_pressed_edge) 会等 begin_session 握手完成(≤500ms),期间 Released 在 channel 里排队。begin_session 完成后 session 已进 Listening,bridge 立即 recv Released → end_session,行为语义正确,仅有轻微停止延迟。request_stop_during_starting 的兜底路径(Starting 阶段第二次 Pressed/Released)仍然有效,是额外安全网。

5. ensure_asr_credentials unreachable_code 清理(正确)

// 修前:非 Windows 执行 return Err(...),接着的 return Ok(()) 不可达 → warning
// 修后:两个分支各自 cfg-gate,编译器每个平台只看到一条 return,warning 消除
#[cfg(not(target_os = "windows"))]
{ return Err("Foundry Local Whisper 当前仅支持 Windows".to_string()); }
#[cfg(target_os = "windows")]
{ return Ok(()); }

逻辑完全等价,清理干净。


附注(非阻塞)

@appergb appergb merged commit f30a787 into beta May 18, 2026
4 checks passed
@appergb appergb deleted the fix/windows-hotkey-toggle-race-468 branch May 18, 2026 10:54
appergb pushed a commit that referenced this pull request May 18, 2026
包含 PR #487 / #488 / #489 三个修复,供 Windows 实机验证:

- #489 快捷键二次按键失效(toggle/stop 失灵,只能 ESC)
  → coordinator.rs hotkey_bridge_loop 改 block_on 顺序 await
- #487 capsule/qa 透明遮罩看不清
  → lib.rs Windows 下追加 apply_acrylic 兜底
- #488 autostart catch fall-through 复活主窗口(#468 残留)
  → App.tsx catch 加 return + 改善 warn 诊断
appergb pushed a commit that referenced this pull request May 18, 2026
…v 安装

v1.3.4-4 / -5 的 Linux 构建在 "Build fcitx5 plugin" 步骤连挂两次,
错误一致:"E: Unable to locate package fcitx5-dev"。同一 job 前面的
"Install Linux bundle deps" 步骤明明已经 apt-get update + 装好了一堆
universe 包,到 fcitx5 这步突然找不到 —— 表象与 GitHub Actions
ubuntu-22.04 runner 镜像弃用过渡期里 apt 元数据漂移的现象一致。

release-tauri.yml "Build fcitx5 plugin" 步骤增加三层防御:
  1) sudo add-apt-repository -y universe(已启用则 no-op)
  2) 本步骤内重新 sudo apt-get update,独立于上游 step 缓存
  3) 安装失败时打印 apt-cache policy / search / os-release / sources.list.d
     便于以后排查

同 commit 同时 bump 五处版本到 1.3.4-6 重发,让 PR #487/#488/#489
三个 Windows P0 修复带上 Linux 包一起进 Beta 通道。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant