fix(client): re-assert host mouse modes after a terminal reconnect (#4079)

Co-authored-by: dengos <dengoswei@gmail.com>
This commit is contained in:
JJ Liebig
2026-09-14 02:42:21 +03:00
committed by GitHub
co-authored by dengos
parent aa961df943
commit 247f6d1f82
3 changed files with 46 additions and 3 deletions
+27 -1
View File
@@ -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);
+2 -2
View File
@@ -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<RawInputEvent>) -> ClientShellInput {
pub(crate) fn handle_raw_events(&mut self, events: Vec<RawInputEvent>) -> ClientShellInput {
let mut outcome = ClientShellInput::default();
if !events.is_empty() && self.endpoint_error.take().is_some() {
self.endpoint_error_deadline = None;
+17
View File
@@ -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");