这应该是连载得最近的一次,书接上回:《或许可以用 TypeScript 编写 hammerspoon》(也就是下面那篇)
这里只会描述通过 TypeScript
实现的过程
以下内容可能产生不适(因为hammerspoon
的 d.ts
全是 interface
一把梭,编码不好看)
TL;DR
- 创建界面
- 实现剪贴板读取
- 存储数据
- 绑定快捷键
创建界面
hammerspoon
有很多种交互接口,其中chooser
就是某小黑帽那种对话式弹窗,用这个挺合适的
// choice 就是当你对着选项按下 enter 之后,这个被选择对象的内容
const completionFn = choice => {
// 一般来说这个判断不可省略,这样可以方便排除取消的情况
if (choice) {}
}
const chooser = hs.chooser.new(completionFn)
这样chooser
就是一个实例,可以使用相应的方法,例如显示或隐藏
chooser.show()
chooser.hide()
剪贴板操作
关于剪贴板的操作都已经封装在hs.pasteboard
这个模块中,通过两个函数获取到我们对于剪贴板历史比较常用的两种内容类型
pasteboard.readString() // 读取最后一次剪贴板的文本
pasteboard.readImage() // 读取最后一次剪贴板的图片数据
如何得知我的剪贴板已经有新内容了?社区基本上的方案都是通过对比剪贴次数来判定更新的,如下
pasteboard.changeCount()
所以操作大概是:使用定时器,在若干时间后检查一次次数,如发生改变即更新剪贴板历史
const clipboard = new Clipboard()
// 我选择 1s 检查一次
export const clipWatcher = hs.timer.new(1, () => {
const now = hs.pasteboard.changeCount()
if (now !== preCount) {
pcall(clipboard.save.bind(clipboard))
preCount = now
}
})
clipWatcher.start()
操作数据
识别数据
只要出现对比差异,就可以执行保存操作
日常使用中一般会复制到文本和图像(截图),先做到如何区分来源类型
通过苹果开发者文档关于 UTI,可以得到大概文本就是public.plain-text
,图像就是public.{pic format}
我截图是png
的,舍远求近直接只识别我自己使用的两种格式:public.png
, public.utf8-plain-text
save() {
const types = hs.pasteboard.contentTypes<ModelChoice['type']>()
for (const type of types) {
if (isImgType(type)) {
this.saveImage(type)
} else if (isTextType(type)) {
this.saveText(type)
}
}
}
保存数据
对应的,当知道数据来源是什么类型之后就可以相应操作了
保存我采用了sqlite
,因为 hammerspoon 带了数据库操作模块hs.sqlite3
。主要原因:
- timer可能会崩溃导致不会继续捕获,重启服务数据丢失
- 数据库查询比较快
- 数据库我还另有其用,不亏
这部分直接看 github
启用
绑定快捷键
hammerspoon 的快捷键模块hs.hotkey
,可以将快捷键绑定到具体操作上
hs.hotkey.bind(clipboardConf.hotkey[0], clipboardConf.hotkey[1], () => {
clipboard.show()
})
一套组合键,chooser
就可以显示了
加载内容
一般来说,在显示对话框时再去加载数据可以保证数据是新的,所以使用chooser.choices(choices)
加载数据,再chooser.show()
展示
this.chooser!.choices(choices)
this.chooser!.show()
至于获取数据的形式,就是需要查询数据库,还是查询文件,还是另有其他方式而已
参考连接
《如何使用 Hammerspoon 实现剪贴板历史》 —— Ahonn
Uniform Type Identifier Concepts