Browse Source

fix(mac): eliminate false-success AXValue setter and enforce universal Quartz CGEvent injection for Chromium/Electron and Antigravity

main
Ali Alavi 4 hours ago
parent
commit
3491f30d23
  1. 48
      mac/src/FocusedInputSync.swift
  2. 17
      mac/src/RelayClient.swift

48
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
}
/**

17
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)
}
}
}

Loading…
Cancel
Save