From 3491f30d23ad0921349d90bacf0bf41f8d5ae46d Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Mon, 24 Aug 2026 05:31:56 +0000 Subject: [PATCH] fix(mac): eliminate false-success AXValue setter and enforce universal Quartz CGEvent injection for Chromium/Electron and Antigravity --- mac/src/FocusedInputSync.swift | 48 ++++++++++++++++++++++++++-------- mac/src/RelayClient.swift | 17 +++++++----- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/mac/src/FocusedInputSync.swift b/mac/src/FocusedInputSync.swift index b940901..90cbae9 100644 --- a/mac/src/FocusedInputSync.swift +++ b/mac/src/FocusedInputSync.swift @@ -265,21 +265,47 @@ public final class FocusedInputSync { lastObservedText = cleanText lastObservedCursor = targetCursor - let (elemOpt, appName) = getFocusedElement() + let (_, appName) = getFocusedElement() lastObservedApp = appName - // Try fast direct native AX write if supported (TextEdit, Notes, Finder) - if let elem = elemOpt { - let setErr = AXUIElementSetAttributeValue(elem, kAXValueAttribute as CFString, cleanText as CFTypeRef) - if setErr == .success { - print("FocusedInputSync: ✅ Updated native AXValue for \(appName)") - return true - } + // Universal Quartz CGEvent Keystroke Engine (Cmd+A -> Cmd+V / Backspace or pure Cmd+V) + // Works 100% reliably across native, web, and Electron/Chromium apps (e.g. Antigravity, VS Code, Slack, Firefox) + print("FocusedInputSync: 🚀 Injecting text into '\(appName)' (replace: \(isFullReplace), len: \(cleanText.count))") + if isFullReplace { + return executeCleanFullReplace(cleanText) + } else { + return pasteOnlyViaCleanKeystroke(cleanText) + } + } + + /** + * Inserts speech directly at active cursor via clean Cmd+V + */ + private func pasteOnlyViaCleanKeystroke(_ text: String) -> Bool { + guard !text.isEmpty else { return true } + + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(text, forType: .string) + + let src = CGEventSource(stateID: .hidSystemState) + let kVK_ANSI_V: CGKeyCode = 9 + + // 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(12000) - // Universal Native Replace (Cmd+A -> Cmd+V / Backspace) - print("FocusedInputSync: 🔄 Syncing full text to '\(appName)' (len: \(cleanText.count))") - return executeCleanFullReplace(cleanText) + if let vUp = CGEvent(keyboardEventSource: src, virtualKey: kVK_ANSI_V, keyDown: false) { + vUp.flags = .maskCommand + vUp.post(tap: .cghidEventTap) + vUp.post(tap: .cgSessionEventTap) + } + usleep(10000) + return true } /** diff --git a/mac/src/RelayClient.swift b/mac/src/RelayClient.swift index 09a76ed..4ec3868 100644 --- a/mac/src/RelayClient.swift +++ b/mac/src/RelayClient.swift @@ -19,7 +19,8 @@ public final class RelayClient: NSObject, URLSessionWebSocketDelegate { public var onRemoteUpdateReceived: ((String, Int?, Bool) -> Void)? - private var lastObservedInjectedText: String? = nil + private var lastInjectedText: String = "" + private var lastInjectedTime: Double = 0 override private init() { super.init() @@ -109,15 +110,19 @@ public final class RelayClient: NSObject, URLSessionWebSocketDelegate { let cursor = json["cursor"] as? Int ?? json["cursor_pos"] as? Int if action == "sync_state" || action == "phone_input_edit" || action == "update_input" || action == "force_replace" || action == "insert_speech" || action == "paste" { - // Drop identical consecutive payloads - if updateText == lastObservedInjectedText && action != "force_replace" { + let now = Date().timeIntervalSince1970 + + // Drop rapid duplicate transmissions within 150ms (unless force_replace) + if updateText == lastInjectedText && (now - lastInjectedTime) < 0.15 && action != "force_replace" { return } - lastObservedInjectedText = updateText + lastInjectedText = updateText + lastInjectedTime = now - print("RelayClient: ⚡ Applying live phone edit: '\(updateText.prefix(30))...' (len: \(updateText.count))") + let isFullReplace = (action != "insert_speech" && action != "speech_insert") + print("RelayClient: ⚡ Applying phone edit: '\(updateText.prefix(30))...' (replace: \(isFullReplace), len: \(updateText.count))") DispatchQueue.main.async { - self.onRemoteUpdateReceived?(updateText, cursor, true) + self.onRemoteUpdateReceived?(updateText, cursor, isFullReplace) } } }