use std::borrow::Cow;
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, KeyBinding};
use crate::display::window::ImeInhibitor;
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> {
pub fn key_input(&mut self, key: KeyEvent) {
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 {
if self.ctx.inline_search_state().char_pending {
self.ctx.window().set_ime_inhibitor(ImeInhibitor::VI, false);
}
self.key_release(key, mode, mods);
return;
}
let text = key.text_with_all_modifiers().unwrap_or_default();
if self.ctx.display().hint_state.active() {
for character in text.chars() {
self.ctx.hint_input(character);
}
return;
}
let inline_state = self.ctx.inline_search_state();
if inline_state.char_pending {
self.ctx.inline_search_input(text);
return;
}
self.reset_search_delay();
if self.process_key_bindings(&key) {
return;
}
if self.ctx.search_active() {
for character in text.chars() {
self.ctx.search_input(character);
}
return;
}
if mode.contains(TermMode::VI) {
return;
}
let mods = if self.alt_send_esc(&key, text) { mods } else { mods & !ModifiersState::ALT };
let build_key_sequence = Self::should_build_sequence(&key, text, mode, mods);
let is_modifier_key = Self::is_modifier_key(&key);
let bytes = if build_key_sequence {
build_sequence(key, mods, mode)
} else {
let mut bytes = Vec::with_capacity(text.len() + 1);
if mods.alt_key() {
bytes.push(b'\x1b');
}
bytes.extend_from_slice(text.as_bytes());
bytes
};
if !bytes.is_empty() {
if !is_modifier_key {
self.ctx.on_terminal_input_start();
}
self.ctx.write_to_pty(bytes);
}
}
fn alt_send_esc(&mut self, key: &KeyEvent, text: &str) -> bool {
#[cfg(not(target_os = "macos"))]
let alt_send_esc = self.ctx.modifiers().state().alt_key();
#[cfg(target_os = "macos")]
let alt_send_esc = {
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))
};
match key.logical_key {
Key::Named(named) => {
if named.to_text().is_some() {
alt_send_esc
} else {
self.ctx.modifiers().state().alt_key()
}
},
_ => alt_send_esc && text.chars().count() == 1,
}
}
fn is_modifier_key(key: &KeyEvent) -> bool {
matches!(
key.logical_key.as_ref(),
Key::Named(NamedKey::Shift)
| Key::Named(NamedKey::Control)
| Key::Named(NamedKey::Alt)
| Key::Named(NamedKey::Super)
)
}
fn should_build_sequence(
key: &KeyEvent,
text: &str,
mode: TermMode,
mods: ModifiersState,
) -> bool {
if mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC) {
return true;
}
let disambiguate = mode.contains(TermMode::DISAMBIGUATE_ESC_CODES)
&& (key.logical_key == Key::Named(NamedKey::Escape)
|| key.location == KeyLocation::Numpad
|| (!mods.is_empty()
&& (mods != ModifiersState::SHIFT
|| matches!(
key.logical_key,
Key::Named(NamedKey::Tab)
| Key::Named(NamedKey::Enter)
| Key::Named(NamedKey::Backspace)
))));
match key.logical_key {
_ if disambiguate => true,
Key::Named(named) => named.to_text().is_none(),
_ => text.is_empty(),
}
}
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();
let mut suppress_chars = None;
let logical_key = if let Key::Character(ch) = key.logical_key.as_ref() {
if (cfg!(target_os = "macos") || (cfg!(windows) && mods.control_key()))
&& mods.alt_key()
{
key.key_without_modifiers()
} else {
Key::Character(ch.to_lowercase().into())
}
} else {
key.logical_key.clone()
};
let mut binding_action = |binding: &KeyBinding| {
let key = match (&binding.trigger, &logical_key) {
(BindingKey::Scancode(_), _) => BindingKey::Scancode(key.physical_key),
(_, code) => {
BindingKey::Keycode { key: code.clone(), location: key.location.into() }
},
};
if binding.is_triggered_by(mode, mods, &key) {
*suppress_chars.get_or_insert(true) &= binding.action != Action::ReceiveChar;
Some(binding.action.clone())
} else {
None
}
};
for i in 0..self.ctx.config().key_bindings().len() {
let binding = &self.ctx.config().key_bindings()[i];
if let Some(action) = binding_action(binding) {
action.execute(&mut self.ctx);
}
}
for i in 0..self.ctx.config().hints.enabled.len() {
let hint = &self.ctx.config().hints.enabled[i];
let binding = match hint.binding.as_ref() {
Some(binding) => binding.key_binding(hint),
None => continue,
};
if let Some(action) = binding_action(binding) {
action.execute(&mut self.ctx);
}
}
suppress_chars.unwrap_or(false)
}
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 text = key.text_with_all_modifiers().unwrap_or_default();
let mods = if self.alt_send_esc(&key, text) { mods } else { mods & !ModifiersState::ALT };
let bytes = match key.logical_key.as_ref() {
Key::Named(NamedKey::Enter)
| Key::Named(NamedKey::Tab)
| Key::Named(NamedKey::Backspace)
if !mode.contains(TermMode::REPORT_ALL_KEYS_AS_ESC) =>
{
return;
},
_ => build_sequence(key, mods, mode),
};
self.ctx.write_to_pty(bytes);
}
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);
}
}
}
}
#[inline(never)]
fn build_sequence(key: KeyEvent, mods: ModifiersState, mode: TermMode) -> Vec<u8> {
let mut 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);
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 associated_text = key.text_with_all_modifiers().filter(|text| {
mode.contains(TermMode::REPORT_ASSOCIATED_TEXT)
&& key.state != ElementState::Released
&& !text.is_empty()
&& !is_control_character(text)
});
let sequence_base = context
.try_build_numpad(&key)
.or_else(|| context.try_build_named_kitty(&key))
.or_else(|| context.try_build_named_normal(&key, associated_text.is_some()))
.or_else(|| context.try_build_control_char_or_mod(&key, &mut modifiers))
.or_else(|| context.try_build_textual(&key, associated_text));
let (payload, terminator) = match sequence_base {
Some(SequenceBase { payload, terminator }) => (payload, terminator),
_ => return Vec::new(),
};
let mut payload = format!("\x1b[{payload}");
if kitty_event_type || !modifiers.is_empty() || associated_text.is_some() {
payload.push_str(&format!(";{}", modifiers.encode_esc_sequence()));
}
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);
}
if let Some(text) = associated_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()
}
pub struct SequenceBuilder {
mode: TermMode,
kitty_seq: bool,
kitty_encode_all: bool,
kitty_event_type: bool,
modifiers: SequenceModifiers,
}
impl SequenceBuilder {
fn try_build_textual(
&self,
key: &KeyEvent,
associated_text: Option<&str>,
) -> Option<SequenceBase> {
let character = match key.logical_key.as_ref() {
Key::Character(character) if self.kitty_seq => character,
_ => return None,
};
if character.chars().count() == 1 {
let shift = self.modifiers.contains(SequenceModifiers::SHIFT);
let ch = character.chars().next().unwrap();
let unshifted_ch = if shift { ch.to_lowercase().next().unwrap() } else { ch };
let alternate_key_code = u32::from(ch);
let mut unicode_key_code = u32::from(unshifted_ch);
if shift && alternate_key_code == unicode_key_code {
if let Key::Character(unmodded) = key.key_without_modifiers().as_ref() {
unicode_key_code = u32::from(unmodded.chars().next().unwrap_or(unshifted_ch));
}
}
let payload = if self.mode.contains(TermMode::REPORT_ALTERNATE_KEYS)
&& alternate_key_code != unicode_key_code
{
format!("{unicode_key_code}:{alternate_key_code}")
} else {
unicode_key_code.to_string()
};
Some(SequenceBase::new(payload.into(), SequenceTerminator::Kitty))
} else if self.kitty_encode_all && associated_text.is_some() {
Some(SequenceBase::new("0".into(), SequenceTerminator::Kitty))
} else {
None
}
}
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))
}
fn try_build_named_kitty(&self, key: &KeyEvent) -> Option<SequenceBase> {
let named = match key.logical_key {
Key::Named(named) if self.kitty_seq => named,
_ => return None,
};
let (base, terminator) = match named {
NamedKey::F3 => ("13", 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))
}
fn try_build_named_normal(
&self,
key: &KeyEvent,
has_associated_text: bool,
) -> Option<SequenceBase> {
let named = match key.logical_key {
Key::Named(named) => named,
_ => return None,
};
let one_based =
if self.modifiers.is_empty() && !self.kitty_event_type && !has_associated_text {
""
} 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 => (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 => ("25", SequenceTerminator::Normal('~')),
NamedKey::F14 => ("26", SequenceTerminator::Normal('~')),
NamedKey::F15 => ("28", SequenceTerminator::Normal('~')),
NamedKey::F16 => ("29", SequenceTerminator::Normal('~')),
NamedKey::F17 => ("31", SequenceTerminator::Normal('~')),
NamedKey::F18 => ("32", SequenceTerminator::Normal('~')),
NamedKey::F19 => ("33", SequenceTerminator::Normal('~')),
NamedKey::F20 => ("34", SequenceTerminator::Normal('~')),
_ => return None,
};
Some(SequenceBase::new(base.into(), terminator))
}
fn try_build_control_char_or_mod(
&self,
key: &KeyEvent,
mods: &mut SequenceModifiers,
) -> 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",
_ => "",
};
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,
};
let press = key.state.is_pressed();
match named {
NamedKey::Shift => mods.set(SequenceModifiers::SHIFT, press),
NamedKey::Control => mods.set(SequenceModifiers::CONTROL, press),
NamedKey::Alt => mods.set(SequenceModifiers::ALT, press),
NamedKey::Super => mods.set(SequenceModifiers::SUPER, press),
_ => (),
}
if base.is_empty() {
None
} else {
Some(SequenceBase::new(base.into(), SequenceTerminator::Kitty))
}
}
}
pub struct SequenceBase {
payload: Cow<'static, str>,
terminator: SequenceTerminator,
}
impl SequenceBase {
fn new(payload: Cow<'static, str>, terminator: SequenceTerminator) -> Self {
Self { payload, terminator }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceTerminator {
Normal(char),
Kitty,
}
impl SequenceTerminator {
fn encode_esc_sequence(self) -> char {
match self {
SequenceTerminator::Normal(char) => char,
SequenceTerminator::Kitty => 'u',
}
}
}
bitflags::bitflags! {
#[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;
}
}
impl SequenceModifiers {
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
}
}
fn is_control_character(text: &str) -> bool {
let codepoint = text.bytes().next().unwrap();
text.len() == 1 && (codepoint < 0x20 || (0x7f..=0x9f).contains(&codepoint))
}