Skip to content

Commit 1a18850

Browse files
mxvshclaude
andcommitted
fix: use Cmd+V fallback for paste to support terminal and web apps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 775ee48 commit 1a18850

1 file changed

Lines changed: 50 additions & 2 deletions

File tree

Wave/Services/PasteService.swift

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,61 @@
11
import AppKit
22
import ApplicationServices
3+
import Carbon.HIToolbox
34

45
struct PasteService {
56
static func paste(text: String) {
7+
// Try AX insertion first — works in native AppKit fields without touching clipboard
8+
if pasteViaAX(text: text) { return }
9+
// Fall back to clipboard + Cmd+V — works everywhere (Terminal, web, Electron, etc.)
10+
pasteViaKeyboard(text: text)
11+
}
12+
13+
@discardableResult
14+
private static func pasteViaAX(_ text: String) -> Bool {
615
let systemWide = AXUIElementCreateSystemWide()
716
var focusedElement: CFTypeRef?
817
guard AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute as CFString, &focusedElement) == .success,
9-
let element = focusedElement else { return }
10-
AXUIElementSetAttributeValue(element as! AXUIElement, kAXSelectedTextAttribute as CFString, text as CFString)
18+
let element = focusedElement else { return false }
19+
let result = AXUIElementSetAttributeValue(element as! AXUIElement, kAXSelectedTextAttribute as CFString, text as CFString)
20+
return result == .success
21+
}
22+
23+
private static func pasteViaKeyboard(_ text: String) {
24+
let pasteboard = NSPasteboard.general
25+
26+
// Preserve whatever was on the clipboard
27+
let previousItems = pasteboard.pasteboardItems?.compactMap { item -> [NSPasteboard.PasteboardType: Data]? in
28+
var dataMap: [NSPasteboard.PasteboardType: Data] = [:]
29+
for type in item.types {
30+
if let data = item.data(forType: type) { dataMap[type] = data }
31+
}
32+
return dataMap.isEmpty ? nil : dataMap
33+
}
34+
35+
pasteboard.clearContents()
36+
pasteboard.setString(text, forType: .string)
37+
38+
// Simulate Cmd+V
39+
let src = CGEventSource(stateID: .hidSystemState)
40+
let vKey = CGKeyCode(kVK_ANSI_V)
41+
let down = CGEvent(keyboardEventSource: src, virtualKey: vKey, keyDown: true)
42+
let up = CGEvent(keyboardEventSource: src, virtualKey: vKey, keyDown: false)
43+
down?.flags = .maskCommand
44+
up?.flags = .maskCommand
45+
down?.post(tap: .cghidEventTap)
46+
up?.post(tap: .cghidEventTap)
47+
48+
// Restore clipboard after the keystroke has been processed
49+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
50+
pasteboard.clearContents()
51+
if let items = previousItems, !items.isEmpty {
52+
for dataMap in items {
53+
let newItem = NSPasteboardItem()
54+
for (type, data) in dataMap { newItem.setData(data, forType: type) }
55+
pasteboard.writeObjects([newItem])
56+
}
57+
}
58+
}
1159
}
1260

1361
static func getSelectedText() -> String? {

0 commit comments

Comments
 (0)