diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-03 02:49:27 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-03 02:49:27 +0300 |
| commit | aa0768b734d95a1f54df1719290d977df796068d (patch) | |
| tree | f173bf0e14b4c951e9ad2dc02dc7e66f102b89b4 /src/client | |
| parent | e1bbc252e2b40da6e0c6d7b0f8d7babb17cc8814 (diff) | |
fix: stop sgr mouse sequences leaking into panes
refs #939
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/input.rs | 60 | ||||
| -rw-r--r-- | src/client/mod.rs | 23 |
2 files changed, 77 insertions, 6 deletions
diff --git a/src/client/input.rs b/src/client/input.rs index 7099311b..bc7faf35 100644 --- a/src/client/input.rs +++ b/src/client/input.rs @@ -39,15 +39,21 @@ pub fn stdin_reader_loop( event_tx: mpsc::Sender<ClientLoopEvent>, should_quit: &Arc<AtomicBool>, host_color_query_sent: bool, + host_mouse_capture_active: Arc<AtomicBool>, ) { #[cfg(windows)] { - let _ = host_color_query_sent; + let _ = (host_color_query_sent, host_mouse_capture_active); windows_stdin_reader_loop(event_tx, should_quit); } #[cfg(unix)] - unix_stdin_reader_loop(event_tx, should_quit, host_color_query_sent); + unix_stdin_reader_loop( + event_tx, + should_quit, + host_color_query_sent, + host_mouse_capture_active, + ); } #[cfg(unix)] @@ -55,6 +61,7 @@ fn unix_stdin_reader_loop( event_tx: mpsc::Sender<ClientLoopEvent>, should_quit: &Arc<AtomicBool>, host_color_query_sent: bool, + host_mouse_capture_active: Arc<AtomicBool>, ) { let stdin = io::stdin(); let mut reader = stdin.lock(); @@ -78,7 +85,11 @@ fn unix_stdin_reader_loop( } } - if stdin_read_ready(&reader, 10) == Some(false) { + let timeout_ms = idle_flush_timeout_ms( + &framer, + host_mouse_capture_active.load(Ordering::Acquire), + ); + if stdin_read_ready(&reader, timeout_ms) == Some(false) { let had_pending = framer.has_pending_input(); let chunks = framer.flush_timeout(); let held_escape = had_pending && chunks.is_empty(); @@ -90,7 +101,12 @@ fn unix_stdin_reader_loop( return; } } - if held_escape && stdin_read_ready(&reader, 10) == Some(false) { + if held_escape + && stdin_read_ready( + &reader, + crate::raw_input::RAW_INPUT_IDLE_FLUSH_TIMEOUT_MS, + ) == Some(false) + { for data in framer.flush_timeout() { if event_tx .blocking_send(ClientLoopEvent::StdinInput(data)) @@ -112,6 +128,18 @@ fn unix_stdin_reader_loop( } } +#[cfg(unix)] +fn idle_flush_timeout_ms( + framer: &crate::raw_input::RawInputByteFramer, + host_mouse_capture_active: bool, +) -> i32 { + if host_mouse_capture_active && framer.has_pending_lone_escape() { + crate::raw_input::MOUSE_ACTIVE_LONE_ESCAPE_FLUSH_TIMEOUT_MS + } else { + crate::raw_input::RAW_INPUT_IDLE_FLUSH_TIMEOUT_MS + } +} + #[cfg(windows)] fn windows_stdin_reader_loop( event_tx: mpsc::Sender<ClientLoopEvent>, @@ -366,6 +394,30 @@ mod tests { _ => panic!("expected StdinInput event"), } } + + #[test] + fn raw_input_idle_flush_timeout_keeps_escape_responsive() { + let timeout_ms = std::hint::black_box(crate::raw_input::RAW_INPUT_IDLE_FLUSH_TIMEOUT_MS); + assert!(timeout_ms <= 20); + } + + #[test] + fn mouse_active_lone_escape_gets_longer_reassembly_window() { + let mut framer = crate::raw_input::RawInputByteFramer::default(); + assert!(framer.push(b"\x1b").is_empty()); + + assert_eq!( + idle_flush_timeout_ms(&framer, false), + crate::raw_input::RAW_INPUT_IDLE_FLUSH_TIMEOUT_MS + ); + assert_eq!( + idle_flush_timeout_ms(&framer, true), + crate::raw_input::MOUSE_ACTIVE_LONE_ESCAPE_FLUSH_TIMEOUT_MS + ); + let mouse_timeout_ms = + std::hint::black_box(crate::raw_input::MOUSE_ACTIVE_LONE_ESCAPE_FLUSH_TIMEOUT_MS); + assert!(mouse_timeout_ms > 100); + } } #[cfg(test)] diff --git a/src/client/mod.rs b/src/client/mod.rs index 92edb900..ceed9826 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -312,7 +312,14 @@ impl std::error::Error for ClientError { impl From<protocol::FramingError> for ClientError { fn from(err: protocol::FramingError) -> Self { - ClientError::Protocol(err) + match err { + protocol::FramingError::UnexpectedEof => ClientError::ConnectionLost(io::Error::new( + io::ErrorKind::UnexpectedEof, + "server closed connection", + )), + protocol::FramingError::Io(err) => ClientError::ConnectionLost(err), + err => ClientError::Protocol(err), + } } } @@ -341,6 +348,7 @@ fn setup_terminal_with_capabilities( mouse_capture: bool, ) -> io::Result<TerminalGuard> { ratatui::init(); + crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout())?; let host_color_scheme_reports = should_enable_host_color_scheme_reports(enable_client_protocols); @@ -536,6 +544,7 @@ fn restore_windows_input_mode_value(mode: u32) { } fn set_mouse_capture(enabled: bool) -> io::Result<()> { + crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout())?; if enabled { execute!(io::stdout(), EnableMouseCapture) } else { @@ -569,6 +578,7 @@ fn restore_terminal_state( DisableBracketedPaste, DisableMouseCapture ); + let _ = crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout()); #[cfg(windows)] if let Some(mode) = restore_windows_input_mode { restore_windows_input_mode_value(mode); @@ -1100,6 +1110,7 @@ fn run_client_with_mode( init_logging(); let loaded_config = crate::config::Config::load(); + crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout())?; let mouse_capture = loaded_config.config.ui.mouse_capture; let mouse_scroll_lines = loaded_config.config.ui.mouse_scroll_lines(); let redraw_on_focus_gained = loaded_config.config.ui.redraw_on_focus_gained; @@ -1283,6 +1294,7 @@ async fn run_client_loop( draw_host_cursor, }; debug!(?negotiated_encoding, "client render encoding active"); + let host_mouse_capture_active = Arc::new(AtomicBool::new(state.mouse_capture_active)); // Channel for events from the stdin, resize, and server reader threads. let (event_tx, mut event_rx) = tokio::sync::mpsc::channel::<ClientLoopEvent>(256); @@ -1292,8 +1304,14 @@ async fn run_client_loop( state.attach_escape.is_none() && should_query_host_terminal_theme(); let stdin_quit = should_quit.clone(); let stdin_tx = event_tx.clone(); + let stdin_mouse_capture_active = host_mouse_capture_active.clone(); std::thread::spawn(move || { - input::stdin_reader_loop(stdin_tx, &stdin_quit, will_query_host_terminal_theme); + input::stdin_reader_loop( + stdin_tx, + &stdin_quit, + will_query_host_terminal_theme, + stdin_mouse_capture_active, + ); }); if will_query_host_terminal_theme { @@ -1533,6 +1551,7 @@ async fn run_client_loop( let _ = enable_windows_virtual_terminal_input(); } state.mouse_capture_active = desired; + host_mouse_capture_active.store(desired, Ordering::Release); } } ServerMessage::Welcome { .. } => { |
