aboutsummaryrefslogtreecommitdiff
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2023-12-06 09:26:07 +0400
committerGitHub <noreply@github.com>2023-12-06 09:26:07 +0400
commitcb03806e2ab85674c45e87e1bb24dfe2fd1a918c (patch)
tree3561fc6785281fb3a963c199fe9a12df4007bed7 /alacritty_terminal
parent7c9d9f3b166f2aade76d35408b5acb5d3ccd1c94 (diff)
downloadalacritty-cb03806e2ab85674c45e87e1bb24dfe2fd1a918c.tar.gz
alacritty-cb03806e2ab85674c45e87e1bb24dfe2fd1a918c.zip
Implement kitty's keyboard protocol
The protocol enables robust key reporting for the applications, so they could bind more keys and the user won't have collisions with the normal control keys. Links: https://sw.kovidgoyal.net/kitty/keyboard-protocol Fixes #6378.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/src/term/mod.rs182
1 files changed, 157 insertions, 25 deletions
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index eeecc50d..2553270c 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -21,8 +21,9 @@ use crate::term::cell::{Cell, Flags, LineLength};
use crate::term::color::Colors;
use crate::vi_mode::{ViModeCursor, ViMotion};
use crate::vte::ansi::{
- self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, Hyperlink, NamedColor,
- NamedMode, NamedPrivateMode, PrivateMode, Rgb, StandardCharset,
+ self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, Hyperlink, KeyboardModes,
+ KeyboardModesApplyBehavior, NamedColor, NamedMode, NamedPrivateMode, PrivateMode, Rgb,
+ StandardCharset,
};
pub mod cell;
@@ -43,33 +44,69 @@ const TITLE_STACK_MAX_DEPTH: usize = 4096;
/// Default semantic escape characters.
pub const SEMANTIC_ESCAPE_CHARS: &str = ",│`|:\"' ()[]{}<>\t";
+/// Max size of the keyboard modes.
+const KEYBOARD_MODE_STACK_MAX_DEPTH: usize = TITLE_STACK_MAX_DEPTH;
+
/// Default tab interval, corresponding to terminfo `it` value.
const INITIAL_TABSTOPS: usize = 8;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TermMode: u32 {
- const NONE = 0;
- const SHOW_CURSOR = 0b0000_0000_0000_0000_0001;
- const APP_CURSOR = 0b0000_0000_0000_0000_0010;
- const APP_KEYPAD = 0b0000_0000_0000_0000_0100;
- const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_1000;
- const BRACKETED_PASTE = 0b0000_0000_0000_0001_0000;
- const SGR_MOUSE = 0b0000_0000_0000_0010_0000;
- const MOUSE_MOTION = 0b0000_0000_0000_0100_0000;
- const LINE_WRAP = 0b0000_0000_0000_1000_0000;
- const LINE_FEED_NEW_LINE = 0b0000_0000_0001_0000_0000;
- const ORIGIN = 0b0000_0000_0010_0000_0000;
- const INSERT = 0b0000_0000_0100_0000_0000;
- const FOCUS_IN_OUT = 0b0000_0000_1000_0000_0000;
- const ALT_SCREEN = 0b0000_0001_0000_0000_0000;
- const MOUSE_DRAG = 0b0000_0010_0000_0000_0000;
- const MOUSE_MODE = 0b0000_0010_0000_0100_1000;
- const UTF8_MOUSE = 0b0000_0100_0000_0000_0000;
- const ALTERNATE_SCROLL = 0b0000_1000_0000_0000_0000;
- const VI = 0b0001_0000_0000_0000_0000;
- const URGENCY_HINTS = 0b0010_0000_0000_0000_0000;
- const ANY = u32::MAX;
+ const NONE = 0;
+ const SHOW_CURSOR = 0b0000_0000_0000_0000_0000_0001;
+ const APP_CURSOR = 0b0000_0000_0000_0000_0000_0010;
+ const APP_KEYPAD = 0b0000_0000_0000_0000_0000_0100;
+ const MOUSE_REPORT_CLICK = 0b0000_0000_0000_0000_0000_1000;
+ const BRACKETED_PASTE = 0b0000_0000_0000_0000_0001_0000;
+ const SGR_MOUSE = 0b0000_0000_0000_0000_0010_0000;
+ const MOUSE_MOTION = 0b0000_0000_0000_0000_0100_0000;
+ const LINE_WRAP = 0b0000_0000_0000_0000_1000_0000;
+ const LINE_FEED_NEW_LINE = 0b0000_0000_0000_0001_0000_0000;
+ const ORIGIN = 0b0000_0000_0000_0010_0000_0000;
+ const INSERT = 0b0000_0000_0000_0100_0000_0000;
+ const FOCUS_IN_OUT = 0b0000_0000_0000_1000_0000_0000;
+ const ALT_SCREEN = 0b0000_0000_0001_0000_0000_0000;
+ const MOUSE_DRAG = 0b0000_0000_0010_0000_0000_0000;
+ const MOUSE_MODE = 0b0000_0000_0010_0000_0100_1000;
+ const UTF8_MOUSE = 0b0000_0000_0100_0000_0000_0000;
+ const ALTERNATE_SCROLL = 0b0000_0000_1000_0000_0000_0000;
+ const VI = 0b0000_0001_0000_0000_0000_0000;
+ const URGENCY_HINTS = 0b0000_0010_0000_0000_0000_0000;
+ const DISAMBIGUATE_ESC_CODES = 0b0000_0100_0000_0000_0000_0000;
+ const REPORT_EVENT_TYPES = 0b0000_1000_0000_0000_0000_0000;
+ const REPORT_ALTERNATE_KEYS = 0b0001_0000_0000_0000_0000_0000;
+ const REPORT_ALL_KEYS_AS_ESC = 0b0010_0000_0000_0000_0000_0000;
+ const REPORT_ASSOCIATED_TEXT = 0b0100_0000_0000_0000_0000_0000;
+ const KITTY_KEYBOARD_PROTOCOL = Self::DISAMBIGUATE_ESC_CODES.bits()
+ | Self::REPORT_EVENT_TYPES.bits()
+ | Self::REPORT_ALTERNATE_KEYS.bits()
+ | Self::REPORT_ALL_KEYS_AS_ESC.bits()
+ | Self::REPORT_ASSOCIATED_TEXT.bits();
+ const ANY = u32::MAX;
+ }
+}
+
+impl From<KeyboardModes> for TermMode {
+ fn from(value: KeyboardModes) -> Self {
+ let mut mode = Self::empty();
+
+ let disambiguate_esc_codes = value.contains(KeyboardModes::DISAMBIGUATE_ESC_CODES);
+ mode.set(TermMode::DISAMBIGUATE_ESC_CODES, disambiguate_esc_codes);
+
+ let report_event_types = value.contains(KeyboardModes::REPORT_EVENT_TYPES);
+ mode.set(TermMode::REPORT_EVENT_TYPES, report_event_types);
+
+ let report_alternate_keys = value.contains(KeyboardModes::REPORT_ALTERNATE_KEYS);
+ mode.set(TermMode::REPORT_ALTERNATE_KEYS, report_alternate_keys);
+
+ let report_all_keys_as_esc = value.contains(KeyboardModes::REPORT_ALL_KEYS_AS_ESC);
+ mode.set(TermMode::REPORT_ALL_KEYS_AS_ESC, report_all_keys_as_esc);
+
+ let report_associated_text = value.contains(KeyboardModes::REPORT_ASSOCIATED_TEXT);
+ mode.set(TermMode::REPORT_ASSOCIATED_TEXT, report_associated_text);
+
+ mode
}
}
@@ -279,6 +316,12 @@ pub struct Term<T> {
/// term is set.
title_stack: Vec<Option<String>>,
+ /// The stack for the keyboard modes.
+ keyboard_mode_stack: Vec<KeyboardModes>,
+
+ /// Currently inactive keyboard mode stack.
+ inactive_keyboard_mode_stack: Vec<KeyboardModes>,
+
/// Information about damaged cells.
damage: TermDamageState,
@@ -303,6 +346,9 @@ pub struct Config {
/// The default value is [`SEMANTIC_ESCAPE_CHARS`].
pub semantic_escape_chars: String,
+ /// Whether to enable kitty keyboard protocol.
+ pub kitty_keyboard: bool,
+
/// OSC52 support mode.
pub osc52: Osc52,
}
@@ -314,6 +360,7 @@ impl Default for Config {
semantic_escape_chars: SEMANTIC_ESCAPE_CHARS.to_owned(),
default_cursor_style: Default::default(),
vi_mode_cursor_style: Default::default(),
+ kitty_keyboard: Default::default(),
osc52: Default::default(),
}
}
@@ -388,7 +435,9 @@ impl<T> Term<T> {
event_proxy,
is_focused: true,
title: None,
- title_stack: Vec::new(),
+ title_stack: Default::default(),
+ keyboard_mode_stack: Default::default(),
+ inactive_keyboard_mode_stack: Default::default(),
selection: None,
damage,
config: options,
@@ -451,7 +500,7 @@ impl<T> Term<T> {
where
T: EventListener,
{
- self.config = options;
+ let old_config = mem::replace(&mut self.config, options);
let title_event = match &self.title {
Some(title) => Event::Title(title.clone()),
@@ -466,6 +515,12 @@ impl<T> Term<T> {
self.grid.update_history(self.config.scrolling_history);
}
+ if self.config.kitty_keyboard != old_config.kitty_keyboard {
+ self.keyboard_mode_stack = Vec::new();
+ self.inactive_keyboard_mode_stack = Vec::new();
+ self.mode.remove(TermMode::KITTY_KEYBOARD_PROTOCOL);
+ }
+
// Damage everything on config updates.
self.mark_fully_damaged();
}
@@ -668,6 +723,11 @@ impl<T> Term<T> {
self.inactive_grid.reset_region(..);
}
+ mem::swap(&mut self.keyboard_mode_stack, &mut self.inactive_keyboard_mode_stack);
+ let keyboard_mode =
+ self.keyboard_mode_stack.last().copied().unwrap_or(KeyboardModes::NO_MODE).into();
+ self.set_keyboard_mode(keyboard_mode, KeyboardModesApplyBehavior::Replace);
+
mem::swap(&mut self.grid, &mut self.inactive_grid);
self.mode ^= TermMode::ALT_SCREEN;
self.selection = None;
@@ -959,6 +1019,19 @@ impl<T> Term<T> {
Point::new(self.grid.cursor.point.line.0 as usize, self.grid.cursor.point.column);
self.damage.damage_point(point);
}
+
+ #[inline]
+ fn set_keyboard_mode(&mut self, mode: TermMode, apply: KeyboardModesApplyBehavior) {
+ let active_mode = self.mode & TermMode::KITTY_KEYBOARD_PROTOCOL;
+ self.mode &= !TermMode::KITTY_KEYBOARD_PROTOCOL;
+ let new_mode = match apply {
+ KeyboardModesApplyBehavior::Replace => mode,
+ KeyboardModesApplyBehavior::Union => active_mode.union(mode),
+ KeyboardModesApplyBehavior::Difference => active_mode.difference(mode),
+ };
+ trace!("Setting keyboard mode to {new_mode:?}");
+ self.mode |= new_mode;
+ }
}
impl<T> Dimensions for Term<T> {
@@ -1194,6 +1267,63 @@ impl<T: EventListener> Handler for Term<T> {
}
#[inline]
+ fn report_keyboard_mode(&mut self) {
+ if !self.config.kitty_keyboard {
+ return;
+ }
+
+ trace!("Reporting active keyboard mode");
+ let current_mode =
+ self.keyboard_mode_stack.last().unwrap_or(&KeyboardModes::NO_MODE).bits();
+ let text = format!("\x1b[?{current_mode}u");
+ self.event_proxy.send_event(Event::PtyWrite(text));
+ }
+
+ #[inline]
+ fn push_keyboard_mode(&mut self, mode: KeyboardModes) {
+ if !self.config.kitty_keyboard {
+ return;
+ }
+
+ trace!("Pushing `{mode:?}` keyboard mode into the stack");
+
+ if self.keyboard_mode_stack.len() >= KEYBOARD_MODE_STACK_MAX_DEPTH {
+ let removed = self.title_stack.remove(0);
+ trace!(
+ "Removing '{:?}' from bottom of keyboard mode stack that exceeds its maximum depth",
+ removed
+ );
+ }
+
+ self.keyboard_mode_stack.push(mode);
+ self.set_keyboard_mode(mode.into(), KeyboardModesApplyBehavior::Replace);
+ }
+
+ #[inline]
+ fn pop_keyboard_modes(&mut self, to_pop: u16) {
+ if !self.config.kitty_keyboard {
+ return;
+ }
+
+ trace!("Attemting to pop {to_pop} keyboard modes from the stack");
+ let new_len = self.keyboard_mode_stack.len().saturating_sub(to_pop as usize);
+ self.keyboard_mode_stack.truncate(new_len);
+
+ // Reload active mode.
+ let mode = self.keyboard_mode_stack.last().copied().unwrap_or(KeyboardModes::NO_MODE);
+ self.set_keyboard_mode(mode.into(), KeyboardModesApplyBehavior::Replace);
+ }
+
+ #[inline]
+ fn set_keyboard_mode(&mut self, mode: KeyboardModes, apply: KeyboardModesApplyBehavior) {
+ if !self.config.kitty_keyboard {
+ return;
+ }
+
+ self.set_keyboard_mode(mode.into(), apply);
+ }
+
+ #[inline]
fn device_status(&mut self, arg: usize) {
trace!("Reporting device status: {}", arg);
match arg {
@@ -1685,6 +1815,8 @@ impl<T: EventListener> Handler for Term<T> {
self.title = None;
self.selection = None;
self.vi_mode_cursor = Default::default();
+ self.keyboard_mode_stack = Default::default();
+ self.inactive_keyboard_mode_stack = Default::default();
// Preserve vi mode across resets.
self.mode &= TermMode::VI;