macOS 剪切板数据查看与修改

孙康

macOS 系统没有内置的剪切板查看器,这对普通用户实际并没有什么影响,多数时候 ⌘ + C⌘ + V 就够用了。但对于一些开发者群体,可能需要查看当前拷贝内容的格式及数据是否正确,或者使用特定格式的数据完成某项工作,那么没有剪切板查看器会严重影响工作效率。PasteShow 是一款开源的系统剪切板查看器,本文展示的代码源于该项目。

macOS 实现一款剪切板查看器是不难的,借助 AppKit 提供的 NSPasteboard 类可以很轻松的实现。系统标准剪切板分为几类,包括 general、font、ruler、find、drag,不同剪切板名称的含义请查看官方文档 NSPasteboard.Name ,这里我们只涉及用于执行普通剪切、复制、粘贴操作的通用剪切板。

检查数据变更

在获取剪切板最新拷贝内容之前,需要知道内容更新的时机。macOS 系统并没有提供一种通知机制可以让开发者知道何时剪切板发生了变更事件,所以这里我们只能使用定时器轮询,检查剪切板是否发生数据变更。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct CopiedInfo {
var changeCount = 0
var copiedItems = [[String: Data]]()
}

class PasteboardManager {
static let shared = PasteboardManager()
var copiedInfo = CopiedInfo()
private let pasteboard = NSPasteboard.general
private var observerTimer = Timer()

private init() {
setupObserverTimer()
}

private func setupObserverTimer() {
observerTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true, block: { [self] _ in
guard copiedInfo.changeCount != pasteboard.changeCount else {
return
}

onPasteboardChanged()
})
}
}

这里设置了一个定时器,0.25s 触发一次,用于检查剪切板数据是否发生变更。是否发生变更很好判断,检查保存的 changeCount 是否和剪切板当前的变更次数一致即可。如果不一致则表明定时器间隔时间内发生了剪切或者复制事件。然后获取剪切板中存储的待粘贴数据。

获取待粘贴数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private func onPasteboardChanged() {
copiedInfo.copiedItems.removeAll()
copiedInfo.changeCount = pasteboard.changeCount

guard pasteboard.pasteboardItems != nil else {
return
}

for item in pasteboard.pasteboardItems! {
var itemInfo = [String: Data]()

for type in item.types {
if let value = item.data(forType: type) {
itemInfo[type.rawValue] = value
}
}
copiedInfo.copiedItems.append(itemInfo)
}
}

使用 data(forType:) 接口可以获取指定类型的剪切板数据,至此待粘贴的所有类型的数据全部获取到了。如果把剪切板数据简单作为字符串打印显示,可以将使用的接口换成 string(forType:),这将强制返回 String 类型的数据。如果需要显示带格式的数据,则需要绘制界面。

修改剪切板存储的数据

NSPasteboard 不仅提供了剪切板数据读取接口,也提供了数据设置接口。不过在修改(增加或者删除同理)数据前需将剪切板存储的数据全部清空,否则设置无法成功。此处为本人实际操作发现,没看到官方文档对此有说明,读者可自行尝试。下面举例说明删除某项数据的代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func removeDataWithReserve(data: Data, forType type: String) {
var pasteItems = [NSPasteboardItem]()
pasteboard.clearContents()
for items in copiedInfo.copiedItems {
let pasteItem = NSPasteboardItem()
for pair in items {
if pair.key == type && pair.value == data {
continue
}
pasteItem.setData(pair.value, forType: NSPasteboard.PasteboardType(pair.key))
}
pasteItems.append(pasteItem)
}
pasteboard.writeObjects(pasteItems)
}

实际上删除某项数据仅仅是清空所有数据,然后把不需要删除的数据添加回去。这里调用了两个接口:setData(_:forType:)writeObjects()。虽然大多数情况下一次复制或者剪切的数据是一项(如复制一段文字、网页、图片等),一项数据内包含多个格式的数据,但类似一次复制多项文件(使用 多选)的情况,剪切板会设置多项数据,如下图所示。所以这里需要把各个项目的剪切板数据使用 setData(_:forType:) 接口设置完毕,再使用 writeObjects() 接口将所有项目的数据添加到剪切板。

同时拷贝多个文件
同时拷贝多个文件

  • Title: macOS 剪切板数据查看与修改
  • Author: 孙康
  • Created at : 2023-08-20 19:57:41
  • Updated at : 2023-08-31 19:55:48
  • Link: https://conradsun.github.io/2023/08647ac6c0.html
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
macOS 剪切板数据查看与修改