Skip to content

Clipboard Pipeline

51hhh edited this page Sep 16, 2026 · 1 revision

剪贴板管线与存储

为什么是轮询

系统剪贴板监听采用 ~500 ms 轮询(arboard),不使用系统通知机制。X11 上有可靠 XFixes 通知时,静态图片首次读取后会跳过重复读取与全量扫描;其他平台及通知失效时保留轮询。

轮询的代价在「自己写入」时最明显:截图复制完立刻贴图,如果等满一轮就会取到上一条。解决办法不是把周期调小,而是写入方主动唤醒轮询。

写入唤醒

writer.rs 写系统剪贴板成功
        ↓
wake::nudge()               条件变量 + 待处理标记
        ↓
轮询的等待当场结束           几毫秒内进历史,而不是最多等满 500 ms

wake.rs 用条件变量加一个待处理标记,避免唤醒信号在轮询正忙时丢失。

入库只有一条路径

这是本项目最容易被违反、后果也最直接的约定:

不要让写入方自己 insert_clip。

原因是哈希基准不一致。watcher 哈希的是它自己从剪贴板读回的 RGBA 重新编码出的 PNG。如果写入方先按自己手里的字节插一条,两边字节对不上,watcher 在下一轮就会再插一条重复记录。

内部复制产生的内容(OCR 结果、译文)同理:它们通过写入抑制机制跳过一次录入,而不是绕过 watcher 直接写库。

去重与置顶

  • 去重基于 SHA-256(content_hash 字段 UNIQUE 约束)
  • 命中重复只更新使用顺序,让条目置顶,不插新行
  • 收藏条目不受历史上限清理影响

图片的去重要点:图片经剪贴板走一圈是原始 RGBA,PNG 字节不稳定(压缩级别、chunk 切分都可能变)。因此凡是需要「认出同一张图」的地方,键都是解码后像素的 sha256(含宽高),不是 PNG 字节。

轮询之间判断「还是上一张图吗」用 rgba_fingerprint(1080p 实测 1.47 ms),替掉了一次完整 PNG 编码。

存储:SQLite + FTS5

clips        主表
clips_fts    FTS5 虚拟表,需要手动同步

FTS 索引不自动跟随:插入时同步写 clips_fts,删除时用 FTS5 'delete' 命令清理。

事务是一致性的唯一保证

insert_clip、delete_clip、clear_history、delete_entries 都用 unchecked_transaction() 包住多条语句。StorageEngine 只持有 &self,并发由外层 Arc<Mutex<_>> 串行化。

没有事务时中途失败会留下两类幽灵数据:

  • 「搜得到但已不存在」的 FTS 行
  • 删不掉的译文记录

而重建 FTS 只在 schema 版本变化时跑一次,索引不会自己长回来。

批量清理的参数上限

历史缩容会一次删除大量行。SQLite 有单语句参数数量上限(32,766),超过 3 万条历史的缩容会直接失败,并且失败后每次启动都会重试同一个必然失败的操作。

处理方式:按 500 个 id 分块,共用同一个事务,中途故障整体回滚。分块是为了绕过参数上限,共用事务是为了保持原子性——两者缺一不可。

容量语义

历史容量 0 表示不限,不是「不保留」。新增、去重置顶、收藏、搜索和容量清理共用同一份持久化的使用顺序,避免「按时间排」和「按使用排」两套顺序打架。

搜索路径

查询长度 路径 基准(2000 行内存库)
≥ 3 字符 FTS5 前缀查询 298 µs(命中大量行)
< 3 字符 LIKE 回退 38.1 µs
无查询 直接分页 23.2 µs(limit 50)

长历史的前端策略

剪贴板列表按文本/图片真实行高做窗口化渲染。1 万条历史只挂载视口附近不足 20 行,同时保留完整滚动高度、无限分页、键盘跳转和无障碍总数/序号。屏外缩略图同步释放。

主窗口被显式隐藏(快捷键、托盘、选择条目、关闭按钮)时,原生层会先通知 WebView 释放列表、预览和翻译快照。这一步必须由原生层主动发起:窗口已经失焦时不会再有新的 blur 事件,只靠前端事件会让上万条历史长期驻留内存。

实测(GNOME Wayland / WebKitGTK,1 万条混合历史含 2000 张图片):全过程只挂载 10–15 行,完整向下、向上、再次向下遍历均无滚动失败或 OOM;第二次遍历后 PSS 相对首次遍历末端只增加约 9 MiB。显式隐藏后列表对象归零,15 秒后 cgroup PSS 回落约 8 MiB。

图片入队的信任边界

剪贴板图片在编码前校验三项:单边尺寸、总像素、精确 RGBA 长度。合法 4K/8K 保持原分辨率;异常提供者不会触发超大 PNG 编码,也不会每 500 ms 重复刷写警告。

普通图片始终由系统复制进入队列——主界面不提供「打开图片」按钮,也不拦截全局 Ctrl/Cmd+O。可编辑 PNG 是唯一例外,走专用的工程拖放入口,普通 PNG 不会绕过剪贴板队列。

相关页面

Clone this wiki locally