Skip to content

feat(hotkey): side-specific modifier combos for dictation (split from #724)#726

Open
HKLHaoBin wants to merge 2 commits into
Open-Less:betafrom
HKLHaoBin:feat/718-side-modifiers
Open

feat(hotkey): side-specific modifier combos for dictation (split from #724)#726
HKLHaoBin wants to merge 2 commits into
Open-Less:betafrom
HKLHaoBin:feat/718-side-modifiers

Conversation

@HKLHaoBin

@HKLHaoBin HKLHaoBin commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

User description

摘要

Fixes #718(部分:桌面侧向修饰键)。

由已关闭的 #724 拆分而来(第 1/3 部分):Win/macOS 侧向组合听写(cmd-left+D、单键 Left Command/Fn 等),SideAwareComboMonitor + ShortcutRecorder.sideSpecificModifiers;非听写快捷键拒绝侧向修饰键;冲突检测按物理修饰键类。Linux 侧向组合在 PR3(evdev)补齐。

关联:#718#724(Felix 拆分建议)。

修复 / 新增 / 改进

  • 新增 side_aware_combo 模块与 Win/macOS 低层 hook
  • 快捷键录制支持 sideSpecificModifiers
  • 侧向组合 overlap 检测与 QA 快捷键拒绝侧向修饰

兼容

  • 不包含:鼠标触发、Linux evdev、Hold 多源 refcount
  • Linux 仍走原有 fcitx 路径,侧向组合暂不可用(PR3 补齐)

测试计划

本地实机验证(回应 @jiangmuran CHANGES_REQUESTED):

  • macOScmd-left+D Hold/Toggle;单键 Left Command;keycode 表(T/B/=/0)
  • Windowsshift-left+D;录制 Metasuper(非 cmd
  • 冲突cmd-left+D vs super+D(Win)/ cmd+D(mac)提示正确
  • QA 快捷键:保存侧向组合应被拒绝
  • Linux:fcitx 原路径不变;侧向组合不可用(文档说明)
  • npm run check:hotkey-side-modifiers + npm run build

请维护者:下载/构建 CI 产物在真机 spot-check;有问题可直接在本 PR 评论。


PR Type

Enhancement, Tests


Description

  • Add side-aware combo detection for dictation (Win/macOS)

    • SideAwareComboMonitor tracks physical modifier side state
    • Dispatch events to coordinator via mpsc channel
  • Enhance ShortcutRecorder with sideSpecificModifiers and modifier presets

  • Reject side-specific modifiers for non-dictation shortcuts

  • Add binding overlap detection based on physical modifier class


Diagram Walkthrough

flowchart LR
  Input["Keyboard Event\n(Win/macOS low-level hook)"]
  Platform["Platform Dispatch\n(macos dispatch_keycode / windows dispatch_vk)"]
  SideCombo["side_aware_combo\n(State machine: modifier side + primary key matching)"]
  Event["ComboHotkeyEvent\n(Pressed / Released)"]
  Coordinator["Coordinator\n(Start / Stop dictation)"]
  Input --> Platform
  Platform --> SideCombo
  SideCombo --> Event
  Event --> Coordinator
Loading

File Walkthrough

Relevant files
Tests
2 files
backend_rust.rs
Add side_aware_combo and combo_hotkey test modules             
+6/-0     
hotkeySideModifiers.test.ts
Test side modifier functionality                                                 
+75/-0   
Enhancement
16 files
combo_hotkey.rs
Derive PartialEq, Eq for ComboHotkeyEvent                               
+1/-1     
hotkeys.rs
Add side modifier rejection and overlap detection               
+81/-18 
qa.rs
Reject side-specific modifiers for QA hotkey                         
+1/-0     
coordinator.rs
Integrate side_aware_combo monitor into coordinator           
+30/-3   
hotkey_loops.rs
Add side-aware combo supervisor and bridge loop                   
+37/-4   
hotkey.rs
Hook macOS KEY_UP and Windows dispatch to side_aware_combo
+55/-3   
lib.rs
Register side_aware_combo module                                                 
+5/-0     
linux_fcitx.rs
Add new HotkeyTrigger key mappings                                             
+6/-0     
shortcut_binding.rs
Implement side-specific modifier validation and overlap   
+228/-1 
side_aware_combo.rs
Implement side-aware combo state machine and platform hooks
+568/-0 
types.rs
Add LeftCommand, LeftShift, RightShift variants                   
+20/-1   
ShortcutRecorder.tsx
Support side-specific modifier recording and presets         
+64/-23 
hotkey.ts
Add side-specific modifier helper functions                           
+108/-2 
types.ts
Add new HotkeyTrigger types                                                           
+5/-2     
RecordingInputSection.tsx
Pass side-specific props to ShortcutRecorder                         
+2/-0     
ShortcutsSection.tsx
Pass side-specific props to ShortcutRecorder                         
+2/-0     
Other
2 files
shortcut_binding.rs
Add stub functions for side modifiers                                       
+26/-0   
side_aware_combo.rs
Add mobile stub for side_aware_combo                                         
+45/-0   
Additional files
1 files
package.json +1/-0     

Split from Open-Less#724: Win/macOS side-aware bindings (cmd-left+D, Left Command, etc.) with ShortcutRecorder.sideSpecificModifiers, overlap detection, and QA rejection. Linux side combos deferred to follow-up PR3 (evdev).

Fixes Open-Less#718 (partial: desktop side modifiers).

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit ae8f38b)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

724 - Partially compliant

Compliant requirements:

  • 左右修饰键(侧向修饰键)听写支持(Win/macOS)
  • 冲突检测和校验(bindings_overlap、物理类比较)
  • 前端 SideSpecificModifiers 录制
  • 移动端 stub 兼容

Non-compliant requirements:

  • 鼠标中键/侧键触发(未包含)
  • Linux evdev 输入层(未包含)
  • Hold 多源 refcount(未包含)

718 - Partially compliant

Compliant requirements:

  • 快捷键支持区分左右修饰键(Left Command、Fn、LeftShift 等)

Non-compliant requirements:

  • 鼠标中键唤起识别独立开关(未包含)
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

macOS 修饰键释放重复触发

macOS 上修饰键释放会通过两个事件路径进入 side_aware_combo 状态机:KEY_UP 分支(新增)和 FLAGS_CHANGED 分支(原有)。在 FLAGS_CHANGED 处理函数 handle_flags_changed 末尾已通过 CGEventSourceKeyState 推测当前键状态并调用了 dispatch_keycode,而 KEY_UP 分支又无条件调用了 dispatch_keycode(keycode, false, 0, false),导致每次修饰键释放(例如 Left Command)会两次调用 handle_side_modifier,可能触发两次 ComboHotkeyEvent::Released,破坏 Hold 模式判断或导致 UI 状态异常。建议在 KEY_UP 分支中跳过修饰键 keycode(side_from_keycode 返回 Some),仅使用 FLAGS_CHANGED 处理修饰键释放,或统一由单一路径处理。

KEY_DOWN => {
    handle_key_down(ctx, event);
    let keycode = unsafe { CGEventGetIntegerValueField(event, KEYBOARD_EVENT_KEYCODE) };
    crate::side_aware_combo::platform::dispatch_keycode(keycode, false, 0, true);
}
KEY_UP => {
    let keycode = unsafe { CGEventGetIntegerValueField(event, KEYBOARD_EVENT_KEYCODE) };
    crate::side_aware_combo::platform::dispatch_keycode(keycode, false, 0, false);
}

@HKLHaoBin

Copy link
Copy Markdown
Contributor Author

稍等,我处理一下ci问题。

@Felix201209 Felix201209 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

按 maintainer 要求从 #724 拆出来的 part 1/3,拆得很干净,approve ✅

经过逐行审查(含状态机锁序、平台 cfg gating、测试矩阵):

  • 无阻断问题SideAwareComboMonitor 状态机锁序安全;mobile / Linux 的编译 gating 正确(mobile_stubs/side_aware_combo.rsshortcut_binding.rs 与真实 API 对齐);overlap 检测逻辑与测试矩阵吻合。
  • scope 干净:只含 side-modifier 相关改动,没夹带无关内容。
  • 测试到位side_aware_combo 单测、shortcut_binding / commands/hotkeys overlap 与拒绝矩阵、前端 hotkeySideModifiers.test.ts、Windows 集成测试都覆盖了。
  • 五项 CI 全绿(Android / Linux / Windows / macOS / pr_agent)。

几个不阻断的小点,留作后续即可,不拦合并:

  1. side combo 事件分发处有个极小的 TOCTOU——切换绑定的瞬间理论上可能向新 binding 投递一个多余 Pressed,不崩、combo_active 基本能吸收。
  2. comboOnly 模式下按单个修饰键不再弹 comboNeedKey 提示,算轻微 UX 回退,建议补回。
  3. check:hotkey-side-modifiers 用了 npx tsx,与其它 node scripts/* 不一致,建议统一或把 tsx 加进 devDependency。

合并后注意:#727 / #728 与本 PR 在 types.ts 和 5 个语言文件上有重叠新增(leftCommand/leftShift/rightShift),它们需要 rebase 去重。

@Felix201209

Copy link
Copy Markdown
Collaborator

交接说明 📌

本 PR 已审查完成、approve,四平台 CI 全绿,无阻断问题(细节见上方 review)。

合并这一步我做不了:beta 的 Protect #beta ruleset 里 code_scanning 规则在「等一个永远不会来的扫描结果」(仓库未配置 CodeQL,已确认没有任何 code scanning 告警被压着),而该 ruleset 的 bypass 名单只有 @jiangmuran 一人。所以最终合并就拜托 @jiangmuran 了——可直接 squash 合入 beta。

#727 有个会导致听写卡死的 HIGH bug、#728 需要在 #726/#727 之后 rebase 瘦身,均已在各自 PR 说明,先不要合。)

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit ae8f38b

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.

[feature] 支持左/右修饰键快捷键与鼠标中键唤起语音识别

2 participants