From 818b48115437cd215775aa174dd1a1f2c670adfb Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Sun, 12 Jul 2026 01:08:38 -0400 Subject: [PATCH] Cover terminal mouse input filtering --- src/bin/dosh-client.rs | 95 +++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index cd333f6..1af2d4c 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -5634,22 +5634,22 @@ async fn run_terminal( if bytes.is_empty() { continue; } - if should_strip_unowned_terminal_reports( + let before_mouse_strip = bytes.len(); + let (stripped_bytes, stripped_unowned_mouse) = strip_unowned_terminal_reports( + bytes, predictor.alternate_screen, predictor.mouse_tracking, - ) { - let before_mouse_strip = bytes.len(); - bytes = strip_stale_mouse_reports(&bytes); - if before_mouse_strip != bytes.len() { - dosh::trace::event( - "client.unowned_mouse_stripped", - &[ - ("before", before_mouse_strip.to_string()), - ("after", bytes.len().to_string()), - ("summary", dosh::trace::bytes_summary(&bytes)), - ], - ); - } + ); + bytes = stripped_bytes; + if stripped_unowned_mouse { + dosh::trace::event( + "client.unowned_mouse_stripped", + &[ + ("before", before_mouse_strip.to_string()), + ("after", bytes.len().to_string()), + ("summary", dosh::trace::bytes_summary(&bytes)), + ], + ); if bytes.is_empty() { continue; } @@ -6983,6 +6983,20 @@ fn should_strip_unowned_terminal_reports(alternate_screen: bool, mouse_tracking: !alternate_screen && !mouse_tracking } +fn strip_unowned_terminal_reports( + bytes: Vec, + alternate_screen: bool, + mouse_tracking: bool, +) -> (Vec, bool) { + if !should_strip_unowned_terminal_reports(alternate_screen, mouse_tracking) { + return (bytes, false); + } + let before = bytes.len(); + let stripped = strip_stale_mouse_reports(&bytes); + let changed = stripped.len() != before; + (stripped, changed) +} + fn input_contains_focus_in(bytes: &[u8]) -> bool { contains_bytes(bytes, b"\x1b[I") || contains_bytes(bytes, b"\x9bI") } @@ -8969,8 +8983,8 @@ mod tests { native_proxy_udp_warning, parse_dynamic_forward, parse_escape_key, parse_local_forward, parse_remote_forward, parse_ssh_config, parse_trace_line, parse_trace_options, parse_trace_report_options, parse_trace_summary, parse_update_options, - post_submit_hold_duration, queue_pending_user_input, raw_contains_host_table, - recv_response_until, refresh_live_addr, release_tag_download_url, + post_submit_hold_duration, queue_pending_user_input, queue_stale_pending_user_input, + raw_contains_host_table, recv_response_until, refresh_live_addr, release_tag_download_url, release_tag_from_effective_url, release_version_from_tag, render_status_clear, render_status_overlay, requested_env, resolved_startup_command, retire_stream_state, retransmit_stream_opens, rewrite_forward_command, sanitize_trace_name, @@ -8980,10 +8994,10 @@ mod tests { should_strip_unowned_terminal_reports, split_after_command_submit, split_trace_tokens, ssh_command_target, ssh_config_uses_proxy, ssh_destination_host, ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports, - strip_terminal_focus_reports, summarize_trace_file, terminal_private_mode_transition, - toml_bare_key_or_quoted, top_trace_events, trace_report_warnings, unix_update_script, - update_installer_url, update_version_status, upsert_managed_block, valid_forward_host, - vscode_safe_alias, windows_update_script, + strip_terminal_focus_reports, strip_unowned_terminal_reports, summarize_trace_file, + terminal_private_mode_transition, toml_bare_key_or_quoted, top_trace_events, + trace_report_warnings, unix_update_script, update_installer_url, update_version_status, + upsert_managed_block, valid_forward_host, vscode_safe_alias, windows_update_script, }; use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::native::EnvVar; @@ -10817,6 +10831,47 @@ mod tests { assert_eq!(pending_bytes, MAX_PENDING_USER_INPUT_BYTES); } + #[test] + fn stale_pending_user_input_is_marked_for_mouse_stripping() { + let mut pending = VecDeque::new(); + let mut pending_bytes = 0usize; + + queue_pending_user_input(&mut pending, &mut pending_bytes, b"\x1b[<35;10;1M".to_vec()) + .unwrap(); + queue_stale_pending_user_input( + &mut pending, + &mut pending_bytes, + b"\x1b[<35;11;1M".to_vec(), + ) + .unwrap(); + + let normal = pending.pop_front().unwrap(); + assert!(!normal.strip_mouse_reports); + assert_eq!(strip_stale_mouse_reports(&normal.bytes), b""); + + let stale = pending.pop_front().unwrap(); + assert!(stale.strip_mouse_reports); + assert_eq!(strip_stale_mouse_reports(&stale.bytes), b""); + } + + #[test] + fn unowned_terminal_mouse_reports_strip_only_without_mouse_owner() { + let input = b"\x1b[<35;10;1Mcmd\r".to_vec(); + + let (stripped, changed) = strip_unowned_terminal_reports(input.clone(), false, false); + assert!(changed); + assert_eq!(stripped, b"cmd\r"); + + let (preserved, changed) = strip_unowned_terminal_reports(input.clone(), false, true); + assert!(!changed); + assert_eq!(preserved, input); + + let (preserved, changed) = + strip_unowned_terminal_reports(b"\x1b[<35;10;1M".to_vec(), true, false); + assert!(!changed); + assert_eq!(preserved, b"\x1b[<35;10;1M"); + } + #[test] fn transient_udp_send_errors_are_not_terminal_fatal() { #[cfg(unix)]