Add extra modifiers/state from kitty keyboard protocol (#696)
This commit is contained in:
parent
2362bc2cc6
commit
2a612e0f24
@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
use std::io::stdout;
|
use std::io::stdout;
|
||||||
|
|
||||||
use crossterm::event::poll;
|
use crossterm::event::{
|
||||||
|
poll, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
|
||||||
|
};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::position,
|
cursor::position,
|
||||||
event::{
|
event::{
|
||||||
@ -70,13 +72,27 @@ fn main() -> Result<()> {
|
|||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
|
||||||
let mut stdout = stdout();
|
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() {
|
if let Err(e) = print_events() {
|
||||||
println!("Error: {:?}\r", e);
|
println!("Error: {:?}\r", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
execute!(stdout, DisableFocusChange, DisableMouseCapture)?;
|
execute!(
|
||||||
|
stdout,
|
||||||
|
PopKeyboardEnhancementFlags,
|
||||||
|
DisableFocusChange,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
|
|
||||||
disable_raw_mode()
|
disable_raw_mode()
|
||||||
}
|
}
|
||||||
|
22
src/event.rs
22
src/event.rs
@ -534,12 +534,19 @@ pub enum MouseButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
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))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct KeyModifiers: u8 {
|
pub struct KeyModifiers: u8 {
|
||||||
const SHIFT = 0b0000_0001;
|
const SHIFT = 0b0000_0001;
|
||||||
const CONTROL = 0b0000_0010;
|
const CONTROL = 0b0000_0010;
|
||||||
const ALT = 0b0000_0100;
|
const ALT = 0b0000_0100;
|
||||||
|
const SUPER = 0b0000_1000;
|
||||||
|
const HYPER = 0b0001_0000;
|
||||||
|
const META = 0b0010_0000;
|
||||||
const NONE = 0b0000_0000;
|
const NONE = 0b0000_0000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -555,10 +562,23 @@ pub enum KeyEventKind {
|
|||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
/// Represents extra state about the key event.
|
/// 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))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct KeyEventState: u8 {
|
pub struct KeyEventState: u8 {
|
||||||
/// The key event origins from the keypad.
|
/// The key event origins from the keypad.
|
||||||
const KEYPAD = 0b0000_0001;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +169,7 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
|
|||||||
b'<' => return parse_csi_sgr_mouse(buffer),
|
b'<' => return parse_csi_sgr_mouse(buffer),
|
||||||
b'I' => Some(Event::FocusGained),
|
b'I' => Some(Event::FocusGained),
|
||||||
b'O' => Some(Event::FocusLost),
|
b'O' => Some(Event::FocusLost),
|
||||||
|
b';' => return parse_csi_modifier_key_code(buffer),
|
||||||
b'0'..=b'9' => {
|
b'0'..=b'9' => {
|
||||||
// Numbered escape code.
|
// Numbered escape code.
|
||||||
if buffer.len() == 3 {
|
if buffer.len() == 3 {
|
||||||
@ -251,9 +252,30 @@ fn parse_modifiers(mask: u8) -> KeyModifiers {
|
|||||||
if modifier_mask & 4 != 0 {
|
if modifier_mask & 4 != 0 {
|
||||||
modifiers |= KeyModifiers::CONTROL;
|
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
|
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 {
|
fn parse_key_event_kind(kind: u8) -> KeyEventKind {
|
||||||
match kind {
|
match kind {
|
||||||
1 => KeyEventKind::Press,
|
1 => KeyEventKind::Press,
|
||||||
@ -265,12 +287,33 @@ fn parse_key_event_kind(kind: u8) -> KeyEventKind {
|
|||||||
|
|
||||||
pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
|
pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
|
||||||
assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [
|
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];
|
split.next();
|
||||||
|
|
||||||
|
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 key = buffer[buffer.len() - 1];
|
||||||
|
|
||||||
let modifiers = parse_modifiers(modifier_mask);
|
|
||||||
|
|
||||||
let keycode = match key {
|
let keycode = match key {
|
||||||
b'A' => KeyCode::Up,
|
b'A' => KeyCode::Up,
|
||||||
b'B' => KeyCode::Down,
|
b'B' => KeyCode::Down,
|
||||||
@ -285,7 +328,7 @@ pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<Intern
|
|||||||
_ => return Err(could_not_parse_event_error()),
|
_ => 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)))
|
Ok(Some(InternalEvent::Event(input_event)))
|
||||||
}
|
}
|
||||||
@ -404,17 +447,18 @@ pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result<Option<Inter
|
|||||||
// codepoint: ASCII Dec value
|
// codepoint: ASCII Dec value
|
||||||
let codepoint = next_parsed::<u32>(&mut split)?;
|
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) {
|
if let Ok((modifier_mask, kind_code)) = modifier_and_kind_parsed(&mut split) {
|
||||||
(
|
(
|
||||||
parse_modifiers(modifier_mask),
|
parse_modifiers(modifier_mask),
|
||||||
parse_key_event_kind(kind_code),
|
parse_key_event_kind(kind_code),
|
||||||
|
parse_modifiers_to_state(modifier_mask),
|
||||||
)
|
)
|
||||||
} else {
|
} 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) {
|
if let Some((special_key_code, state)) = translate_functional_key_code(codepoint) {
|
||||||
(special_key_code, state)
|
(special_key_code, state)
|
||||||
} else if let Some(c) = char::from_u32(codepoint) {
|
} 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<Option<Inter
|
|||||||
ModifierKeyCode::LeftShift | ModifierKeyCode::RightShift => {
|
ModifierKeyCode::LeftShift | ModifierKeyCode::RightShift => {
|
||||||
modifiers.set(KeyModifiers::SHIFT, true)
|
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(
|
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)))
|
Ok(Some(InternalEvent::Event(input_event)))
|
||||||
@ -1169,5 +1225,97 @@ mod tests {
|
|||||||
KeyEventKind::Release,
|
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,
|
||||||
|
)))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user