Keeps text prompts and photos in a floating always-on-top panel and
copies them back to the clipboard on click. Double-Shift captures the
selection from any app; sections, search, copy-as-list, merge, and
local JSON persistence mirror shadcn's Copper workflow.
💘 Generated with Crush
Assisted-by: Crush:kimi-k3
79 lines
2.8 KiB
Swift
79 lines
2.8 KiB
Swift
import AppKit
|
|
|
|
// Capture reads the current selection by simulating Cmd+C, watching the
|
|
// pasteboard change count, then restoring the previous clipboard contents so
|
|
// the user's own clipboard is untouched. Requires Accessibility permission
|
|
// for the synthesized events to be delivered.
|
|
enum Capture {
|
|
static func captureSelectionIntoStore() {
|
|
let sourceApp = NSWorkspace.shared.frontmostApplication?.localizedName
|
|
guard let text = captureSelection() else { return }
|
|
let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return }
|
|
Task { @MainActor in
|
|
Store.shared.addText(text, sourceApp: sourceApp)
|
|
PanelController.shared.show()
|
|
}
|
|
}
|
|
|
|
static func captureSelection() -> String? {
|
|
let pasteboard = NSPasteboard.general
|
|
let backup = backupPasteboard(pasteboard)
|
|
let oldChangeCount = pasteboard.changeCount
|
|
|
|
simulateCopy()
|
|
|
|
let deadline = Date().addingTimeInterval(0.5)
|
|
var result: String?
|
|
while Date() < deadline {
|
|
if pasteboard.changeCount != oldChangeCount {
|
|
result = pasteboard.string(forType: .string)
|
|
break
|
|
}
|
|
usleep(10_000)
|
|
}
|
|
|
|
restorePasteboard(pasteboard, backup: backup)
|
|
return result
|
|
}
|
|
|
|
private static func simulateCopy() {
|
|
let source = CGEventSource(stateID: .hidSystemState)
|
|
let keyCodeC: CGKeyCode = 8 // kVK_ANSI_C
|
|
let keyDown = CGEvent(keyboardEventSource: source, virtualKey: keyCodeC, keyDown: true)
|
|
keyDown?.flags = .maskCommand
|
|
keyDown?.post(tap: .cghidEventTap)
|
|
let keyUp = CGEvent(keyboardEventSource: source, virtualKey: keyCodeC, keyDown: false)
|
|
keyUp?.flags = .maskCommand
|
|
keyUp?.post(tap: .cghidEventTap)
|
|
}
|
|
|
|
private static func backupPasteboard(_ pasteboard: NSPasteboard) -> [[String: Data]] {
|
|
var backup: [[String: Data]] = []
|
|
for item in pasteboard.pasteboardItems ?? [] {
|
|
var entry: [String: Data] = [:]
|
|
for type in item.types {
|
|
if let data = item.data(forType: type) {
|
|
entry[type.rawValue] = data
|
|
}
|
|
}
|
|
backup.append(entry)
|
|
}
|
|
return backup
|
|
}
|
|
|
|
private static func restorePasteboard(_ pasteboard: NSPasteboard, backup: [[String: Data]]) {
|
|
pasteboard.clearContents()
|
|
let items = backup.map { entry -> NSPasteboardItem in
|
|
let item = NSPasteboardItem()
|
|
for (type, data) in entry {
|
|
item.setData(data, forType: NSPasteboard.PasteboardType(type))
|
|
}
|
|
return item
|
|
}
|
|
if !items.isEmpty {
|
|
pasteboard.writeObjects(items)
|
|
}
|
|
}
|
|
}
|