From 60403ba4c3d29914a5c42a68ed9779eb3128c970 Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:19:31 -0400 Subject: [PATCH] Stop repainting healthy idle TUIs --- src/bin/dosh-client.rs | 108 +++++++---------------------------------- 1 file changed, 18 insertions(+), 90 deletions(-) diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index 268ca4d..2ac87ec 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -77,7 +77,6 @@ const POST_SUBMIT_ALL_INPUT_HOLD: Duration = Duration::from_millis(120); const STALE_TERMINAL_INPUT_AFTER: Duration = Duration::from_secs(2); const POST_RECONNECT_STALE_INPUT_GRACE: Duration = Duration::from_secs(5); const FOCUS_REPAINT_COOLDOWN: Duration = Duration::from_secs(1); -const ALT_SCREEN_IDLE_REPAINT_AFTER: Duration = Duration::from_secs(15); const LOCAL_SLEEP_REPAINT_AFTER: Duration = Duration::from_secs(5); const LOCAL_SLEEP_REPAINT_RETRY_AFTER: Duration = Duration::from_secs(1); const LOCAL_SLEEP_REPAINT_RETRY_WINDOW: Duration = Duration::from_secs(10); @@ -6614,9 +6613,8 @@ async fn run_terminal( let mut startup_input_hold_until: Option = None; let mut startup_gate_mode = StartupGateMode::HoldControl; let mut stale_terminal_input_suppress_until: Option = None; - let mut last_terminal_frame_at = Instant::now(); let mut last_focus_repaint_at = Instant::now() - FOCUS_REPAINT_COOLDOWN; - let mut last_idle_repaint_attempt_at = Instant::now() - ALT_SCREEN_IDLE_REPAINT_AFTER; + let mut last_idle_repaint_attempt_at = Instant::now() - LOCAL_SLEEP_REPAINT_RETRY_AFTER; let mut last_status_tick_at = Instant::now(); let mut wake_repaint_retry_until: Option = None; if let Some(frame) = first_frame { @@ -6624,7 +6622,6 @@ async fn run_terminal( render_frame(&frame)?; note_snapshot_rendered(&frame, &mut disconnect_status, &mut status_restore_pending); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); } if frame.closed { return Ok(()); @@ -6694,7 +6691,6 @@ async fn run_terminal( &mut disconnect_status, &mut status_restore_pending, ); - last_terminal_frame_at = Instant::now(); wake_repaint_retry_until = None; send_ack(&socket, addr, &cred, &mut send_seq).await?; if frame.closed { @@ -6779,7 +6775,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); last_packet_at = Instant::now(); last_focus_repaint_at = Instant::now(); wake_repaint_retry_until = None; @@ -6839,7 +6834,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); last_packet_at = Instant::now(); wake_repaint_retry_until = None; flush_pending_user_input( @@ -7063,7 +7057,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); } last_packet_at = Instant::now(); flush_pending_user_input( @@ -7137,7 +7130,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); wake_repaint_retry_until = None; } last_packet_at = Instant::now(); @@ -7202,7 +7194,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); wake_repaint_retry_until = None; } last_packet_at = Instant::now(); @@ -7232,7 +7223,6 @@ async fn run_terminal( predictor.clear_pending()?; if !forward_only { predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); wake_repaint_retry_until = None; if render_resync_needed { if frame.closed { @@ -7388,7 +7378,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); wake_repaint_retry_until = None; } last_packet_at = Instant::now(); @@ -8008,7 +7997,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); last_idle_repaint_attempt_at = Instant::now(); wake_repaint_retry_until = None; repainted_this_tick = true; @@ -8058,8 +8046,6 @@ async fn run_terminal( .await?; let now = Instant::now(); if !repainted_this_tick && !render_resync_needed && !frame_renderer.has_pending() && should_repaint_idle_terminal( - predictor.alternate_screen, - last_terminal_frame_at, last_idle_repaint_attempt_at, status_tick_gap, wake_repaint_retry_until, @@ -8095,7 +8081,6 @@ async fn run_terminal( &mut status_restore_pending, ); predictor.observe_output(&frame.bytes); - last_terminal_frame_at = Instant::now(); last_packet_at = Instant::now(); wake_repaint_retry_until = None; flush_pending_user_input( @@ -8670,8 +8655,6 @@ fn strip_terminal_focus_reports(bytes: &[u8]) -> Vec { } fn should_repaint_idle_terminal( - alternate_screen: bool, - last_terminal_frame_at: Instant, last_attempt_at: Instant, status_tick_gap: Duration, wake_repaint_retry_until: Option, @@ -8679,12 +8662,8 @@ fn should_repaint_idle_terminal( ) -> bool { let sleep_wake_gap = status_tick_gap >= LOCAL_SLEEP_REPAINT_AFTER; let wake_retry_active = wake_repaint_retry_until.is_some_and(|deadline| now < deadline); - let stale_alternate_screen = alternate_screen - && now.duration_since(last_terminal_frame_at) >= ALT_SCREEN_IDLE_REPAINT_AFTER; - if sleep_wake_gap || wake_retry_active { - return now.duration_since(last_attempt_at) >= LOCAL_SLEEP_REPAINT_RETRY_AFTER; - } - stale_alternate_screen && now.duration_since(last_attempt_at) >= ALT_SCREEN_IDLE_REPAINT_AFTER + (sleep_wake_gap || wake_retry_active) + && now.duration_since(last_attempt_at) >= LOCAL_SLEEP_REPAINT_RETRY_AFTER } fn arm_stale_terminal_input_suppression(suppress_until: &mut Option) { @@ -11206,16 +11185,16 @@ const TERMINAL_CLEANUP: &[u8] = concat!( #[cfg(test)] mod tests { use super::{ - ALT_SCREEN_IDLE_REPAINT_AFTER, CachedCredential, DisconnectStatus, DynamicForward, - FRAME_GAP_RESYNC_AFTER_MS, FrameBuffer, LOCAL_SLEEP_REPAINT_AFTER, - LOCAL_SLEEP_REPAINT_RETRY_WINDOW, LocalForward, MAX_PENDING_USER_INPUT_BYTES, - NativeIdentityContext, POST_RECONNECT_STALE_INPUT_GRACE, POST_SUBMIT_ALL_INPUT_HOLD, - PendingStreamControl, PendingStreamOpen, PendingWindowAdjust, PredictMode, Predictor, - RESTART_STATUS_SCRIPT, RemoteForward, STALE_TERMINAL_INPUT_AFTER, STARTUP_INPUT_HOLD, - STDIN_QUEUE_CAPACITY, STREAM_CONTROL_RETRANSMIT_MAX_ATTEMPTS, STREAM_INITIAL_WINDOW, - SshConfig, SshPathTokenContext, StartupGateMode, StatusAction, TERMINAL_CLEANUP, - TERMINAL_SNAPSHOT_RESET, UpdateOptions, UpdateRole, auth_allows, cache_key, - cache_server_prefix, cleanup_stream_state, clear_cached_credentials, + CachedCredential, DisconnectStatus, DynamicForward, FRAME_GAP_RESYNC_AFTER_MS, FrameBuffer, + LOCAL_SLEEP_REPAINT_AFTER, LOCAL_SLEEP_REPAINT_RETRY_AFTER, + LOCAL_SLEEP_REPAINT_RETRY_WINDOW, LocalForward, + MAX_PENDING_USER_INPUT_BYTES, NativeIdentityContext, POST_RECONNECT_STALE_INPUT_GRACE, + POST_SUBMIT_ALL_INPUT_HOLD, PendingStreamControl, PendingStreamOpen, PendingWindowAdjust, + PredictMode, Predictor, RESTART_STATUS_SCRIPT, RemoteForward, STALE_TERMINAL_INPUT_AFTER, + STARTUP_INPUT_HOLD, STDIN_QUEUE_CAPACITY, STREAM_CONTROL_RETRANSMIT_MAX_ATTEMPTS, + STREAM_INITIAL_WINDOW, SshConfig, SshPathTokenContext, StartupGateMode, StatusAction, + TERMINAL_CLEANUP, TERMINAL_SNAPSHOT_RESET, UpdateOptions, UpdateRole, auth_allows, + cache_key, cache_server_prefix, cleanup_stream_state, clear_cached_credentials, ensure_tui_safe_status_overlay, expand_ssh_path_tokens, first_resolved_addr, imported_host_block, input_contains_focus_in, input_prefix_before_escape, is_local_status_target, is_resume_response_for_client, @@ -14478,86 +14457,35 @@ mod tests { } #[test] - fn idle_repaint_runs_for_stale_alternate_screen_or_sleep_gap() { + fn idle_repaint_runs_only_after_sleep_or_an_armed_wake_retry() { let now = Instant::now(); - let stale = now - ALT_SCREEN_IDLE_REPAINT_AFTER - Duration::from_secs(1); - let recent = now - Duration::from_secs(1); + let stale = now - LOCAL_SLEEP_REPAINT_RETRY_AFTER - Duration::from_secs(1); let just_attempted = now - Duration::from_millis(500); - assert!(should_repaint_idle_terminal( - true, - stale, + assert!(!should_repaint_idle_terminal( stale, Duration::from_secs(1), None, now )); assert!(should_repaint_idle_terminal( - false, - recent, stale, LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), None, now )); assert!(should_repaint_idle_terminal( - false, - recent, - recent, - LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), + stale, + Duration::from_secs(1), Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW), now )); assert!(!should_repaint_idle_terminal( - false, - recent, just_attempted, LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW), now )); assert!(!should_repaint_idle_terminal( - false, - stale, - stale, - Duration::from_secs(1), - None, - now - )); - assert!(!should_repaint_idle_terminal( - true, - recent, - stale, - Duration::from_secs(1), - None, - now - )); - assert!(should_repaint_idle_terminal( - true, - stale, - recent, - LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), - None, - now - )); - assert!(should_repaint_idle_terminal( - false, - recent, - stale, - Duration::from_secs(1), - Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW), - now - )); - assert!(!should_repaint_idle_terminal( - false, - recent, - just_attempted, - Duration::from_secs(1), - Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW), - now - )); - assert!(!should_repaint_idle_terminal( - false, - recent, stale, Duration::from_secs(1), Some(now - Duration::from_millis(1)),