feat(hotkey): 会话激活期间 Esc 由 OpenLess 独占——不再透传宿主应用#855
Open
bigsongeth wants to merge 2 commits into
Open
Conversation
Open-Less#798 给 end_session 加了「转写 vs 取消」的 select! 赛跑,但取消信号本身 送不进来:hotkey bridge 线程为修 Open-Less#468/Open-Less#475 的 latch 竞态改成了串行 block_on,Hold 松手后 end_session 在 bridge 线程上同步跑完整段转写 + 润色,期间 Esc 的 HotkeyEvent::Cancelled 只能在同一条 channel 里排队。 流程跑完后才被取出,此时 phase 已回 Idle,cancel 变 no-op —— 用户按 Esc 毫无反应,必须干等转写完成。听写键为修饰键(默认 fn 等)时必现; 组合键用户不受影响(按键事件走 ComboHotkeyMonitor 的独立线程),这也是 Open-Less#798 验证时没暴露的原因。 修法:把 Cancelled 从 HotkeyEvent 枚举拆出,Esc 在 tap/hook 回调里改发 独立的 Sender<()>,由专用 esc-cancel-bridge 线程消费并调 cancel_session (纯同步快路径,不 await)。cancelled 旗标即时置位,end_session 的 select! 赛跑与润色流的 should_cancel 立即命中。macOS CGEventTap 与 Windows WH_KEYBOARD_LL 同步修改;Less Computer 修饰键 monitor 的 Esc 转发同样迁移。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
胶囊转圈时按 Esc,取消会话的同时宿主应用也收到了这个键——一次按键双重 生效(如顺带取消了 Claude 正在生成的回复)。参照输入法的交互模型:组合窗 激活时 Esc 只取消候选词、宿主应用收不到;胶囊显示进行中的会话时同理, Esc 的语义就是「取消它」,应当被独占消费。 实现:hotkey.rs 新增进程级 ESC_EXCLUSIVE 旗标,由 emit_capsule(所有会话 状态变化的单一出口)维护——胶囊为 Recording/Transcribing/Polishing 且 dictation phase 非 Idle 时置位,终止帧清除。phase 条件排除 QA 会话:QA 的 Esc 由聚焦的浮窗窗口处理,吞键反而会挡掉。置位期间 macOS active tap 对 Esc keydown 返回 null 删除事件,Windows 底层钩子返回 LRESULT(1)—— Windows 本就吞听写触发键,Esc 是补齐同一语义。keyup 不吞:宿主应用几乎 都在 keydown 响应 Esc,孤儿 keyup 无害。 依赖 Open-Less#853(独立取消通道):没有它,Processing 期间吞掉的 Esc 无法真正 触发取消,会变成「吃掉按键但什么都不做」。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
PR Reviewer Guide 🔍(Review updated until commit 5323d42)Here are some key observations to aid the review process:
|
bigsongeth
marked this pull request as ready for review
July 24, 2026 05:02
Contributor
|
Persistent review updated to latest commit 5323d42 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
User description
问题
胶囊转圈(录音/转写/润色)时按 Esc,OpenLess 取消会话的同时,宿主应用也收到了这个 Esc——一次按键双重生效。典型场景:在 Claude 里边对话边听写,按 Esc 想取消转写,结果顺带把 Claude 正在生成的回复也取消了。
交互模型
参照输入法:组合窗激活时按 Esc,取消的是候选词,宿主应用收不到这个键——因为此刻用户的意图对象显然是输入法。胶囊显示进行中的会话时完全同构:Esc 的语义就是「取消这个会话」,应当被独占消费。一次输入触发两个语义(且第二个是破坏性的)违反基本交互原则。
Windows 底层钩子本来就吞掉听写触发键(返回
LRESULT(1)),唯独 Esc 一直放行——本 PR 是把同一语义补齐到 Esc,并给 macOS 补上对应能力(active tap 返回 null 删除事件)。实现
hotkey.rs:进程级ESC_EXCLUSIVE旗标 +set_esc_exclusive()。置位期间 macOS tap 对 Esc keydown 返回 null,Windows 钩子返回LRESULT(1)。keyup 不吞(宿主应用几乎都在 keydown 响应 Esc,孤儿 keyup 无害)。capsule_focus.rs:在emit_capsule(所有会话状态变化的单一出口,含 fix(capsule): 终止态 2s 自动隐藏 — 覆盖 Done/Cancelled/Error 全路径 #77 审计保证的全部终止路径)维护旗标——胶囊为Recording/Transcribing/Polishing且 dictation phase 非 Idle 时置位,终止帧(Done/Cancelled/Error/Idle)自然清除。验证
cargo check --tests干净;cargo test794 passed。🤖 Generated with Claude Code
PR Type
Bug fix, Enhancement
Description
Esc 取消改走独立通道,修复转写/润色期间按 Esc 无响应
新增 esc-cancel-bridge 专用线程,即时处理 Esc 事件
会话激活期间 Esc 由 OpenLess 独占,不透传宿主应用
Windows 钩子与 macOS tap 同步修改,Linux 适配器签名更新
Diagram Walkthrough
flowchart LR ESC["Esc 按键"] --> cancel_tx["独立 Sender<()>"] cancel_tx --> esc_cancel_bridge["esc-cancel-bridge 线程"] esc_cancel_bridge --> cancel_session["cancel_session (立即)"] cancel_session --> set_cancelled["置 cancelled 旗标"] set_cancelled --> select_cancel["end_session 的 select! 命中"] ESC --> esc_exclusive{"会话激活?<br/>exclusive?"} esc_exclusive -->|是| swallow["吞掉 keydown<br/>不透传宿主"]File Walkthrough
coordinator.rs
Spawn esc cancel bridgeopenless-all/app/src-tauri/src/coordinator.rs
capsule_focus.rs
Add esc exclusive logicopenless-all/app/src-tauri/src/coordinator/capsule_focus.rs
hotkey.rs
Rework Esc handling: independent channel and exclusive flagopenless-all/app/src-tauri/src/hotkey.rs
hotkey_loops.rs
Add esc cancel bridge loopopenless-all/app/src-tauri/src/coordinator/hotkey_loops.rs
处理
hotkey.rs
Update mobile stubopenless-all/app/src-tauri/src/mobile_stubs/hotkey.rs