Reset terminal modes before snapshot repaint
ci / test (push) Waiting to run
ci / fuzz-smoke (push) Waiting to run
ci / windows-client (push) Waiting to run
ci / package-release (linux-x86_64, ubuntu-latest) (push) Waiting to run
ci / package-release (macos-aarch64, macos-14) (push) Waiting to run
ci / package-release (macos-x86_64, macos-13) (push) Waiting to run
ci / package-release (windows-x86_64, windows-latest) (push) Waiting to run
ci / publish-gitea-release (push) Blocked by required conditions
ci / remote-bench (push) Waiting to run

This commit is contained in:
DuProcess
2026-07-13 00:24:45 -04:00
parent bf50b38ce2
commit b1cc6bf8ae
+53 -9
View File
@@ -9418,11 +9418,39 @@ async fn detach_once(socket: &UdpSocket, cred: &CachedCredential, seq: u64) -> R
fn render_frame(frame: &Frame) -> Result<()> { fn render_frame(frame: &Frame) -> Result<()> {
let mut stdout = std::io::stdout(); let mut stdout = std::io::stdout();
stdout.write_all(&frame.bytes)?; stdout.write_all(&render_frame_bytes(frame))?;
stdout.flush()?; stdout.flush()?;
Ok(()) Ok(())
} }
fn render_frame_bytes(frame: &Frame) -> Vec<u8> {
if !frame.snapshot {
return frame.bytes.clone();
}
let mut bytes = Vec::with_capacity(TERMINAL_SNAPSHOT_RESET.len() + frame.bytes.len());
bytes.extend_from_slice(TERMINAL_SNAPSHOT_RESET);
bytes.extend_from_slice(&frame.bytes);
bytes
}
const TERMINAL_SNAPSHOT_RESET: &[u8] = concat!(
"\x1b[0m",
"\x1b[?25h",
"\x1b[?1003l",
"\x1b[?1002l",
"\x1b[?1001l",
"\x1b[?1000l",
"\x1b[?1004l",
"\x1b[?1015l",
"\x1b[?1006l",
"\x1b[?1005l",
"\x1b[?2004l",
"\x1b[?47l",
"\x1b[?1047l",
"\x1b[?1049l"
)
.as_bytes();
/// Seconds of silence from the server before the disconnect status line appears. /// Seconds of silence from the server before the disconnect status line appears.
/// Short enough to give quick feedback on a lost link, long enough that a normal /// Short enough to give quick feedback on a lost link, long enough that a normal
/// idle period (the run loop only pings after 2s of quiet) never flashes it. /// idle period (the run loop only pings after 2s of quiet) never flashes it.
@@ -9754,11 +9782,11 @@ mod tests {
NativeIdentityContext, POST_RECONNECT_STALE_INPUT_GRACE, POST_SUBMIT_ALL_INPUT_HOLD, NativeIdentityContext, POST_RECONNECT_STALE_INPUT_GRACE, POST_SUBMIT_ALL_INPUT_HOLD,
PendingStreamOpen, PredictMode, Predictor, RESTART_STATUS_SCRIPT, RemoteForward, PendingStreamOpen, PredictMode, Predictor, RESTART_STATUS_SCRIPT, RemoteForward,
STALE_TERMINAL_INPUT_AFTER, STARTUP_INPUT_HOLD, STREAM_INITIAL_WINDOW, SshConfig, STALE_TERMINAL_INPUT_AFTER, STARTUP_INPUT_HOLD, STREAM_INITIAL_WINDOW, SshConfig,
SshPathTokenContext, StartupGateMode, StatusAction, UpdateOptions, UpdateRole, auth_allows, SshPathTokenContext, StartupGateMode, StatusAction, TERMINAL_SNAPSHOT_RESET, UpdateOptions,
cache_key, cache_server_prefix, cleanup_stream_state, clear_cached_credentials, UpdateRole, auth_allows, cache_key, cache_server_prefix, cleanup_stream_state,
effective_update_artifact_tag, ensure_tui_safe_status_overlay, expand_ssh_path_tokens, clear_cached_credentials, effective_update_artifact_tag, ensure_tui_safe_status_overlay,
input_contains_focus_in, input_matches_escape, is_local_status_target, expand_ssh_path_tokens, input_contains_focus_in, input_matches_escape,
is_resume_response_for_client, latest_release_download_url, is_local_status_target, is_resume_response_for_client, latest_release_download_url,
load_first_native_identity_with_prompt, native_proxy_udp_warning, load_first_native_identity_with_prompt, native_proxy_udp_warning,
newest_client_trace_path_from, parse_dynamic_forward, parse_escape_key, newest_client_trace_path_from, parse_dynamic_forward, parse_escape_key,
parse_local_forward, parse_remote_forward, parse_single_remote_path, parse_ssh_config, parse_local_forward, parse_remote_forward, parse_single_remote_path, parse_ssh_config,
@@ -9766,9 +9794,9 @@ mod tests {
parse_update_options, post_submit_hold_duration, queue_or_send_stream_data, parse_update_options, post_submit_hold_duration, queue_or_send_stream_data,
queue_pending_user_input, queue_stale_pending_user_input, raw_contains_host_table, queue_pending_user_input, queue_stale_pending_user_input, raw_contains_host_table,
recv_response_until, refresh_live_addr, release_tag_download_url, recv_response_until, refresh_live_addr, release_tag_download_url,
release_tag_from_effective_url, release_version_from_tag, render_status_clear, release_tag_from_effective_url, release_version_from_tag, render_frame_bytes,
render_status_overlay, requested_env, resolved_startup_command, retire_stream_state, render_status_clear, render_status_overlay, requested_env, resolved_startup_command,
retransmit_stream_opens, rewrite_forward_command, sanitize_trace_name, retire_stream_state, retransmit_stream_opens, rewrite_forward_command, sanitize_trace_name,
selected_predict_mode, selected_udp_host, send_stream_eof, server_version_mismatch, selected_predict_mode, selected_udp_host, send_stream_eof, server_version_mismatch,
should_flush_terminal_input_after_contact, should_health_log_client_start, should_flush_terminal_input_after_contact, should_health_log_client_start,
should_hold_during_startup_gate, should_hold_post_submit_input, should_hold_during_startup_gate, should_hold_post_submit_input,
@@ -10424,6 +10452,22 @@ mod tests {
} }
} }
#[test]
fn snapshot_render_resets_local_terminal_modes_before_authoritative_bytes() {
let frame = test_frame(7, true);
let bytes = render_frame_bytes(&frame);
assert!(bytes.starts_with(TERMINAL_SNAPSHOT_RESET));
assert!(bytes.ends_with(b"frame-7"));
}
#[test]
fn live_frame_render_preserves_terminal_output_exactly() {
let frame = test_frame(7, false);
assert_eq!(render_frame_bytes(&frame), b"frame-7");
}
#[test] #[test]
fn frame_buffer_renders_only_contiguous_frames() { fn frame_buffer_renders_only_contiguous_frames() {
let mut buffer = FrameBuffer::default(); let mut buffer = FrameBuffer::default();