Preserve clipboard contents after paste
- Save clipboard before pasting transcript (text, images, any content) - Restore original clipboard contents after paste completes - User's copied content is no longer lost after dictation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ A simple menu bar app for voice dictation using OpenAI Whisper (local, offline).
|
|||||||
- 🎤 Global hotkey (⌃⌥D) to start/stop recording
|
- 🎤 Global hotkey (⌃⌥D) to start/stop recording
|
||||||
- 🔒 Fully offline - uses local Whisper model
|
- 🔒 Fully offline - uses local Whisper model
|
||||||
- ⚡ Automatic paste into any focused app
|
- ⚡ Automatic paste into any focused app
|
||||||
|
- 📋 Clipboard preservation - your copied content is restored after paste
|
||||||
- ⚙️ Settings window with model selection dropdown
|
- ⚙️ Settings window with model selection dropdown
|
||||||
- 📥 Built-in model downloader with progress indicator
|
- 📥 Built-in model downloader with progress indicator
|
||||||
- 🚀 Launch at login support
|
- 🚀 Launch at login support
|
||||||
|
|||||||
@@ -825,12 +825,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate {
|
|||||||
// MARK: - Paste
|
// MARK: - Paste
|
||||||
func pasteText(_ text: String) {
|
func pasteText(_ text: String) {
|
||||||
let pasteboard = NSPasteboard.general
|
let pasteboard = NSPasteboard.general
|
||||||
|
|
||||||
|
// Save current clipboard contents
|
||||||
|
let savedItems = saveClipboard()
|
||||||
|
|
||||||
|
// Set transcript to clipboard
|
||||||
pasteboard.clearContents()
|
pasteboard.clearContents()
|
||||||
pasteboard.setString(text, forType: .string)
|
pasteboard.setString(text, forType: .string)
|
||||||
|
|
||||||
NSLog("Transcribed: \(text)")
|
NSLog("Transcribed: \(text)")
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||||
|
// Simulate Cmd+V
|
||||||
let source = CGEventSource(stateID: .hidSystemState)
|
let source = CGEventSource(stateID: .hidSystemState)
|
||||||
|
|
||||||
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0x09, keyDown: true)
|
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0x09, keyDown: true)
|
||||||
@@ -841,11 +847,49 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate {
|
|||||||
keyUp?.flags = .maskCommand
|
keyUp?.flags = .maskCommand
|
||||||
keyUp?.post(tap: .cghidEventTap)
|
keyUp?.post(tap: .cghidEventTap)
|
||||||
|
|
||||||
|
// Restore original clipboard after a short delay
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||||
|
self.restoreClipboard(savedItems)
|
||||||
|
}
|
||||||
|
|
||||||
self.statusItem.button?.title = "🎤"
|
self.statusItem.button?.title = "🎤"
|
||||||
self.updateStatus("Ready")
|
self.updateStatus("Ready")
|
||||||
if self.playSounds { NSSound(named: "Glass")?.play() }
|
if self.playSounds { NSSound(named: "Glass")?.play() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveClipboard() -> [[NSPasteboard.PasteboardType: Data]] {
|
||||||
|
let pasteboard = NSPasteboard.general
|
||||||
|
var savedItems: [[NSPasteboard.PasteboardType: Data]] = []
|
||||||
|
|
||||||
|
for item in pasteboard.pasteboardItems ?? [] {
|
||||||
|
var itemData: [NSPasteboard.PasteboardType: Data] = [:]
|
||||||
|
for type in item.types {
|
||||||
|
if let data = item.data(forType: type) {
|
||||||
|
itemData[type] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !itemData.isEmpty {
|
||||||
|
savedItems.append(itemData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return savedItems
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreClipboard(_ savedItems: [[NSPasteboard.PasteboardType: Data]]) {
|
||||||
|
guard !savedItems.isEmpty else { return }
|
||||||
|
|
||||||
|
let pasteboard = NSPasteboard.general
|
||||||
|
pasteboard.clearContents()
|
||||||
|
|
||||||
|
for itemData in savedItems {
|
||||||
|
let item = NSPasteboardItem()
|
||||||
|
for (type, data) in itemData {
|
||||||
|
item.setData(data, forType: type)
|
||||||
|
}
|
||||||
|
pasteboard.writeObjects([item])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Main
|
// MARK: - Main
|
||||||
|
|||||||
Reference in New Issue
Block a user