vOOice/VoiceInk/Services/SelectedTextService.swift
2025-09-22 13:53:27 +02:00

73 lines
3.0 KiB
Swift

import Foundation
import AppKit
class SelectedTextService {
// Private pasteboard type to avoid clipboard history pollution
private static let privatePasteboardType = NSPasteboard.PasteboardType("com.prakashjoshipax.VoiceInk.transient")
static func fetchSelectedText() -> String? {
// Don't check for selected text within VoiceInk itself
guard let frontmostApp = NSWorkspace.shared.frontmostApplication,
frontmostApp.bundleIdentifier != "com.prakashjoshipax.VoiceInk" else {
return nil
}
let pasteboard = NSPasteboard.general
let originalClipboardText = pasteboard.string(forType: .string)
// Save original clipboard content (all UTIs with their data)
let originalPasteboardItems = pasteboard.pasteboardItems?.map { item in
item.types.reduce(into: [NSPasteboard.PasteboardType: Data]()) { acc, type in
if let data = item.data(forType: type) {
acc[type] = data
}
}
}
// Clear clipboard to prepare for selection detection
pasteboard.clearContents()
// Simulate Cmd+C to copy any selected text
let source = CGEventSource(stateID: .hidSystemState)
let cmdDown = CGEvent(keyboardEventSource: source, virtualKey: 0x37, keyDown: true)
cmdDown?.flags = .maskCommand
let cDown = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: true)
cDown?.flags = .maskCommand
let cUp = CGEvent(keyboardEventSource: source, virtualKey: 0x08, keyDown: false)
cUp?.flags = .maskCommand
let cmdUp = CGEvent(keyboardEventSource: source, virtualKey: 0x37, keyDown: false)
cmdDown?.post(tap: .cghidEventTap)
cDown?.post(tap: .cghidEventTap)
cUp?.post(tap: .cghidEventTap)
cmdUp?.post(tap: .cghidEventTap)
// Wait for copy operation to complete
Thread.sleep(forTimeInterval: 0.1)
// Read the copied text
let selectedText = pasteboard.string(forType: .string)
// Restore original clipboard content
pasteboard.clearContents()
if let originalItems = originalPasteboardItems, !originalItems.isEmpty {
let restoredItems: [NSPasteboardItem] = originalItems.compactMap { dataMap in
guard !dataMap.isEmpty else { return nil }
let item = NSPasteboardItem()
for (type, data) in dataMap {
item.setData(data, forType: type)
}
return item
}
if !restoredItems.isEmpty {
pasteboard.writeObjects(restoredItems)
} else if let originalClipboardText {
_ = pasteboard.setString(originalClipboardText, forType: .string)
}
} else if let originalClipboardText {
_ = pasteboard.setString(originalClipboardText, forType: .string)
}
return selectedText
}
}