summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--alacritty/src/config/bindings.rs226
-rw-r--r--alacritty/src/config/ui_config.rs10
-rw-r--r--alacritty/src/input/keyboard.rs584
-rw-r--r--alacritty/src/input/mod.rs (renamed from alacritty/src/input.rs)143
-rw-r--r--alacritty_terminal/src/term/mod.rs182
-rw-r--r--docs/escape_support.md4
7 files changed, 850 insertions, 300 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 238769ca..e383ebde 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `window.blur` config option to request blur for transparent windows
- `--option` argument for `alacritty msg create-window`
- Support for `DECRQM`/`DECRPM` escape sequences
+- Support for kitty's keyboard protocol
### Changed
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs
index d836c5e6..36f5f521 100644
--- a/alacritty/src/config/bindings.rs
+++ b/alacritty/src/config/bindings.rs
@@ -7,7 +7,9 @@ use serde::de::{self, Error as SerdeError, MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use toml::Value as SerdeValue;
use winit::event::MouseButton;
-use winit::keyboard::{Key, KeyCode, KeyLocation, ModifiersState, NamedKey, PhysicalKey};
+use winit::keyboard::{
+ Key, KeyCode, KeyLocation as WinitKeyLocation, ModifiersState, NamedKey, PhysicalKey,
+};
use winit::platform::scancode::PhysicalKeyExtScancode;
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
@@ -413,10 +415,13 @@ macro_rules! trigger {
BindingKey::Keycode { key: Key::Character($key.into()), location: $location }
}};
(KeyBinding, $key:literal,) => {{
- BindingKey::Keycode { key: Key::Character($key.into()), location: KeyLocation::Standard }
+ BindingKey::Keycode { key: Key::Character($key.into()), location: KeyLocation::Any }
+ }};
+ (KeyBinding, $key:ident, $location:expr) => {{
+ BindingKey::Keycode { key: Key::Named(NamedKey::$key), location: $location }
}};
(KeyBinding, $key:ident,) => {{
- BindingKey::Keycode { key: Key::Named(NamedKey::$key), location: KeyLocation::Standard }
+ BindingKey::Keycode { key: Key::Named(NamedKey::$key), location: KeyLocation::Any }
}};
(MouseBinding, $base:ident::$button:ident,) => {{
$base::$button
@@ -432,6 +437,8 @@ pub fn default_mouse_bindings() -> Vec<MouseBinding> {
)
}
+// NOTE: key sequences which are not present here, like F5-F20, PageUp/PageDown codes are
+// built on the fly in input/keyboard.rs.
pub fn default_key_bindings() -> Vec<KeyBinding> {
let mut bindings = bindings!(
KeyBinding;
@@ -439,56 +446,28 @@ pub fn default_key_bindings() -> Vec<KeyBinding> {
Copy, +BindingMode::VI; Action::ClearSelection;
Paste, ~BindingMode::VI; Action::Paste;
"l", ModifiersState::CONTROL; Action::ClearLogNotice;
- "l", ModifiersState::CONTROL, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x0c".into());
- Tab, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[Z".into());
- Backspace, ModifiersState::ALT, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b\x7f".into());
- Backspace, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x7f".into());
+ "l", ModifiersState::CONTROL; Action::ReceiveChar;
Home, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToTop;
End, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToBottom;
PageUp, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageUp;
PageDown, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageDown;
- Home, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[1;2H".into());
- End, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[1;2F".into());
- PageUp, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[5;2~".into());
- PageDown, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[6;2~".into());
+ // App cursor mode.
Home, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOH".into());
- Home, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[H".into());
End, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOF".into());
- End, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[F".into());
ArrowUp, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOA".into());
- ArrowUp, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[A".into());
ArrowDown, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOB".into());
- ArrowDown, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[B".into());
ArrowRight, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOC".into());
- ArrowRight, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[C".into());
ArrowLeft, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOD".into());
- ArrowLeft, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[D".into());
- Backspace, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x7f".into());
- Insert, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[2~".into());
- Delete, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[3~".into());
- PageUp, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[5~".into());
- PageDown, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[6~".into());
- F1, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOP".into());
- F2, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOQ".into());
- F3, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOR".into());
- F4, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOS".into());
- F5, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[15~".into());
- F6, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[17~".into());
- F7, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[18~".into());
- F8, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[19~".into());
- F9, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[20~".into());
- F10, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[21~".into());
- F11, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[23~".into());
- F12, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[24~".into());
- F13, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[25~".into());
- F14, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[26~".into());
- F15, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[28~".into());
- F16, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[29~".into());
- F17, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[31~".into());
- F18, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[32~".into());
- F19, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[33~".into());
- F20, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[34~".into());
-
+ // Legacy keys handling which can't be automatically encoded.
+ F1, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC, ~BindingMode::DISAMBIGUATE_ESC_CODES; Action::Esc("\x1bOP".into());
+ F2, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC, ~BindingMode::DISAMBIGUATE_ESC_CODES; Action::Esc("\x1bOQ".into());
+ F3, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC, ~BindingMode::DISAMBIGUATE_ESC_CODES; Action::Esc("\x1bOR".into());
+ F4, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC, ~BindingMode::DISAMBIGUATE_ESC_CODES; Action::Esc("\x1bOS".into());
+ Tab, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC; Action::Esc("\x1b[Z".into());
+ Tab, ModifiersState::SHIFT | ModifiersState::ALT, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC; Action::Esc("\x1b\x1b[Z".into());
+ Backspace, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC; Action::Esc("\x7f".into());
+ Backspace, ModifiersState::ALT, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC; Action::Esc("\x1b\x7f".into());
+ Backspace, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH, ~BindingMode::REPORT_ALL_KEYS_AS_ESC; Action::Esc("\x7f".into());
// Vi mode.
Space, ModifiersState::SHIFT | ModifiersState::CONTROL, ~BindingMode::SEARCH; Action::ToggleViMode;
Space, ModifiersState::SHIFT | ModifiersState::CONTROL, +BindingMode::VI, ~BindingMode::SEARCH; Action::ScrollToBottom;
@@ -557,72 +536,6 @@ pub fn default_key_bindings() -> Vec<KeyBinding> {
Enter, ModifiersState::SHIFT, +BindingMode::SEARCH, ~BindingMode::VI; SearchAction::SearchFocusPrevious;
);
- // Code Modifiers
- // ---------+---------------------------
- // 2 | Shift
- // 3 | Alt
- // 4 | Shift + Alt
- // 5 | Control
- // 6 | Shift + Control
- // 7 | Alt + Control
- // 8 | Shift + Alt + Control
- // ---------+---------------------------
- //
- // from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
- let mut modifiers = vec![
- ModifiersState::SHIFT,
- ModifiersState::ALT,
- ModifiersState::SHIFT | ModifiersState::ALT,
- ModifiersState::CONTROL,
- ModifiersState::SHIFT | ModifiersState::CONTROL,
- ModifiersState::ALT | ModifiersState::CONTROL,
- ModifiersState::SHIFT | ModifiersState::ALT | ModifiersState::CONTROL,
- ];
-
- for (index, mods) in modifiers.drain(..).enumerate() {
- let modifiers_code = index + 2;
- bindings.extend(bindings!(
- KeyBinding;
- Delete, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[3;{}~", modifiers_code));
- ArrowUp, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}A", modifiers_code));
- ArrowDown, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}B", modifiers_code));
- ArrowRight, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}C", modifiers_code));
- ArrowLeft, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}D", modifiers_code));
- F1, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}P", modifiers_code));
- F2, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}Q", modifiers_code));
- F3, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}R", modifiers_code));
- F4, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}S", modifiers_code));
- F5, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[15;{}~", modifiers_code));
- F6, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[17;{}~", modifiers_code));
- F7, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[18;{}~", modifiers_code));
- F8, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[19;{}~", modifiers_code));
- F9, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[20;{}~", modifiers_code));
- F10, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[21;{}~", modifiers_code));
- F11, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[23;{}~", modifiers_code));
- F12, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[24;{}~", modifiers_code));
- F13, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[25;{}~", modifiers_code));
- F14, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[26;{}~", modifiers_code));
- F15, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[28;{}~", modifiers_code));
- F16, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[29;{}~", modifiers_code));
- F17, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[31;{}~", modifiers_code));
- F18, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[32;{}~", modifiers_code));
- F19, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[33;{}~", modifiers_code));
- F20, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[34;{}~", modifiers_code));
- ));
-
- // We're adding the following bindings with `Shift` manually above, so skipping them here.
- if modifiers_code != 2 {
- bindings.extend(bindings!(
- KeyBinding;
- Insert, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[2;{}~", modifiers_code));
- PageUp, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[5;{}~", modifiers_code));
- PageDown, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[6;{}~", modifiers_code));
- End, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}F", modifiers_code));
- Home, mods, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc(format!("\x1b[1;{}H", modifiers_code));
- ));
- }
- }
-
bindings.extend(platform_key_bindings());
bindings
@@ -683,7 +596,6 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> {
"7", ModifiersState::SUPER; Action::SelectTab7;
"8", ModifiersState::SUPER; Action::SelectTab8;
"9", ModifiersState::SUPER; Action::SelectLastTab;
-
"0", ModifiersState::SUPER; Action::ResetFontSize;
"=", ModifiersState::SUPER; Action::IncreaseFontSize;
"+", ModifiersState::SUPER; Action::IncreaseFontSize;
@@ -712,12 +624,46 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> {
vec![]
}
-#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BindingKey {
Scancode(PhysicalKey),
Keycode { key: Key, location: KeyLocation },
}
+/// Key location for matching bindings.
+#[derive(Debug, Clone, Copy, Eq)]
+pub enum KeyLocation {
+ /// The key is in its standard position.
+ Standard,
+ /// The key is on the numeric pad.
+ Numpad,
+ /// The key could be anywhere on the keyboard.
+ Any,
+}
+
+impl From<WinitKeyLocation> for KeyLocation {
+ fn from(value: WinitKeyLocation) -> Self {
+ match value {
+ WinitKeyLocation::Standard => KeyLocation::Standard,
+ WinitKeyLocation::Left => KeyLocation::Any,
+ WinitKeyLocation::Right => KeyLocation::Any,
+ WinitKeyLocation::Numpad => KeyLocation::Numpad,
+ }
+ }
+}
+
+impl PartialEq for KeyLocation {
+ fn eq(&self, other: &Self) -> bool {
+ matches!(
+ (self, other),
+ (_, KeyLocation::Any)
+ | (KeyLocation::Any, _)
+ | (KeyLocation::Standard, KeyLocation::Standard)
+ | (KeyLocation::Numpad, KeyLocation::Numpad)
+ )
+ }
+}
+
impl<'a> Deserialize<'a> for BindingKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -729,23 +675,26 @@ impl<'a> Deserialize<'a> for BindingKey {
Err(_) => {
let keycode = String::deserialize(value.clone()).map_err(D::Error::custom)?;
let (key, location) = if keycode.chars().count() == 1 {
- (Key::Character(keycode.to_lowercase().into()), KeyLocation::Standard)
+ (Key::Character(keycode.to_lowercase().into()), KeyLocation::Any)
} else {
// Translate legacy winit codes into their modern counterparts.
match keycode.as_str() {
- "Up" => (Key::Named(NamedKey::ArrowUp), KeyLocation::Standard),
- "Back" => (Key::Named(NamedKey::Backspace), KeyLocation::Standard),
- "Down" => (Key::Named(NamedKey::ArrowDown), KeyLocation::Standard),
- "Left" => (Key::Named(NamedKey::ArrowLeft), KeyLocation::Standard),
- "Right" => (Key::Named(NamedKey::ArrowRight), KeyLocation::Standard),
- "At" => (Key::Character("@".into()), KeyLocation::Standard),
- "Colon" => (Key::Character(":".into()), KeyLocation::Standard),
- "Period" => (Key::Character(".".into()), KeyLocation::Standard),
+ "Back" => (Key::Named(NamedKey::Backspace), KeyLocation::Any),
+ "Up" => (Key::Named(NamedKey::ArrowUp), KeyLocation::Any),
+ "Down" => (Key::Named(NamedKey::ArrowDown), KeyLocation::Any),
+ "Left" => (Key::Named(NamedKey::ArrowLeft), KeyLocation::Any),
+ "Right" => (Key::Named(NamedKey::ArrowRight), KeyLocation::Any),
+ "At" => (Key::Character("@".into()), KeyLocation::Any),
+ "Colon" => (Key::Character(":".into()), KeyLocation::Any),
+ "Period" => (Key::Character(".".into()), KeyLocation::Any),
+ "LBracket" => (Key::Character("[".into()), KeyLocation::Any),
+ "RBracket" => (Key::Character("]".into()), KeyLocation::Any),
+ "Semicolon" => (Key::Character(";".into()), KeyLocation::Any),
+ "Backslash" => (Key::Character("\\".into()), KeyLocation::Any),
+
+ // The keys which has alternative on numeric pad.
+ "Enter" => (Key::Named(NamedKey::Enter), KeyLocation::Standard),
"Return" => (Key::Named(NamedKey::Enter), KeyLocation::Standard),
- "LBracket" => (Key::Character("[".into()), KeyLocation::Standard),
- "RBracket" => (Key::Character("]".into()), KeyLocation::Standard),
- "Semicolon" => (Key::Character(";".into()), KeyLocation::Standard),
- "Backslash" => (Key::Character("\\".into()), KeyLocation::Standard),
"Plus" => (Key::Character("+".into()), KeyLocation::Standard),
"Comma" => (Key::Character(",".into()), KeyLocation::Standard),
"Slash" => (Key::Character("/".into()), KeyLocation::Standard),
@@ -781,13 +730,12 @@ impl<'a> Deserialize<'a> for BindingKey {
"Numpad8" => (Key::Character("8".into()), KeyLocation::Numpad),
"Numpad9" => (Key::Character("9".into()), KeyLocation::Numpad),
"Numpad0" => (Key::Character("0".into()), KeyLocation::Numpad),
- _ if keycode.starts_with("Dead") => (
- Key::deserialize(value).map_err(D::Error::custom)?,
- KeyLocation::Standard,
- ),
+ _ if keycode.starts_with("Dead") => {
+ (Key::deserialize(value).map_err(D::Error::custom)?, KeyLocation::Any)
+ },
_ => (
Key::Named(NamedKey::deserialize(value).map_err(D::Error::custom)?),
- KeyLocation::Standard,
+ KeyLocation::Any,
),
}
};
@@ -808,11 +756,13 @@ bitflags! {
/// Modes available for key bindings.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BindingMode: u8 {
- const APP_CURSOR = 0b0000_0001;
- const APP_KEYPAD = 0b0000_0010;
- const ALT_SCREEN = 0b0000_0100;
- const VI = 0b0000_1000;
- const SEARCH = 0b0001_0000;
+ const APP_CURSOR = 0b0000_0001;
+ const APP_KEYPAD = 0b0000_0010;
+ const ALT_SCREEN = 0b0000_0100;
+ const VI = 0b0000_1000;
+ const SEARCH = 0b0001_0000;
+ const DISAMBIGUATE_ESC_CODES = 0b0010_0000;
+ const REPORT_ALL_KEYS_AS_ESC = 0b0100_0000;
}
}
@@ -824,6 +774,14 @@ impl BindingMode {
binding_mode.set(BindingMode::ALT_SCREEN, mode.contains(TermMode::ALT_SCREEN));
binding_mode.set(BindingMode::VI, mode.contains(TermMode::VI));
binding_mode.set(BindingMode::SEARCH, search);
+ binding_mode.set(
+ BindingMode::DISAMBIGUATE_ESC_CODES,
+ mode.contains(TermMode::DISAMBIGUATE_ESC_CODES),
+ );
+ binding_mode.set(
+ BindingMode::REPORT_ALL_KEYS_AS_ESC,
+ mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC),
+ );
binding_mode
}
}
diff --git a/alacritty/src/config/ui_config.rs b/alacritty/src/config/ui_config.rs
index a76bbb78..f4f67cb6 100644
--- a/alacritty/src/config/ui_config.rs
+++ b/alacritty/src/config/ui_config.rs
@@ -10,14 +10,15 @@ use log::{error, warn};
use serde::de::{Error as SerdeError, MapAccess, Visitor};
use serde::{self, Deserialize, Deserializer};
use unicode_width::UnicodeWidthChar;
-use winit::keyboard::{Key, KeyLocation, ModifiersState};
+use winit::keyboard::{Key, ModifiersState};
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
use alacritty_terminal::term::search::RegexSearch;
use crate::config::bell::BellConfig;
use crate::config::bindings::{
- self, Action, Binding, BindingKey, KeyBinding, ModeWrapper, ModsWrapper, MouseBinding,
+ self, Action, Binding, BindingKey, KeyBinding, KeyLocation, ModeWrapper, ModsWrapper,
+ MouseBinding,
};
use crate::config::color::Colors;
use crate::config::cursor::Cursor;
@@ -152,11 +153,12 @@ impl UiConfig {
/// Derive [`TermConfig`] from the config.
pub fn term_options(&self) -> TermConfig {
TermConfig {
+ semantic_escape_chars: self.selection.semantic_escape_chars.clone(),
scrolling_history: self.scrolling.history() as usize,
- default_cursor_style: self.cursor.style(),
vi_mode_cursor_style: self.cursor.vi_mode_style(),
- semantic_escape_chars: self.selection.semantic_escape_chars.clone(),
+ default_cursor_style: self.cursor.style(),
osc52: self.terminal.osc52.0,
+ kitty_keyboard: true,
}
}
diff --git a/alacritty/src/input/keyboard.rs b/alacritty/src/input/keyboard.rs
new file mode 100644
index 00000000..94633cb1
--- /dev/null
+++ b/alacritty/src/input/keyboard.rs
@@ -0,0 +1,584 @@
+use std::borrow::Cow;
+use std::mem;
+
+use winit::event::{ElementState, KeyEvent};
+#[cfg(target_os = "macos")]
+use winit::keyboard::ModifiersKeyState;
+use winit::keyboard::{Key, KeyLocation, ModifiersState, NamedKey};
+#[cfg(target_os = "macos")]
+use winit::platform::macos::OptionAsAlt;
+
+use alacritty_terminal::event::EventListener;
+use alacritty_terminal::term::TermMode;
+use winit::platform::modifier_supplement::KeyEventExtModifierSupplement;
+
+use crate::config::{Action, BindingKey, BindingMode};
+use crate::event::TYPING_SEARCH_DELAY;
+use crate::input::{ActionContext, Execute, Processor};
+use crate::scheduler::{TimerId, Topic};
+
+impl<T: EventListener, A: ActionContext<T>> Processor<T, A> {
+ /// Process key input.
+ pub fn key_input(&mut self, key: KeyEvent) {
+ // IME input will be applied on commit and shouldn't trigger key bindings.
+ if self.ctx.display().ime.preedit().is_some() {
+ return;
+ }
+
+ let mode = *self.ctx.terminal().mode();
+ let mods = self.ctx.modifiers().state();
+
+ if key.state == ElementState::Released {
+ self.key_release(key, mode, mods);
+ return;
+ }
+
+ let text = key.text_with_all_modifiers().unwrap_or_default();
+
+ // All key bindings are disabled while a hint is being selected.
+ if self.ctx.display().hint_state.active() {
+ for character in text.chars() {
+ self.ctx.hint_input(character);
+ }
+ return;
+ }
+
+ // First key after inline search is captured.
+ let inline_state = self.ctx.inline_search_state();
+ if mem::take(&mut inline_state.char_pending) {
+ if let Some(c) = text.chars().next() {
+ inline_state.character = Some(c);
+
+ // Immediately move to the captured character.
+ self.ctx.inline_search_next();
+ }
+
+ // Ignore all other characters in `text`.
+ return;
+ }
+
+ // Reset search delay when the user is still typing.
+ self.reset_search_delay();
+
+ // Key bindings suppress the character input.
+ if self.process_key_bindings(&key) {
+ return;
+ }
+
+ if self.ctx.search_active() {
+ for character in text.chars() {
+ self.ctx.search_input(character);
+ }
+
+ return;
+ }
+
+ // Vi mode on its own doesn't have any input, the search input was done before.
+ if mode.contains(TermMode::VI) {
+ return;
+ }
+
+ let build_key_sequence = Self::should_build_sequence(&key, text, mode, mods);
+
+ let bytes = if build_key_sequence {
+ build_sequence(key, mods, mode)
+ } else {
+ let mut bytes = Vec::with_capacity(text.len() + 1);
+ if self.alt_send_esc() && text.len() == 1 {
+ bytes.push(b'\x1b');
+ }
+
+ bytes.extend_from_slice(text.as_bytes());
+ bytes
+ };
+
+ // Write only if we have something to write.
+ if !bytes.is_empty() {
+ self.ctx.on_terminal_input_start();
+ self.ctx.write_to_pty(bytes);
+ }
+ }
+
+ /// Check whether we should try to build escape sequence for the [`KeyEvent`].
+ fn should_build_sequence(
+ key: &KeyEvent,
+ text: &str,
+ mode: TermMode,
+ mods: ModifiersState,
+ ) -> bool {
+ if mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC) {
+ true
+ } else if mode.contains(TermMode::DISAMBIGUATE_ESC_CODES) {
+ let on_numpad = key.location == KeyLocation::Numpad;
+ let is_escape = key.logical_key == Key::Named(NamedKey::Escape);
+ is_escape || (!mods.is_empty() && mods != ModifiersState::SHIFT) || on_numpad
+ } else {
+ // `Delete` key always has text attached to it, but it's a named key, thus needs to be
+ // excluded here as well.
+ text.is_empty() || key.logical_key == Key::Named(NamedKey::Delete)
+ }
+ }
+
+ /// Whether we should send `ESC` due to `Alt` being pressed.
+ #[cfg(not(target_os = "macos"))]
+ fn alt_send_esc(&mut self) -> bool {
+ self.ctx.modifiers().state().alt_key()
+ }
+
+ #[cfg(target_os = "macos")]
+ fn alt_send_esc(&mut self) -> bool {
+ let option_as_alt = self.ctx.config().window.option_as_alt();
+ self.ctx.modifiers().state().alt_key()
+ && (option_as_alt == OptionAsAlt::Both
+ || (option_as_alt == OptionAsAlt::OnlyLeft
+ && self.ctx.modifiers().lalt_state() == ModifiersKeyState::Pressed)
+ || (option_as_alt == OptionAsAlt::OnlyRight
+ && self.ctx.modifiers().ralt_state() == ModifiersKeyState::Pressed))
+ }
+
+ /// Attempt to find a binding and execute its action.
+ ///
+ /// The provided mode, mods, and key must match what is allowed by a binding
+ /// for its action to be executed.
+ fn process_key_bindings(&mut self, key: &KeyEvent) -> bool {
+ let mode = BindingMode::new(self.ctx.terminal().mode(), self.ctx.search_active());
+ let mods = self.ctx.modifiers().state();
+
+ // Don't suppress char if no bindings were triggered.
+ let mut suppress_chars = None;
+
+ for i in 0..self.ctx.config().key_bindings().len() {
+ let binding = &self.ctx.config().key_bindings()[i];
+
+ // We don't want the key without modifier, because it means something else most of
+ // the time. However what we want is to manually lowercase the character to account
+ // for both small and capital letters on regular characters at the same time.
+ let logical_key = if let Key::Character(ch) = key.logical_key.as_ref() {
+ Key::Character(ch.to_lowercase().into())
+ } else {
+ key.logical_key.clone()
+ };
+
+ let key = match (&binding.trigger, logical_key) {
+ (BindingKey::Scancode(_), _) => BindingKey::Scancode(key.physical_key),
+ (_, code) => BindingKey::Keycode { key: code, location: key.location.into() },
+ };
+
+ if binding.is_triggered_by(mode, mods, &key) {
+ // Pass through the key if any of the bindings has the `ReceiveChar` action.
+ *suppress_chars.get_or_insert(true) &= binding.action != Action::ReceiveChar;
+
+ // Binding was triggered; run the action.
+ binding.action.clone().execute(&mut self.ctx);
+ }
+ }
+
+ suppress_chars.unwrap_or(false)
+ }
+
+ /// Handle key release.
+ fn key_release(&mut self, key: KeyEvent, mode: TermMode, mods: ModifiersState) {
+ if !mode.contains(TermMode::REPORT_EVENT_TYPES)
+ || mode.contains(TermMode::VI)
+ || self.ctx.search_active()
+ || self.ctx.display().hint_state.active()
+ {
+ return;
+ }
+
+ let bytes: Cow<'static, [u8]> = match key.logical_key.as_ref() {
+ // NOTE: Echo the key back on release to follow kitty/foot behavior. When
+ // KEYBOARD_REPORT_ALL_KEYS_AS_ESC is used, we build proper escapes for
+ // the keys below.
+ _ if mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC) => {
+ build_sequence(key, mods, mode).into()
+ },
+ // Winit uses different keys for `Backspace` so we expliictly specify the
+ // values, instead of using what was passed to us from it.
+ Key::Named(NamedKey::Tab) => [b'\t'].as_slice().into(),
+ Key::Named(NamedKey::Enter) => [b'\r'].as_slice().into(),
+ Key::Named(NamedKey::Backspace) => [b'\x7f'].as_slice().into(),
+ Key::Named(NamedKey::Escape) => [b'\x1b'].as_slice().into(),
+ _ => build_sequence(key, mods, mode).into(),
+ };
+
+ self.ctx.write_to_pty(bytes);
+ }
+
+ /// Reset search delay.
+ fn reset_search_delay(&mut self) {
+ if self.ctx.search_active() {
+ let timer_id = TimerId::new(Topic::DelayedSearch, self.ctx.window().id());
+ let scheduler = self.ctx.scheduler_mut();
+ if let Some(timer) = scheduler.unschedule(timer_id) {
+ scheduler.schedule(timer.event, TYPING_SEARCH_DELAY, false, timer.id);
+ }
+ }
+ }
+}
+
+/// Build a key's keyboard escape sequence based on the given `key`, `mods`, and `mode`.
+///
+/// The key sequences for `APP_KEYPAD` and alike are handled inside the bindings.
+#[inline(never)]
+fn build_sequence(key: KeyEvent, mods: ModifiersState, mode: TermMode) -> Vec<u8> {
+ let modifiers = mods.into();
+
+ let kitty_seq = mode.intersects(
+ TermMode::REPORT_ALL_KEYS_AS_ESC
+ | TermMode::DISAMBIGUATE_ESC_CODES
+ | TermMode::REPORT_EVENT_TYPES,
+ );
+
+ let kitty_encode_all = mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC);
+ // The default parameter is 1, so we can omit it.
+ let kitty_event_type = mode.contains(TermMode::REPORT_EVENT_TYPES)
+ && (key.repeat || key.state == ElementState::Released);
+
+ let context =
+ SequenceBuilder { mode, modifiers, kitty_seq, kitty_encode_all, kitty_event_type };
+
+ let sequence_base = context
+ .try_build_numpad(&key)
+ .or_else(|| context.try_build_named(&key))
+ .or_else(|| context.try_build_control_char_or_mod(&key))
+ .or_else(|| context.try_build_textual(&key));
+
+ let (payload, terminator) = match sequence_base {
+ Some(SequenceBase { payload, terminator }) => (payload, terminator),
+ _ => return Vec::new(),
+ };
+
+ let mut payload = format!("\x1b[{}", payload);
+
+ // Add modifiers information.
+ if kitty_event_type
+ || !modifiers.is_empty()
+ || (mode.contains(TermMode::REPORT_ASSOCIATED_TEXT) && key.text.is_some())
+ {
+ payload.push_str(&format!(";{}", modifiers.encode_esc_sequence()));
+ }
+
+ // Push event type.
+ if kitty_event_type {
+ payload.push(':');
+ let event_type = match key.state {
+ _ if key.repeat => '2',
+ ElementState::Pressed => '1',
+ ElementState::Released => '3',
+ };
+ payload.push(event_type);
+ }
+
+ // Associated text is not reported when the control/alt/logo is pressesed.
+ if mode.contains(TermMode::REPORT_ASSOCIATED_TEXT)
+ && key.state != ElementState::Released
+ && (modifiers.is_empty() || modifiers == SequenceModifiers::SHIFT)
+ {
+ if let Some(text) = key.text {
+ let mut codepoints = text.chars().map(u32::from);
+ if let Some(codepoint) = codepoints.next() {
+ payload.push_str(&format!(";{codepoint}"));
+ }
+ for codepoint in codepoints {
+ payload.push_str(&format!(":{codepoint}"));
+ }
+ }
+ }
+
+ payload.push(terminator.encode_esc_sequence());
+
+ payload.into_bytes()
+}
+
+/// Helper to build escape sequence payloads from [`KeyEvent`].
+pub struct SequenceBuilder {
+ mode: TermMode,
+ /// The emitted sequence should follow the kitty keyboard protocol.
+ kitty_seq: bool,
+ /// Encode all the keys according to the protocol.
+ kitty_encode_all: bool,
+ /// Report event types.
+ kitty_event_type: bool,
+ modifiers: SequenceModifiers,
+}
+
+impl SequenceBuilder {
+ /// Try building sequence from the event's emitting text.
+ fn try_build_textual(&self, key: &KeyEvent) -> Option<SequenceBase> {
+ let character = match key.logical_key.as_ref() {
+ Key::Character(character) => character,
+ _ => return None,
+ };
+
+ if character.chars().count() == 1 {
+ let character = character.chars().next().unwrap();
+ let base_character = character.to_lowercase().next().unwrap();
+
+ let codepoint = u32::from(character);
+ let base_codepoint = u32::from(base_character);
+
+ // NOTE: Base layouts are ignored, since winit doesn't expose this information
+ // yet.
+ let payload = if self.mode.contains(TermMode::REPORT_ALTERNATE_KEYS)
+ && codepoint != base_codepoint
+ {
+ format!("{codepoint}:{base_codepoint}")
+ } else {
+ codepoint.to_string()
+ };
+
+ Some(SequenceBase::new(payload.into(), SequenceTerminator::Kitty))
+ } else if self.kitty_encode_all
+ && self.mode.contains(TermMode::REPORT_ASSOCIATED_TEXT)
+ && key.text.is_some()
+ {
+ // Fallback when need to report text, but we don't have any key associated with this
+ // text.