Add support for functional key codes from kitty keyboard protocol (#691)

This commit is contained in:
Jesse Weaver 2022-07-30 02:33:59 -06:00 committed by GitHub
parent 069497b43b
commit 551659dee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 412 additions and 61 deletions

View File

@ -45,29 +45,24 @@ fn match_event(read_event: Event) {
}
fn main() {
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('z'),
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::SHIFT,
code: KeyCode::Left,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT,
code: KeyCode::Delete,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT,
code: KeyCode::Right,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT | KeyModifiers::CONTROL,
code: KeyCode::Home,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent::new(
KeyCode::Char('z'),
KeyModifiers::CONTROL,
)));
match_event(Event::Key(KeyEvent::new(
KeyCode::Left,
KeyModifiers::SHIFT,
)));
match_event(Event::Key(KeyEvent::new(
KeyCode::Delete,
KeyModifiers::ALT,
)));
match_event(Event::Key(KeyEvent::new(
KeyCode::Right,
KeyModifiers::ALT | KeyModifiers::SHIFT,
)));
match_event(Event::Key(KeyEvent::new(
KeyCode::Home,
KeyModifiers::ALT | KeyModifiers::CONTROL,
)));
}

View File

@ -377,7 +377,7 @@ impl Command for PushKeyboardEnhancementFlags {
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Keyboard progressive enhancement not implemented on Windows.",
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
))
}
@ -404,7 +404,7 @@ impl Command for PopKeyboardEnhancementFlags {
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Keyboard progressive enhancement not implemented on Windows.",
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
))
}
@ -553,6 +553,15 @@ pub enum KeyEventKind {
Release,
}
bitflags! {
/// Represents extra state about the key event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyEventState: u8 {
/// The key event origins from the keypad.
const KEYPAD = 0b0000_0001;
}
}
/// Represents a key event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, Clone, Copy)]
@ -563,6 +572,11 @@ pub struct KeyEvent {
pub modifiers: KeyModifiers,
/// Kind of event.
pub kind: KeyEventKind,
/// Keyboard state.
///
/// Only set if [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
pub state: KeyEventState,
}
impl KeyEvent {
@ -571,6 +585,7 @@ impl KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
@ -583,6 +598,21 @@ impl KeyEvent {
code,
modifiers,
kind,
state: KeyEventState::empty(),
}
}
pub const fn new_with_kind_and_state(
code: KeyCode,
modifiers: KeyModifiers,
kind: KeyEventKind,
state: KeyEventState,
) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind,
state,
}
}
@ -610,6 +640,7 @@ impl From<KeyCode> for KeyEvent {
code,
modifiers: KeyModifiers::empty(),
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
}
@ -620,31 +651,104 @@ impl PartialEq for KeyEvent {
code: lhs_code,
modifiers: lhs_modifiers,
kind: lhs_kind,
state: lhs_state,
} = self.normalize_case();
let KeyEvent {
code: rhs_code,
modifiers: rhs_modifiers,
kind: rhs_kind,
state: rhs_state,
} = other.normalize_case();
(lhs_code == rhs_code) && (lhs_modifiers == rhs_modifiers) && (lhs_kind == rhs_kind)
(lhs_code == rhs_code)
&& (lhs_modifiers == rhs_modifiers)
&& (lhs_kind == rhs_kind)
&& (lhs_state == rhs_state)
}
}
impl Eq for KeyEvent {}
impl Hash for KeyEvent {
fn hash<H: Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, hash_state: &mut H) {
let KeyEvent {
code,
modifiers,
kind,
state,
} = self.normalize_case();
code.hash(state);
modifiers.hash(state);
kind.hash(state);
code.hash(hash_state);
modifiers.hash(hash_state);
kind.hash(hash_state);
state.hash(hash_state);
}
}
/// Represents a media key (as part of [`KeyCode::Media`]).
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MediaKeyCode {
/// Play media key.
Play,
/// Pause media key.
Pause,
/// Play/Pause media key.
PlayPause,
/// Reverse media key.
Reverse,
/// Stop media key.
Stop,
/// Fast-forward media key.
FastForward,
/// Rewind media key.
Rewind,
/// Next-track media key.
TrackNext,
/// Previous-track media key.
TrackPrevious,
/// Record media key.
Record,
/// Lower-volume media key.
LowerVolume,
/// Raise-volume media key.
RaiseVolume,
/// Mute media key.
MuteVolume,
}
/// Represents a modifier key (as part of [`KeyCode::Modifier`]).
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModifierKeyCode {
/// Left Shift key.
LeftShift,
/// Left Control key.
LeftControl,
/// Left Alt key.
LeftAlt,
/// Left Super key.
LeftSuper,
/// Left Hyper key.
LeftHyper,
/// Left Meta key.
LeftMeta,
/// Right Shift key.
RightShift,
/// Right Control key.
RightControl,
/// Right Alt key.
RightAlt,
/// Right Super key.
RightSuper,
/// Right Hyper key.
RightHyper,
/// Right Meta key.
RightMeta,
/// Iso Level3 Shift key.
IsoLevel3Shift,
/// Iso Level5 Shift key.
IsoLevel5Shift,
}
/// Represents a key.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -689,6 +793,61 @@ pub enum KeyCode {
Null,
/// Escape key.
Esc,
/// Caps Lock key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
CapsLock,
/// Scroll Lock key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
ScrollLock,
/// Num Lock key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
NumLock,
/// Print Screen key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
PrintScreen,
/// Pause key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
Pause,
/// Menu key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
Menu,
/// The "Begin" key (often mapped to the 5 key when Num Lock is turned on).
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
KeypadBegin,
/// A media key.
///
/// **Note:** these keys can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
Media(MediaKeyCode),
/// A modifier key.
///
/// **Note:** these keys can only be read if **both**
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] and
/// [`KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES`] have been enabled with
/// [`PushKeyboardEnhancementFlags`].
Modifier(ModifierKeyCode),
}
/// An internal event.

View File

@ -2,8 +2,8 @@ use std::io;
use crate::{
event::{
Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent,
MouseEventKind,
Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MediaKeyCode,
ModifierKeyCode, MouseButton, MouseEvent, MouseEventKind,
},
ErrorKind, Result,
};
@ -160,11 +160,11 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
b'B' => Some(Event::Key(KeyCode::Down.into())),
b'H' => Some(Event::Key(KeyCode::Home.into())),
b'F' => Some(Event::Key(KeyCode::End.into())),
b'Z' => Some(Event::Key(KeyEvent {
code: KeyCode::BackTab,
modifiers: KeyModifiers::SHIFT,
kind: KeyEventKind::Press,
})),
b'Z' => Some(Event::Key(KeyEvent::new_with_kind(
KeyCode::BackTab,
KeyModifiers::SHIFT,
KeyEventKind::Press,
))),
b'M' => return parse_csi_normal_mouse(buffer),
b'<' => return parse_csi_sgr_mouse(buffer),
b'I' => Some(Event::FocusGained),
@ -290,6 +290,107 @@ pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<Intern
Ok(Some(InternalEvent::Event(input_event)))
}
fn translate_functional_key_code(codepoint: u32) -> Option<(KeyCode, KeyEventState)> {
if let Some(keycode) = match codepoint {
57399 => Some(KeyCode::Char('0')),
57400 => Some(KeyCode::Char('1')),
57401 => Some(KeyCode::Char('2')),
57402 => Some(KeyCode::Char('3')),
57403 => Some(KeyCode::Char('4')),
57404 => Some(KeyCode::Char('5')),
57405 => Some(KeyCode::Char('6')),
57406 => Some(KeyCode::Char('7')),
57407 => Some(KeyCode::Char('8')),
57408 => Some(KeyCode::Char('9')),
57409 => Some(KeyCode::Char('.')),
57410 => Some(KeyCode::Char('/')),
57411 => Some(KeyCode::Char('*')),
57412 => Some(KeyCode::Char('-')),
57413 => Some(KeyCode::Char('+')),
57414 => Some(KeyCode::Enter),
57415 => Some(KeyCode::Char('=')),
57416 => Some(KeyCode::Char(',')),
57417 => Some(KeyCode::Left),
57418 => Some(KeyCode::Right),
57419 => Some(KeyCode::Up),
57420 => Some(KeyCode::Down),
57421 => Some(KeyCode::PageUp),
57422 => Some(KeyCode::PageDown),
57423 => Some(KeyCode::Home),
57424 => Some(KeyCode::End),
57425 => Some(KeyCode::Insert),
57426 => Some(KeyCode::Delete),
57427 => Some(KeyCode::KeypadBegin),
_ => None,
} {
return Some((keycode, KeyEventState::KEYPAD));
}
if let Some(keycode) = match codepoint {
57358 => Some(KeyCode::CapsLock),
57359 => Some(KeyCode::ScrollLock),
57360 => Some(KeyCode::NumLock),
57361 => Some(KeyCode::PrintScreen),
57362 => Some(KeyCode::Pause),
57363 => Some(KeyCode::Menu),
57376 => Some(KeyCode::F(13)),
57377 => Some(KeyCode::F(14)),
57378 => Some(KeyCode::F(15)),
57379 => Some(KeyCode::F(16)),
57380 => Some(KeyCode::F(17)),
57381 => Some(KeyCode::F(18)),
57382 => Some(KeyCode::F(19)),
57383 => Some(KeyCode::F(20)),
57384 => Some(KeyCode::F(21)),
57385 => Some(KeyCode::F(22)),
57386 => Some(KeyCode::F(23)),
57387 => Some(KeyCode::F(24)),
57388 => Some(KeyCode::F(25)),
57389 => Some(KeyCode::F(26)),
57390 => Some(KeyCode::F(27)),
57391 => Some(KeyCode::F(28)),
57392 => Some(KeyCode::F(29)),
57393 => Some(KeyCode::F(30)),
57394 => Some(KeyCode::F(31)),
57395 => Some(KeyCode::F(32)),
57396 => Some(KeyCode::F(33)),
57397 => Some(KeyCode::F(34)),
57398 => Some(KeyCode::F(35)),
57428 => Some(KeyCode::Media(MediaKeyCode::Play)),
57429 => Some(KeyCode::Media(MediaKeyCode::Pause)),
57430 => Some(KeyCode::Media(MediaKeyCode::PlayPause)),
57431 => Some(KeyCode::Media(MediaKeyCode::Reverse)),
57432 => Some(KeyCode::Media(MediaKeyCode::Stop)),
57433 => Some(KeyCode::Media(MediaKeyCode::FastForward)),
57434 => Some(KeyCode::Media(MediaKeyCode::Rewind)),
57435 => Some(KeyCode::Media(MediaKeyCode::TrackNext)),
57436 => Some(KeyCode::Media(MediaKeyCode::TrackPrevious)),
57437 => Some(KeyCode::Media(MediaKeyCode::Record)),
57438 => Some(KeyCode::Media(MediaKeyCode::LowerVolume)),
57439 => Some(KeyCode::Media(MediaKeyCode::RaiseVolume)),
57440 => Some(KeyCode::Media(MediaKeyCode::MuteVolume)),
57441 => Some(KeyCode::Modifier(ModifierKeyCode::LeftShift)),
57442 => Some(KeyCode::Modifier(ModifierKeyCode::LeftControl)),
57443 => Some(KeyCode::Modifier(ModifierKeyCode::LeftAlt)),
57444 => Some(KeyCode::Modifier(ModifierKeyCode::LeftSuper)),
57445 => Some(KeyCode::Modifier(ModifierKeyCode::LeftHyper)),
57446 => Some(KeyCode::Modifier(ModifierKeyCode::LeftMeta)),
57447 => Some(KeyCode::Modifier(ModifierKeyCode::RightShift)),
57448 => Some(KeyCode::Modifier(ModifierKeyCode::RightControl)),
57449 => Some(KeyCode::Modifier(ModifierKeyCode::RightAlt)),
57450 => Some(KeyCode::Modifier(ModifierKeyCode::RightSuper)),
57451 => Some(KeyCode::Modifier(ModifierKeyCode::RightHyper)),
57452 => Some(KeyCode::Modifier(ModifierKeyCode::RightMeta)),
57453 => Some(KeyCode::Modifier(ModifierKeyCode::IsoLevel3Shift)),
57454 => Some(KeyCode::Modifier(ModifierKeyCode::IsoLevel5Shift)),
_ => None,
} {
return Some((keycode, KeyEventState::empty()));
}
None
}
pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [
assert!(buffer.ends_with(&[b'u']));
@ -303,7 +404,7 @@ 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 (modifiers, kind) =
let (mut modifiers, kind) =
if let Ok((modifier_mask, kind_code)) = modifier_and_kind_parsed(&mut split) {
(
parse_modifiers(modifier_mask),
@ -313,32 +414,54 @@ pub(crate) fn parse_csi_u_encoded_key_code(buffer: &[u8]) -> Result<Option<Inter
(KeyModifiers::NONE, KeyEventKind::Press)
};
let keycode = {
if let Some(c) = char::from_u32(codepoint) {
match c {
'\x1B' => KeyCode::Esc,
'\r' => KeyCode::Enter,
// Issue #371: \n = 0xA, which is also the keycode for Ctrl+J. The only reason we get
// newlines as input is because the terminal converts \r into \n for us. When we
// enter raw mode, we disable that, so \n no longer has any meaning - it's better to
// use Ctrl+J. Waiting to handle it here means it gets picked up later
'\n' if !crate::terminal::sys::is_raw_mode_enabled() => KeyCode::Enter,
'\t' => {
if modifiers.contains(KeyModifiers::SHIFT) {
KeyCode::BackTab
} else {
KeyCode::Tab
let (keycode, state) = {
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) {
(
match c {
'\x1B' => KeyCode::Esc,
'\r' => KeyCode::Enter,
// Issue #371: \n = 0xA, which is also the keycode for Ctrl+J. The only reason we get
// newlines as input is because the terminal converts \r into \n for us. When we
// enter raw mode, we disable that, so \n no longer has any meaning - it's better to
// use Ctrl+J. Waiting to handle it here means it gets picked up later
'\n' if !crate::terminal::sys::is_raw_mode_enabled() => KeyCode::Enter,
'\t' => {
if modifiers.contains(KeyModifiers::SHIFT) {
KeyCode::BackTab
} else {
KeyCode::Tab
}
}
}
'\x7F' => KeyCode::Backspace,
_ => KeyCode::Char(c),
}
'\x7F' => KeyCode::Backspace,
_ => KeyCode::Char(c),
},
KeyEventState::empty(),
)
} else {
return Err(could_not_parse_event_error());
}
};
let input_event = Event::Key(KeyEvent::new_with_kind(keycode, modifiers, kind));
if let KeyCode::Modifier(modifier_keycode) = keycode {
match modifier_keycode {
ModifierKeyCode::LeftAlt | ModifierKeyCode::RightAlt => {
modifiers.set(KeyModifiers::ALT, true)
}
ModifierKeyCode::LeftControl | ModifierKeyCode::RightControl => {
modifiers.set(KeyModifiers::CONTROL, true)
}
ModifierKeyCode::LeftShift | ModifierKeyCode::RightShift => {
modifiers.set(KeyModifiers::SHIFT, true)
}
_ => {}
}
}
let input_event = Event::Key(KeyEvent::new_with_kind_and_state(
keycode, modifiers, kind, state,
));
Ok(Some(InternalEvent::Event(input_event)))
}
@ -568,7 +691,7 @@ pub(crate) fn parse_utf8_char(buffer: &[u8]) -> Result<Option<char>> {
#[cfg(test)]
mod tests {
use crate::event::{KeyModifiers, MouseButton, MouseEvent};
use crate::event::{KeyEventState, KeyModifiers, MouseButton, MouseEvent};
use super::*;
@ -928,6 +1051,60 @@ mod tests {
KeyModifiers::empty()
)))),
);
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57358u").unwrap(),
Some(InternalEvent::Event(Event::Key(KeyEvent::new(
KeyCode::CapsLock,
KeyModifiers::empty()
)))),
);
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57376u").unwrap(),
Some(InternalEvent::Event(Event::Key(KeyEvent::new(
KeyCode::F(13),
KeyModifiers::empty()
)))),
);
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57428u").unwrap(),
Some(InternalEvent::Event(Event::Key(KeyEvent::new(
KeyCode::Media(MediaKeyCode::Play),
KeyModifiers::empty()
)))),
);
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57441u").unwrap(),
Some(InternalEvent::Event(Event::Key(KeyEvent::new(
KeyCode::Modifier(ModifierKeyCode::LeftShift),
KeyModifiers::SHIFT,
)))),
);
}
#[test]
fn test_parse_csi_u_encoded_keypad_code() {
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57399u").unwrap(),
Some(InternalEvent::Event(Event::Key(
KeyEvent::new_with_kind_and_state(
KeyCode::Char('0'),
KeyModifiers::empty(),
KeyEventKind::Press,
KeyEventState::KEYPAD,
)
))),
);
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57419u").unwrap(),
Some(InternalEvent::Event(Event::Key(
KeyEvent::new_with_kind_and_state(
KeyCode::Up,
KeyModifiers::empty(),
KeyEventKind::Press,
KeyEventState::KEYPAD,
)
))),
);
}
#[test]
@ -973,4 +1150,24 @@ mod tests {
)))),
);
}
#[test]
fn test_parse_csi_u_encoded_key_code_has_modifier_on_modifier_press() {
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57449u").unwrap(),
Some(InternalEvent::Event(Event::Key(KeyEvent::new_with_kind(
KeyCode::Modifier(ModifierKeyCode::RightAlt),
KeyModifiers::ALT,
KeyEventKind::Press,
)))),
);
assert_eq!(
parse_csi_u_encoded_key_code(b"\x1B[57449;3:3u").unwrap(),
Some(InternalEvent::Event(Event::Key(KeyEvent::new_with_kind(
KeyCode::Modifier(ModifierKeyCode::RightAlt),
KeyModifiers::ALT,
KeyEventKind::Release,
)))),
);
}
}