Clipboard SDK
Clipboard SDK
概述
Clipboard SDK 提供插件访问系统剪贴板的能力,包括:
- 基础操作:读写剪贴板内容(文本、HTML、图片、文件)
- 历史管理:查询、搜索、删除剪贴板历史记录
- 复制粘贴:将内容复制到剪贴板并自动粘贴到当前活动应用
- 选中文本:读取当前活动应用的文本选区,并在 fallback 后恢复原剪贴板内容
- 自动标签:为文本内容打标签(URL、API Key、Token、账号/密码、邮箱),便于分类与匹配
所有操作通过 TuffTransport 在主进程执行,避免 WebContents 焦点问题导致的剪贴板操作失败。
通信策略(Transport First)
useClipboard()已切换到ClipboardEvents.*事件域(如getHistory、read、copyAndPaste)。- Clipboard SDK 不再依赖
clipboard:*raw channel 事件作为主通道。 - 插件代码必须使用 SDK / typed transport 入口。历史
clipboard:*raw channel 名称仅作为迁移参考,不再作为受支持的扩展面。 system.captureSelection()使用 typed App/System event,要求 verified plugin 与clipboard.read;不暴露 raw selection channel。
介绍
快速开始
import { system, useClipboard } from '@talex-touch/utils/plugin/sdk'
const clipboard = useClipboard()
// 写入文本
await clipboard.writeText('Hello World')
// 读取剪贴板
const content = await clipboard.read()
console.log(content.text, content.formats)
// 复制并粘贴到当前应用
await clipboard.copyAndPaste({ text: 'Pasted content' })
// 读取当前活动应用的选中文本
const selection = await system.captureSelection()
if (!selection.text) console.warn(selection.issueCode)
API 参考
useClipboard()
返回统一的剪贴板 SDK 实例。
const clipboard = useClipboard()
选中文本捕获
system.captureSelection() / captureSelectedText()
读取当前活动应用的文本选区。宿主会在访问辅助功能 API、快捷键或剪贴板前验证插件 identity 与 clipboard.read 权限。macOS 优先使用 AXSelectedText;fallback 会保存全部剪贴板格式、发送平台复制快捷键并恢复原快照。
返回值包含 text、supportLevel、capturedAt,以及可选的 issueCode、issueMessage、limitations。empty、disabled、failed、unsupported 都是显式非成功状态。不得把选中文本写入普通日志、持久历史或审计 metadata。
import { system } from '@talex-touch/utils/plugin/sdk'
const result = await system.captureSelection()
if (!result.text) {
console.warn(result.issueCode, result.issueMessage)
return
}
// 仅在本次用户触发的动作中使用 result.text。
基础操作
writeText(text)
写入纯文本到剪贴板。
await clipboard.writeText('Hello World')
write(options)
写入多种格式内容到剪贴板。
// 写入文本和 HTML
await clipboard.write({
text: 'Hello',
html: '<b>Hello</b>'
})
// 写入图片(data URL)
await clipboard.write({
image: 'data:image/png;base64,...'
})
// 写入文件路径
await clipboard.write({
files: ['/path/to/file.txt', '/path/to/image.png']
})
参数:
| 字段 | 类型 | 说明 |
|---|---|---|
text | string | 纯文本内容 |
html | string | HTML 格式内容 |
image | string | 图片 data URL |
files | string[] | 文件路径数组 |
read()
读取当前剪贴板内容。
const content = await clipboard.read()
// {
// text: 'Hello',
// html: '<b>Hello</b>',
// hasImage: false,
// hasFiles: false,
// formats: ['text/plain', 'text/html']
// }
返回值:
| 字段 | 类型 | 说明 |
|---|---|---|
text | string | 文本内容 |
html | string | HTML 内容 |
hasImage | boolean | 是否包含图片 |
hasFiles | boolean | 是否包含文件 |
formats | string[] | 可用格式列表 |
readImage()
读取剪贴板中的图片。
const image = await clipboard.readImage()
if (image) {
console.log(image.dataUrl, image.width, image.height)
}
返回值:{ dataUrl: string, width: number, height: number } | null
getHistoryImageUrl(id)
获取历史图片条目的原图地址(tfile://),适合避免在插件侧传输大体积 base64。
const url = await clipboard.getHistoryImageUrl(123)
if (url) {
console.log('original image:', url)
}
history.annotate({ id, note, tags })
给一条历史记录写上用户自己的备注和标签。
两个字段互相独立:只传 note 不会动标签,只传 tags 不会动备注,所以一个只编辑备注的输入框不必始终持有最新标签。传 null 或空字符串清空备注,传空数组清空标签。
返回的是实际入库的内容——主进程会去首尾空白、把换行压成空格、按不区分大小写去重、并按上限截断(备注 200 字符,标签 24 字符 / 最多 12 个)。拿返回值回填界面,而不是回填用户输入的原文。
标签和备注写在记录的 metadata 里,而 metadata 已经在关键词搜索的匹配范围内,所以打完标签立刻就能搜到。
const { note, tags } = await clipboard.history.annotate({
id: 123,
note: 'ego lite 的生产 key',
tags: ['prod', 'openai'],
})
返回值:{ updated: boolean, note: string | null, tags: string[] },updated 为 false 表示这条记录已经不存在。
previewHistoryImage(id)
把历史图片条目交给操作系统自己的预览器打开(macOS 上是「预览」,其他平台是系统注册的默认程序)。
传的是记录 id 而不是路径:主进程自己在剪贴板图片目录里查文件,所以插件无法借此打开目录之外的任何文件。记录里没有可用原图时返回 false。
const opened = await clipboard.previewHistoryImage(123)
if (!opened) {
console.warn('这条记录没有可预览的原图')
}
返回值:boolean
readFiles()
读取剪贴板中的文件路径。
const files = await clipboard.readFiles()
// ['/path/to/file1.txt', '/path/to/file2.png']
clear()
清空剪贴板。
await clipboard.clear()
复制并粘贴
copyAndPaste(options)
将内容写入剪贴板,然后模拟粘贴快捷键到当前活动应用。这是插件"输出"内容到用户当前应用的推荐方式。
// 粘贴文本
await clipboard.copyAndPaste({ text: 'Hello' })
// 粘贴带格式的文本
await clipboard.copyAndPaste({
text: 'Hello',
html: '<b>Hello</b>'
})
// 粘贴图片
await clipboard.copyAndPaste({
image: 'data:image/png;base64,...'
})
// 粘贴文件
await clipboard.copyAndPaste({
files: ['/path/to/file.txt']
})
// 自定义延迟和行为
await clipboard.copyAndPaste({
text: 'Hello',
delayMs: 200, // 粘贴前延迟(默认 150ms)
hideCoreBox: true // 粘贴前隐藏 CoreBox(默认 true)
})
参数:
| 字段 | 类型 | 说明 |
|---|---|---|
text | string | 文本内容 |
html | string | HTML 内容 |
image | string | 图片 data URL |
files | string[] | 文件路径 |
delayMs | number | 粘贴前延迟毫秒数 |
hideCoreBox | boolean | 是否在粘贴前隐藏 CoreBox |
返回值:Promise<boolean> - 操作是否成功
历史记录
通过 clipboard.history 访问历史记录相关操作。
history.getLatest()
获取最新的剪贴板记录。
const latest = await clipboard.history.getLatest()
if (latest) {
console.log(latest.type, latest.content)
}
history.getHistory(options)
分页获取剪贴板历史。
const { history, total, page, pageSize } = await clipboard.history.getHistory({
page: 1,
pageSize: 20
})
history.searchHistory(options)
高级搜索剪贴板历史。
// 关键词搜索
const result = await clipboard.history.searchHistory({
keyword: 'hello'
})
// 按类型筛选
const textItems = await clipboard.history.searchHistory({
type: 'text'
})
// 时间范围筛选
const recent = await clipboard.history.searchHistory({
startTime: Date.now() - 24 * 60 * 60 * 1000 // 最近 24 小时
})
// 组合筛选
const filtered = await clipboard.history.searchHistory({
type: 'text',
isFavorite: true,
sourceApp: 'com.apple.Safari',
page: 1,
pageSize: 10
})
搜索参数:
| 字段 | 类型 | 说明 |
|---|---|---|
keyword | string | 内容关键词 |
type | 'text' | 'image' | 'files' | 类型筛选 |
startTime | number | 开始时间戳 |
endTime | number | 结束时间戳 |
isFavorite | boolean | 是否收藏 |
sourceApp | string | 来源应用 |
page | number | 页码 |
pageSize | number | 每页数量 |
sortOrder | 'asc' | 'desc' | 排序方向 |
history.setFavorite(options)
设置收藏状态。
await clipboard.history.setFavorite({
id: 123,
isFavorite: true
})
history.deleteItem(options)
删除历史记录。
await clipboard.history.deleteItem({ id: 123 })
history.clearHistory()
清空所有历史记录。
await clipboard.history.clearHistory()
监听剪贴板变化
history.onDidChange(callback)
监听剪贴板变化事件。
重要:插件必须先调用
box.allowClipboard(types)声明要监听的类型,否则不会收到任何事件。
import { useBox, ClipboardType } from '@talex-touch/utils/plugin/sdk'
const box = useBox()
const clipboard = useClipboard()
// 1. 先声明要监听的类型
await box.allowClipboard(ClipboardType.TEXT | ClipboardType.IMAGE)
// 2. 注册监听器
const unsubscribe = clipboard.history.onDidChange((item) => {
console.log('剪贴板变化:', item.type, item.content)
})
// 3. 停止监听
unsubscribe()
ClipboardType 枚举:
| 值 | 二进制 | 说明 |
|---|---|---|
TEXT | 0b0001 | 文本 |
IMAGE | 0b0010 | 图片 |
FILE | 0b0100 | 文件 |
预设组合:
import { ClipboardTypePresets } from '@talex-touch/utils/plugin/sdk'
// 仅文本
await box.allowClipboard(ClipboardTypePresets.TEXT_ONLY)
// 文本和图片
await box.allowClipboard(ClipboardTypePresets.TEXT_AND_IMAGE)
// 所有类型
await box.allowClipboard(ClipboardTypePresets.ALL)
剪贴板项目结构
interface PluginClipboardItem {
id?: number
type: 'text' | 'image' | 'files'
content: string // 文本内容 / 图片 dataURL / 文件路径 JSON
thumbnail?: string // 缩略图 dataURL
rawContent?: string // HTML 原始内容
sourceApp?: string // 来源应用
timestamp?: Date // 时间戳
isFavorite?: boolean // 是否收藏
meta?: Record<string, unknown> // 元数据(含 tags)
}
自动标签(tags)
当剪贴板内容为文本时,系统会进行轻量规则识别并写入 meta.tags,用于快速分类、提示与匹配。标签仅表示类别,不会包含原始敏感值。
当前标签集合:
url:URL/链接api_key:API Key(常见前缀或字段形式)token:Token/Bearerpassword:密码字段account:账号/用户名字段email:邮箱
使用示例:
const latest = await clipboard.history.getLatest()
const tags = Array.isArray(latest?.meta?.tags) ? latest?.meta?.tags : []
if (tags.includes('api_key') || tags.includes('password')) {
// 例如:提示用户注意敏感信息
}
建议:对
history.searchHistory()的结果在插件侧按meta.tags做二次筛选,以实现更精准的匹配逻辑。
技术原理
- 读写、历史记录与自动粘贴均在主进程执行,通过 TuffTransport 返回结果,避免渲染进程焦点导致的剪贴板失败。
- 历史记录落盘在数据库中,
meta/metadata承载附加信息并用于筛选与匹配。 - 自动标签基于轻量规则匹配生成,只输出类别,不暴露原始敏感内容。
迁移指南
从 useClipboardHistory 迁移
useClipboardHistory() 已废弃,请迁移到 useClipboard():
// 旧代码
const history = useClipboardHistory()
await history.getLatest()
await history.applyToActiveApp({ text: 'Hello' })
// 新代码
const clipboard = useClipboard()
await clipboard.history.getLatest()
await clipboard.copyAndPaste({ text: 'Hello' })
最佳实践
- 使用 copyAndPaste 而非手动操作:
copyAndPaste会自动处理 CoreBox 隐藏、延迟、跨平台粘贴等细节。 - 合理设置监听类型:只监听需要的类型,避免不必要的性能开销。
- 及时取消监听:在插件卸载或不需要时调用
unsubscribe()。 - 处理空值:
readImage()和history.getLatest()可能返回null。