#[cfg(not(windows))] use crossterm::event::KeyboardEnhancementFlags; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct TerminalKey { pub code: KeyCode, pub modifiers: KeyModifiers, pub kind: crossterm::event::KeyEventKind, pub shifted_codepoint: Option, } impl TerminalKey { pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self { Self { code, modifiers, kind: crossterm::event::KeyEventKind::Press, shifted_codepoint: None, } } pub fn with_kind(mut self, kind: crossterm::event::KeyEventKind) -> Self { self.kind = kind; self } #[allow(dead_code)] // Reserved for the upcoming raw input parser to preserve shifted/base key pairs. pub fn with_shifted_codepoint(mut self, shifted_codepoint: u32) -> Self { self.shifted_codepoint = Some(shifted_codepoint); self } pub fn as_key_event(self) -> KeyEvent { KeyEvent::new_with_kind(self.code, self.modifiers, self.kind) } } impl From for TerminalKey { fn from(value: KeyEvent) -> Self { Self::new(value.code, value.modifiers).with_kind(value.kind) } } #[cfg(not(windows))] pub fn ime_compatible_keyboard_enhancement_flags() -> KeyboardEnhancementFlags { KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES | KeyboardEnhancementFlags::REPORT_EVENT_TYPES | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ModifyOtherKeysMode { Mode1, Mode2, } impl ModifyOtherKeysMode { pub fn set_sequence(self) -> &'static [u8] { match self { Self::Mode1 => b"\x1b[>4;1m", Self::Mode2 => b"\x1b[>4;2m", } } } pub fn host_modify_other_keys_mode() -> Option { #[cfg(windows)] let alacritty_window_id = std::env::var_os("ALACRITTY_WINDOW_ID").is_some(); #[cfg(not(windows))] let alacritty_window_id = false; host_modify_other_keys_mode_for_env( std::env::var("TMUX").is_ok(), std::env::var("TERM_PROGRAM").ok().as_deref(), std::env::var_os("WEZTERM_PANE").is_some(), alacritty_window_id, ) } fn host_modify_other_keys_mode_for_env( in_tmux: bool, term_program: Option<&str>, wezterm_pane: bool, alacritty_window_id: bool, ) -> Option { if in_tmux { return Some(ModifyOtherKeysMode::Mode2); } if wezterm_pane || alacritty_window_id || term_program.is_some_and(|program| program.eq_ignore_ascii_case("wezterm")) { return Some(ModifyOtherKeysMode::Mode1); } None } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeyboardProtocol { Legacy, Kitty { flags: u16 }, } impl KeyboardProtocol { pub fn from_kitty_flags(flags: u16) -> Self { if flags == 0 { Self::Legacy } else { Self::Kitty { flags } } } pub(crate) fn reports_event_types(self) -> bool { matches!(self, Self::Kitty { flags } if flags & 0b0000_0010 != 0) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MouseProtocolMode { None, Press, PressRelease, ButtonMotion, AnyMotion, } impl MouseProtocolMode { pub fn reporting_enabled(self) -> bool { self != Self::None } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MouseProtocolEncoding { Default, Utf8, Sgr, } #[cfg(test)] mod tests { use super::*; #[test] fn protocol_from_zero_flags_is_legacy() { assert_eq!( KeyboardProtocol::from_kitty_flags(0), KeyboardProtocol::Legacy ); } #[test] fn protocol_from_nonzero_flags_is_kitty() { assert_eq!( KeyboardProtocol::from_kitty_flags(7), KeyboardProtocol::Kitty { flags: 7 } ); } #[cfg(not(windows))] #[test] fn keyboard_enhancement_flags_stay_ime_compatible() { let flags = ime_compatible_keyboard_enhancement_flags(); assert!(flags.contains(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)); assert!(flags.contains(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)); assert!(flags.contains(KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS)); assert!(!flags.contains(KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES)); } #[test] fn modify_other_keys_mode_is_enabled_for_tmux() { assert_eq!( host_modify_other_keys_mode_for_env(true, Some("WezTerm"), true, true), Some(ModifyOtherKeysMode::Mode2) ); } #[test] fn modify_other_keys_mode_is_enabled_for_wezterm_hosts() { assert_eq!( host_modify_other_keys_mode_for_env(false, Some("WezTerm"), false, false), Some(ModifyOtherKeysMode::Mode1) ); assert_eq!( host_modify_other_keys_mode_for_env(false, None, true, false), Some(ModifyOtherKeysMode::Mode1) ); } #[test] fn modify_other_keys_mode_is_enabled_for_alacritty_hosts() { assert_eq!( host_modify_other_keys_mode_for_env(false, None, false, true), Some(ModifyOtherKeysMode::Mode1) ); } #[test] fn modify_other_keys_mode_is_not_enabled_for_unknown_hosts() { assert_eq!( host_modify_other_keys_mode_for_env(false, Some("ghostty"), false, false), None ); assert_eq!( host_modify_other_keys_mode_for_env(false, None, false, false), None ); } }