From 9a38c329aa4ac466067631bd4a2187035f401081 Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Wed, 26 Aug 2026 11:03:18 +0000 Subject: [PATCH] fix(sync): single-instance mac enforcement to stop duplicate pastes, unified speech sync pipeline from android --- .../java/com/soniox/remotemic/MainActivity.kt | 9 ++++++++- mac/src/AppDelegate.swift | 10 ++++++++++ server/relay_server.py | 15 +-------------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt index 3b1dd52..c2d9f3e 100644 --- a/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt +++ b/android/app/src/main/java/com/soniox/remotemic/MainActivity.kt @@ -283,6 +283,7 @@ class MainActivity : AppCompatActivity() { isApplyingRemoteUpdate = true lastLocalText = mergedText lastLocalUserEditTime = System.currentTimeMillis() + currentRevision++ binding.etTranscript.setText(mergedText) binding.etTranscript.setTextColor(Color.WHITE) binding.etTranscript.setSelection(newCursor) @@ -290,7 +291,13 @@ class MainActivity : AppCompatActivity() { binding.tvCharCount.text = "$wordCount کلمه" isApplyingRemoteUpdate = false - AppLogger.log("Main", "تزریق گفتار در نشانگر: '$trimmedSpeech' (موقعیت جدید: $newCursor)") + // Guarantees immediate 1:1 synchronization with Mac input box + streamDictationClient?.sendPhoneEdit(mergedText, newCursor) + lifecycleScope.launch { + sendDirectPaste(mergedText, newCursor) + } + + AppLogger.log("Main", "تزریق گفتار در نشانگر و همگام‌سازی با مک: '$trimmedSpeech' (موقعیت جدید: $newCursor)") } private fun setupUI() { diff --git a/mac/src/AppDelegate.swift b/mac/src/AppDelegate.swift index 25b0fa3..8db1a1f 100644 --- a/mac/src/AppDelegate.swift +++ b/mac/src/AppDelegate.swift @@ -16,6 +16,16 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { private var remoteListener: NWListener? public func applicationDidFinishLaunching(_ notification: Notification) { + // 1. Enforce strict single-instance: Terminate any previous/duplicate running instances of SonioxVoice + let currentPid = ProcessInfo.processInfo.processIdentifier + let apps = NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.bundleIdentifier ?? "com.soniox.voice") + for app in apps { + if app.processIdentifier != currentPid { + print("AppDelegate: 🛑 Terminating duplicate instance PID \(app.processIdentifier)") + app.terminate() + } + } + // Completely silent operation by default UserDefaults.standard.set(false, forKey: "SonioxPlaySounds") diff --git a/server/relay_server.py b/server/relay_server.py index 4a8c095..ff28dd1 100644 --- a/server/relay_server.py +++ b/server/relay_server.py @@ -298,20 +298,7 @@ async def handle_phone_stream_ws(request): clean_final = sanitize_text(raw_final, allow_multiline=False, preserve_trailing_space=True) logger.info("⚡ Session %s final text: '%s'", sid, clean_final) - # 1. DIRECT FAST-PATH TO MAC (Sub-10ms injection without waiting for phone roundtrip!) - if clean_final: - mac_payload = { - "type": "insert_speech", - "action": "insert_speech", - "source": "server_direct", - "text": clean_final, - "session_id": sid, - "timestamp": time.time() - } - await broadcast_state(mac_payload) - logger.info("🚀 Fast-Path: Directly dispatched speech '%s' to Mac", clean_final) - - # 2. Return final speech text to phone (for passive UI mirroring) + # Return final speech text to phone (phone updates UI and broadcasts full state to Mac) if is_ws_open(ws): await ws.send_str(json.dumps({ "type": "final",