1. 剪贴板历史: 后台自动记录

copypaste 在后台运行一个独立的监控线程, 以 200 ms 一次的频率调用 Windows 的 GetClipboardSequenceNumber() 来检测剪贴板变化, 一旦序列号增加就从系统剪贴板读取内容并写入历史列表。

这一设计的好处是几乎无 CPU 占用: 序列号查询是原子操作, 不会唤醒剪贴板所有者进程, 不会干扰你正在使用的应用。

  • 自动去重: 复制相同内容时, 自动识别并把原有条目置顶, 不会出现 100 条相同记录的尴尬。
  • 200 条容量上限: 超出容量时按 FIFO 驱逐最旧的非置顶条目, 重要内容因置顶永远保留。
  • 大文本保护: 大于 10 KB 的文本自动丢弃, 避免误记录大段代码、文档、长日志。
  • 纯文本支持: 当前仅记录纯文本剪贴板, 图片、文件、其他格式暂不记录。
实现: clipboard-win 读取 + Win32 GetClipboardSequenceNumber 轮询 + 200 ms 间隔

2. Alt+Space: 全局热键唤出

在任何应用、任何窗口里按下 Alt+Space, 即可瞬间唤出 copypaste 的剪贴板历史窗口。再按一次即可隐藏。

相比 Win+V, Alt+Space 几乎是单手就能按的组合, 左手小指 + 大拇指就能完成, 效率提升一个数量级。

热键基于 tauri-plugin-global-shortcut 实现, 监听系统级按键事件。开发过程中踩过一个坑: 底层 global-hotkey crate 在 Windows 上通过 GetAsyncKeyState 轮询来发 Released 事件, 所以 on_shortcut 回调会在按下和松开时各触发一次。必须在回调里判断 event.state == ShortcutState::Pressed, 否则会出现"按下显示、松开又隐藏"的 bug。

3. 点击即粘贴: 自动切回前一个窗口

这是 copypaste 的核心差异化能力。点击任意条目后会自动完成四步:

  • 把条目内容写入剪贴板
  • 隐藏 copypaste 窗口
  • 通过 Win32 API 切回上一个活动窗口
  • 模拟 Ctrl+V 按键, 把内容粘贴过去

整个过程无需手动复制再切换再粘贴, 一次点击完成所有事。例如你正在浏览器找资料, 想把一段 URL 发到 IM 对话框里, 流程从「切到 IM、点输入框、Ctrl+V」变成「Alt+Space → 数字键 → 完成」。

实现的关键 Win32 API

自动粘贴在 Windows 上不是一件简单的事, 因为 Windows 有前台锁定(Foreground Lock)机制: 普通用户进程无法把焦点强制切到另一个窗口。copypaste 用以下三个 API 绕过这个限制:

  • AttachThreadInput: 把 copypaste 的当前线程连到目标窗口所在线程, 突破前台锁定。这是必须的, 否则 SetForegroundWindow 只会返回成功但实际不切换。
  • SetForegroundWindow + BringWindowToTop: 实际切换焦点, 双保险。
  • keybd_event(而非 SendInput): 模拟真实的键盘事件。测试中 4 次独立 SendInput(Ctrl↓ / V↓ / V↑ / Ctrl↑)在 OS 队列切换时可能让 Ctrl 状态丢失, 结果只打出字母 v。即使打包成一个数组发送, 部分目标窗口仍收不到 Ctrl。改用 keybd_event + 真实扫描码后才稳定。

为了稳定性, 还会校验 GetForegroundWindow() == target_hwnd, 不匹配就放弃按键, 避免把内容贴到错误窗口。如果目标窗口被最小化, 会先用 IsIconic 检查、ShowWindow(SW_RESTORE) 恢复。

4. 数字键快捷粘贴: 键盘流

列表中的条目有从 190(代表第 10 条)的快捷数字。在剪贴板历史窗口中按下对应数字键, 无需鼠标点击即可直接粘贴该条目。

配合 Alt+Space 唤出 + 1~9 粘贴, 整个流程双手不离主键盘区域, 鼠标完全闲置。这是高频复制场景下的核心工作流

5. 上下箭头 + Enter: 精确选中

当列表里有 10 条以上条目, 或者你想精确选某一条而非凭记忆按数字时, 用 浏览, 按 Enter 粘贴当前选中项。

窗口有键盘焦点时, 这套快捷键原生工作, 无需学习成本。

6. 置顶: 永久保留常用片段

每条记录的右侧有一个 📌 图标, 点击即可切换置顶状态。置顶条目:

  • 永远排在列表最前面, 数字键 1~9 优先匹配置顶项
  • 不被 200 条 FIFO 清理机制驱逐
  • 点击 📌 取消置顶后会回到普通列表, 重新参与 FIFO

适合放常用命令、邮箱、地址、签名档、SQL 模板等每天用几十次的内容。

7. 拼音搜索: 输入首字母秒找

列表上方有搜索框, 输入关键词即可过滤历史条目。copypaste 内置了拼音首字母匹配:

  • 输入 wx 即可匹配内容含"微信""文章""五星"等拼音首字母为 wx 的条目
  • 输入 dz 匹配"地址""电子""定制"
  • 中英文混合: 拼音前缀匹配中文部分, 精确匹配英文部分

拼音字典通过 vendor UMD 文件 pinyin-pro.umd.js 加载, 无构建步骤, 直接拷到 ui/vendor/ 即可。

8. 系统托盘: 常驻但不打扰

关闭 copypaste 主窗口不会退出进程, 而是缩到系统托盘(任务栏右下角)。

  • 左键托盘图标: 唤出/隐藏主窗口
  • 右键托盘图标: 弹出菜单(显示窗口 / 退出 / 关于)
  • 窗口失焦自动隐藏: 点击 copypaste 窗口外的任意位置, 窗口自动隐藏(除非用图钉钉住)

9. 图钉: 钉住窗口不自动隐藏

窗口右上角有一个图钉按钮。

  • 红色: 已钉住, 窗口失焦后不自动隐藏, 适合需要边参考历史边工作
  • 灰色: 未钉住, 默认行为, 失焦自动隐藏

注意: 图钉只影响失焦隐藏行为, 不会影响 Alt+Space 切换显示。

10. 透明无边框 + 圆角阴影

主窗口做了无边框(decorations: false)+ 透明背景(transparent: true)的配置, 配合 CSS 的圆角和阴影, 视觉上像一个悬浮的"卡片", 不会挡到下面的应用。

可通过顶栏(去掉原生标题栏后自定义的标题区)拖拽移动窗口位置。

11. 本地持久化: 数据不丢

所有剪贴板历史保存在 %APPDATA%/com.my-launcher.app/data.json, 即 C:\Users\<你>\AppData\Roaming\com.my-launcher.app\data.json

写入采用原子操作: 先写到 data.json.tmp, 再 rename 覆盖正式文件。这样即使断电、程序崩溃, 也不会写出半截损坏的 JSON。

向后兼容旧版格式: 自动忽略已移除的 text_snippets 字段, 不会因为升级而丢失数据。

12. Tauri 命令(IPC 接口)

前端通过以下 Tauri 命令与 Rust 后端交互, 完整接口:

  • get_clipboard_history: 获取完整历史列表
  • delete_clipboard_item(id): 删除单条
  • toggle_pin_clipboard_item(id): 切换固定状态
  • clear_clipboard_history: 清空(保留置顶项)
  • paste_text(text): 写剪贴板 + 隐藏窗口 + 切回前窗 + Ctrl+V, 一站式粘贴

技术栈总览

  • 前端: Vanilla JS / HTML / CSS, 无打包步骤, 无运行时依赖(拼音库直接 vendor)
  • 后端: Rust + Tauri v2
  • 剪贴板读取: clipboard-win 5 + GetClipboardSequenceNumber
  • 自动粘贴: windows crate 0.61(Win32_UI_WindowsAndMessaging + Win32_UI_Input_KeyboardAndMouse + Win32_System_Threading)
  • 全局热键: tauri-plugin-global-shortcut
  • 持久化: JSON + 原子写入
  • 体积优化: opt-level = "z" + lto = "fat" + strip = true, 最终安装包 3 MB

浏览器预览(开发期)

开发期不需要每次都打 Tauri 包, 双击 ui/index.html 即可在浏览器里看到 1:1 的效果。mock.js 在检测到没有 window.__TAURI__ 时自动加载, 提供:

  • 演示用的假剪贴板历史
  • 命令桩(所有 Tauri 命令的假实现)
  • UI 100% 复用, 不进 Tauri 真机

使用注意事项

  • 仅支持 Windows 10 / 11(依赖 Win32 API)
  • 管理员窗口需要 copypaste 也以管理员身份运行才能被自动粘贴
  • 当前仅记录纯文本, 不记录图片、文件、富文本格式
  • 大于 10 KB 的文本会被自动丢弃