fix(tui): avoid flaky keyboard enhancement on Windows Terminal (fixes #514)

This commit is contained in:
James Peter
2026-01-13 22:48:21 +10:00
parent 667966f875
commit fd8d7ece99
2 changed files with 80 additions and 11 deletions
+2 -1
View File
@@ -101,7 +101,8 @@ impl App<'_> {
let post_frame_redraw = Arc::new(AtomicBool::new(false));
let frame_timer = Arc::new(FrameTimer::new());
let enhanced_keys_supported = supports_keyboard_enhancement().unwrap_or(false);
let enhanced_keys_supported = supports_keyboard_enhancement().unwrap_or(false)
&& crate::tui::should_enable_keyboard_enhancement();
// Spawn a dedicated thread for reading the crossterm event loop and
// re-publishing the events as AppEvents, as appropriate.
+78 -10
View File
@@ -106,12 +106,11 @@ pub fn init(config: &Config) -> Result<(Tui, TerminalInfo)> {
let terminal_info = query_terminal_info();
enable_raw_mode()?;
// Enable keyboard enhancement flags only when supported. On some Windows 10
// consoles/environments, attempting to push these flags can interfere with
// input delivery (reported as a freeze where keypresses don’t register).
// We already normalize key kinds when enhancement is unsupported elsewhere,
// so it’s safe to skip enabling here.
if supports_keyboard_enhancement().unwrap_or(false) {
// Enable keyboard enhancement flags only when supported *and* the current
// terminal environment is known to handle them reliably. Some Windows
// terminal stacks (including WSL/ConPTY-derived PTYs) can report support
// but still deliver broken input when these flags are enabled.
if supports_keyboard_enhancement().unwrap_or(false) && should_enable_keyboard_enhancement() {
let _ = execute!(
stdout(),
PushKeyboardEnhancementFlags(
@@ -121,7 +120,7 @@ pub fn init(config: &Config) -> Result<(Tui, TerminalInfo)> {
)
);
} else {
tracing::info!("Keyboard enhancement flags not supported; skipping enable.");
tracing::info!("Keyboard enhancement flags unsupported or disabled; skipping enable.");
}
set_panic_hook();
@@ -229,7 +228,7 @@ pub fn restore() -> Result<()> {
// Belt-and-suspenders: on terminals that do not maintain a clean stack,
// explicitly set enhancement flags to empty, then pop again. This avoids
// leaving kitty/xterm enhanced keyboard protocols active after exit.
if supports_keyboard_enhancement().unwrap_or(false) {
if supports_keyboard_enhancement().unwrap_or(false) && should_enable_keyboard_enhancement() {
let _ = execute!(stdout(), PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::empty()));
let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
}
@@ -261,7 +260,7 @@ pub fn leave_alt_screen_only() -> Result<()> {
// Pop keyboard enhancement flags so keys like Enter/Arrows don't emit
// enhanced escape sequences (e.g., kitty/xterm modifyOtherKeys) into the buffer.
let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
if supports_keyboard_enhancement().unwrap_or(false) {
if supports_keyboard_enhancement().unwrap_or(false) && should_enable_keyboard_enhancement() {
let _ = execute!(stdout(), PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::empty()));
let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
}
@@ -273,7 +272,7 @@ pub fn leave_alt_screen_only() -> Result<()> {
/// Restores title and colors and performs a full clear to ensure a clean frame.
pub fn enter_alt_screen_only(theme_fg: ratatui::style::Color, theme_bg: ratatui::style::Color) -> Result<()> {
// Re-enable enhanced keyboard and focus/paste signaling for full TUI fidelity.
if supports_keyboard_enhancement().unwrap_or(false) {
if supports_keyboard_enhancement().unwrap_or(false) && should_enable_keyboard_enhancement() {
let _ = execute!(
stdout(),
PushKeyboardEnhancementFlags(
@@ -397,3 +396,72 @@ fn should_enable_focus_change() -> bool {
// Default: enabled for modern terminals (xterm-256color, iTerm2, Alacritty, kitty, tmux, etc.)
true
}
pub(crate) fn should_enable_keyboard_enhancement() -> bool {
// Hard overrides first
if env::var("CODE_DISABLE_KBD_ENHANCEMENT")
.map(|v| v == "1")
.unwrap_or(false)
{
return false;
}
if env::var("CODE_ENABLE_KBD_ENHANCEMENT")
.map(|v| v == "1")
.unwrap_or(false)
{
return true;
}
// Windows Terminal + ConPTY/WSL stacks are a frequent source of flaky input
// when keyboard enhancement flags are enabled.
if env::var("WT_SESSION").is_ok() {
return false;
}
// Git Bash / MSYS2 frequently struggles with these sequences.
if env::var("MSYSTEM").is_ok() {
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::should_enable_keyboard_enhancement;
#[test]
fn keyboard_enhancement_respects_env_overrides() {
let prev_wt_session = std::env::var("WT_SESSION").ok();
let prev_enable = std::env::var("CODE_ENABLE_KBD_ENHANCEMENT").ok();
let prev_disable = std::env::var("CODE_DISABLE_KBD_ENHANCEMENT").ok();
unsafe {
std::env::set_var("WT_SESSION", "1");
std::env::remove_var("CODE_ENABLE_KBD_ENHANCEMENT");
std::env::remove_var("CODE_DISABLE_KBD_ENHANCEMENT");
}
assert!(!should_enable_keyboard_enhancement());
unsafe {
std::env::set_var("CODE_ENABLE_KBD_ENHANCEMENT", "1");
}
assert!(should_enable_keyboard_enhancement());
unsafe {
std::env::set_var("CODE_DISABLE_KBD_ENHANCEMENT", "1");
}
assert!(!should_enable_keyboard_enhancement());
restore_env("WT_SESSION", prev_wt_session);
restore_env("CODE_ENABLE_KBD_ENHANCEMENT", prev_enable);
restore_env("CODE_DISABLE_KBD_ENHANCEMENT", prev_disable);
}
fn restore_env(key: &str, value: Option<String>) {
match value {
Some(value) => unsafe { std::env::set_var(key, value) },
None => unsafe { std::env::remove_var(key) },
}
}
}