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

@appergbappergb commented May 18, 2026

Copy link
Copy Markdown
Collaborator

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(asyncmove{handle_pressed_edge(...).await});}HotkeyEvent::Released => { async_runtime::spawn(asyncmove{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
Contributor

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

@appergbappergb changed the title fix(windows): 快捷键二次按键失效 - 串行化 Pressed/Released edge (#468)fix(windows): 快捷键二次按键失效 - 串行化 Pressed/Released edgeMay 18, 2026
@appergb

Copy link
Copy Markdown
CollaboratorAuthor

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

@claude

claudeBot commented May 18, 2026

Copy link
Copy Markdown

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"))]{returnErr("Foundry Local Whisper 当前仅支持 Windows".to_string());}#[cfg(target_os = "windows")]{returnOk(());}

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


附注(非阻塞)

@appergb
appergb merged commit f30a787 into betaMay 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 通道。
appergb pushed a commit that referenced this pull request May 22, 2026
首个 1.3.4 正式版,汇总 1.3.4-1 → 1.3.4-12 Beta 周期全部改动(自 v1.3.2-tauri)。
主要内容:
- Windows:静默启动前端兜底、hotkey toggle 二次按键修复、IME 切回、
capsule/QA 透明窗口 Acrylic 兜底、流式 SendInput 节流、IME 提交超时缩短
(#466#468#469#470#487#489#491 等)
- Linux:fcitx5 包名拆分 / 安装前缀修复、主窗口控件修复(#517)
- ASR:智谱批量 ASR 分片与 endpoint 修复、新增更大的 Foundry Whisper 模型
(#507#508#511)
- UI:多语言文案精简、SavedToast 统一定位、设置/风格/市场界面与图标重构、
划词追问框与语音胶囊磨砂玻璃质感、更新/登录/网络检测/市场加载修复
本次只动版本号,逻辑改动已在上述 Beta 周期 PR 落盘。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for freeto 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

@appergb