GitHub ↗

02 · 方案设计:post_process 钩子 + 智能粘贴

02 · 方案设计:post_process 钩子 + 智能粘贴

目标行为

SenseVoice 转写完成
  ↓ stdin(完整文本,含中英混排)
voxpaste 工具
  ├─ 1. 整体写入剪贴板(wl-copy)
  ├─ 2. hyprctl activewindow -j 解析焦点窗口 → class / XWayland 判定
  ├─ 3. 按应用族选择粘贴和弦:
  │      GUI 应用          → Ctrl+V
  │      foot / kitty      → Shift+Insert(终端直通粘贴)
  │      alacritty/ghostty → Ctrl+Shift+V
  │      其他终端          → Shift+Insert → Ctrl+V
  ├─ 4. wtype 发送和弦(修饰键+单键,rime 不拦截"组合键",只拦字母)
  └─ 5. stdout 返回空串
  ↓
voxtype 无内容可打(fallback_on_empty = false)

核心洞察:被 rime 劫持的是「字母键击」。Ctrl+V 是修饰键+键的组合,rime 组词逻辑不消费它;应用收到后执行的是自己的粘贴动作——文本作为整体插入,IME 全程不接触内容。

接线方式

~/.config/voxtype/config.toml:

[output.post_process]
command = "~/.local/bin/voxpaste"
timeout_ms = 3000
fallback_on_empty = false   # 关键:空输出不回退原文,否则会双重输入
  • command 收到转写文本于 stdin,stdout 输出交给 voxtype 继续处理
  • 我们在脚本内完成全部注入后返回空串 → voxtype 的打字环节变成 no-op
  • fallback_on_empty = false 必须显式设置:若为 true,空输出可能回退为原文本导致 voxtype 又打一遍(待实测确认语义)

失败语义

环节失败 行为
wl-copy 不可用 返回原文(让 voxtype 打字兜底)+ 桌面通知说明
hyprctl 解析失败 按 GUI 默认和弦 Ctrl+V 尝试
和弦发送失败 文本已在剪贴板 → notify-send 提示手动 Ctrl+V
超时(3s) voxtype 侧按超时策略处理;工具内所有外部调用自带短超时

实现要点

  1. 语言/形态:Go 静态编译单二进制,部署到 ~/.local/bin/voxpaste,chezmoi 纳管
  2. 环境健壮性:daemon 环境通常已有 WAYLAND_DISPLAY,但保留缺失时的 /run/user/<uid> 回退
  3. 剪贴板竞争:wl-copy 后 sleep ~150ms 再发和弦(合成器/剪贴板管理器同步延迟)
  4. XWayland 判定:hyprctl activewindow -j 的输出含 xwayland 字段;此类窗口需 xsel 写 X11 CLIPBOARD + xdotool 发和弦(P2)
  5. 空输入:stdin 为空直接透传空串,不做任何动作
  6. 日志:写 ~/.local/state/voxpaste/log,便于排查「没贴上」类问题

测试计划

用例 期望
foot 中说「今天讨论 docker 部署」 「今天讨论 docker 部署」顺序正确
Chromium 输入框混排 同上
kitty 中混排 Shift+Insert 路径生效
fcitx5 中文模式 + 英文模式各测一轮 结果一致
剪贴板原有内容 被覆盖(可接受,后续可考虑恢复)

明确不做

  • 不修改 voxtype 本体或其配置结构
  • 不做通用剪贴板管理(交给 cliphist)
  • 不尝试恢复用户剪贴板旧内容(复杂度高、收益低)