Skip to content

Commit 8c879d9

Browse files
committed
fix: preserve clipboard contents after text injection
- Deep-copy all pasteboard item data (not just strings) before overwriting - Restore clipboard even when previous content was empty - Handle non-string clipboard content (images, files, rich text) - Fix NSPasteboardItem invalidation by copying raw Data eagerly
1 parent 1f72ed4 commit 8c879d9

1 file changed

Lines changed: 70 additions & 5 deletions

File tree

Sources/VocaMac/Services/TextInjector.swift

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ final class TextInjector {
1717
/// Virtual key code for the V key
1818
private let kVK_V: CGKeyCode = 9
1919

20+
// MARK: - Types
21+
22+
/// Deep copy of a single pasteboard item's data across all its types
23+
private struct PasteboardItemSnapshot {
24+
/// Map from pasteboard type to raw data
25+
let dataByType: [(NSPasteboard.PasteboardType, Data)]
26+
}
27+
28+
/// Deep copy of the entire pasteboard state
29+
private struct PasteboardSnapshot {
30+
let items: [PasteboardItemSnapshot]
31+
}
32+
2033
// MARK: - Public API
2134

2235
/// Inject text at the current cursor position in any application
@@ -40,8 +53,10 @@ final class TextInjector {
4053

4154
let pasteboard = NSPasteboard.general
4255

43-
// Save current clipboard text
44-
let previousText = preserveClipboard ? pasteboard.string(forType: .string) : nil
56+
// Deep-copy current clipboard state before we overwrite it.
57+
// NSPasteboardItem objects are invalidated when the pasteboard is cleared,
58+
// so we must extract the raw data eagerly.
59+
let snapshot = preserveClipboard ? captureSnapshot(pasteboard) : nil
4560

4661
// Set transcribed text to clipboard
4762
pasteboard.clearContents()
@@ -54,15 +69,65 @@ final class TextInjector {
5469
simulatePaste()
5570

5671
// Restore clipboard after paste completes
57-
if preserveClipboard, let previous = previousText {
72+
if preserveClipboard {
5873
DispatchQueue.main.asyncAfter(deadline: .now() + clipboardRestoreDelay) {
59-
pasteboard.clearContents()
60-
pasteboard.setString(previous, forType: .string)
74+
if let snapshot = snapshot {
75+
self.restoreSnapshot(snapshot, to: pasteboard)
76+
} else {
77+
// Previous clipboard was empty; clear the transcribed text
78+
pasteboard.clearContents()
79+
}
80+
NSLog("[TextInjector] Clipboard restored")
6181
}
6282
}
6383
}
6484
}
6585

86+
// MARK: - Clipboard Snapshot Management
87+
88+
/// Deep-copy every item and type from the pasteboard into plain `Data` values.
89+
/// This must be called *before* `clearContents()` because NSPasteboardItem
90+
/// objects are invalidated when the pasteboard changes.
91+
private func captureSnapshot(_ pasteboard: NSPasteboard) -> PasteboardSnapshot? {
92+
guard let pasteboardItems = pasteboard.pasteboardItems, !pasteboardItems.isEmpty else {
93+
return nil
94+
}
95+
96+
var itemSnapshots: [PasteboardItemSnapshot] = []
97+
98+
for item in pasteboardItems {
99+
var dataByType: [(NSPasteboard.PasteboardType, Data)] = []
100+
for type in item.types {
101+
if let data = item.data(forType: type) {
102+
dataByType.append((type, data))
103+
}
104+
}
105+
if !dataByType.isEmpty {
106+
itemSnapshots.append(PasteboardItemSnapshot(dataByType: dataByType))
107+
}
108+
}
109+
110+
guard !itemSnapshots.isEmpty else { return nil }
111+
return PasteboardSnapshot(items: itemSnapshots)
112+
}
113+
114+
/// Write a previously captured snapshot back to the pasteboard.
115+
private func restoreSnapshot(_ snapshot: PasteboardSnapshot, to pasteboard: NSPasteboard) {
116+
pasteboard.clearContents()
117+
118+
var newItems: [NSPasteboardItem] = []
119+
for itemSnapshot in snapshot.items {
120+
let newItem = NSPasteboardItem()
121+
for (type, data) in itemSnapshot.dataByType {
122+
newItem.setData(data, forType: type)
123+
}
124+
newItems.append(newItem)
125+
}
126+
127+
pasteboard.writeObjects(newItems)
128+
NSLog("[TextInjector] Restored clipboard with %ld items", newItems.count)
129+
}
130+
66131
// MARK: - Paste Simulation
67132

68133
/// Simulate Cmd+V keystroke to paste from clipboard

0 commit comments

Comments
 (0)