Commit Graph
19 Commits
Author SHA1 Message Date
aeoformandChris233 33467a36ea feat(linux): 选区读取集成进 fcitx5 插件(clipboard addon),删除外部工具依赖 (#961)
* feat(linux): 选区读取集成进 fcitx5 插件(clipboard addon),删除外部工具依赖

- openless 插件新增 GetSelectionText DBus 方法:直读 fcitx clipboard addon
  的 PRIMARY 选区缓存(X11 XFIXES / Wayland data-control 统一维护),与
  fcitx 剪贴板模块来源一致,不触碰用户剪贴板
- CMake 增加 Fcitx5::Module::Clipboard 依赖
- Rust 侧 selection 改走插件 GetSelectionText,删除 wl-paste/xclip/xsel
  外部工具路径及 linux_selection_tools_missing 提醒机制(前端
  selection_warning 状态机、i18n、测试一并删除)
- acceptQaSessionEvent 的 idle 分支不再依赖 selection_warning 字段,
  一律视为新会话 token(事件保序,语义等价)
- 打字提问路径(QA 面板 / less-computer)不再 emit Polishing capsule:
  回答在面板内可见,避免 Linux 输入法 auxDown 闪「✨ 润色中...」

(cherry picked from commit 384f8876a490cb3e8e992af05cdad33f2af6a8ec)

* fix(linux): bundle and sync fcitx5 selection plugin

(cherry picked from commit 7e8bf825c08f5e935c7a25746c121136d92b7926)

* fix(linux): use DBus selection reader during validation

(cherry picked from commit d13403343b08fe1b4070e0f0d2f219a0a69d363e)

* refactor(linux): 收尾 PR 961 审查发现的小毛病

- capsule 反馈参数从裸 bool 改为具名枚举 CapsuleFeedback(Show/Hide):
  语音/听写路径 Show、打字路径 Hide,调用点自文档化,避免将来误传
- AppImage 插件子路径提取为 APPIMAGE_PLUGIN_SUBDIR 常量并注明与 CI
  bundle.resources 的契约,避免三处硬编码静默失配
- selection.rs 移除文件级 Linux 大赦(lib.rs 的 crate 级兜底仍在),
  Duration import 按平台限定,消除 Linux unused 隐患

---------

Co-authored-by: Chris233 <h-chris233@outlook.com>
2026-08-19 20:34:10 +08:00
TRIPandsim b68a5901c3 feat(selection-polish): Linux 支持——fcitx5 插件热键路由 + PRIMARY 选区校验 (#927)
* feat(selection-polish): Linux 支持——fcitx5 插件热键路由 + PRIMARY 选区校验

- linux_fcitx.rs:新增 SetSelectionPolishHotkeyRaw DBus 方法 + sync_selection_polish_binding;
  start_dictation_signal_listener 增加 selection_polish_trigger 参数,监听
  SelectionPolishEvent 信号 → HotkeyEvent::SelectionPolishShortcutPressed,
  初始同步与 fcitx5 重启重同步均含选区润色触发键
- hotkey.rs Linux adapter:update_modifier_shortcuts 转发 selection_polish_trigger
  (移除 disabled on Linux 注释)
- hotkey_loops.rs:Linux 启动 fcitx5 监听时传入 selection_polish_trigger
- selection.rs Linux 分支:is_captured = PRIMARY 读到选区即可;validate 重读
  PRIMARY 与捕获文本比较(用户改选/清空即拒绝粘贴);selection_text_matches
  cfg 覆盖 linux
- fcitx5 插件 openless.cpp:新增 SelectionPolishEvent 信号 +
  SetSelectionPolishHotkeyRaw 方法(INI 持久化 SelectionPolishRawSym/States),
  按键分发仿 QA 模式;需重装插件(scripts/inject-fcitx5-plugin.sh)生效
- 注:Linux 代码由 Linux CI 验证编译;fcitx 插件 .so 需 Linux 环境重编译安装

* fix(selection-polish): 补传 selection_polish_trigger 到 fcitx 监听器(第二个调用点)

coordinator.rs 的 hotkey 安装路径调 start_dictation_signal_listener 时漏传
selection_polish_trigger(签名 7 参数只传 6 个),Linux CI E0061。hotkey_loops.rs
已传,此处遗漏导致 Linux 编译失败。

---------

Co-authored-by: sim <sim@local>
2026-08-06 19:32:39 +08:00
leaionandleaion 0063497c6e fix(linux): fcitx5 plugin crash on empty triggerKeyList with raw hotkey (#883)
When the raw hotkey path (SetHotkeyRaw) matches and triggerRawStates==0,
the code unconditionally indexes triggerKeyList_[0]. With an empty
KeyList (common in raw mode), this is undefined behavior and segfaults
fcitx5 on every hotkey press. Only access the list when the KeyList
path matched; use raw values for the raw path.

Co-authored-by: leaion <leaion@sohu.com>
2026-08-03 14:54:30 +08:00
93c6f6f448 fix(hotkey): 修饰键热键被当组合键用时不再误唤起听写(150ms 仲裁 + 即时撤销,胶囊不再赖着) (#858)
* fix(hotkey): 修饰键热键被当组合键用时不再唤起听写

modifier-only 触发键(如 Option)按住期间只要按下普通键,就说明用户在打
Option+任意字母/数字键这类组合键、不是想说话。此前 tap/hook 只看修饰键自身的边
沿:按下即 Pressed → 开录音,Auto 模式下快速松手还会被判成「短按锁存」,录音一直
开着停不下来。

- hotkey.rs:macOS CGEventTap / Windows WH_KEYBOARD_LL 在触发键按住期间收到普通
  键 KEY_DOWN 时发一次新的 TriggerCombined 边沿(每次按住只发一次;修饰键叠加不
  算,Shift 翻译修饰键行为不变),之后仍照常发 Released。
- coordinator:TriggerCombined 清掉按住态(随后的 Released 被 was_held 吞掉,不
  再走 Hold 松手 / Auto 短按锁存),并只取消「这一次按下开出来的」会话 ——
  toggle 停止 / 被冷却拦下 / 路由给 QA 的按下什么都不动,绝不误杀正在转写的上一
  条。同时清掉冷却与防抖时间戳,紧接着那次真想说话的按下不会被静默吞掉。
- Less Computer 的 modifier 触发键走同一条撤销路径(顺带熄灭整屏描边)。

* feat(hotkey): 修饰键热键加 150ms 组合键仲裁窗口——组合键不再闪一下胶囊

上一提交是「事后撤销」:Option+任意字母/数字键仍会真的开麦、弹胶囊、烧一次 ASR
建连,然后被取消——功能对了,观感上闪一下。

改为按下后先等 150ms 再开会话,期间监听器若已报告叠加了普通键,这次按下整条作废:
麦克风不开、胶囊不弹。等待只加在 modifier-only 触发键的「开录」分支上:

- 自定义组合键(Cmd+Shift+D)本身没有歧义,不等;
- toggle 停止 / QA 路由 / 防抖与冷却判定都在窗口之前,时序不变(尤其不挤占
  is_queued_chain_press 的 120ms 排队接力窗口);
- 窗口没盖住的慢速组合键(按住 Option 半秒再按 Tab)仍由 TriggerCombined 事后撤销
  兜底,两条路径互补。

150ms 覆盖绝大多数组合键的「修饰键→普通键」间隔,又低于人从按键到开口的反应时间,
不吃首字。

* fix(hotkey): 补上移动端 hotkey stub 的 TriggerCombined / trigger_combined_since_press

Android CI(cargo check --target android)红:`mobile_stubs/hotkey.rs` 是移动端的
HotkeyEvent / HotkeyMonitor 替身,桌面端新增的枚举变体与方法必须同步补上,否则
coordinator 里那两条 match 臂和仲裁窗口的调用在移动端找不到符号。

移动端没有键盘监听器,`trigger_combined_since_press` 恒为 false(仲裁窗口等价于
直接放行)。

* fix(hotkey): 组合键撤销改走独立通道——按下第二个键的那一帧胶囊就消失

150ms 仲裁窗口只盖住了「修饰键→普通键」间隔够快的组合键;慢一点的(实测日志里
press→combined 中位数 428ms)仍然走事后撤销兜底,本该只是「闪一下」,实际却晚了
几百毫秒才收——观感上像录音真的起来了。

原因不是手速,是排队:TriggerCombined 和 Pressed/Released 挤同一条 channel,而
bridge 为修 #468/#475 的 latch 竞态改成了串行 block_on —— 这次按下自己的
begin_session(开麦 + ASR 握手)正卡在 bridge 线程上,撤销只能在队列里等它跑完。
日志里那个 428ms 因此是上界,真实键间隔被 begin_session 的耗时撑大了。

修法同 #853(Esc 取消):把撤销从 HotkeyEvent 枚举拆出,tap/hook 回调改发独立的
Sender<()>,由专用 combo-abort-bridge 线程消费。macOS CGEventTap 与 Windows
WH_KEYBOARD_LL 同步修改;Less Computer 的修饰键 monitor 走同一条通道,只是 handler
换成 cancel_less_computer_press。

并发面两处:
- 撤销与 Released 之间不再有先后保证。改为只看 hotkey_press_began_session
  (每个 Pressed 边沿都会重置)判断要不要撤销,不再看 hotkey_trigger_held ——
  否则 Released 抢先跑完清了按住态,撤销会认不出这条会话是自己开的、放着不管,
  Auto 模式下就是一条停不下来的录音。清 hotkey_trigger_held 只为吞掉后面的
  Released,与撤销与否无关。加了回归测试覆盖这个倒序。
- 撤销落在 begin_session 还在 await 的中途,由既有的
  startup_race_status_for_starting / CancelRaced 检查点接住(audit HIGH #1),
  与 Esc 取消同一条路径。

至此两条路径分工:仲裁窗口内的快组合键胶囊根本不出现;窗口没盖住的慢组合键胶囊
冒个头,按下第二个键即刻消失。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hotkey): 取消先收胶囊再拆麦克风 + watchdog 碎觉——撤销不再拖着胶囊一秒

上一提交把撤销决策做到了「按下第二个键的那一帧」(tap → handle_trigger_combined
实测 0.14ms),但真机日志显示胶囊仍然晚 0.8~1 秒才消失。11 次组合键测试分成干净的
两组:这次按下已经把 recorder 开起来的 5 次是 769/900/931/965/986ms,还没开起来的
6 次是 0ms。

链路:cancel_session 先 stop_recorder_for_session 才 emit_capsule。
Recorder::stop() 要 join 音频线程,音频线程退出前要 join liveness watchdog,
watchdog 睡在 1000ms 的检查间隔里 —— 所以停采要等它当前那一觉睡完,胶囊排在后面。
按 Esc 取消走同一个函数,同样拖一秒。

两处一起改(缺一不可,见下):

- coordinator/dictation.rs:cancel_session 把 UI 收尾(finish_cancel_session_state
  → emit_capsule → schedule_capsule_idle → 熄描边)整块提到三个拆资源调用之前。
  emit_capsule 仍排在 finish_cancel_session_state 之后 —— 它要读 state.voice_agent /
  phase 拼 payload,再提前会发出「还在进行中」的那一帧。拆资源都按 session_id 取,
  不依赖 phase,挪到后面语义不变。
- recorder.rs:watchdog 的一个检查间隔切成 50ms 的碎觉来睡,每觉醒来重看 stop_flag。
  停采的等待从最坏 1000ms 降到 50ms。判据(回调静默 3s / 首帧 5s)用的是真实时间差
  而非醒来次数,灵敏度不变;代价是录音期间线程多醒几次,醒来只读一个时间戳。

为什么必须成对:UI 先收之后,胶囊消失到麦克风真正释放之间有一段窗口,其间旧
recorder 还占着设备。`Recorder` 没有 Drop 停采,recorder 槽被下一条会话覆盖后旧音频
线程会继续跑、抓着麦克风不放。只做第一处会把这段窗口留在 ~1 秒,紧接着那次真想说话
的按下(最快也要 250ms 防抖 + 150ms 仲裁)就可能撞上;watchdog 切碎后窗口降到几十
毫秒,够不着。

副作用:正常说完话松手结束(end_session 也走 Recorder::stop())同样少等将近一秒。
可观察的代价:胶囊消失后系统菜单栏的录音小圆点会多亮几十毫秒。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(hotkey): resolve modifier combo abort after beta merge

* test(hotkey): initialize mac companion press id

* fix(hotkey): clone Linux fcitx combo channel

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Chris233 <h-chris233@outlook.com>
2026-08-01 18:01:11 +08:00
aeoformandClaude Opus 4.7 7e21123bad fix(linux): fcitx5 hotkey intercept + remove backdrop-filter + clipboard cleanup
- fcitx5 plugin: fix Alt+a hotkey leaking 'a' character by normalizing
  case comparison (Alt produces uppercase sym, Key::check expects lowercase)
- fcitx5 plugin: use PreInputMethod + filterAndAccept to block engine
  before commit happens (same strategy as clipboard addon)
- Linux: remove backdrop-filter (unreliable on WebKitGTK, #553),
  replace with opaque background
- Linux: skip clipboard writes after streaming insert (fcitx5 commits
  directly, clipboard fallback corrupts user data)
- SettingsModal: render via React Portal to document.body (#580, fixes
  scrollbars overlapping modal)
- Remove is_no_compositing_mode command and CSS fallback (no longer needed)
- Remove StartupShell loading screen (app renders FloatingShell immediately)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 21:59:12 +08:00
aeoformandClaude Opus 4.7 eb05bc1ca1 fix(linux): fcitx5 auxDown persistent + hotkey mode save + hide clipboard/capsule settings
- coordinator.rs: terminal state (Done/Error/Cancelled) auto-clears after 3s;
  fix RETRY_GEN comparison (fetch_add returns old value, use latest_gen > gen+1);
  remove broken 300ms retry
- HotkeySettingsContext.tsx: fix queueSetSettings never calling setSettings()
  (latestPrefsRef was already updated to the new object before microtask ran)
- openless.cpp: add PostInputMethod event hook to restore auxDown after
  fcitx5 processes key events
- RecordingInputSection.tsx: hide clipboard/paste/capsule settings on Linux;
  streaming insert shown as standalone card instead of inside clipboard group
- ShortcutsSection.tsx: hide "capsule confirm" shortcut hint on Linux

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:53:32 +08:00
aeoformandClaude Opus 4.7 28ef33267c fix(inject): fail loudly when RPM injection cannot complete
exit 0 on rpmrebuild failure produced a broken RPM silently.
Use exit 1 and print to stderr so the CI pipeline catches it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:59:53 +08:00
aeoformandClaude Opus 4.7 f5bf1ff153 fix(inject): remove AppImage support, detect Debian multiarch dynamically
- AppImage cannot work for fcitx5 plugin injection: fcitx5 runs on the
  host system and loads addons from host paths (/usr/lib/.../fcitx5).
  Files placed inside the AppImage mount are invisible to fcitx5.
- For Debian, use dpkg-architecture to detect the multiarch triplet
  (x86_64-linux-gnu, aarch64-linux-gnu, etc.) instead of hardcoding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:53:53 +08:00
aeoformandClaude Opus 4.7 0e5f90bcf2 fix(inject): use correct RPM lib path, remove broken AppImage file mode
- RPM: use /usr/lib64/fcitx5/ (RPM 64-bit standard) instead of
  Debian multiarch path
- AppImage: only support AppDir directory input, remove .AppImage file
  extraction which couldn't repack and would lose changes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:32:10 +08:00
aeoformandClaude Opus 4.7 19ab7429cd chore(linux): fcitx5 插件打包注入 + 清理 rdev 残留 + 声明系统依赖
- fcitx5 插件改为打包时注入系统路径(inject-fcitx5-plugin.sh),不再运行时复制
- ensure_plugin_installed 改为纯版本检查,不做文件 I/O
- 清理 hotkey/permissions/unicode_keystroke 中 rdev 相关注释和日志
- tauri.linux.conf.json 声明 deb/rpm 运行时依赖及 fcitx5 推荐
- prebuild 脚本自动复制插件文件供 Tauri resources 打包

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 23:08:00 +08:00
aeoformandClaude Opus 4.7 7403597be8 fix(review): 修复 PR review 指出的 Stale Status 和 Retry Race
1. clearAuxDown 找不到 IC 时也会清掉 lastAuxText_,避免 FocusIn 时
   重放旧状态(如"已插入"→切窗口→突然显示"已插入")
2. 300ms 延迟重试前检查 LAST_AUX,状态已变则跳过,避免旧文字覆盖

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 20:58:38 +08:00
aeoformandClaude Opus 4.7 57a4919712 feat(linux): fcitx5 候选框听写状态提示 + AppImage 插件自动安装 + 热键启动容错
- fcitx5 插件新增 SetAuxDown/ClearAuxDown DBus 接口,在候选词列表下方
  显示听写状态文字(收音中→识别中→润色中→已插入),切窗口自动跟随焦点
- 优先使用当前焦点 IC 展示 auxDown,失焦 IC 降级兜底,避免面板不渲染
- flushUI 排空旧事件后再设 auxDown,防止按键事件竞态覆盖状态文字
- start_dictation_signal_listener 启动时等待 fcitx5 最多 30s,监听
  NameOwnerChanged 在 fcitx5 重启后自动重新同步快捷键绑定
- AppImage 打包 libopenless.so 为资源,启动时检测缺失自动安装到
  ~/.local/lib/fcitx5/ 和 ~/.local/share/fcitx5/addon/
- Linux 胶囊窗口不在 Wayland/X11 显示,状态完全走 fcitx5 输入面板

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 20:40:40 +08:00
aeoformandClaude Opus 4.7 9cd8b9a8fc fix(linux): clear previous hotkey trigger when switching between custom 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>
2026-05-16 09:53:25 +08:00
aeoformandClaude Opus 4.7 60250c839d fix(linux): support custom combo hotkeys and surface fcitx5 errors
- Add SetCustomDictationTrigger(s) to plugin for custom key combos
- Add fcitx5 availability check before hotkey install, surface error
- Sync custom dictation combo to fcitx5 plugin from coordinator

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 09:32:12 +08:00
aeoformandClaude Opus 4.7 323dbed401 fix(linux): add QA and translation modifier hotkey support to fcitx5 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>
2026-05-16 09:21:38 +08:00
aeoformandClaude Opus 4.7 b349a4dcba fix(linux): remove dead rdev code and leftover Wayland references
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>
2026-05-16 09:01:56 +08:00
aeoformandClaude Opus 4.7 dbae3b7a5f fix(linux): correct fcitx5 InputContext::destroyed signal connection 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>
2026-05-16 08:53:06 +08:00
aeoformandClaude Opus 4.7 07132c2087 fix(linux): track fcitx5 InputContext lifetime via destroyed signal
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>
2026-05-16 08:53:06 +08:00
aeoformandClaude Opus 4.7 2912aefed5 feat(linux): replace enigo with fcitx5 plugin for Wayland input
- 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>
2026-05-16 08:53:06 +08:00