Probe for kitty keyboard protocol support

We unconditionally request kitty keyboard protocol's progressive
enhancements.

It seems that a lot of terminals fail to parse CSI commands that
contain '=' such as \x1b[=5u.

1. [Midnight Commander](0ea77d2ec7)
2. Prompt 3 App (private bug tracker)
3. JetBrains IDEs https://youtrack.jetbrains.com/issue/IJPL-166234
4. Termux https://github.com/termux/termux-app/issues/4338
5. Amazon Linux Web Console https://github.com/amazonlinux/amazon-linux-2023/issues/871

It is difficult to fix the four remaining ones in a
timely manner, so let's query for support as described in
https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
This uses CSI 5 n (device status report), which is the older brother
of CSI 6 n (cursor position report) we use as of recently.

Query asynchronously and enable progressive enhancements as soon
as we get a response. In theory this allow `cat malicious-file.txt`
leading us to believe the protocol is supported.

See #10994
This commit is contained in:
Johannes Altmanninger 2025-01-03 22:19:41 +01:00
parent edfdf210c4
commit e49dde87cc

View file

@ -24,7 +24,7 @@ use std::ops::ControlFlow;
use std::os::fd::RawFd; use std::os::fd::RawFd;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::ptr; use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering, Ordering::Relaxed};
// The range of key codes for inputrc-style keyboard functions. // The range of key codes for inputrc-style keyboard functions.
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1; pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1;
@ -436,10 +436,23 @@ pub fn update_wait_on_sequence_key_ms(vars: &EnvStack) {
static TERMINAL_PROTOCOLS: AtomicBool = AtomicBool::new(false); static TERMINAL_PROTOCOLS: AtomicBool = AtomicBool::new(false);
#[repr(u8)]
enum Capability {
Unknown,
Supported,
NotSupported,
}
static KITTY_KEYBOARD_SUPPORTED: AtomicU8 = AtomicU8::new(Capability::Unknown as _);
macro_rules! kitty_progressive_enhancements {
() => {
"\x1b[=5u"
};
}
static IS_TMUX: RelaxedAtomicBool = RelaxedAtomicBool::new(false); static IS_TMUX: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
pub static IN_MIDNIGHT_COMMANDER_PRE_CSI_U: RelaxedAtomicBool = RelaxedAtomicBool::new(false); pub static IN_MIDNIGHT_COMMANDER_PRE_CSI_U: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
static IN_ITERM_PRE_CSI_U: RelaxedAtomicBool = RelaxedAtomicBool::new(false); static IN_ITERM_PRE_CSI_U: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
static IN_JETBRAINS: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
pub fn terminal_protocol_hacks() { pub fn terminal_protocol_hacks() {
use std::env::var_os; use std::env::var_os;
@ -454,10 +467,8 @@ pub fn terminal_protocol_hacks() {
version < (3, 5, 6) version < (3, 5, 6)
}), }),
); );
IN_JETBRAINS.store( // Request kitty progressive enhancement value and primary device attribute.
var_os("TERMINAL_EMULATOR") let _ = write_loop(&STDOUT_FILENO, b"\x1b[?u\x1b[5n");
.is_some_and(|term| term.as_os_str().as_bytes() == b"JetBrains-JediTerm"),
);
} }
fn parse_version(version: &wstr) -> Option<(i64, i64, i64)> { fn parse_version(version: &wstr) -> Option<(i64, i64, i64)> {
@ -488,14 +499,13 @@ pub fn terminal_protocols_enable_ifn() {
"\x1b[?2004h" "\x1b[?2004h"
} else if IN_ITERM_PRE_CSI_U.load() { } else if IN_ITERM_PRE_CSI_U.load() {
concat!("\x1b[?2004h", "\x1b[>4;1m", "\x1b[>5u", "\x1b=",) concat!("\x1b[?2004h", "\x1b[>4;1m", "\x1b[>5u", "\x1b=",)
} else if IN_JETBRAINS.load() { } else if KITTY_KEYBOARD_SUPPORTED.load(Relaxed) != Capability::Supported as _ {
// Jetbrains IDE terminals vomit CSI u
concat!("\x1b[?2004h", "\x1b[>4;1m", "\x1b=",) concat!("\x1b[?2004h", "\x1b[>4;1m", "\x1b=",)
} else { } else {
concat!( concat!(
"\x1b[?2004h", // Bracketed paste "\x1b[?2004h", // Bracketed paste
"\x1b[>4;1m", // XTerm's modifyOtherKeys "\x1b[>4;1m", // XTerm's modifyOtherKeys
"\x1b[=5u", // CSI u with kitty progressive enhancement kitty_progressive_enhancements!(),
"\x1b=", // set application keypad mode, so the keypad keys send unique codes "\x1b=", // set application keypad mode, so the keypad keys send unique codes
) )
}; };
@ -513,7 +523,7 @@ pub(crate) fn terminal_protocols_disable_ifn() {
} }
let sequences = if IN_ITERM_PRE_CSI_U.load() { let sequences = if IN_ITERM_PRE_CSI_U.load() {
concat!("\x1b[?2004l", "\x1b[>4;0m", "\x1b[<1u", "\x1b>",) concat!("\x1b[?2004l", "\x1b[>4;0m", "\x1b[<1u", "\x1b>",)
} else if IN_JETBRAINS.load() { } else if KITTY_KEYBOARD_SUPPORTED.load(Relaxed) != Capability::Supported as _ {
concat!("\x1b[?2004l", "\x1b[>4;0m", "\x1b>",) concat!("\x1b[?2004l", "\x1b[>4;0m", "\x1b>",)
} else { } else {
concat!( concat!(
@ -1086,6 +1096,21 @@ pub trait InputEventQueuer {
_ => return None, _ => return None,
}, },
b'u' => { b'u' => {
if private_mode == Some(b'?') {
FLOG!(
reader,
"Received kitty progressive enhancement flags, marking as supported"
);
KITTY_KEYBOARD_SUPPORTED.store(Capability::Supported as _, Relaxed);
if !IN_MIDNIGHT_COMMANDER_PRE_CSI_U.load() && !IN_ITERM_PRE_CSI_U.load() {
let _ = write_loop(
&STDOUT_FILENO,
kitty_progressive_enhancements!().as_bytes(),
);
}
return None;
}
// Treat numpad keys the same as their non-numpad counterparts. Could add a numpad modifier here. // Treat numpad keys the same as their non-numpad counterparts. Could add a numpad modifier here.
let key = match params[0][0] { let key = match params[0][0] {
57399 => '0', 57399 => '0',
@ -1133,6 +1158,16 @@ pub trait InputEventQueuer {
self.push_front(CharEvent::Implicit(ImplicitEvent::FocusOut)); self.push_front(CharEvent::Implicit(ImplicitEvent::FocusOut));
return None; return None;
} }
b'n' => {
if KITTY_KEYBOARD_SUPPORTED.load(Relaxed) == Capability::Unknown as _ {
FLOG!(
reader,
"Did not receive kitty progressive enhancement flags, marking as unsupported"
);
KITTY_KEYBOARD_SUPPORTED.store(Capability::NotSupported as _, Relaxed);
}
return None;
}
_ => return None, _ => return None,
}; };
Some(key) Some(key)