aboutsummaryrefslogtreecommitdiff
path: root/src/client/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/mod.rs')
-rw-r--r--src/client/mod.rs162
1 files changed, 161 insertions, 1 deletions
diff --git a/src/client/mod.rs b/src/client/mod.rs
index 834edd2b..8845e446 100644
--- a/src/client/mod.rs
+++ b/src/client/mod.rs
@@ -354,6 +354,28 @@ fn setup_terminal_with_capabilities(
}
}
+ #[cfg(windows)]
+ let windows_virtual_terminal_input =
+ if enable_client_protocols && windows_vti_input_backend_enabled() {
+ enable_windows_virtual_terminal_input()
+ } else {
+ WindowsVirtualTerminalInputSetup::default()
+ };
+
+ #[cfg(windows)]
+ if enable_client_protocols
+ && windows_vti_input_backend_enabled()
+ && windows_virtual_terminal_input.active
+ && windows_win32_input_mode_enabled()
+ {
+ if let Err(err) = enable_windows_win32_input_mode(&mut io::stdout()) {
+ if let Some(mode) = windows_virtual_terminal_input.restore_mode {
+ restore_windows_input_mode_value(mode);
+ }
+ return Err(err);
+ }
+ }
+
let modify_other_keys_mode = enable_client_protocols
.then(|| {
crate::input::host_modify_other_keys_mode(
@@ -371,6 +393,8 @@ fn setup_terminal_with_capabilities(
Ok(TerminalGuard {
reset_modify_other_keys: modify_other_keys_mode.is_some(),
reset_host_color_scheme_reports: host_color_scheme_reports,
+ #[cfg(windows)]
+ restore_windows_input_mode: windows_virtual_terminal_input.restore_mode,
})
}
@@ -382,6 +406,8 @@ fn should_enable_host_color_scheme_reports(enable_client_protocols: bool) -> boo
struct TerminalGuard {
reset_modify_other_keys: bool,
reset_host_color_scheme_reports: bool,
+ #[cfg(windows)]
+ restore_windows_input_mode: Option<u32>,
}
fn write_host_color_scheme_report_mode(
@@ -411,6 +437,90 @@ fn write_terminal_restore_postlude(
writer.flush()
}
+#[cfg(windows)]
+#[derive(Default)]
+struct WindowsVirtualTerminalInputSetup {
+ active: bool,
+ restore_mode: Option<u32>,
+}
+
+#[cfg(windows)]
+fn enable_windows_virtual_terminal_input() -> WindowsVirtualTerminalInputSetup {
+ use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
+ use windows_sys::Win32::System::Console::{
+ GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_VIRTUAL_TERMINAL_INPUT,
+ STD_INPUT_HANDLE,
+ };
+
+ let handle: HANDLE = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
+ if handle.is_null() || handle == INVALID_HANDLE_VALUE {
+ tracing::warn!("failed to get Windows console input handle for VT input");
+ return WindowsVirtualTerminalInputSetup::default();
+ }
+
+ let mut mode = 0;
+ if unsafe { GetConsoleMode(handle, &mut mode) } == 0 {
+ tracing::warn!("failed to read Windows console input mode for VT input");
+ return WindowsVirtualTerminalInputSetup::default();
+ }
+
+ let desired = windows_virtual_terminal_input_mode(mode);
+ if desired == mode {
+ return WindowsVirtualTerminalInputSetup {
+ active: true,
+ restore_mode: None,
+ };
+ }
+
+ if unsafe { SetConsoleMode(handle, desired) } == 0 {
+ tracing::warn!("failed to enable Windows virtual terminal input");
+ return WindowsVirtualTerminalInputSetup::default();
+ }
+
+ let mut applied = 0;
+ if unsafe { GetConsoleMode(handle, &mut applied) } == 0 {
+ tracing::warn!("failed to verify Windows virtual terminal input mode");
+ let _ = unsafe { SetConsoleMode(handle, mode) };
+ return WindowsVirtualTerminalInputSetup::default();
+ }
+ if applied & ENABLE_VIRTUAL_TERMINAL_INPUT == 0 {
+ tracing::warn!("Windows virtual terminal input bit did not stick");
+ let _ = unsafe { SetConsoleMode(handle, mode) };
+ return WindowsVirtualTerminalInputSetup::default();
+ }
+
+ WindowsVirtualTerminalInputSetup {
+ active: true,
+ restore_mode: Some(mode),
+ }
+}
+
+#[cfg(windows)]
+fn windows_vti_input_backend_enabled() -> bool {
+ std::env::var("HERDR_WINDOWS_INPUT_BACKEND")
+ .map(|backend| !backend.eq_ignore_ascii_case("crossterm"))
+ .unwrap_or(true)
+}
+
+#[cfg(any(windows, test))]
+fn windows_virtual_terminal_input_mode(mode: u32) -> u32 {
+ mode | 0x0200
+}
+
+#[cfg(windows)]
+fn restore_windows_input_mode_value(mode: u32) {
+ use windows_sys::Win32::Foundation::{HANDLE, INVALID_HANDLE_VALUE};
+ use windows_sys::Win32::System::Console::{GetStdHandle, SetConsoleMode, STD_INPUT_HANDLE};
+
+ let handle: HANDLE = unsafe { GetStdHandle(STD_INPUT_HANDLE) };
+ if handle.is_null() || handle == INVALID_HANDLE_VALUE {
+ return;
+ }
+ if unsafe { SetConsoleMode(handle, mode) } == 0 {
+ tracing::warn!("failed to restore Windows console input mode");
+ }
+}
+
fn set_mouse_capture(enabled: bool) -> io::Result<()> {
if enabled {
execute!(io::stdout(), EnableMouseCapture)
@@ -424,7 +534,11 @@ fn set_mouse_capture(enabled: bool) -> io::Result<()> {
}
}
-fn restore_terminal_state(reset_modify_other_keys: bool, reset_host_color_scheme_reports: bool) {
+fn restore_terminal_state(
+ reset_modify_other_keys: bool,
+ reset_host_color_scheme_reports: bool,
+ #[cfg(windows)] restore_windows_input_mode: Option<u32>,
+) {
let _ = clear_received_kitty_graphics(&mut io::stdout());
// Reset modifyOtherKeys if we enabled it.
@@ -434,14 +548,25 @@ fn restore_terminal_state(reset_modify_other_keys: bool, reset_host_color_scheme
}
let _ = pop_keyboard_enhancement_flags();
+
let _ = execute!(
io::stdout(),
DisableFocusChange,
DisableBracketedPaste,
DisableMouseCapture
);
+ #[cfg(windows)]
+ if let Some(mode) = restore_windows_input_mode {
+ restore_windows_input_mode_value(mode);
+ }
+
ratatui::restore();
let _ = write_terminal_restore_postlude(&mut io::stdout(), reset_host_color_scheme_reports);
+
+ #[cfg(windows)]
+ if windows_vti_input_backend_enabled() && windows_win32_input_mode_enabled() {
+ let _ = disable_windows_win32_input_mode(&mut io::stdout());
+ }
}
#[cfg(not(windows))]
@@ -467,11 +592,32 @@ fn pop_keyboard_enhancement_flags() -> io::Result<()> {
Ok(())
}
+#[cfg(windows)]
+fn windows_win32_input_mode_enabled() -> bool {
+ std::env::var("HERDR_WINDOWS_INPUT_PROBE")
+ .map(|probe| probe.eq_ignore_ascii_case("win32"))
+ .unwrap_or(true)
+}
+
+#[cfg(windows)]
+fn enable_windows_win32_input_mode(writer: &mut impl std::io::Write) -> io::Result<()> {
+ writer.write_all(b"\x1b[?9001h")?;
+ writer.flush()
+}
+
+#[cfg(windows)]
+fn disable_windows_win32_input_mode(writer: &mut impl std::io::Write) -> io::Result<()> {
+ writer.write_all(b"\x1b[?9001l")?;
+ writer.flush()
+}
+
impl Drop for TerminalGuard {
fn drop(&mut self) {
restore_terminal_state(
self.reset_modify_other_keys,
self.reset_host_color_scheme_reports,
+ #[cfg(windows)]
+ self.restore_windows_input_mode,
);
}
}
@@ -745,11 +891,15 @@ fn run_client_with_mode(
// Install a panic hook to restore the terminal on panic (same as monolithic).
let panic_resets_modify_other_keys = terminal_guard.reset_modify_other_keys;
let panic_resets_host_color_scheme_reports = terminal_guard.reset_host_color_scheme_reports;
+ #[cfg(windows)]
+ let panic_restore_windows_input_mode = terminal_guard.restore_windows_input_mode;
let original_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
restore_terminal_state(
panic_resets_modify_other_keys,
panic_resets_host_color_scheme_reports,
+ #[cfg(windows)]
+ panic_restore_windows_input_mode,
);
original_hook(info);
}));
@@ -1072,6 +1222,10 @@ async fn run_client_loop(
let desired = enabled;
if desired != state.mouse_capture_active {
set_mouse_capture(desired).map_err(ClientError::ConnectionFailed)?;
+ #[cfg(windows)]
+ if windows_vti_input_backend_enabled() {
+ let _ = enable_windows_virtual_terminal_input();
+ }
state.mouse_capture_active = desired;
}
}
@@ -1543,6 +1697,12 @@ mod tests {
}
}
+ #[test]
+ fn windows_virtual_terminal_input_mode_sets_only_vti_bit() {
+ assert_eq!(windows_virtual_terminal_input_mode(0x01f0), 0x03f0);
+ assert_eq!(windows_virtual_terminal_input_mode(0x03f0), 0x03f0);
+ }
+
struct EnvVarsRemovedGuard {
previous: Vec<(&'static str, Option<OsString>)>,
}