alacritty/alacritty/src/input/keyboard.rs

585 lines
23 KiB
Rust

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.
Some(SequenceBase::new("0".into(), SequenceTerminator::Kitty))
} else {
None
}
}
/// Try building from numpad key.
///
/// `None` is returned when the key is neither known nor numpad.
fn try_build_numpad(&self, key: &KeyEvent) -> Option<SequenceBase> {
if !self.kitty_seq || key.location != KeyLocation::Numpad {
return None;
}
let base = match key.logical_key.as_ref() {
Key::Character("0") => "57399",
Key::Character("1") => "57400",
Key::Character("2") => "57401",
Key::Character("3") => "57402",
Key::Character("4") => "57403",
Key::Character("5") => "57404",
Key::Character("6") => "57405",
Key::Character("7") => "57406",
Key::Character("8") => "57407",
Key::Character("9") => "57408",
Key::Character(".") => "57409",
Key::Character("/") => "57410",
Key::Character("*") => "57411",
Key::Character("-") => "57412",
Key::Character("+") => "57413",
Key::Character("=") => "57415",
Key::Named(named) => match named {
NamedKey::Enter => "57414",
NamedKey::ArrowLeft => "57417",
NamedKey::ArrowRight => "57418",
NamedKey::ArrowUp => "57419",
NamedKey::ArrowDown => "57420",
NamedKey::PageUp => "57421",
NamedKey::PageDown => "57422",
NamedKey::Home => "57423",
NamedKey::End => "57424",
NamedKey::Insert => "57425",
NamedKey::Delete => "57426",
_ => return None,
},
_ => return None,
};
Some(SequenceBase::new(base.into(), SequenceTerminator::Kitty))
}
/// Try building from [`NamedKey`].
fn try_build_named(&self, key: &KeyEvent) -> Option<SequenceBase> {
let named = match key.logical_key {
Key::Named(named) => named,
_ => return None,
};
// The default parameter is 1, so we can omit it.
let one_based = if self.modifiers.is_empty() && !self.kitty_event_type { "" } else { "1" };
let (base, terminator) = match named {
NamedKey::PageUp => ("5", SequenceTerminator::Normal('~')),
NamedKey::PageDown => ("6", SequenceTerminator::Normal('~')),
NamedKey::Insert => ("2", SequenceTerminator::Normal('~')),
NamedKey::Delete => ("3", SequenceTerminator::Normal('~')),
NamedKey::Home => (one_based, SequenceTerminator::Normal('H')),
NamedKey::End => (one_based, SequenceTerminator::Normal('F')),
NamedKey::ArrowLeft => (one_based, SequenceTerminator::Normal('D')),
NamedKey::ArrowRight => (one_based, SequenceTerminator::Normal('C')),
NamedKey::ArrowUp => (one_based, SequenceTerminator::Normal('A')),
NamedKey::ArrowDown => (one_based, SequenceTerminator::Normal('B')),
NamedKey::F1 => (one_based, SequenceTerminator::Normal('P')),
NamedKey::F2 => (one_based, SequenceTerminator::Normal('Q')),
NamedKey::F3 => {
// F3 in kitty protocol diverges from alacritty's terminfo.
if self.kitty_seq {
("13", SequenceTerminator::Normal('~'))
} else {
(one_based, SequenceTerminator::Normal('R'))
}
},
NamedKey::F4 => (one_based, SequenceTerminator::Normal('S')),
NamedKey::F5 => ("15", SequenceTerminator::Normal('~')),
NamedKey::F6 => ("17", SequenceTerminator::Normal('~')),
NamedKey::F7 => ("18", SequenceTerminator::Normal('~')),
NamedKey::F8 => ("19", SequenceTerminator::Normal('~')),
NamedKey::F9 => ("20", SequenceTerminator::Normal('~')),
NamedKey::F10 => ("21", SequenceTerminator::Normal('~')),
NamedKey::F11 => ("23", SequenceTerminator::Normal('~')),
NamedKey::F12 => ("24", SequenceTerminator::Normal('~')),
NamedKey::F13 => ("57376", SequenceTerminator::Kitty),
NamedKey::F14 => ("57377", SequenceTerminator::Kitty),
NamedKey::F15 => ("57378", SequenceTerminator::Kitty),
NamedKey::F16 => ("57379", SequenceTerminator::Kitty),
NamedKey::F17 => ("57380", SequenceTerminator::Kitty),
NamedKey::F18 => ("57381", SequenceTerminator::Kitty),
NamedKey::F19 => ("57382", SequenceTerminator::Kitty),
NamedKey::F20 => ("57383", SequenceTerminator::Kitty),
NamedKey::F21 => ("57384", SequenceTerminator::Kitty),
NamedKey::F22 => ("57385", SequenceTerminator::Kitty),
NamedKey::F23 => ("57386", SequenceTerminator::Kitty),
NamedKey::F24 => ("57387", SequenceTerminator::Kitty),
NamedKey::F25 => ("57388", SequenceTerminator::Kitty),
NamedKey::F26 => ("57389", SequenceTerminator::Kitty),
NamedKey::F27 => ("57390", SequenceTerminator::Kitty),
NamedKey::F28 => ("57391", SequenceTerminator::Kitty),
NamedKey::F29 => ("57392", SequenceTerminator::Kitty),
NamedKey::F30 => ("57393", SequenceTerminator::Kitty),
NamedKey::F31 => ("57394", SequenceTerminator::Kitty),
NamedKey::F32 => ("57395", SequenceTerminator::Kitty),
NamedKey::F33 => ("57396", SequenceTerminator::Kitty),
NamedKey::F34 => ("57397", SequenceTerminator::Kitty),
NamedKey::F35 => ("57398", SequenceTerminator::Kitty),
NamedKey::ScrollLock => ("57359", SequenceTerminator::Kitty),
NamedKey::PrintScreen => ("57361", SequenceTerminator::Kitty),
NamedKey::Pause => ("57362", SequenceTerminator::Kitty),
NamedKey::ContextMenu => ("57363", SequenceTerminator::Kitty),
NamedKey::MediaPlay => ("57428", SequenceTerminator::Kitty),
NamedKey::MediaPause => ("57429", SequenceTerminator::Kitty),
NamedKey::MediaPlayPause => ("57430", SequenceTerminator::Kitty),
NamedKey::MediaStop => ("57432", SequenceTerminator::Kitty),
NamedKey::MediaFastForward => ("57433", SequenceTerminator::Kitty),
NamedKey::MediaRewind => ("57434", SequenceTerminator::Kitty),
NamedKey::MediaTrackNext => ("57435", SequenceTerminator::Kitty),
NamedKey::MediaTrackPrevious => ("57436", SequenceTerminator::Kitty),
NamedKey::MediaRecord => ("57437", SequenceTerminator::Kitty),
NamedKey::AudioVolumeDown => ("57438", SequenceTerminator::Kitty),
NamedKey::AudioVolumeUp => ("57439", SequenceTerminator::Kitty),
NamedKey::AudioVolumeMute => ("57440", SequenceTerminator::Kitty),
_ => return None,
};
Some(SequenceBase::new(base.into(), terminator))
}
/// Try building escape from control characters (e.g. Enter) and modifiers.
fn try_build_control_char_or_mod(&self, key: &KeyEvent) -> Option<SequenceBase> {
if !self.kitty_encode_all && !self.kitty_seq {
return None;
}
let named = match key.logical_key {
Key::Named(named) => named,
_ => return None,
};
let base = match named {
NamedKey::Tab => "9",
NamedKey::Enter => "13",
NamedKey::Escape => "27",
NamedKey::Space => "32",
NamedKey::Backspace => "127",
_ => "",
};
// Fail when the key is not a named control character and the active mode prohibits us
// from encoding modifier keys.
if !self.kitty_encode_all && base.is_empty() {
return None;
}
let base = match (named, key.location) {
(NamedKey::Shift, KeyLocation::Left) => "57441",
(NamedKey::Control, KeyLocation::Left) => "57442",
(NamedKey::Alt, KeyLocation::Left) => "57443",
(NamedKey::Super, KeyLocation::Left) => "57444",
(NamedKey::Hyper, KeyLocation::Left) => "57445",
(NamedKey::Meta, KeyLocation::Left) => "57446",
(NamedKey::Shift, _) => "57447",
(NamedKey::Control, _) => "57448",
(NamedKey::Alt, _) => "57449",
(NamedKey::Super, _) => "57450",
(NamedKey::Hyper, _) => "57451",
(NamedKey::Meta, _) => "57452",
(NamedKey::CapsLock, _) => "57358",
(NamedKey::NumLock, _) => "57360",
_ => base,
};
if base.is_empty() {
None
} else {
Some(SequenceBase::new(base.into(), SequenceTerminator::Kitty))
}
}
}
pub struct SequenceBase {
/// The base of the payload, which is the `number` and optionally an alt base from the kitty
/// spec.
payload: Cow<'static, str>,
terminator: SequenceTerminator,
}
impl SequenceBase {
fn new(payload: Cow<'static, str>, terminator: SequenceTerminator) -> Self {
Self { payload, terminator }
}
}
#[derive(Debug, Clone, Copy)]
pub enum SequenceTerminator {
/// The normal key esc sequence terminator defined by xterm/dec.
Normal(char),
/// The terminator is for kitty escape sequence.
Kitty,
}
impl SequenceTerminator {
fn encode_esc_sequence(self) -> char {
match self {
SequenceTerminator::Normal(char) => char,
SequenceTerminator::Kitty => 'u',
}
}
}
bitflags::bitflags! {
/// The modifiers encoding for escape sequence.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SequenceModifiers : u8 {
const SHIFT = 0b0000_0001;
const ALT = 0b0000_0010;
const CONTROL = 0b0000_0100;
const SUPER = 0b0000_1000;
// NOTE: Kitty protocol defines additional modifiers to what is present here, like
// Capslock, but it's not a modifier as per winit.
}
}
impl SequenceModifiers {
/// Get the value which should be passed to escape sequence.
pub fn encode_esc_sequence(self) -> u8 {
self.bits() + 1
}
}
impl From<ModifiersState> for SequenceModifiers {
fn from(mods: ModifiersState) -> Self {
let mut modifiers = Self::empty();
modifiers.set(Self::SHIFT, mods.shift_key());
modifiers.set(Self::ALT, mods.alt_key());
modifiers.set(Self::CONTROL, mods.control_key());
modifiers.set(Self::SUPER, mods.super_key());
modifiers
}
}