Client: instant SIGWINCH-driven terminal resize (mosh-style)
The client only polled crossterm size() every 250ms, so window resizes (esp. in VSCode/terminals that signal rather than expecting polling) lagged or felt unresponsive. Add a SIGWINCH select branch that resizes the instant the signal fires, matching mosh (which reacts to SIGWINCH + TIOCGWINSZ). Keep the 250ms poll as a fallback. Client-only; wire VERSION unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+37
-10
@@ -40,6 +40,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream, UdpSocket, UnixStream};
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const STREAM_INITIAL_WINDOW: usize = 1024 * 1024;
|
||||
@@ -2137,6 +2138,11 @@ async fn run_terminal(
|
||||
let mut status_tick = tokio::time::interval(Duration::from_secs(1));
|
||||
let mut resize_tick = tokio::time::interval(Duration::from_millis(250));
|
||||
let mut last_size = terminal_size();
|
||||
// React to terminal resize the instant it happens via SIGWINCH (mosh-style),
|
||||
// instead of waiting up to one `resize_tick`. The 250ms poll below stays as a
|
||||
// fallback for environments where the signal doesn't fire. `signal()` can fail
|
||||
// (rare), in which case we rely on the poll.
|
||||
let mut winch = signal(SignalKind::window_change()).ok();
|
||||
let mut frame_buffer = FrameBuffer::default();
|
||||
// Resolve the prediction display policy (off / experimental / always). An
|
||||
// env var wins for ad-hoc tuning; otherwise the client config's
|
||||
@@ -2231,17 +2237,16 @@ async fn run_terminal(
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
_ = resize_tick.tick() => {
|
||||
if let Ok((cols, rows)) = size()
|
||||
&& cols > 0
|
||||
&& rows > 0
|
||||
&& (cols, rows) != last_size
|
||||
{
|
||||
last_size = (cols, rows);
|
||||
if cred.mode != "view-only" && cred.mode != "forward-only" {
|
||||
send_resize(&socket, addr, &cred, &mut send_seq, cols, rows).await?;
|
||||
}
|
||||
_ = async {
|
||||
match winch.as_mut() {
|
||||
Some(w) => { w.recv().await; }
|
||||
None => std::future::pending::<()>().await,
|
||||
}
|
||||
} => {
|
||||
maybe_send_resize(&socket, addr, &cred, &mut send_seq, &mut last_size).await?;
|
||||
}
|
||||
_ = resize_tick.tick() => {
|
||||
maybe_send_resize(&socket, addr, &cred, &mut send_seq, &mut last_size).await?;
|
||||
}
|
||||
recv = socket.recv_from(&mut recv_buf) => {
|
||||
let (n, _) = recv?;
|
||||
@@ -3713,6 +3718,28 @@ impl FrameBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the current terminal size and, if it changed since `last_size`, send a
|
||||
/// resize to the server. Shared by the SIGWINCH branch and the poll fallback.
|
||||
async fn maybe_send_resize(
|
||||
socket: &UdpSocket,
|
||||
addr: SocketAddr,
|
||||
cred: &CachedCredential,
|
||||
send_seq: &mut u64,
|
||||
last_size: &mut (u16, u16),
|
||||
) -> Result<()> {
|
||||
if let Ok((cols, rows)) = size()
|
||||
&& cols > 0
|
||||
&& rows > 0
|
||||
&& (cols, rows) != *last_size
|
||||
{
|
||||
*last_size = (cols, rows);
|
||||
if cred.mode != "view-only" && cred.mode != "forward-only" {
|
||||
send_resize(socket, addr, cred, send_seq, cols, rows).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_resize(
|
||||
socket: &UdpSocket,
|
||||
addr: SocketAddr,
|
||||
|
||||
Reference in New Issue
Block a user