Skip to content

feat(linux): evdev input for side combos and mouse dictation (split from #724)#728

Open
HKLHaoBin wants to merge 17 commits into
Open-Less:betafrom
HKLHaoBin:feat/718-linux-evdev
Open

feat(linux): evdev input for side combos and mouse dictation (split from #724)#728
HKLHaoBin wants to merge 17 commits into
Open-Less:betafrom
HKLHaoBin:feat/718-linux-evdev

Conversation

@HKLHaoBin

@HKLHaoBin HKLHaoBin commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

User description

摘要

Completes #718(Linux 路径)。

由已关闭的 #724 拆分而来(第 3/3 部分):linux_evdev_input + evdev 依赖;Linux 侧向组合与鼠标输入;prefs 变更时 refresh_linux_evdev_monitor;monitor 停止时清除 status_message

堆叠 PR:含 #726 + #727 全部变更,用于 Linux 端到端验收。建议 #726/#727 合并后 rebase,或作为完整验收 PR 审查。

关联:#718#724

修复 / 新增 / 改进

  • Linux evdev 输入层(侧向组合 + 鼠标)
  • 热更新 monitor + input 组权限提示

兼容

  • 依赖 PR1/PR2 的 side-aware 与 mouse 模块(本 PR 已 stack)

测试计划

Linux 实机验证(请维护者复测,回应 @jiangmuran):

  • 仅侧向组合:绑定 cmd-left+D / shift-left+D → evdev monitor 启动、听写触发
  • 仅鼠标:开启中/侧键、无侧向组合 → 鼠标事件经 evdev
  • Prefs 热更新:切换鼠标开关或改听写绑定 → monitor 重启,无需重启应用
  • 权限提示:无 /dev/input/event* 读权限时出现 input 组提示;monitor 停止后清除
  • 完整 [feature] 支持左/右修饰键快捷键与鼠标中键唤起语音识别 #718:侧向组合 + 鼠标 + Hold refcount 联合验收

本地已验证: npm run buildnpm run check:hotkey-side-modifiers


PR Type

Enhancement, Tests


Description

  • Add side-aware combo detection for left/right modifiers on Win/macOS/Linux

  • Add independent mouse middle/side button dictation triggers with HoldSourceTracker

  • Implement Linux evdev input layer for side combos and mouse events

  • Integrate all into coordinator with hot-reload of evdev monitor on prefs change


Diagram Walkthrough

flowchart LR
  Input("Keyboard / Mouse Input")
  Platform("Low-level hook (Win/macOS) or evdev (Linux)")
  SideCombo("side_aware_combo (state machine)")
  MouseDict("mouse_dictation (global hook/evdev)")
  Hold("hold_source_tracker (refcount)")
  Dict("Coordinator -> Start/Stop dictation")
  Input --> Platform
  Platform --> SideCombo
  Platform --> MouseDict
  SideCombo --> Hold
  MouseDict --> Hold
  Hold --> Dict
Loading

File Walkthrough

Relevant files
Enhancement
13 files
side_aware_combo.rs
Side-specific modifier combos state machine                           
+568/-0 
linux_evdev_input.rs
Linux evdev input for side combos and mouse                           
+481/-0 
mouse_dictation.rs
Global mouse middle/side button dictation monitor               
+356/-0 
hold_source_tracker.rs
Refcount multiple dictation hold sources                                 
+125/-0 
hotkey_loops.rs
Integrate side-aware, mouse, evdev into loops                       
+280/-10
coordinator.rs
Add new monitors and hold tracker to coordinator                 
+235/-6 
shortcut_binding.rs
Side modifier validation, normalization, overlap                 
+228/-1 
hotkey.rs
macOS/Windows hooks for side-aware and mouse                         
+66/-3   
hotkeys.rs
Reject side-specific for non-dictation shortcuts                 
+81/-18 
settings.rs
Persist mouse dictation pref changes                                         
+15/-0   
types.rs
Add new HotkeyTrigger variants and mouse prefs                     
+36/-1   
dictation.rs
Multi-source pressed/released edge handling                           
+44/-16 
lib.rs
Register new modules and start mouse listener                       
+17/-0   
Other
4 files
side_aware_combo.rs
Mobile stub for side-aware combo                                                 
+45/-0   
mouse_dictation.rs
Mobile stub for mouse dictation                                                   
+38/-0   
linux_evdev_input.rs
Mobile stub for Linux evdev input                                               
+32/-0   
shortcut_binding.rs
Mobile stub for new functions                                                       
+26/-0   
Tests
2 files
hotkeySideModifiers.test.ts
Frontend tests for side modifier recording                             
+75/-0   
backend_rust.rs
Include side_aware_combo test module                                         
+6/-0     
Additional files
18 files
package.json +1/-0     
Cargo.toml +2/-0     
combo_hotkey.rs +1/-1     
mod.rs +5/-0     
qa.rs +1/-0     
linux_fcitx.rs +6/-0     
ShortcutRecorder.tsx +64/-23 
en.ts +4/-0     
ja.ts +4/-0     
ko.ts +4/-0     
zh-CN.ts +4/-0     
zh-TW.ts +4/-0     
hotkey.ts +108/-2 
mock-data.ts +2/-0     
stylePrefs.test.ts +2/-0     
types.ts +9/-2     
RecordingInputSection.tsx +25/-0   
ShortcutsSection.tsx +2/-0     

HKLHaoBin and others added 2 commits June 21, 2026 17:35
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>
Split from Open-Less#724: Win/macOS global mouse hooks, prefs toggles, HoldSourceTracker for multi-source hold release. Linux evdev path deferred to PR3.

Fixes Open-Less#718 (partial: mouse triggers).

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 578f6d1)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

718 - Partially compliant

Compliant requirements:

  • 支持配置左/右侧修饰键(通过SideAwareComboMonitor实现,在Linux上使用evdev)
  • 增加鼠标中键/侧键独立开关(通过mouse_middle_button_dictation和mouse_side_button_dictation偏好)
  • 两种触发方式可以共存(多源Hold refcount)或分别关闭(偏好控制)

Non-compliant requirements:

(无)

Requires further human verification:

  • 端到端功能验证(特别是鼠标中键触发和侧向组合在真实Linux桌面上的表现)
⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns

没有直接的安全漏洞。代码需要读取 /dev/input/event*,并在权限不足时向用户提示加入 input 组,这是合理的降级处理。未发现敏感信息泄漏或注入风险。

⚡ Recommended focus areas for review

潜在的重复事件处理

在Linux上,当同时启用侧向组合快捷键(side-aware combo)和鼠标按键听写时,mouse_dictation_supervisor_loop#[cfg(target_os = "linux")] 分支下会同时启动 LinuxEvdevMonitor(通过 refresh_linux_evdev_monitor)和 MouseDictationMonitor(通过 start_mouse_dictation_monitor)。这两个监控器都会读取 /dev/input/event* 设备并派发鼠标事件,可能导致同一鼠标按键产生重复的听写触发事件。应当确保在鼠标听写生效时,LinuxEvdevMonitor 不再处理鼠标事件,或者将两者的设备读取合并为一个实例。

pub(super) fn mouse_dictation_supervisor_loop(inner: Arc<Inner>) {
    loop {
        if inner.shutdown.load(Ordering::SeqCst) {
            return;
        }
        let prefs = inner.prefs.get();
        let config = crate::mouse_dictation::MouseDictationConfig {
            middle_enabled: prefs.mouse_middle_button_dictation,
            side_enabled: prefs.mouse_side_button_dictation,
        };
        let needs_side_combo = crate::shortcut_binding::binding_requires_side_aware_hook(
            &prefs.dictation_hotkey,
        );
        let needs_mouse = config.middle_enabled || config.side_enabled;

        #[cfg(target_os = "linux")]
        if needs_side_combo && !needs_mouse {
            refresh_linux_evdev_monitor(&inner);
            if inner.linux_evdev.lock().is_some() {
                return;
            }
            log::warn!("[coord] linux evdev monitor for side combo failed; retry in 3s");
            std::thread::sleep(std::time::Duration::from_secs(3));
            continue;
        }

        #[cfg(not(target_os = "linux"))]
        if !needs_mouse {
            sync_release_mouse_hold_sources(&inner);
            inner.mouse_dictation.lock().take();
            return;
        }

        #[cfg(target_os = "linux")]
        if !needs_mouse && !needs_side_combo {
            sync_release_mouse_hold_sources(&inner);
            inner.mouse_dictation.lock().take();
            inner.linux_evdev.lock().take();
            return;
        }

        if inner.mouse_dictation.lock().is_some() {
            crate::mouse_dictation::MouseDictationMonitor::update_config(config);
            #[cfg(target_os = "linux")]
            refresh_linux_evdev_monitor(&inner);
            return;
        }

        let (tx, rx) = mpsc::channel::<(HotkeyEvent, TriggerSource)>();
        match start_mouse_dictation_monitor(config, tx.clone()) {
            Ok(monitor) => {
                *inner.mouse_dictation.lock() = Some(monitor);
                let inner_clone = Arc::clone(&inner);
                std::thread::Builder::new()
                    .name("openless-mouse-dictation-bridge".into())
                    .spawn(move || mouse_dictation_bridge_loop(inner_clone, rx))
                    .ok();
                #[cfg(target_os = "linux")]
                refresh_linux_evdev_monitor(&inner);
                return;
            }
            Err(err) => {
                log::warn!("[coord] mouse dictation monitor failed: {err}; retry in 3s");
                std::thread::sleep(std::time::Duration::from_secs(3));
            }
        }
    }
}

@HKLHaoBin

Copy link
Copy Markdown
Contributor Author

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

@Felix201209

Copy link
Copy Markdown
Collaborator

Linux evdev 这层本身写得不错(见下),但这个 PR 现在还不能合,需要先 rebase 瘦身 🙏

现状问题:diff 累计回到了整个 #724

本 PR 的分支叠在 feat/718-side-modifiers(#726) 和 feat/718-mouse-dictation(#727) 之上,所以对 beta 的 diff 累计成了 ~2788 行 / 38 文件——等于把已经拆开的 #724 又整体带了回来。这里面包含了 #727 那个会让听写卡死到重启的 HIGH bug(详见 #727 的 review),所以现在合等于把那个 bug 也合进去。

正确路径

  1. feat(hotkey): side-specific modifier combos for dictation (split from #724) #726 合并;
  2. feat(dictation): mouse middle/side buttons with Hold refcount (split from #724) #727 修好 HIGH bug + 去掉与 feat(hotkey): side-specific modifier combos for dictation (split from #724) #726 重复的 types.ts/语言文件改动后合并;
  3. 把本 PR rebase 到最新 beta —— diff 会瘦到只剩 linux_evdev_input.rs + mobile_stubs/linux_evdev_input.rs + Cargo.toml/lock(evdev 依赖) + 少量 Linux #[cfg] 接线,那时单独审查、单独合很干净。

对 Linux evdev 增量的审查(rebase 后即可落地)

  • 无阻断问题:权限不足时能给出 udev 提示、不 panic;监视器生命周期 OK;非 Linux 构建不受影响(mobile stub 与 API 对齐)。
  • evdev = "0.12" 依赖合理(社区事实标准、维护活跃,风险低)。
  • ⚠️ 建议跟进(不阻断):读取循环是 fetch_events() + sleep(10ms) 轮询,空闲时会持续吃满约一个 CPU 核,对笔记本是功耗回退。建议改成 epoll(nix 已是传递依赖) 或 evdev 的异步 EventStream,让线程在有事件时才唤醒。
  • 小点:is_available() 只判断 /dev/input 目录存在,并不代表有读权限;udev 提示串在两处略有出入,建议统一成一个常量。

先留开着,按上面顺序 rebase 后再推进。

HKLHaoBin and others added 2 commits June 21, 2026 18:09
- Fix hold-mode tests to pass TriggerSource::KeyboardDictation

- Hide mouse toggles on Linux until PR3 evdev

- Remove PR1 side-modifier type/i18n/mock remnants

- Complete mouse pref wire/deserialize/default in types.rs

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

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 0bac973

HKLHaoBin and others added 4 commits June 21, 2026 18:15
…ents

Co-authored-by: Cursor <cursoragent@cursor.com>
…d source

On update_config disable or Drop, swap held flags and send Released events so HoldSourceTracker does not retain stale mouse sources after prefs refresh.

Co-authored-by: Cursor <cursoragent@cursor.com>
…base

Co-authored-by: Cursor <cursoragent@cursor.com>
@HKLHaoBin HKLHaoBin force-pushed the feat/718-linux-evdev branch from 0bac973 to 23a4f69 Compare June 21, 2026 10:39
…inux

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

Copy link
Copy Markdown
Contributor Author

@Felix201209 已按 review 处理 rebase 瘦身 🙏

分支结构

相对 feat/718-mouse-dictation 的 evdev 增量:~680 行 / 8 文件(commit 23a4f692

合并顺序(与 review 一致):#726#727 → 本 PR。待前两 PR 进 beta 后,对 beta 的 diff 会自然瘦到 evdev-only。

本次未做(review 标注不阻断):epoll/EventStream 空闲 CPU、udev 提示常量统一。

请 re-review。

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 23a4f69

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

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 6ea63bb

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit ebc9fe7

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 5cf792d

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit a997cd1

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit a5871ee

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit d22ded2

@github-actions

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 578f6d1

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.

2 participants