1. 剪贴板历史: 后台自动记录
copypaste 在后台运行一个独立的监控线程, 以 200 ms 一次的频率调用 Windows 的 GetClipboardSequenceNumber() 来检测剪贴板变化, 一旦序列号增加就从系统剪贴板读取内容并写入历史列表。
这一设计的好处是几乎无 CPU 占用: 序列号查询是原子操作, 不会唤醒剪贴板所有者进程, 不会干扰你正在使用的应用。
- 自动去重: 复制相同内容时, 自动识别并把原有条目置顶, 不会出现 100 条相同记录的尴尬。
- 200 条容量上限: 超出容量时按 FIFO 驱逐最旧的非置顶条目, 重要内容因置顶永远保留。
- 大文本保护: 大于 10 KB 的文本自动丢弃, 避免误记录大段代码、文档、长日志。
- 纯文本支持: 当前仅记录纯文本剪贴板, 图片、文件、其他格式暂不记录。
实现:clipboard-win读取 + Win32GetClipboardSequenceNumber轮询 + 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. 数字键快捷粘贴: 键盘流
列表中的条目有从 1 到 9 和 0(代表第 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-win5 +GetClipboardSequenceNumber - 自动粘贴:
windowscrate 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 的文本会被自动丢弃