From 170006ea1f7f4738eeb3bfb5bd81d1b8b0e29982 Mon Sep 17 00:00:00 2001 From: hariel1985 Date: Mon, 2 Feb 2026 14:14:45 +0100 Subject: [PATCH] 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 --- README.md | 1 + macos/src/main.swift | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/README.md b/README.md index 3c70784..b782b0d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ A simple menu bar app for voice dictation using OpenAI Whisper (local, offline). - 🎤 Global hotkey (⌃⌥D) to start/stop recording - 🔒 Fully offline - uses local Whisper model - ⚡ Automatic paste into any focused app +- 📋 Clipboard preservation - your copied content is restored after paste - ⚙️ Settings window with model selection dropdown - 📥 Built-in model downloader with progress indicator - 🚀 Launch at login support diff --git a/macos/src/main.swift b/macos/src/main.swift index 6f82cfb..70e1ca8 100644 --- a/macos/src/main.swift +++ b/macos/src/main.swift @@ -825,12 +825,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate { // MARK: - Paste func pasteText(_ text: String) { let pasteboard = NSPasteboard.general + + // Save current clipboard contents + let savedItems = saveClipboard() + + // Set transcript to clipboard pasteboard.clearContents() pasteboard.setString(text, forType: .string) NSLog("Transcribed: \(text)") DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + // Simulate Cmd+V let source = CGEventSource(stateID: .hidSystemState) let keyDown = CGEvent(keyboardEventSource: source, virtualKey: 0x09, keyDown: true) @@ -841,11 +847,49 @@ class AppDelegate: NSObject, NSApplicationDelegate, URLSessionDownloadDelegate { keyUp?.flags = .maskCommand 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.updateStatus("Ready") 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