diff --git a/app/src-tauri/src/bridge.rs b/app/src-tauri/src/bridge.rs index da13d69..904941b 100644 --- a/app/src-tauri/src/bridge.rs +++ b/app/src-tauri/src/bridge.rs @@ -29,6 +29,9 @@ pub struct Bridge { tx: Mutex>, /// Serializes reconnect attempts. reconnect_lock: Mutex<()>, + /// The current reader task; aborted on reconnect so a stale connection can't + /// keep delivering duplicate output (which doubled keystroke echo). + reader: Mutex>, /// Pending request id → reply slot. pending: Arc>>>, /// surface id → output channel into the webview. @@ -102,13 +105,13 @@ async fn spawn_connection( app: &AppHandle, pending: Arc>>>, out_channels: Arc>>>>, -) -> Result> { +) -> Result<(mpsc::Sender, tokio::task::JoinHandle<()>)> { let stream = ensure_daemon(sock).await?; let (read_half, write_half) = stream.into_split(); let (tx, rx) = mpsc::channel::(256); spawn_writer(write_half, rx); - spawn_reader(read_half, app.clone(), pending, out_channels); - Ok(tx) + let reader = spawn_reader(read_half, app.clone(), pending, out_channels); + Ok((tx, reader)) } impl Bridge { @@ -116,7 +119,7 @@ impl Bridge { let sock = socket_path()?; let pending: Arc>>> = Arc::default(); let out_channels: Arc>>>> = Arc::default(); - let tx = spawn_connection(&sock, &app, pending.clone(), out_channels.clone()).await?; + let (tx, reader) = spawn_connection(&sock, &app, pending.clone(), out_channels.clone()).await?; Ok(Self { next_id: AtomicU64::new(1), app, @@ -124,6 +127,7 @@ impl Bridge { gen: AtomicU64::new(0), tx: Mutex::new(tx), reconnect_lock: Mutex::new(()), + reader: Mutex::new(reader), pending, out_channels, }) @@ -139,8 +143,12 @@ impl Bridge { } // Drop in-flight reply slots — their connection is gone; they'll error out. self.pending.lock().await.clear(); - let new_tx = spawn_connection(&self.sock, &self.app, self.pending.clone(), self.out_channels.clone()).await?; + // Kill the old reader FIRST so it can't keep delivering output on a stale + // connection alongside the new one (the cause of doubled keystroke echo). + self.reader.lock().await.abort(); + let (new_tx, new_reader) = spawn_connection(&self.sock, &self.app, self.pending.clone(), self.out_channels.clone()).await?; *self.tx.lock().await = new_tx; + *self.reader.lock().await = new_reader; self.gen.fetch_add(1, Ordering::Release); let _ = self.app.emit("spacesh:reconnected", ()); Ok(()) @@ -205,7 +213,7 @@ fn spawn_reader( app: AppHandle, pending: Arc>>>, out_channels: Arc>>>>, -) { +) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { loop { match read_frame(&mut read_half).await { @@ -232,7 +240,7 @@ fn spawn_reader( } } } - }); + }) } // ---- Tauri commands ----