Skip to content

Commit

Permalink
Add extra modifiers/state from kitty keyboard protocol (#696)
Browse files Browse the repository at this point in the history
  • Loading branch information
pianohacker committed Aug 10, 2022
1 parent 2362bc2 commit 2a612e0
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 12 deletions.
22 changes: 19 additions & 3 deletions examples/event-read.rs
Expand Up @@ -4,7 +4,9 @@

use std::io::stdout;

use crossterm::event::poll;
use crossterm::event::{
poll, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
};
use crossterm::{
cursor::position,
event::{
Expand Down Expand Up @@ -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()
}
22 changes: 21 additions & 1 deletion src/event.rs
Expand Up @@ -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;
}
}
Expand All @@ -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;
}
}

Expand Down
164 changes: 156 additions & 8 deletions src/event/sys/unix/parse.rs
Expand Up @@ -169,6 +169,7 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
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 {
Expand Down Expand Up @@ -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,
Expand All @@ -265,11 +287,32 @@ fn parse_key_event_kind(kind: u8) -> KeyEventKind {

pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
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,
Expand All @@ -285,7 +328,7 @@ pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<Intern
_ => 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)))
}
Expand Down Expand Up @@ -404,17 +447,18 @@ pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result<Option<Inter
// codepoint: ASCII Dec value
let codepoint = next_parsed::<u32>(&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) {
Expand Down Expand Up @@ -455,12 +499,24 @@ pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result<Option<Inter
ModifierKeyCode::LeftShift | ModifierKeyCode::RightShift => {
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)))
Expand Down Expand Up @@ -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,
)))),
);
}
}

0 comments on commit 2a612e0

Please sign in to comment.