Browse Source

fix(sync): single-instance mac enforcement to stop duplicate pastes, unified speech sync pipeline from android

main
Ali Alavi 17 hours ago
parent
commit
9a38c329aa
  1. 9
      android/app/src/main/java/com/soniox/remotemic/MainActivity.kt
  2. 10
      mac/src/AppDelegate.swift
  3. 15
      server/relay_server.py

9
android/app/src/main/java/com/soniox/remotemic/MainActivity.kt

@ -283,6 +283,7 @@ class MainActivity : AppCompatActivity() {
isApplyingRemoteUpdate = true isApplyingRemoteUpdate = true
lastLocalText = mergedText lastLocalText = mergedText
lastLocalUserEditTime = System.currentTimeMillis() lastLocalUserEditTime = System.currentTimeMillis()
currentRevision++
binding.etTranscript.setText(mergedText) binding.etTranscript.setText(mergedText)
binding.etTranscript.setTextColor(Color.WHITE) binding.etTranscript.setTextColor(Color.WHITE)
binding.etTranscript.setSelection(newCursor) binding.etTranscript.setSelection(newCursor)
@ -290,7 +291,13 @@ class MainActivity : AppCompatActivity() {
binding.tvCharCount.text = "$wordCount کلمه" binding.tvCharCount.text = "$wordCount کلمه"
isApplyingRemoteUpdate = false 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() { private fun setupUI() {

10
mac/src/AppDelegate.swift

@ -16,6 +16,16 @@ public final class AppDelegate: NSObject, NSApplicationDelegate {
private var remoteListener: NWListener? private var remoteListener: NWListener?
public func applicationDidFinishLaunching(_ notification: Notification) { 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 // Completely silent operation by default
UserDefaults.standard.set(false, forKey: "SonioxPlaySounds") UserDefaults.standard.set(false, forKey: "SonioxPlaySounds")

15
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) clean_final = sanitize_text(raw_final, allow_multiline=False, preserve_trailing_space=True)
logger.info("⚡ Session %s final text: '%s'", sid, clean_final) 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): if is_ws_open(ws):
await ws.send_str(json.dumps({ await ws.send_str(json.dumps({
"type": "final", "type": "final",

Loading…
Cancel
Save