|
|
|
@ -245,7 +245,7 @@ public final class FocusedInputSync { |
|
|
|
|
|
|
|
/// Applies updated full text or inserts speech directly into active Mac input |
|
|
|
@discardableResult |
|
|
|
public func applyRemoteUpdate(text: String, cursor: Int? = nil, isFullReplace: Bool = true) -> Bool { |
|
|
|
public func applyRemoteUpdate(text: String, cursor: Int? = nil, isFullReplace: Bool = false) -> Bool { |
|
|
|
isApplyingRemoteChange = true |
|
|
|
remoteChangeExpiryTime = Date().timeIntervalSince1970 + 0.60 |
|
|
|
|
|
|
|
@ -268,70 +268,55 @@ public final class FocusedInputSync { |
|
|
|
let (_, appName) = getFocusedElement() |
|
|
|
lastObservedApp = appName |
|
|
|
|
|
|
|
print("FocusedInputSync: 🚀 Injecting text into '\(appName)' via Ultra-Reliable Quartz CGEvent (replace: \(isFullReplace))") |
|
|
|
return pasteViaKeystroke(cleanText, isFullReplace: isFullReplace) |
|
|
|
print("FocusedInputSync: 🚀 Injecting text into '\(appName)' via Clean Quartz CGEvent (replace: \(isFullReplace), len: \(cleanText.count))") |
|
|
|
return pasteViaCleanKeystroke(cleanText, isFullReplace: isFullReplace) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Rock-Solid Quartz CGEvent Keystroke Engine |
|
|
|
* Uses full modifier sequence: Cmd Down -> Key Down -> Key Up -> Cmd Up. |
|
|
|
* Works on 100% of macOS applications with 0 permission hurdles (uses Accessibility). |
|
|
|
* Clean, Universal Quartz CGEvent Keystroke Engine |
|
|
|
* Sets pasteboard, sends clean Cmd+A (if replace) and Cmd+V (virtualKey 9 with .maskCommand). |
|
|
|
*/ |
|
|
|
private func pasteViaKeystroke(_ text: String, isFullReplace: Bool) -> Bool { |
|
|
|
private func pasteViaCleanKeystroke(_ text: String, isFullReplace: Bool) -> Bool { |
|
|
|
let pasteboard = NSPasteboard.general |
|
|
|
pasteboard.clearContents() |
|
|
|
pasteboard.setString(text, forType: .string) |
|
|
|
|
|
|
|
let src = CGEventSource(stateID: .hidSystemState) |
|
|
|
let kVK_Command: CGKeyCode = 55 |
|
|
|
let kVK_ANSI_A: CGKeyCode = 0 |
|
|
|
let kVK_ANSI_V: CGKeyCode = 9 |
|
|
|
|
|
|
|
if isFullReplace { |
|
|
|
// Full Cmd + A sequence |
|
|
|
let cmdDown = CGEvent(keyboardEventSource: src, virtualKey: kVK_Command, keyDown: true) |
|
|
|
cmdDown?.flags = .maskCommand |
|
|
|
cmdDown?.post(tap: .cghidEventTap) |
|
|
|
usleep(8000) |
|
|
|
|
|
|
|
let aDown = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_A, keyDown: true) |
|
|
|
aDown?.flags = .maskCommand |
|
|
|
aDown?.post(tap: .cghidEventTap) |
|
|
|
// Select all: Cmd + A |
|
|
|
if let aDown = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_A, keyDown: true) { |
|
|
|
aDown.flags = .maskCommand |
|
|
|
aDown.post(tap: .cghidEventTap) |
|
|
|
aDown.post(tap: .cgSessionEventTap) |
|
|
|
} |
|
|
|
usleep(12000) |
|
|
|
|
|
|
|
let aUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_A, keyDown: false) |
|
|
|
aUp?.flags = .maskCommand |
|
|
|
aUp?.post(tap: .cghidEventTap) |
|
|
|
usleep(8000) |
|
|
|
|
|
|
|
let cmdUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_Command, keyDown: false) |
|
|
|
cmdUp?.flags = [] |
|
|
|
cmdUp?.post(tap: .cghidEventTap) |
|
|
|
|
|
|
|
usleep(30000) // 30ms settling delay for selection |
|
|
|
if let aUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_A, keyDown: false) { |
|
|
|
aUp.flags = .maskCommand |
|
|
|
aUp.post(tap: .cghidEventTap) |
|
|
|
aUp.post(tap: .cgSessionEventTap) |
|
|
|
} |
|
|
|
usleep(25000) // 25ms settling delay for selection |
|
|
|
} |
|
|
|
|
|
|
|
// Full Cmd + V sequence |
|
|
|
let cmdDown = CGEvent(keyboardEventSource: src, virtualKey: kVK_Command, keyDown: true) |
|
|
|
cmdDown?.flags = .maskCommand |
|
|
|
cmdDown?.post(tap: .cghidEventTap) |
|
|
|
usleep(8000) |
|
|
|
|
|
|
|
let vDown = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_V, keyDown: true) |
|
|
|
vDown?.flags = .maskCommand |
|
|
|
vDown?.post(tap: .cghidEventTap) |
|
|
|
// Paste: Cmd + V |
|
|
|
if let vDown = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_V, keyDown: true) { |
|
|
|
vDown.flags = .maskCommand |
|
|
|
vDown.post(tap: .cghidEventTap) |
|
|
|
vDown.post(tap: .cgSessionEventTap) |
|
|
|
} |
|
|
|
usleep(15000) |
|
|
|
|
|
|
|
let vUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_V, keyDown: false) |
|
|
|
vUp?.flags = .maskCommand |
|
|
|
vUp?.post(tap: .cghidEventTap) |
|
|
|
usleep(8000) |
|
|
|
|
|
|
|
let cmdUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_Command, keyDown: false) |
|
|
|
cmdUp?.flags = [] |
|
|
|
cmdUp?.post(tap: .cghidEventTap) |
|
|
|
if let vUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_V, keyDown: false) { |
|
|
|
vUp.flags = .maskCommand |
|
|
|
vUp.post(tap: .cghidEventTap) |
|
|
|
vUp.post(tap: .cgSessionEventTap) |
|
|
|
} |
|
|
|
|
|
|
|
print("FocusedInputSync: ✅ CGEvent Cmd+V Posted successfully!") |
|
|
|
print("FocusedInputSync: ✅ Clean Cmd+V Posted successfully for text: '\(text.prefix(25))...'") |
|
|
|
return true |
|
|
|
} |
|
|
|
} |