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 std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::{TcpListener, TcpStream, UdpSocket, UnixStream};
|
use tokio::net::{TcpListener, TcpStream, UdpSocket, UnixStream};
|
||||||
|
use tokio::signal::unix::{SignalKind, signal};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
const STREAM_INITIAL_WINDOW: usize = 1024 * 1024;
|
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 status_tick = tokio::time::interval(Duration::from_secs(1));
|
||||||
let mut resize_tick = tokio::time::interval(Duration::from_millis(250));
|
let mut resize_tick = tokio::time::interval(Duration::from_millis(250));
|
||||||
let mut last_size = terminal_size();
|
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();
|
let mut frame_buffer = FrameBuffer::default();
|
||||||
// Resolve the prediction display policy (off / experimental / always). An
|
// Resolve the prediction display policy (off / experimental / always). An
|
||||||
// env var wins for ad-hoc tuning; otherwise the client config's
|
// env var wins for ad-hoc tuning; otherwise the client config's
|
||||||
@@ -2231,17 +2237,16 @@ async fn run_terminal(
|
|||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = resize_tick.tick() => {
|
_ = async {
|
||||||
if let Ok((cols, rows)) = size()
|
match winch.as_mut() {
|
||||||
&& cols > 0
|
Some(w) => { w.recv().await; }
|
||||||
&& rows > 0
|
None => std::future::pending::<()>().await,
|
||||||
&& (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?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} => {
|
||||||
|
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) => {
|
recv = socket.recv_from(&mut recv_buf) => {
|
||||||
let (n, _) = recv?;
|
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(
|
async fn send_resize(
|
||||||
socket: &UdpSocket,
|
socket: &UdpSocket,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
|
|||||||
Reference in New Issue
Block a user