forked from chbrown/macos-pasteboard
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pbv.swift
executable file
·170 lines (145 loc) · 5.35 KB
/
pbv.swift
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env swift
import Cocoa
import Foundation
let newline = Data([0x0A] as [UInt8])
/**
Write the given string to STDERR.
- parameter str: native string to write encode in utf-8.
- parameter appendNewline: whether or not to write a newline (U+000A) after the given string (defaults to true)
*/
func printErr(_ str: String, appendNewline: Bool = true) {
// writing to STDERR takes a bit of boilerplate, compared to print()
if let data = str.data(using: .utf8) {
FileHandle.standardError.write(data)
if appendNewline {
FileHandle.standardError.write(newline)
}
}
}
/**
Read Data from NSPasteboard.
- parameter pasteboard: specific pasteboard to read from
- parameter dataTypeName: name of pasteboard data type to read as
- throws: `NSError` if data cannot be read as given dataType
*/
func pasteboardData(_ pasteboard: NSPasteboard, dataTypeName: String) throws -> Data {
let dataType = NSPasteboard.PasteboardType(rawValue: dataTypeName)
if let string = pasteboard.string(forType: dataType) {
let data = string.data(using: .utf8)! // supposedly force-unwrapping using UTF-8 never fails
return data
}
if let data = pasteboard.data(forType: dataType) {
return data
}
throw NSError(
domain: "pbv",
code: 0,
userInfo: [
NSLocalizedDescriptionKey: "Could not access pasteboard contents as String or Data for type: '\(dataTypeName)'",
]
)
}
/**
Read Data from NSPasteboard, trying each dataType in turn.
- parameter pasteboard: specific pasteboard to read from
- parameter dataTypeNames: names of pasteboard data type to try reading as
- throws: `NSError` if data cannot be read as _any_ of the given dataTypes
*/
func bestPasteboardData(_ pasteboard: NSPasteboard, dataTypeNames: [String]) throws -> Data {
for dataTypeName in dataTypeNames {
if let data = try? pasteboardData(pasteboard, dataTypeName: dataTypeName) {
return data
}
}
let dataTypeNamesJoined = dataTypeNames.map { "'\($0)'" }.joined(separator: ", ")
throw NSError(
domain: "pbv",
code: 0,
userInfo: [
NSLocalizedDescriptionKey: "Could not access pasteboard contents as String or Data for types: \(dataTypeNamesJoined)",
]
)
}
func streamBestPasteboardData(_ pasteboard: NSPasteboard, dataTypeNames: [String], timeInterval: TimeInterval = 0.1) -> Timer {
var lastChangeCount = pasteboard.changeCount
return Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { _ in
let changeCount = pasteboard.changeCount
if changeCount != lastChangeCount {
printErr("#\(changeCount)")
lastChangeCount = changeCount
do {
let data = try bestPasteboardData(pasteboard, dataTypeNames: dataTypeNames)
FileHandle.standardOutput.write(data)
} catch {
printErr(error.localizedDescription)
}
}
}
}
func printTypes(_ pasteboard: NSPasteboard) {
printErr("Available types for the '\(pasteboard.name.rawValue)' pasteboard:")
// Apple documentation says `types` "is an array NSString objects",
// but that's wrong: they are PasteboardType structures.
if let types = pasteboard.types {
for type in types {
printErr(" \(type.rawValue)")
}
} else {
printErr(" (not available)")
}
}
// CLI helpers
private func basename(_ pathOption: String?) -> String? {
if let path = pathOption {
return URL(fileURLWithPath: path).lastPathComponent
}
return nil
}
private func printUsage(_ pasteboard: NSPasteboard) {
let process = basename(CommandLine.arguments.first) ?? "pbv"
printErr("""
Usage: \(process) [-h|--help]
\(process) [dataType [dataType [...]]] [-s|--stream]
Read contents of pasteboard as 'dataType'. If multiple types are specified,
tries each from left to right, stopping at first success. If omitted,
defaults to 'public.utf8-plain-text'.
Options:
-h|--help Show this help and exit
-s|--stream Start an infinite loop polling the Pasteboard 'changeCount',
running as usual whenever it changes
""")
printTypes(pasteboard)
}
// CLI entry point
func main() {
let pasteboard: NSPasteboard = .general
// CommandLine.arguments[0] is the fullpath to this file
// CommandLine.arguments[1+] should be the desired type(s)
var args = Array(CommandLine.arguments.dropFirst())
if args.contains("-h") || args.contains("--help") {
printUsage(pasteboard)
exit(0)
}
let streamIndex = args.firstIndex { arg in arg == "-s" || arg == "--stream" }
if let index = streamIndex {
args.remove(at: index)
}
let types = args.isEmpty ? ["public.utf8-plain-text"] : args
if streamIndex != nil {
let timer = streamBestPasteboardData(pasteboard, dataTypeNames: types)
let runLoop = RunLoop.main
runLoop.add(timer, forMode: .default)
runLoop.run()
} else {
do {
let data = try bestPasteboardData(pasteboard, dataTypeNames: types)
FileHandle.standardOutput.write(data)
exit(0)
} catch {
printErr(error.localizedDescription)
printTypes(pasteboard)
exit(1)
}
}
}
main()