diff options
| author | JJ Liebig <jonathan.liebig@gmail.com> | 2026-08-31 21:22:56 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-31 21:22:56 +0200 |
| commit | ef2674bab8a3b38984578473c1a80589ebcbb333 (patch) | |
| tree | 2671b283d352158726ab06d52a6aec607ce98014 /src | |
| parent | d2cb0961663582a5069b1528edb93a9a8b23e1bc (diff) | |
fix(windows): enable mouse reporting over openssh (#3461)
refs #2810
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/mod.rs | 57 | ||||
| -rw-r--r-- | src/terminal_modes.rs | 42 |
2 files changed, 77 insertions, 22 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs index a5493ca2..53e818d7 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -373,12 +373,18 @@ fn setup_terminal_with_capabilities( let host_color_scheme_reports = should_enable_host_color_scheme_reports(enable_client_protocols); - if enable_client_protocols { - if mouse_capture { - set_mouse_capture(true, false)?; + #[cfg(windows)] + let windows_ssh_session = is_ssh_session(); + #[cfg(windows)] + let mut windows_virtual_terminal_input = + if windows_vti_input_backend_enabled() && windows_ssh_session { + enable_windows_virtual_terminal_input() } else { - set_mouse_capture(false, false)?; - } + WindowsVirtualTerminalInputSetup::default() + }; + + if enable_client_protocols { + set_mouse_capture(mouse_capture, false)?; execute!(io::stdout(), EnableBracketedPaste, EnableFocusChange)?; if host_color_scheme_reports { write_host_color_scheme_report_mode(&mut io::stdout(), true)?; @@ -388,21 +394,14 @@ fn setup_terminal_with_capabilities( if should_query_host_terminal_theme() { write_host_color_scheme_report_mode(&mut io::stdout(), false)?; } - if mouse_capture { - set_mouse_capture(true, false)?; - } else { - set_mouse_capture(false, false)?; - } + set_mouse_capture(mouse_capture, false)?; execute!(io::stdout(), EnableBracketedPaste)?; } #[cfg(windows)] - let windows_virtual_terminal_input = - if enable_client_protocols && windows_vti_input_backend_enabled() { - enable_windows_virtual_terminal_input() - } else { - WindowsVirtualTerminalInputSetup::default() - }; + if enable_client_protocols && windows_vti_input_backend_enabled() && !windows_ssh_session { + windows_virtual_terminal_input = enable_windows_virtual_terminal_input(); + } #[cfg(windows)] if enable_client_protocols @@ -573,6 +572,14 @@ fn restore_windows_input_mode_value(mode: u32) { fn set_mouse_capture(enabled: bool, sgr_pixels: bool) -> io::Result<()> { crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout())?; + #[cfg(windows)] + if is_ssh_session() && windows_vti_input_backend_enabled() { + return crate::terminal_modes::set_windows_ssh_mouse_reporting( + &mut io::stdout(), + enabled, + sgr_pixels, + ); + } if enabled { execute!(io::stdout(), EnableMouseCapture)?; if sgr_pixels { @@ -609,10 +616,9 @@ fn restore_terminal_state( io::stdout(), EnableLineWrap, DisableFocusChange, - DisableBracketedPaste, - DisableMouseCapture + DisableBracketedPaste ); - let _ = crate::terminal_modes::clear_host_mouse_reporting(&mut io::stdout()); + let _ = set_mouse_capture(false, false); #[cfg(windows)] if let Some(mode) = restore_windows_input_mode { restore_windows_input_mode_value(mode); @@ -712,6 +718,10 @@ fn is_remote_client_process() -> bool { std::env::var(crate::remote::REMOTE_KEYBINDINGS_ENV_VAR).is_ok() } +fn is_ssh_session() -> bool { + std::env::var_os("SSH_CONNECTION").is_some() || std::env::var_os("SSH_TTY").is_some() +} + /// Time to wait for the server's Welcome reply during the handshake. /// /// A local client talks to an already-connected server, so 5s is plenty. The @@ -754,8 +764,7 @@ fn direct_graphics_profile_allowed(direct_attach: bool) -> bool { std::env::var_os("KITTY_WINDOW_ID").is_some(), direct_attach || is_remote_client_process() - || std::env::var_os("SSH_CONNECTION").is_some() - || std::env::var_os("SSH_TTY").is_some() + || is_ssh_session() || std::env::var_os("TMUX").is_some() || std::env::var_os("STY").is_some(), io::stdin().is_terminal() && io::stdout().is_terminal(), @@ -1868,10 +1877,14 @@ async fn run_client_loop( let mouse_mode_changed = enabled != state.mouse_capture_active || next_sgr_pixels != host_sgr_pixels_active.load(Ordering::Acquire); if mouse_mode_changed { + #[cfg(windows)] + if enabled && windows_vti_input_backend_enabled() && is_ssh_session() { + let _ = enable_windows_virtual_terminal_input(); + } set_mouse_capture(enabled, next_sgr_pixels) .map_err(ClientError::ConnectionFailed)?; #[cfg(windows)] - if enabled && windows_vti_input_backend_enabled() { + if enabled && windows_vti_input_backend_enabled() && !is_ssh_session() { let _ = enable_windows_virtual_terminal_input(); } } diff --git a/src/terminal_modes.rs b/src/terminal_modes.rs index 5b6a5d3b..c9f08ceb 100644 --- a/src/terminal_modes.rs +++ b/src/terminal_modes.rs @@ -6,6 +6,13 @@ use crossterm::event::{PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags const DISABLE_HOST_MOUSE_REPORTING_SEQUENCE: &[u8] = b"\x1b[?1006l\x1b[?1016l\x1b[?1015l\x1b[?1005l\x1b[?1003l\x1b[?1002l\x1b[?1000l"; +#[cfg(any(windows, test))] +const WINDOWS_SSH_MOUSE_REPORTING_ENABLE_SEQUENCE: &[u8] = + b"\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h"; +#[cfg(any(windows, test))] +const WINDOWS_SSH_MOUSE_REPORTING_DISABLE_SEQUENCE: &[u8] = + b"\x1b[?1016l\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l"; + #[cfg(not(windows))] pub(crate) fn clear_host_mouse_reporting<W: Write>(writer: &mut W) -> io::Result<()> { writer.write_all(DISABLE_HOST_MOUSE_REPORTING_SEQUENCE)?; @@ -17,6 +24,27 @@ pub(crate) fn clear_host_mouse_reporting<W: Write>(_writer: &mut W) -> io::Resul Ok(()) } +#[cfg(any(windows, test))] +pub(crate) fn set_windows_ssh_mouse_reporting<W: Write>( + writer: &mut W, + enabled: bool, + sgr_pixels: bool, +) -> io::Result<()> { + writer.write_all(if enabled { + WINDOWS_SSH_MOUSE_REPORTING_ENABLE_SEQUENCE + } else { + WINDOWS_SSH_MOUSE_REPORTING_DISABLE_SEQUENCE + })?; + if enabled { + writer.write_all(if sgr_pixels { + b"\x1b[?1016h" + } else { + b"\x1b[?1016l" + })?; + } + writer.flush() +} + #[cfg(not(windows))] pub(crate) fn set_host_kitty_keyboard_report_all<W: Write>( writer: &mut W, @@ -73,4 +101,18 @@ mod tests { ); } } + + #[test] + fn windows_ssh_mouse_reporting_setup_and_teardown_request_required_modes() { + let mut output = Vec::new(); + + set_windows_ssh_mouse_reporting(&mut output, true, true).unwrap(); + set_windows_ssh_mouse_reporting(&mut output, true, false).unwrap(); + set_windows_ssh_mouse_reporting(&mut output, false, false).unwrap(); + + assert_eq!( + output, + b"\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h\x1b[?1016h\x1b[?1000h\x1b[?1002h\x1b[?1003h\x1b[?1006h\x1b[?1016l\x1b[?1016l\x1b[?1006l\x1b[?1003l\x1b[?1002l\x1b[?1000l" + ); + } } |
