Skip to content

feat(linux): fcitx5 plugin for Wayland input + deb packaging#451

Merged
appergb merged 16 commits into
Open-Less:betafrom
aeoform:beta
May 18, 2026
Merged

feat(linux): fcitx5 plugin for Wayland input + deb packaging#451
appergb merged 16 commits into
Open-Less:betafrom
aeoform:beta

Conversation

@aeoform
Copy link
Copy Markdown
Contributor

@aeoform aeoform commented May 15, 2026

User description

Summary

  • fcitx5 C++ plugin (scripts/linux-fcitx5-plugin/): DBus interface with CommitText, SetHotkey/SetHotkeyRaw/SetQaHotkeyRaw/SetTranslationHotkeyRaw methods and DictationKeyEvent/QaShortcutEvent/TranslationModifierEvent signals. Replaces enigo/XTest for Wayland and X11 compatibility.
  • Fix: QA panel + translation modifier hotkeys: Added SetQaHotkeyRaw/SetTranslationHotkeyRaw DBus methods, QaShortcutEvent/TranslationModifierEvent signals. PlaceholderAdapter::update_modifier_shortcuts now syncs QA and translation triggers to the plugin via DBus. Built-in Shift key translation modifier also detected by the plugin.
  • linux_fcitx.rs: DBus client connecting Rust backend to the plugin. start_dictation_signal_listener now listens for all OpenLess1 signals (DictationKeyEvent / QaShortcutEvent / TranslationModifierEvent).
  • Wayland + X11 unified on fcitx5: Removed all is_wayland_session() distinctions. Linux uses fcitx5 plugin for hotkey source and text commit on both Wayland and X11.
  • Capsule window removed from Linux: emit_capsule skips all window operations on Linux. Target app always holds keyboard focus.
  • Deleted ~290 lines of dead rdev code: Linux hotkey platform module no longer contains rdev::listen, dispatch_event, trigger_to_rdev_key, or 10 test functions. rdev dependency removed from Cargo.toml.
  • Frontend/backend cleanup: Removed WaylandHotkeyCallout component, is_wayland_session function, wayland_cli_mode IPC, wayland sections from 5 i18n files.
  • DBus injection note: CommitText on the session bus is open to all same-user processes. This matches fcitx5/IBus standard security model, documented in plugin header comment.

Test plan

  • Linux/Wayland: dictation text inserted via fcitx5 plugin, capsule window does not appear
  • Linux/X11: dictation text inserted via fcitx5 plugin, capsule window does not appear
  • Linux: QA panel hotkey works, translation modifier hotkey works
  • macOS/Windows CI: cargo check passes (cfg gate verified)
  • Linux cargo check + unit tests: pass
  • deb install: /usr/lib/fcitx5/libopenless.so exists
  • fcitx5 restart: fcitx5 -rd shows no errors

PR Type

Enhancement, Bug fix


Description

  • Add Linux fcitx5 DBus plugin

  • Commit dictation text via CommitText

  • Sync hotkeys and modifier triggers

  • Remove Wayland-only rdev paths


Diagram Walkthrough

flowchart LR
  Prefs["Settings / Preferences"] -- "save bindings" --> Coord["Coordinator"]
  Coord -- "sync / listen" --> Client["linux_fcitx.rs DBus client"]
  Client -- "SetHotkeyRaw / CommitText" --> Plugin["fcitx5 OpenLess plugin"]
  Plugin -- "DictationKeyEvent / QA signals" --> Coord
  Coord -- "insert text" --> Insert["insertion.rs / unicode_keystroke.rs"]
Loading

File Walkthrough

Relevant files
Enhancement
11 files
hotkey.rs
Replace Linux rdev hotkey listener                                             
+20/-378
linux_fcitx.rs
Add fcitx5 DBus client and sync helpers                                   
+305/-0 
coordinator.rs
Start plugin listener and sync bindings                                   
+91/-10 
unicode_keystroke.rs
Route Linux Unicode input through plugin                                 
+10/-27 
insertion.rs
Prefer fcitx5 commit with clipboard fallback                         
+15/-0   
lib.rs
Gate Linux fcitx module at runtime                                             
+3/-10   
types.rs
Rename Linux adapter to fcitx5                                                     
+5/-5     
openless.cpp
Implement fcitx5 plugin DBus interface                                     
+432/-0 
types.ts
Update frontend adapter type union                                             
+1/-1     
Settings.tsx
Remove Wayland callout from settings                                         
+1/-186 
PermissionsSection.tsx
Map adapter label to fcitx5                                                           
+1/-1     
Bug fix
1 files
dictation.rs
Simplify Linux insertion and error paths                                 
+24/-90 
Dependencies
1 files
Cargo.toml
Remove rdev and add Linux dbus                                                     
+2/-1     
Configuration changes
4 files
build.sh
Add standalone plugin build script                                             
+29/-0   
CMakeLists.txt
Define plugin build and install rules                                       
+45/-0   
openless.conf.in
Add fcitx5 addon metadata template                                             
+15/-0   
release-tauri.yml
Bundle fcitx5 plugin into Linux releases                                 
+62/-6   
Documentation
3 files
AdvancedSection.tsx
Refresh Linux streaming input hints                                           
+1/-1     
en.ts
Update English Linux and adapter copy                                       
+2/-20   
zh-CN.ts
Update Simplified Chinese Linux copy                                         
+2/-20   
Additional files
7 files
cli.rs +1/-4     
commands.rs +0/-12   
selection.rs +1/-1     
ja.ts +2/-20   
ko.ts +2/-20   
zh-TW.ts +2/-20   
ipc.ts +0/-6     

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 15, 2026

PR Reviewer Guide 🔍

(Review updated until commit 39ce12c)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Fn mapping

HotkeyTrigger::Fn is translated to Control_R, so if the Linux hotkey picker exposes Fn, selecting it will actually bind the right Ctrl key instead. That makes the configured trigger and the active plugin shortcut diverge.

fn trigger_to_keysym(trigger: crate::types::HotkeyTrigger) -> u32 {
    match trigger {
        crate::types::HotkeyTrigger::RightControl => KEYSYM_CONTROL_R,
        crate::types::HotkeyTrigger::LeftControl => KEYSYM_CONTROL_L,
        crate::types::HotkeyTrigger::RightOption | crate::types::HotkeyTrigger::RightAlt => KEYSYM_ALT_R,
        crate::types::HotkeyTrigger::LeftOption => KEYSYM_ALT_L,
        crate::types::HotkeyTrigger::RightCommand => KEYSYM_SUPER_R,
        crate::types::HotkeyTrigger::Fn => KEYSYM_CONTROL_R,
        crate::types::HotkeyTrigger::Custom => unreachable!(),
    }
Stale binding

When the custom dictation hotkey cannot be converted into an fcitx5 key string, the helper returns without clearing the previous plugin binding. If a user clears the shortcut or saves an unsupported combination, the old shortcut can keep firing even though the settings were changed.

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 15, 2026

已修复 stale sync 问题:

ensure_modifier_hotkey_monitor 在 monitor 已存在、仅更新 binding 时,现在也会同步到 fcitx5 插件(sync_binding_to_plugin),确保设置里改热键后插件和 coordinator 热键一致。

其他两个 review 点:

  • DBus 安全 — session bus 本身有用户隔离,与 fcitx5 其他插件的安全模型一致。后续可加 sender PID 验证。
  • Linux fallback — 已删除 enigo/XTest,因为 Wayland 不支持,而 X11 用户有 fcitx5 插件(必装)。不插件的 Linux 流式输入会显示错误,非流式路径仍有剪贴板兜底。

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 265babd

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 15, 2026

后续我会考虑加上其他的输入法版本。

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 15, 2026

已修复安装路径问题:cmake 运行时通过 FCITX_INSTALL_ADDONDIR 探测当前 distro 的实际插件目录(multiarch 感知),通过 $GITHUB_ENV 传递给 tauri --config JSON。

关于 savedIc_ 野指针

  • 事件处理(hotkey)和 DBus 处理(commitText)都在 fcitx5 主事件循环同线程执行,无并发问题
  • 听写会话通常很短(几个秒),用户在这期间不太可能切换/关闭目标 app
  • InputContext 在被激活使用时不会被 fcitx5 销毁
  • foreachFocused 兜底处理了 savedIc_ 为空的情况
  • 后续可以通过 InputContext 的生命周期信号(如 destroyed)加弱引用追踪器

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit e7cfc14

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 9e81339

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 1802ed7

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 15, 2026

Linux 统一走 fcitx5 输入法的决策说明

最新提交(dce03b6)移除了 Linux 上的 enigo XTest 路径,X11 也和 Wayland 一样统一走 fcitx5 插件 CommitText 直写。

原因:

  • enigo XTest 在 Wayland 完全不可用,而 X11 上 fcitx5 也一样能用
  • XTest 合成键盘事件在不同输入法栈(ibus / fcitx5 / 无输入法)下行为不一致,容易出现"打了字母但输入法吞掉"、"中英文状态不对"等奇怪问题
  • fcitx5 CommitText 是输入法原生提交接口,直接送字上屏,不受当前输入法状态影响,也不会触发候选框
  • 维护一条路径比两条路径省心,出问题只需排查 fcitx5 插件(C++ DBus)和 Rust DBus 客户端

降级保障:

  • fcitx5 插件不可用时(fcitx5 未运行或插件未加载),自动降级到剪贴板拷贝,用户手动粘贴
  • deb 包的 Depends 已包含 fcitx5 + fcitx5-module-dbus,apt 安装时自动装上

AppImage:

  • AppImage 产物不含 fcitx5 插件(无法安装系统路径),用户需确保系统已装 fcitx5

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit dce03b6

aeoform and others added 9 commits May 16, 2026 08:53
- fcitx5 C++ plugin (scripts/linux-fcitx5-plugin/) with DBus interface:
  CommitText, SetHotkey/SetHotkeyRaw, DictationKeyEvent signal
- linux_fcitx.rs: DBus client to call plugin from Rust
- coordinator/dictation.rs: Wayland insertion via fcitx5 commit_text,
  streaming insert enabled on Wayland
- insertion.rs: fcitx5 commit_text on Linux with clipboard fallback
- unicode_keystroke.rs: Linux path uses fcitx5 commit_text
- Capsule window show-once on Linux to avoid stealing focus

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add #[cfg(target_os = "linux")] to mod linux_fcitx to fix macOS/Windows
  CI compilation (dbus crate is Linux-only).
- Wayland: skip capsule window show/hide entirely in emit_capsule so the
  target app never loses keyboard focus. Text is committed via fcitx5
  plugin commit_string — no window means the compositor forwards the
  commit to the right app.
- X11 keeps existing behavior (show capsule window once per session).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add "Build fcitx5 plugin" step to release-tauri.yml: compiles
  the C++ plugin via cmake, copies .so + .conf to
  src-tauri/linux-fcitx5-plugin/ for the Tauri bundler.
- Override tauri build --config to include deb.files / rpm.files
  that place the plugin at /usr/lib/fcitx5/ so it's auto-detected
  after install.
- Add fcitx5, fcitx5-module-dbus as deb/rpm dependencies.
- Local builds unaffected (config is CI-only via --config flag).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When ensure_modifier_hotkey_monitor finds an existing monitor and
updates the rdev/CGEventTap binding, it must also sync the new binding
to the fcitx5 plugin on Wayland. Previously the early return skipped
this, leaving plugin and coordinator out of sync after a hotkey change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
FCITX_INSTALL_ADDONDIR varies by distro (multiarch Ubuntu →
/usr/lib/x86_64-linux-gnu/fcitx5/; Fedora → /usr/lib64/fcitx5/;
Arch → /usr/lib/fcitx5/). Extract from cmake cache at build time
via GITHUB_ENV instead of hardcoding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prevents savedIc_ from becoming a dangling pointer by connecting to the
InputContext::destroyed signal when the IC is saved. On destruction the
pointer is cleared automatically, and commitText falls through to
foreachFocused for the current focused IC.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…syntax

ic->connect(ic->destroyed, ...) is invalid — InputContext has no two-arg
connect overload. Use ic->destroyed.connect(callback) directly, which
calls Signal<void()>::connect. Also simplified ScopedConnection storage
from unique_ptr to direct member (ScopedConnection is move-assignable).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fcitx5 插件 Wayland/X11 都可使用,不再为 X11 单独维护 enigo XTest。
Linux 统一走 fcitx5 CommitText 直写,插件不可用时降级到剪贴板拷贝。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove 290 lines of unused rdev listener code from the Linux hotkey
platform module, drop the rdev dependency from Cargo.toml, and clean up
capsule_window_visible init leftover. Update doc comments and frontend
i18n to reflect the unified fcitx5-only path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit b349a4d

…plugin

Extend the fcitx5 plugin with SetQaHotkeyRaw/SetTranslationHotkeyRaw
DBus methods and corresponding signals, so that QA panel toggle and
translation modifier hotkeys work on Linux (not just the main dictation
hotkey). Built-in Shift key translation modifier also handled by the
plugin. Document DBus security model in plugin header.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 323dbed

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 416da60

…om combo and preset modifier

setHotkey/setHotkeyRaw now clear hasCustomDictationKey_ to prevent stale custom
combo from persisting after switching to a preset modifier. setCustomDictationTrigger
now persists TriggerRawSym=TriggerRawStates=0 to raw config so the old raw
trigger doesn't reload after fcitx5 restart.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 9cd8b9a

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 16, 2026

我这里没有Fn键,我不能确定是否有问题,需要有人帮忙测试一下

Fn key does not generate a standard X11 keysym on Linux. The previous
mapping to Super_L (0xffeb) was incorrect and would bind the
Windows/Super key instead. Map to RightControl (0xffe4) to match the
existing fallback convention used on Windows (VK_RCONTROL) and in the
coordinator's hotkey matching logic.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 16, 2026

已修复:HotkeyTrigger::Fn → Super_L 映射问题。

  • Fn 键在 Linux 上没有标准 X11 keysym,原来映射到 Super_L (0xffeb) 是错误的,实际绑定的是 Windows/Super 键
  • 改为 Control_R (0xffe4),与 Windows 平台 VK_RCONTROL 回退策略一致,也和 coordinator 中 Fn => ControlRight 的 keymatching 逻辑保持一致
  • macOS 平台不受影响(不经过这套代码,用 native CGEventTap key code 63)

commit: f043176

@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit f043176

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 16, 2026

我不认为这是bug,用户应该在输入时自行把光标放在准备输入的位置

@appergb
Copy link
Copy Markdown
Collaborator

appergb commented May 16, 2026

@claude 审查他的代码,主要关注以下问题:

1.他主要对哪些方向进行了更改?
2.代码质量如何?
3.是否可以合并?

@H-Chris233
Copy link
Copy Markdown
Collaborator

@claude 审查他的代码,主要关注以下问题:

1.他主要对哪些方向进行了更改? 2.代码质量如何? 3.是否可以合并?

claude似乎没有反应,你再试一遍?

@appergb
Copy link
Copy Markdown
Collaborator

appergb commented May 17, 2026

@claude 审查他的代码,主要关注以下问题:

1.他主要对哪些方向进行了更改?
2.代码质量如何?
3.是否可以合并?

1 similar comment
@appergb
Copy link
Copy Markdown
Collaborator

appergb commented May 17, 2026

@claude 审查他的代码,主要关注以下问题:

1.他主要对哪些方向进行了更改?
2.代码质量如何?
3.是否可以合并?

@aeoform
Copy link
Copy Markdown
Contributor Author

aeoform commented May 17, 2026

claude运行似乎出现了错误

@Open-Less Open-Less deleted a comment from claude Bot May 17, 2026
appergb added a commit that referenced this pull request May 17, 2026
PR #451 (来自 fork aeoform/openless, head ref 也叫 beta) 触发 @claude 时,
claude-code-action 执行 `git fetch origin pull/451/head:beta` 把 PR head
拉到本地 beta 分支,但此时 worktree 正 checkout 在 refs/heads/beta,
git 拒绝 fetch 到已 checkout 的分支:

    fatal: refusing to fetch into branch 'refs/heads/beta' checked out at ...
    Action failed with error: Command failed: git fetch origin --depth=20 pull/451/head:beta

修复: 在 actions/checkout 后立即 git checkout --detach,释放
refs/heads/<default-branch> 这个本地分支名,让 action 自己 fetch 不撞名.
仅在 fork head ref == base ref name 的 corner case 才会触发问题,
detach 对其他场景无副作用.

失败 run: https://github.com/Open-Less/openless/actions/runs/25978532215

Co-authored-by: baiqing <lbx12309@icloud.com>
@appergb
Copy link
Copy Markdown
Collaborator

appergb commented May 17, 2026

@claude 审查一下他主要更改了哪些部分,是否会在其他平台上造成问题。

@Open-Less Open-Less deleted a comment from claude Bot May 17, 2026
…nator.rs conflict

- Accept upstream's show_capsule_window_for_recording wrapper + CapsuleShowStrategy
  (our Linux fallback logic is subsumed by the upstream strategy pattern)
- Keep Linux on NoActivate strategy; show_capsule_window_no_activate returns true
  on Linux to suppress capsule window (no popup on Wayland/fcitx5)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Persistent review updated to latest commit 39ce12c

@appergb appergb merged commit c4bbec9 into Open-Less:beta May 18, 2026
1 check passed
appergb pushed a commit that referenced this pull request May 18, 2026
PR #451 (fcitx5 plugin) 把 Linux 加进 capsule_show_strategy_for_platform
的 NoActivate 分支,但漏改单元测试 capsule_show_strategy_matches_
platform_activation_contract 的 cfg —— 测试断言 Linux 还应该是
FallbackShow,结果 Linux CI red(Run Rust backend unit tests
panicked at coordinator.rs:3617)。

修法:测试的两组 #[cfg] 同步 Linux。同时在实现函数前加显式提示
注释:改 cfg 列表必须同步更新对应测试,防止下次再漏。

cargo test --lib capsule_show_strategy 本地通过。
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.

3 participants