diff --git a/src/client/mod.rs b/src/client/mod.rs index 8775ac19..59ff94c1 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -73,6 +73,12 @@ use terminal_setup::{ effective_mouse_capture, effective_sgr_pixel_mouse, set_mouse_capture, setup_direct_attach_terminal, setup_terminal, should_draw_host_cursor, }; + +fn refresh_host_mouse_capture(enabled: bool, sgr_pixels: bool) { + if let Err(err) = set_mouse_capture(enabled, sgr_pixels) { + warn!(err = %err, "failed to re-assert host mouse capture"); + } +} #[cfg(windows)] use terminal_setup::{ enable_windows_virtual_terminal_input, is_ssh_session, windows_vti_input_backend_enabled, @@ -783,9 +789,16 @@ async fn run_client_loop( continue; } } + let events = crate::raw_input::parse_raw_input_bytes_sync(&data); + if crate::raw_input::events_require_host_mode_refresh(&events) { + refresh_host_mouse_capture( + state.mouse_capture_active, + host_sgr_pixels_active.load(Ordering::Acquire), + ); + } let (outcome, frame) = { let shell = state.shell.as_mut().expect("checked shell mode"); - let outcome = shell.handle_input_bytes(&data); + let outcome = shell.handle_raw_events(events); let frame = outcome .repaint .then(|| shell.compose(state.reported_size.0, state.reported_size.1)) @@ -1008,6 +1021,14 @@ async fn run_client_loop( write_stream.active_surface_available(), ); if state.shell.is_some() { + if events.iter().any(|event| { + matches!(event, crate::protocol::ClientInputEvent::FocusGained) + }) { + refresh_host_mouse_capture( + state.mouse_capture_active, + host_sgr_pixels_active.load(Ordering::Acquire), + ); + } let image_target = state .shell .as_ref() @@ -1084,6 +1105,11 @@ async fn run_client_loop( set_mouse_capture(state.mouse_capture_active, false) .map_err(ClientError::ConnectionFailed)?; host_sgr_pixels_active.store(false, Ordering::Release); + } else { + refresh_host_mouse_capture( + state.mouse_capture_active, + host_sgr_pixels_active.load(Ordering::Acquire), + ); } state.reported_size = (new_cols, new_rows); state.reported_cell_size = (cell_width_px, cell_height_px); diff --git a/src/client/shell/input.rs b/src/client/shell/input.rs index 60202d1d..eb1f8f2f 100644 --- a/src/client/shell/input.rs +++ b/src/client/shell/input.rs @@ -84,7 +84,7 @@ impl ClientShellState { ) } - #[cfg(any(unix, test))] + #[cfg(test)] pub(crate) fn handle_input_bytes(&mut self, data: &[u8]) -> ClientShellInput { self.handle_raw_events(crate::raw_input::parse_raw_input_bytes_sync(data)) } @@ -155,7 +155,7 @@ impl ClientShellState { outcome } - pub(super) fn handle_raw_events(&mut self, events: Vec) -> ClientShellInput { + pub(crate) fn handle_raw_events(&mut self, events: Vec) -> ClientShellInput { let mut outcome = ClientShellInput::default(); if !events.is_empty() && self.endpoint_error.take().is_some() { self.endpoint_error_deadline = None; diff --git a/src/raw_input.rs b/src/raw_input.rs index fc6ff52e..147d6e39 100644 --- a/src/raw_input.rs +++ b/src/raw_input.rs @@ -543,6 +543,13 @@ pub(crate) fn events_require_host_surface_redraw( .any(|event| matches!(event, RawInputEvent::OuterFocusGained)) } +#[cfg(any(unix, test))] +pub(crate) fn events_require_host_mode_refresh(events: &[RawInputEvent]) -> bool { + events + .iter() + .any(|event| matches!(event, RawInputEvent::OuterFocusGained)) +} + #[cfg(any(not(windows), test))] pub(crate) fn events_require_host_terminal_appearance_query(events: &[RawInputEvent]) -> bool { events @@ -1355,6 +1362,16 @@ mod tests { assert!(!events_require_host_surface_redraw(&events, true)); } + #[test] + fn outer_focus_gained_requests_host_mode_refresh() { + assert!(events_require_host_mode_refresh( + &parse_raw_input_bytes_sync(b"\x1b[I") + )); + assert!(!events_require_host_mode_refresh( + &parse_raw_input_bytes_sync(b"\x1b[O") + )); + } + #[test] fn outer_focus_gained_requests_host_appearance_query() { let gained = parse_raw_input_bytes_sync(b"\x1b[I");