From f4879090f6bf5af18bb70937e83063a73fe396da Mon Sep 17 00:00:00 2001 From: DuProcess <273172371+DuProcess@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:57:53 -0400 Subject: [PATCH] Keep terminal focus reports local --- src/bin/dosh-client.rs | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index 4c34f9c..864ba5b 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -4401,8 +4401,9 @@ async fn run_terminal( if input_matches_escape(&bytes, escape_key.as_deref()) { break; } + let saw_focus_in = input_contains_focus_in(&bytes); if !forward_only - && input_contains_focus_in(&bytes) + && saw_focus_in && last_focus_repaint_at.elapsed() >= FOCUS_REPAINT_COOLDOWN { last_focus_repaint_at = Instant::now(); @@ -4436,6 +4437,10 @@ async fn run_terminal( .await?; } } + bytes = strip_terminal_focus_reports(&bytes); + if bytes.is_empty() { + continue; + } if should_strip_stale_terminal_reports( last_packet_at.elapsed(), stale_terminal_input_suppress_until, @@ -5641,6 +5646,26 @@ fn input_contains_focus_in(bytes: &[u8]) -> bool { contains_bytes(bytes, b"\x1b[I") || contains_bytes(bytes, b"\x9bI") } +fn strip_terminal_focus_reports(bytes: &[u8]) -> Vec { + let mut out = Vec::with_capacity(bytes.len()); + let mut offset = 0; + while offset < bytes.len() { + match bytes.get(offset..) { + Some([0x1b, b'[', b'I' | b'O', ..]) => { + offset += 3; + } + Some([0x9b, b'I' | b'O', ..]) => { + offset += 2; + } + _ => { + out.push(bytes[offset]); + offset += 1; + } + } + } + out +} + fn should_repaint_idle_alternate_screen( alternate_screen: bool, last_terminal_frame_at: Instant, @@ -7347,8 +7372,8 @@ mod tests { should_hold_during_startup_gate, should_hold_post_submit_input, should_repaint_idle_alternate_screen, split_after_command_submit, ssh_destination_host, ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports, - toml_bare_key_or_quoted, update_check_requested, update_version_status, - upsert_managed_block, valid_forward_host, vscode_safe_alias, + strip_terminal_focus_reports, toml_bare_key_or_quoted, update_check_requested, + update_version_status, upsert_managed_block, valid_forward_host, vscode_safe_alias, }; use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::native::EnvVar; @@ -8661,6 +8686,17 @@ mod tests { assert!(!input_contains_focus_in(b"\x1b[A")); } + #[test] + fn focus_reports_are_never_forwarded_as_user_input() { + assert_eq!(strip_terminal_focus_reports(b"\x1b[Ihello\x1b[O"), b"hello"); + assert_eq!(strip_terminal_focus_reports(b"\x9bIhello\x9bO"), b"hello"); + assert_eq!(strip_terminal_focus_reports(b"\x1b[A"), b"\x1b[A"); + assert_eq!( + strip_terminal_focus_reports(b"before\x1b[Iafter"), + b"beforeafter" + ); + } + #[test] fn idle_repaint_only_runs_for_stale_alternate_screen() { let now = Instant::now();