From 2a612e0f2478d8ca279eac8b7d2cdde247a63c62 Mon Sep 17 00:00:00 2001 From: Jesse Weaver Date: Wed, 10 Aug 2022 01:09:14 -0600 Subject: [PATCH] Add extra modifiers/state from kitty keyboard protocol (#696) --- examples/event-read.rs | 22 ++++- src/event.rs | 22 ++++- src/event/sys/unix/parse.rs | 164 ++++++++++++++++++++++++++++++++++-- 3 files changed, 196 insertions(+), 12 deletions(-) diff --git a/examples/event-read.rs b/examples/event-read.rs index 2a32cefba..549e05a91 100644 --- a/examples/event-read.rs +++ b/examples/event-read.rs @@ -4,7 +4,9 @@ use std::io::stdout; -use crossterm::event::poll; +use crossterm::event::{ + poll, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags, +}; use crossterm::{ cursor::position, event::{ @@ -70,13 +72,27 @@ fn main() -> Result<()> { enable_raw_mode()?; let mut stdout = stdout(); - execute!(stdout, EnableFocusChange, EnableMouseCapture)?; + execute!( + stdout, + EnableFocusChange, + EnableMouseCapture, + PushKeyboardEnhancementFlags( + KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES + | KeyboardEnhancementFlags::REPORT_EVENT_TYPES + ) + )?; if let Err(e) = print_events() { println!("Error: {:?}\r", e); } - execute!(stdout, DisableFocusChange, DisableMouseCapture)?; + execute!( + stdout, + PopKeyboardEnhancementFlags, + DisableFocusChange, + DisableMouseCapture + )?; disable_raw_mode() } diff --git a/src/event.rs b/src/event.rs index e32ea9515..42411b543 100644 --- a/src/event.rs +++ b/src/event.rs @@ -534,12 +534,19 @@ pub enum MouseButton { } bitflags! { - /// Represents key modifiers (shift, control, alt). + /// Represents key modifiers (shift, control, alt, etc.). + /// + /// **Note:** `SUPER`, `HYPER`, and `META` can only be read if + /// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with + /// [`PushKeyboardEnhancementFlags`]. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct KeyModifiers: u8 { const SHIFT = 0b0000_0001; const CONTROL = 0b0000_0010; const ALT = 0b0000_0100; + const SUPER = 0b0000_1000; + const HYPER = 0b0001_0000; + const META = 0b0010_0000; const NONE = 0b0000_0000; } } @@ -555,10 +562,23 @@ pub enum KeyEventKind { bitflags! { /// Represents extra state about the key event. + /// + /// **Note:** This state can only be read if + /// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with + /// [`PushKeyboardEnhancementFlags`]. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct KeyEventState: u8 { /// The key event origins from the keypad. const KEYPAD = 0b0000_0001; + /// Caps Lock was enabled for this key event. + /// + /// **Note:** this is set for the initial press of Num Lock itself. + const CAPS_LOCK = 0b0000_1000; + /// Num Lock was enabled for this key event. + /// + /// **Note:** this is set for the initial press of Num Lock itself. + const NUM_LOCK = 0b0000_1000; + const NONE = 0b0000_0000; } } diff --git a/src/event/sys/unix/parse.rs b/src/event/sys/unix/parse.rs index 37d5ecf70..d96a2f366 100644 --- a/src/event/sys/unix/parse.rs +++ b/src/event/sys/unix/parse.rs @@ -169,6 +169,7 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result> { b'<' => return parse_csi_sgr_mouse(buffer), b'I' => Some(Event::FocusGained), b'O' => Some(Event::FocusLost), + b';' => return parse_csi_modifier_key_code(buffer), b'0'..=b'9' => { // Numbered escape code. if buffer.len() == 3 { @@ -251,9 +252,30 @@ fn parse_modifiers(mask: u8) -> KeyModifiers { if modifier_mask & 4 != 0 { modifiers |= KeyModifiers::CONTROL; } + if modifier_mask & 8 != 0 { + modifiers |= KeyModifiers::SUPER; + } + if modifier_mask & 16 != 0 { + modifiers |= KeyModifiers::HYPER; + } + if modifier_mask & 32 != 0 { + modifiers |= KeyModifiers::META; + } modifiers } +fn parse_modifiers_to_state(mask: u8) -> KeyEventState { + let modifier_mask = mask.saturating_sub(1); + let mut state = KeyEventState::empty(); + if modifier_mask & 64 != 0 { + state |= KeyEventState::CAPS_LOCK; + } + if modifier_mask & 128 != 0 { + state |= KeyEventState::NUM_LOCK; + } + state +} + fn parse_key_event_kind(kind: u8) -> KeyEventKind { match kind { 1 => KeyEventKind::Press, @@ -265,11 +287,32 @@ fn parse_key_event_kind(kind: u8) -> KeyEventKind { pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result> { assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ + // + let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) + .map_err(|_| could_not_parse_event_error())?; + let mut split = s.split(';'); - let modifier_mask = buffer[buffer.len() - 2]; - let key = buffer[buffer.len() - 1]; + split.next(); - let modifiers = parse_modifiers(modifier_mask); + let (modifiers, kind) = + if let Ok((modifier_mask, kind_code)) = modifier_and_kind_parsed(&mut split) { + ( + parse_modifiers(modifier_mask), + parse_key_event_kind(kind_code), + ) + } else if buffer.len() > 3 { + ( + parse_modifiers( + (buffer[buffer.len() - 2] as char) + .to_digit(10) + .ok_or_else(could_not_parse_event_error)? as u8, + ), + KeyEventKind::Press, + ) + } else { + (KeyModifiers::NONE, KeyEventKind::Press) + }; + let key = buffer[buffer.len() - 1]; let keycode = match key { b'A' => KeyCode::Up, @@ -285,7 +328,7 @@ pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result return Err(could_not_parse_event_error()), }; - let input_event = Event::Key(KeyEvent::new(keycode, modifiers)); + let input_event = Event::Key(KeyEvent::new_with_kind(keycode, modifiers, kind)); Ok(Some(InternalEvent::Event(input_event))) } @@ -404,17 +447,18 @@ pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result(&mut split)?; - let (mut modifiers, kind) = + let (mut modifiers, kind, state_from_modifiers) = if let Ok((modifier_mask, kind_code)) = modifier_and_kind_parsed(&mut split) { ( parse_modifiers(modifier_mask), parse_key_event_kind(kind_code), + parse_modifiers_to_state(modifier_mask), ) } else { - (KeyModifiers::NONE, KeyEventKind::Press) + (KeyModifiers::NONE, KeyEventKind::Press, KeyEventState::NONE) }; - let (keycode, state) = { + let (keycode, state_from_keycode) = { if let Some((special_key_code, state)) = translate_functional_key_code(codepoint) { (special_key_code, state) } else if let Some(c) = char::from_u32(codepoint) { @@ -455,12 +499,24 @@ pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result { modifiers.set(KeyModifiers::SHIFT, true) } + ModifierKeyCode::LeftSuper | ModifierKeyCode::RightSuper => { + modifiers.set(KeyModifiers::SUPER, true) + } + ModifierKeyCode::LeftHyper | ModifierKeyCode::RightHyper => { + modifiers.set(KeyModifiers::HYPER, true) + } + ModifierKeyCode::LeftMeta | ModifierKeyCode::RightMeta => { + modifiers.set(KeyModifiers::META, true) + } _ => {} } } let input_event = Event::Key(KeyEvent::new_with_kind_and_state( - keycode, modifiers, kind, state, + keycode, + modifiers, + kind, + state_from_keycode | state_from_modifiers, )); Ok(Some(InternalEvent::Event(input_event))) @@ -1169,5 +1225,97 @@ mod tests { KeyEventKind::Release, )))), ); + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[57450u").unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Modifier(ModifierKeyCode::RightSuper), + KeyModifiers::SUPER, + )))), + ); + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[57451u").unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Modifier(ModifierKeyCode::RightHyper), + KeyModifiers::HYPER, + )))), + ); + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[57452u").unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Modifier(ModifierKeyCode::RightMeta), + KeyModifiers::META, + )))), + ); + } + + #[test] + fn test_parse_csi_u_encoded_key_code_with_extra_modifiers() { + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[97;9u").unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::SUPER + )))), + ); + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[97;17u").unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::HYPER, + )))), + ); + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[97;33u").unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Char('a'), + KeyModifiers::META, + )))), + ); + } + + #[test] + fn test_parse_csi_u_encoded_key_code_with_extra_state() { + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[97;65u").unwrap(), + Some(InternalEvent::Event(Event::Key( + KeyEvent::new_with_kind_and_state( + KeyCode::Char('a'), + KeyModifiers::empty(), + KeyEventKind::Press, + KeyEventState::CAPS_LOCK, + ) + ))), + ); + assert_eq!( + parse_csi_u_encoded_key_code(b"\x1B[49;129u").unwrap(), + Some(InternalEvent::Event(Event::Key( + KeyEvent::new_with_kind_and_state( + KeyCode::Char('1'), + KeyModifiers::empty(), + KeyEventKind::Press, + KeyEventState::NUM_LOCK, + ) + ))), + ); + } + + #[test] + fn test_parse_csi_special_key_code_with_types() { + assert_eq!( + parse_event(b"\x1B[;1:3B", false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new_with_kind( + KeyCode::Down, + KeyModifiers::empty(), + KeyEventKind::Release, + )))), + ); + assert_eq!( + parse_event(b"\x1B[1;1:3B", false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new_with_kind( + KeyCode::Down, + KeyModifiers::empty(), + KeyEventKind::Release, + )))), + ); } }