|
1 | 1 | import AppKit |
2 | | -import ApplicationServices |
3 | 2 | import Carbon.HIToolbox |
4 | 3 |
|
5 | 4 | struct PasteService { |
@@ -49,14 +48,51 @@ struct PasteService { |
49 | 48 | } |
50 | 49 | } |
51 | 50 |
|
52 | | - static func getSelectedText() -> String? { |
53 | | - let systemWide = AXUIElementCreateSystemWide() |
54 | | - var focusedElement: CFTypeRef? |
55 | | - guard AXUIElementCopyAttributeValue(systemWide, kAXFocusedUIElementAttribute as CFString, &focusedElement) == .success, |
56 | | - let element = focusedElement else { return nil } |
57 | | - var value: CFTypeRef? |
58 | | - guard AXUIElementCopyAttributeValue(element as! AXUIElement, kAXSelectedTextAttribute as CFString, &value) == .success, |
59 | | - let text = value as? String, !text.isEmpty else { return nil } |
60 | | - return text |
| 51 | + @MainActor |
| 52 | + static func getSelectedText() async -> String? { |
| 53 | + let pasteboard = NSPasteboard.general |
| 54 | + |
| 55 | + // Save current clipboard state |
| 56 | + let savedItems = pasteboard.pasteboardItems?.compactMap { item -> [NSPasteboard.PasteboardType: Data]? in |
| 57 | + var dataMap: [NSPasteboard.PasteboardType: Data] = [:] |
| 58 | + for type in item.types { |
| 59 | + if let data = item.data(forType: type) { dataMap[type] = data } |
| 60 | + } |
| 61 | + return dataMap.isEmpty ? nil : dataMap |
| 62 | + } |
| 63 | + let savedChangeCount = pasteboard.changeCount |
| 64 | + |
| 65 | + // Simulate Cmd+C — works in every app (browsers, Electron, Terminal) |
| 66 | + let src = CGEventSource(stateID: .hidSystemState) |
| 67 | + let cKey = CGKeyCode(kVK_ANSI_C) |
| 68 | + let down = CGEvent(keyboardEventSource: src, virtualKey: cKey, keyDown: true) |
| 69 | + let up = CGEvent(keyboardEventSource: src, virtualKey: cKey, keyDown: false) |
| 70 | + down?.flags = .maskCommand |
| 71 | + up?.flags = .maskCommand |
| 72 | + down?.post(tap: .cghidEventTap) |
| 73 | + up?.post(tap: .cghidEventTap) |
| 74 | + |
| 75 | + // Suspend (not block) until clipboard is updated |
| 76 | + try? await Task.sleep(for: .milliseconds(80)) |
| 77 | + |
| 78 | + // Only read if the clipboard actually changed (i.e. something was selected) |
| 79 | + let text: String? |
| 80 | + if pasteboard.changeCount != savedChangeCount { |
| 81 | + text = pasteboard.string(forType: .string) |
| 82 | + } else { |
| 83 | + text = nil |
| 84 | + } |
| 85 | + |
| 86 | + // Restore original clipboard |
| 87 | + pasteboard.clearContents() |
| 88 | + if let items = savedItems, !items.isEmpty { |
| 89 | + for dataMap in items { |
| 90 | + let newItem = NSPasteboardItem() |
| 91 | + for (type, data) in dataMap { newItem.setData(data, forType: type) } |
| 92 | + pasteboard.writeObjects([newItem]) |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + return text?.isEmpty == false ? text : nil |
61 | 97 | } |
62 | 98 | } |
0 commit comments