import Cocoa import ApplicationServices import CoreGraphics public struct MacInputState: Codable { public let source: String public let app: String public let text: String public let cursor: Int public let selection: Int public let revision: Int64 public let timestamp: Double public init(app: String, text: String, cursor: Int, selection: Int, revision: Int64) { self.source = "mac" self.app = app self.text = text self.cursor = cursor self.selection = selection self.revision = revision self.timestamp = Date().timeIntervalSince1970 } } public final class FocusedInputSync { public static let shared = FocusedInputSync() private let systemWideElement: AXUIElement private var isApplyingRemoteChange: Bool = false private var lastObservedText: String = "" private var lastObservedCursor: Int = -1 private var lastObservedApp: String = "" private var localRevision: Int64 = 0 private var remoteChangeExpiryTime: Double = 0 // Known Web, Electron, Terminal, and Custom UI apps that do NOT accept direct AXValue writes // but require fast, reliable native keystroke paste (Cmd+V / Cmd+A + Cmd+V) private let pastePreferredApps: Set = [ "antigravity", "antigravity helper", "google chrome", "chromium", "brave browser", "arc", "microsoft edge", "firefox", "safari", "code", "cursor", "visual studio code", "slack", "discord", "telegram", "whatsapp", "signal", "ghostty", "iterm2", "iterm", "terminal", "alacritty", "kitty", "jetbrains", "idea", "webstorm", "pycharm", "datagrip", "sublime text", "notion", "obsidian", "linear", "warp" ] private init() { self.systemWideElement = AXUIElementCreateSystemWide() enableGlobalAccessibility() } private func enableGlobalAccessibility() { AXUIElementSetAttributeValue(systemWideElement, "AXEnhancedUserInterface" as CFString, kCFBooleanTrue) AXUIElementSetAttributeValue(systemWideElement, "AXManualAccessibility" as CFString, kCFBooleanTrue) } public func isAppPastePreferred(_ appName: String) -> Bool { let lower = appName.lowercased() for pref in pastePreferredApps { if lower.contains(pref) { return true } } return false } /// Resolves the actual user-facing application, bypassing system overlays like UserNotificationCenter public func getRealFrontmostApp() -> NSRunningApplication? { // 1. If standard frontmost app is a real regular user app, return it if let front = NSWorkspace.shared.frontmostApplication, front.activationPolicy == .regular, let bundleId = front.bundleIdentifier, !bundleId.contains("notificationcenter"), !bundleId.contains("controlcenter"), !bundleId.contains("WindowManager"), !bundleId.contains("Soniox") { return front } // 2. Otherwise, find top on-screen Layer 0 window from CGWindowList let windowList = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? [] for win in windowList { let layer = win[kCGWindowLayer as String] as? Int ?? -1 let pid = win[kCGWindowOwnerPID as String] as? pid_t ?? 0 let bounds = win[kCGWindowBounds as String] as? [String: Any] ?? [:] let width = bounds["Width"] as? CGFloat ?? 0 let height = bounds["Height"] as? CGFloat ?? 0 // Only consider standard app windows (layer 0, reasonable size) if layer == 0 && width > 100 && height > 100 { if let app = NSRunningApplication(processIdentifier: pid), app.activationPolicy == .regular, let bundleId = app.bundleIdentifier, !bundleId.contains("notificationcenter"), !bundleId.contains("controlcenter"), !bundleId.contains("WindowManager"), !bundleId.contains("Soniox") { return app } } } return NSWorkspace.shared.frontmostApplication } private func findFocusedDescendant(_ elem: AXUIElement, depth: Int = 0) -> AXUIElement? { if depth > 12 { return nil } var isFocusedObj: CFTypeRef? if AXUIElementCopyAttributeValue(elem, kAXFocusedAttribute as CFString, &isFocusedObj) == .success, let isFocused = isFocusedObj as? Bool, isFocused { var roleObj: CFTypeRef? AXUIElementCopyAttributeValue(elem, kAXRoleAttribute as CFString, &roleObj) let role = (roleObj as? String) ?? "" if role != "AXWindow" && role != "AXApplication" && role != "AXGroup" && role != "AXSplitGroup" && role != "AXScrollArea" { return elem } } var childrenObj: CFTypeRef? if AXUIElementCopyAttributeValue(elem, kAXChildrenAttribute as CFString, &childrenObj) == .success, let children = childrenObj as? [AXUIElement] { for child in children { if let found = findFocusedDescendant(child, depth: depth + 1) { return found } } } return nil } public func getFocusedElement() -> (AXUIElement?, String) { guard let frontApp = getRealFrontmostApp() else { return (nil, "App") } let appName = frontApp.localizedName ?? "App" let appElem = AXUIElementCreateApplication(frontApp.processIdentifier) AXUIElementSetAttributeValue(appElem, "AXEnhancedUserInterface" as CFString, kCFBooleanTrue) AXUIElementSetAttributeValue(appElem, "AXManualAccessibility" as CFString, kCFBooleanTrue) var targetElem: AXUIElement? // 1. Try system-wide focused element var focusedObj: CFTypeRef? if AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute as CFString, &focusedObj) == .success, let obj = focusedObj { let elem = obj as! AXUIElement var roleObj: CFTypeRef? AXUIElementCopyAttributeValue(elem, kAXRoleAttribute as CFString, &roleObj) let role = (roleObj as? String) ?? "" if role != "AXWindow" && role != "AXApplication" { targetElem = elem } } // 2. Try App focused element if targetElem == nil { if AXUIElementCopyAttributeValue(appElem, kAXFocusedUIElementAttribute as CFString, &focusedObj) == .success, let obj = focusedObj { let elem = obj as! AXUIElement var roleObj: CFTypeRef? AXUIElementCopyAttributeValue(elem, kAXRoleAttribute as CFString, &roleObj) let role = (roleObj as? String) ?? "" if role != "AXWindow" && role != "AXApplication" { targetElem = elem } } } // 3. Try App focused window's focused element if targetElem == nil { var focusedWinObj: CFTypeRef? if AXUIElementCopyAttributeValue(appElem, kAXFocusedWindowAttribute as CFString, &focusedWinObj) == .success, let win = focusedWinObj { var winFocObj: CFTypeRef? if AXUIElementCopyAttributeValue((win as! AXUIElement), kAXFocusedUIElementAttribute as CFString, &winFocObj) == .success, let obj = winFocObj { targetElem = (obj as! AXUIElement) } } } // 4. Recursive search in tree if targetElem == nil { targetElem = findFocusedDescendant(appElem) } return (targetElem, appName) } /// Inspects the current focused element and returns a state snapshot if changed on Mac public func inspectCurrentState() -> MacInputState? { let now = Date().timeIntervalSince1970 if isApplyingRemoteChange || now < remoteChangeExpiryTime { return nil } let (elemOpt, appName) = getFocusedElement() // If app changed let appChanged = (appName != lastObservedApp) if appChanged { lastObservedApp = appName } guard let elem = elemOpt else { // When in opaque Electron/Antigravity containers, do not emit ghost empty text if appChanged && !lastObservedText.isEmpty { localRevision += 1 return MacInputState(app: appName, text: lastObservedText, cursor: lastObservedCursor, selection: 0, revision: localRevision) } return nil } // Check role var roleObj: CFTypeRef? AXUIElementCopyAttributeValue(elem, kAXRoleAttribute as CFString, &roleObj) let role = roleObj as? String ?? "" let isTextRole = (role == "AXTextField" || role == "AXTextArea" || role == "AXComboBox" || role == "AXSearchField") // Extract text var text = "" var valObj: CFTypeRef? if AXUIElementCopyAttributeValue(elem, kAXValueAttribute as CFString, &valObj) == .success, let val = valObj { if let str = val as? String { text = str } else if let attrStr = val as? NSAttributedString { text = attrStr.string } } if text.isEmpty && isTextRole { var countObj: CFTypeRef? if AXUIElementCopyAttributeValue(elem, kAXNumberOfCharactersAttribute as CFString, &countObj) == .success, let count = countObj as? Int, count > 0 { var range = CFRange(location: 0, length: count) if let axRange = AXValueCreate(.cfRange, &range) { var strObj: CFTypeRef? if AXUIElementCopyParameterizedAttributeValue(elem, kAXStringForRangeParameterizedAttribute as CFString, axRange, &strObj) == .success, let str = strObj as? String { text = str } } } } // Ghost empty suppression: If an opaque web app reports empty string, but we had existing text, // and element is NOT an explicit empty native text field, ignore the empty read. if text.isEmpty && !lastObservedText.isEmpty && !isTextRole && isAppPastePreferred(appName) { return nil } // Extract cursor & selection var cursor = text.count var selLen = 0 var rangeObj: CFTypeRef? if AXUIElementCopyAttributeValue(elem, kAXSelectedTextRangeAttribute as CFString, &rangeObj) == .success, let axRange = rangeObj { var range = CFRange() if AXValueGetValue(axRange as! AXValue, .cfRange, &range) { cursor = range.location selLen = range.length } } // Check if text or cursor actually changed on Mac if text == lastObservedText && cursor == lastObservedCursor && !appChanged { return nil } lastObservedText = text lastObservedCursor = cursor lastObservedApp = appName localRevision += 1 return MacInputState(app: appName, text: text, cursor: cursor, selection: selLen, revision: localRevision) } /// Applies updated full text or inserts at cursor position directly into active Mac input @discardableResult public func applyRemoteUpdate(text: String, cursor: Int? = nil, isFullReplace: Bool = true) -> Bool { isApplyingRemoteChange = true // 750ms quiet window to prevent self-echo and race conditions remoteChangeExpiryTime = Date().timeIntervalSince1970 + 0.75 defer { DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) { self.isApplyingRemoteChange = false } } // 1. Clean and normalize text var cleanText = text.components(separatedBy: .newlines).joined(separator: " ") cleanText = cleanText.replacingOccurrences(of: "\t", with: " ") cleanText = cleanText.replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) cleanText = cleanText.trimmingCharacters(in: .whitespacesAndNewlines) let targetCursor = cursor ?? cleanText.count lastObservedText = cleanText lastObservedCursor = targetCursor let (elemOpt, appName) = getFocusedElement() lastObservedApp = appName // If the target app is a Web/Electron/IDE application (e.g. Antigravity, Chrome, VS Code, Discord, Slack) // or no native AX element was resolved, execute process-targeted synthetic paste! if isAppPastePreferred(appName) || elemOpt == nil { print("FocusedInputSync: 🎯 Target app '\(appName)' is Web/Electron. Executing Process-Targeted Paste.") return pasteViaKeystroke(cleanText, appName: appName, isFullReplace: isFullReplace) } // For Native AppKit/Cocoa apps (e.g. TextEdit, Notes, Finder): if let elem = elemOpt { if isFullReplace { let setErr = AXUIElementSetAttributeValue(elem, kAXValueAttribute as CFString, cleanText as CFTypeRef) if setErr == .success { if let cursor = cursor { var range = CFRange(location: min(cursor, cleanText.count), length: 0) if let axRange = AXValueCreate(.cfRange, &range) { AXUIElementSetAttributeValue(elem, kAXSelectedTextRangeAttribute as CFString, axRange) } } print("FocusedInputSync: ✅ Updated native AXValue for \(appName)") return true } } else { let setSelErr = AXUIElementSetAttributeValue(elem, kAXSelectedTextAttribute as CFString, cleanText as CFTypeRef) if setSelErr == .success { print("FocusedInputSync: ✅ Inserted native text via AXSelectedText for \(appName)") return true } } } // Reliable fallback for any situation return pasteViaKeystroke(cleanText, appName: appName, isFullReplace: isFullReplace) } /** * Ultra-reliable Process-Targeted synthetic keystroke injection (AppleScript System Events + Quartz HID) * Forces frontmost focus on the target app process and injects Cmd+V directly into its input element. */ private func pasteViaKeystroke(_ text: String, appName: String, isFullReplace: Bool) -> Bool { let pasteboard = NSPasteboard.general // 1. Snapshot previous clipboard to restore after paste let oldString = pasteboard.string(forType: .string) // 2. Set new speech text to pasteboard pasteboard.clearContents() pasteboard.setString(text, forType: .string) // 3. Ensure target app is activated if let realApp = getRealFrontmostApp() { realApp.activate(options: []) usleep(25000) // 25ms focus settling } // 4. Engine 1: Process-Targeted AppleScript System Events var appleScriptSuccess = false let safeAppName = appName.replacingOccurrences(of: "\"", with: "\\\"") let scriptSource = isFullReplace ? """ tell application "\(safeAppName)" to activate tell application "System Events" tell process "\(safeAppName)" set frontmost to true keystroke "a" using command down delay 0.03 keystroke "v" using command down end tell end tell """ : """ tell application "\(safeAppName)" to activate tell application "System Events" tell process "\(safeAppName)" set frontmost to true keystroke "v" using command down end tell end tell """ if let script = NSAppleScript(source: scriptSource) { var errorDict: NSDictionary? script.executeAndReturnError(&errorDict) if errorDict == nil { appleScriptSuccess = true print("FocusedInputSync: ✅ Process-Targeted AppleScript Injected into '\(safeAppName)' (replace: \(isFullReplace))") } else { print("FocusedInputSync: ⚠️ Targeted AppleScript warning: \(errorDict ?? [:]), trying general System Events") // General fallback without process qualification let generalScriptSource = isFullReplace ? "tell application \"System Events\" to {keystroke \"a\" using command down, delay 0.03, keystroke \"v\" using command down}" : "tell application \"System Events\" to keystroke \"v\" using command down" if let genScript = NSAppleScript(source: generalScriptSource) { var genErr: NSDictionary? genScript.executeAndReturnError(&genErr) if genErr == nil { appleScriptSuccess = true } } } } // 5. Engine 2: Quartz HID Event Tap Fallback (if AppleScript didn't run) if !appleScriptSuccess { let src = CGEventSource(stateID: .hidSystemState) if isFullReplace { // Select all: Cmd + A (virtualKey 0 = 'a') if let aDown = CGEvent(keyboardEventSource: src, virtualKey: 0, keyDown: true) { aDown.flags = .maskCommand aDown.post(tap: .cghidEventTap) } usleep(15000) if let aUp = CGEvent(keyboardEventSource: src, virtualKey: 0, keyDown: false) { aUp.flags = .maskCommand aUp.post(tap: .cghidEventTap) } usleep(40000) } // Paste: Cmd + V (virtualKey 9 = 'v') if let vDown = CGEvent(keyboardEventSource: src, virtualKey: 9, keyDown: true) { vDown.flags = .maskCommand vDown.post(tap: .cghidEventTap) } usleep(15000) if let vUp = CGEvent(keyboardEventSource: src, virtualKey: 9, keyDown: false) { vUp.flags = .maskCommand vUp.post(tap: .cghidEventTap) } print("FocusedInputSync: ✅ Injected via Quartz HID Event Tap (replace: \(isFullReplace))") } // 6. Asynchronously restore previous clipboard content after 600ms if let previousText = oldString, previousText != text { DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + 0.60) { if pasteboard.string(forType: .string) == text { pasteboard.clearContents() pasteboard.setString(previousText, forType: .string) } } } return true } }