diff options
| author | divens <divens@users.noreply.github.com> | 2026-07-07 14:41:44 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-07 14:41:44 +0200 |
| commit | 39a75c54934617ef9d38fe02bfab777291e39d7e (patch) | |
| tree | fa784ed52e78a6c29db27d2318a5baf9b097d2ad /zellij-client | |
| parent | UI tweaks (#5337) (diff) | |
| download | sl0ppij-39a75c54934617ef9d38fe02bfab777291e39d7e.tar.gz sl0ppij-39a75c54934617ef9d38fe02bfab777291e39d7e.tar.xz sl0ppij-39a75c54934617ef9d38fe02bfab777291e39d7e.zip | |
fix(windows): route Windows Terminal through the VT input path (#5335)
Windows Terminal does not set TERM by default, so zellij on WT was
using the native-console crossterm path that reads INPUT_RECORDs.
That path can't observe bracketed-paste sequences (`\e[200~/\e[201~`),
which is one of the reasons multi-line paste has never worked
correctly on Windows (issue #4885).
WT sets `WT_SESSION` per-process. Use that as a second trigger for
the VT input path so default WT installs get raw VT byte input (and
with it, termwiz's bracketed-paste detection, the SGR mouse format,
correct emoji delivery, etc.) without requiring the user to set TERM.
Introduce `use_vt_path()` in `os_input_output_windows.rs` as the
single source of truth for the gate. Previously the stdin handler
had one TERM check and `enable_mouse_support` / `disable_mouse_support`
each had their own — so adding `WT_SESSION` to just the stdin gate
caused mouse setup to take the native-console branch, which calls
`crossterm::event::EnableMouseCapture` and clobbers the
`ENABLE_VIRTUAL_TERMINAL_INPUT` flag the stdin path had just set.
That broke all input from WT until the helper aligned every site.
Diffstat (limited to 'zellij-client')
| -rw-r--r-- | zellij-client/src/os_input_output_windows.rs | 28 | ||||
| -rw-r--r-- | zellij-client/src/stdin_handler.rs | 18 |
2 files changed, 32 insertions, 14 deletions
diff --git a/zellij-client/src/os_input_output_windows.rs b/zellij-client/src/os_input_output_windows.rs index 293c06b5..a152fa81 100644 --- a/zellij-client/src/os_input_output_windows.rs +++ b/zellij-client/src/os_input_output_windows.rs @@ -9,6 +9,30 @@ use std::io::Write; use std::path::Path; use zellij_utils::ipc::{IpcReceiverWithContext, IpcSenderWithContext}; +/// Whether zellij should use the VT byte path on Windows (raw stdin via +/// `ReadFile` + termwiz/kitty parsing) instead of the native-console path +/// (crossterm `INPUT_RECORD`s). +/// +/// True when either of these environment variables is set: +/// +/// * `TERM` — set automatically by terminal emulators that expose a Unix-style +/// environment (Alacritty, WezTerm, WSL sessions inheriting bash's env), or +/// set manually by users who want zellij on the VT path (e.g. adding +/// `TERM=xterm-256color` to a Windows Terminal / PowerShell profile). +/// * `WT_SESSION` — set per-session by Windows Terminal itself. Detecting it +/// means default WT installs land on the VT path without the user having to +/// set `TERM`, so they get bracketed paste, kitty keyboard, SGR mouse, and +/// the other features that depend on raw VT input. +/// +/// Every VT-path-conditional code site (stdin handler, mouse mode setup, +/// future input-related code) should call this so the gates can't drift — +/// when they did drift, mouse setup using `crossterm::EnableMouseCapture` +/// clobbered the `ENABLE_VIRTUAL_TERMINAL_INPUT` flag the stdin path had +/// just set and broke all input from WT. +pub(crate) fn use_vt_path() -> bool { + std::env::var("TERM").is_ok() || std::env::var("WT_SESSION").is_ok() +} + /// Windows async signal listener. /// /// Polls `crossterm::terminal::size()` at 100ms intervals for resize events, @@ -206,7 +230,7 @@ fn enable_vt_processing_on_stdout() { /// Windows Terminal) and use crossterm's Console API approach. pub(crate) fn enable_mouse_support(stdout: &mut dyn Write) -> Result<()> { let err_context = "failed to enable mouse mode"; - if std::env::var("TERM").is_ok() { + if use_vt_path() { enable_vt_processing_on_stdout(); stdout .write_all(super::os_input_output::ENABLE_MOUSE_SUPPORT.as_bytes()) @@ -236,7 +260,7 @@ pub(crate) fn restore_console_mode() { /// See `enable_mouse_support()` for rationale on VT vs Console API paths. pub(crate) fn disable_mouse_support(stdout: &mut dyn Write) -> Result<()> { let err_context = "failed to disable mouse mode"; - if std::env::var("TERM").is_ok() { + if use_vt_path() { stdout .write_all(super::os_input_output::DISABLE_MOUSE_SUPPORT.as_bytes()) .context(err_context)?; diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs index 26418188..535d9b67 100644 --- a/zellij-client/src/stdin_handler.rs +++ b/zellij-client/src/stdin_handler.rs @@ -1,5 +1,7 @@ use crate::keyboard_parser::{KittyKeyboardParser, KittyParseOutcome}; use crate::os_input_output::ClientOsApi; +#[cfg(windows)] +use crate::os_input_output_windows::use_vt_path; use crate::stdin_ansi_parser::{PendingPartial, StdinAnsiParser}; #[cfg(windows)] use crate::stdin_handler_windows::enable_vt_input; @@ -21,19 +23,11 @@ pub(crate) fn stdin_loop( explicitly_disable_kitty_keyboard_protocol: bool, resize_sender: Option<std::sync::mpsc::Sender<()>>, ) { - // On Windows, choose between two input strategies early — we need this - // decision before the startup ANSI query below. - // - // 1. Native console (no TERM env var): Use crossterm's event::read() which - // reads INPUT_RECORDs via ReadConsoleInput. Works in cmd.exe, PowerShell, - // and Windows Terminal where ALT is reported as a modifier flag. - // - // 2. Terminal emulator (TERM is set, e.g. Alacritty): Enable - // ENABLE_VIRTUAL_TERMINAL_INPUT so ReadFile on stdin returns raw VT bytes, - // bypassing conpty's lossy VT→INPUT_RECORD translation. Then use the - // termwiz byte parser (same as Unix) which understands ESC-prefixed ALT. + // On Windows we choose between the VT byte path (termwiz/kitty parsing) + // and the native-console path (crossterm INPUT_RECORDs) early, before the + // startup ANSI query below. See `use_vt_path()` for the trigger conditions. #[cfg(windows)] - let use_vt_reader = std::env::var("TERM").is_ok() && enable_vt_input(); + let use_vt_reader = use_vt_path() && enable_vt_input(); // Send the startup host query string so the host terminal replies // with its live pixel dimensions, fg/bg, sync-output support, and |
