mirror of
https://github.com/wez/wezterm.git
synced 2024-12-21 12:21:32 +03:00
1146 lines
40 KiB
Rust
1146 lines
40 KiB
Rust
//! This module provides an InputParser struct to help with parsing
|
|
//! input received from a terminal.
|
|
use crate::escape::csi::MouseReport;
|
|
use crate::escape::parser::Parser;
|
|
use crate::escape::{Action, CSI};
|
|
use crate::keymap::{Found, KeyMap};
|
|
use crate::readbuf::ReadBuffer;
|
|
use bitflags::bitflags;
|
|
use serde::{Deserialize, Serialize};
|
|
use std;
|
|
|
|
#[cfg(windows)]
|
|
use winapi::um::wincon::{
|
|
INPUT_RECORD, KEY_EVENT, KEY_EVENT_RECORD, MOUSE_EVENT, MOUSE_EVENT_RECORD,
|
|
WINDOW_BUFFER_SIZE_EVENT, WINDOW_BUFFER_SIZE_RECORD,
|
|
};
|
|
|
|
bitflags! {
|
|
#[derive(Default, Serialize, Deserialize)]
|
|
pub struct Modifiers: u8 {
|
|
const NONE = 0;
|
|
const SHIFT = 1<<1;
|
|
const ALT = 1<<2;
|
|
const CTRL = 1<<3;
|
|
const SUPER = 1<<4;
|
|
}
|
|
}
|
|
bitflags! {
|
|
#[derive(Default, Serialize, Deserialize)]
|
|
pub struct MouseButtons: u8 {
|
|
const NONE = 0;
|
|
const LEFT = 1<<1;
|
|
const RIGHT = 1<<2;
|
|
const MIDDLE = 1<<3;
|
|
const VERT_WHEEL = 1<<4;
|
|
const HORZ_WHEEL = 1<<5;
|
|
/// if set then the wheel movement was in the positive
|
|
/// direction, else the negative direction
|
|
const WHEEL_POSITIVE = 1<<6;
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum InputEvent {
|
|
Key(KeyEvent),
|
|
Mouse(MouseEvent),
|
|
/// Detected that the user has resized the terminal
|
|
Resized {
|
|
cols: usize,
|
|
rows: usize,
|
|
},
|
|
/// For terminals that support Bracketed Paste mode,
|
|
/// pastes are collected and reported as this variant.
|
|
Paste(String),
|
|
/// The program has woken the input thread.
|
|
Wake,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct MouseEvent {
|
|
pub x: u16,
|
|
pub y: u16,
|
|
pub mouse_buttons: MouseButtons,
|
|
pub modifiers: Modifiers,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct KeyEvent {
|
|
/// Which key was pressed
|
|
pub key: KeyCode,
|
|
|
|
/// Which modifiers are down
|
|
pub modifiers: Modifiers,
|
|
}
|
|
|
|
/// Which key is pressed. Not all of these are probable to appear
|
|
/// on most systems. A lot of this list is @wez trawling docs and
|
|
/// making an entry for things that might be possible in this first pass.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum KeyCode {
|
|
/// The decoded unicode character
|
|
Char(char),
|
|
|
|
Hyper,
|
|
Super,
|
|
Meta,
|
|
|
|
/// Ctrl-break on windows
|
|
Cancel,
|
|
Backspace,
|
|
Tab,
|
|
Clear,
|
|
Enter,
|
|
Shift,
|
|
Escape,
|
|
LeftShift,
|
|
RightShift,
|
|
Control,
|
|
LeftControl,
|
|
RightControl,
|
|
Alt,
|
|
LeftAlt,
|
|
RightAlt,
|
|
Menu,
|
|
LeftMenu,
|
|
RightMenu,
|
|
Pause,
|
|
CapsLock,
|
|
PageUp,
|
|
PageDown,
|
|
End,
|
|
Home,
|
|
LeftArrow,
|
|
RightArrow,
|
|
UpArrow,
|
|
DownArrow,
|
|
Select,
|
|
Print,
|
|
Execute,
|
|
PrintScreen,
|
|
Insert,
|
|
Delete,
|
|
Help,
|
|
LeftWindows,
|
|
RightWindows,
|
|
Applications,
|
|
Sleep,
|
|
Numpad0,
|
|
Numpad1,
|
|
Numpad2,
|
|
Numpad3,
|
|
Numpad4,
|
|
Numpad5,
|
|
Numpad6,
|
|
Numpad7,
|
|
Numpad8,
|
|
Numpad9,
|
|
Multiply,
|
|
Add,
|
|
Separator,
|
|
Subtract,
|
|
Decimal,
|
|
Divide,
|
|
/// F1-F24 are possible
|
|
Function(u8),
|
|
NumLock,
|
|
ScrollLock,
|
|
BrowserBack,
|
|
BrowserForward,
|
|
BrowserRefresh,
|
|
BrowserStop,
|
|
BrowserSearch,
|
|
BrowserFavorites,
|
|
BrowserHome,
|
|
VolumeMute,
|
|
VolumeDown,
|
|
VolumeUp,
|
|
MediaNextTrack,
|
|
MediaPrevTrack,
|
|
MediaStop,
|
|
MediaPlayPause,
|
|
ApplicationLeftArrow,
|
|
ApplicationRightArrow,
|
|
ApplicationUpArrow,
|
|
ApplicationDownArrow,
|
|
|
|
#[doc(hidden)]
|
|
InternalPasteStart,
|
|
#[doc(hidden)]
|
|
InternalPasteEnd,
|
|
}
|
|
|
|
impl KeyCode {
|
|
/// if SHIFT is held and we have KeyCode::Char('c') we want to normalize
|
|
/// that keycode to KeyCode::Char('C'); that is what this function does.
|
|
/// In theory we should give the same treatment to keys like `[` -> `{`
|
|
/// but that assumes something about the keyboard layout and is probably
|
|
/// better done in the gui frontend rather than this layer.
|
|
/// In fact, this function might be better off if it lived elsewhere.
|
|
pub fn normalize_shift_to_upper_case(self, modifiers: Modifiers) -> KeyCode {
|
|
if modifiers.contains(Modifiers::SHIFT) {
|
|
match self {
|
|
KeyCode::Char(c) if c.is_ascii_lowercase() => KeyCode::Char(c.to_ascii_uppercase()),
|
|
_ => self,
|
|
}
|
|
} else {
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Return true if the key represents a modifier key.
|
|
pub fn is_modifier(self) -> bool {
|
|
match self {
|
|
Self::Hyper
|
|
| Self::Super
|
|
| Self::Meta
|
|
| Self::Shift
|
|
| Self::LeftShift
|
|
| Self::RightShift
|
|
| Self::Control
|
|
| Self::LeftControl
|
|
| Self::RightControl
|
|
| Self::Alt
|
|
| Self::LeftAlt
|
|
| Self::RightAlt
|
|
| Self::LeftWindows
|
|
| Self::RightWindows => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum InputState {
|
|
Normal,
|
|
EscapeMaybeAlt,
|
|
Pasting(usize),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct InputParser {
|
|
key_map: KeyMap<InputEvent>,
|
|
buf: ReadBuffer,
|
|
state: InputState,
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
mod windows {
|
|
use super::*;
|
|
use std;
|
|
use winapi::um::winuser;
|
|
|
|
fn modifiers_from_ctrl_key_state(state: u32) -> Modifiers {
|
|
use winapi::um::wincon::*;
|
|
|
|
let mut mods = Modifiers::NONE;
|
|
|
|
if (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0 {
|
|
mods |= Modifiers::ALT;
|
|
}
|
|
|
|
if (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0 {
|
|
mods |= Modifiers::CTRL;
|
|
}
|
|
|
|
if (state & SHIFT_PRESSED) != 0 {
|
|
mods |= Modifiers::SHIFT;
|
|
}
|
|
|
|
// TODO: we could report caps lock, numlock and scrolllock
|
|
|
|
mods
|
|
}
|
|
impl InputParser {
|
|
fn decode_key_record<F: FnMut(InputEvent)>(
|
|
&mut self,
|
|
event: &KEY_EVENT_RECORD,
|
|
callback: &mut F,
|
|
) {
|
|
// TODO: do we want downs instead of ups?
|
|
if event.bKeyDown == 0 {
|
|
return;
|
|
}
|
|
|
|
let key_code = match std::char::from_u32(*unsafe { event.uChar.UnicodeChar() } as u32) {
|
|
Some(unicode) if unicode > '\x00' => {
|
|
let mut buf = [0u8; 4];
|
|
self.buf
|
|
.extend_with(unicode.encode_utf8(&mut buf).as_bytes());
|
|
self.process_bytes(callback, true);
|
|
return;
|
|
}
|
|
_ => match event.wVirtualKeyCode as i32 {
|
|
winuser::VK_CANCEL => KeyCode::Cancel,
|
|
winuser::VK_BACK => KeyCode::Backspace,
|
|
winuser::VK_TAB => KeyCode::Tab,
|
|
winuser::VK_CLEAR => KeyCode::Clear,
|
|
winuser::VK_RETURN => KeyCode::Enter,
|
|
winuser::VK_SHIFT => KeyCode::Shift,
|
|
winuser::VK_CONTROL => KeyCode::Control,
|
|
winuser::VK_MENU => KeyCode::Menu,
|
|
winuser::VK_PAUSE => KeyCode::Pause,
|
|
winuser::VK_CAPITAL => KeyCode::CapsLock,
|
|
winuser::VK_ESCAPE => KeyCode::Escape,
|
|
winuser::VK_PRIOR => KeyCode::PageUp,
|
|
winuser::VK_NEXT => KeyCode::PageDown,
|
|
winuser::VK_END => KeyCode::End,
|
|
winuser::VK_HOME => KeyCode::Home,
|
|
winuser::VK_LEFT => KeyCode::LeftArrow,
|
|
winuser::VK_RIGHT => KeyCode::RightArrow,
|
|
winuser::VK_UP => KeyCode::UpArrow,
|
|
winuser::VK_DOWN => KeyCode::DownArrow,
|
|
winuser::VK_SELECT => KeyCode::Select,
|
|
winuser::VK_PRINT => KeyCode::Print,
|
|
winuser::VK_EXECUTE => KeyCode::Execute,
|
|
winuser::VK_SNAPSHOT => KeyCode::PrintScreen,
|
|
winuser::VK_INSERT => KeyCode::Insert,
|
|
winuser::VK_DELETE => KeyCode::Delete,
|
|
winuser::VK_HELP => KeyCode::Help,
|
|
winuser::VK_LWIN => KeyCode::LeftWindows,
|
|
winuser::VK_RWIN => KeyCode::RightWindows,
|
|
winuser::VK_APPS => KeyCode::Applications,
|
|
winuser::VK_SLEEP => KeyCode::Sleep,
|
|
winuser::VK_NUMPAD0 => KeyCode::Numpad0,
|
|
winuser::VK_NUMPAD1 => KeyCode::Numpad1,
|
|
winuser::VK_NUMPAD2 => KeyCode::Numpad2,
|
|
winuser::VK_NUMPAD3 => KeyCode::Numpad3,
|
|
winuser::VK_NUMPAD4 => KeyCode::Numpad4,
|
|
winuser::VK_NUMPAD5 => KeyCode::Numpad5,
|
|
winuser::VK_NUMPAD6 => KeyCode::Numpad6,
|
|
winuser::VK_NUMPAD7 => KeyCode::Numpad7,
|
|
winuser::VK_NUMPAD8 => KeyCode::Numpad8,
|
|
winuser::VK_NUMPAD9 => KeyCode::Numpad9,
|
|
winuser::VK_MULTIPLY => KeyCode::Multiply,
|
|
winuser::VK_ADD => KeyCode::Add,
|
|
winuser::VK_SEPARATOR => KeyCode::Separator,
|
|
winuser::VK_SUBTRACT => KeyCode::Subtract,
|
|
winuser::VK_DECIMAL => KeyCode::Decimal,
|
|
winuser::VK_DIVIDE => KeyCode::Divide,
|
|
winuser::VK_F1 => KeyCode::Function(1),
|
|
winuser::VK_F2 => KeyCode::Function(2),
|
|
winuser::VK_F3 => KeyCode::Function(3),
|
|
winuser::VK_F4 => KeyCode::Function(4),
|
|
winuser::VK_F5 => KeyCode::Function(5),
|
|
winuser::VK_F6 => KeyCode::Function(6),
|
|
winuser::VK_F7 => KeyCode::Function(7),
|
|
winuser::VK_F8 => KeyCode::Function(8),
|
|
winuser::VK_F9 => KeyCode::Function(9),
|
|
winuser::VK_F10 => KeyCode::Function(10),
|
|
winuser::VK_F11 => KeyCode::Function(11),
|
|
winuser::VK_F12 => KeyCode::Function(12),
|
|
winuser::VK_F13 => KeyCode::Function(13),
|
|
winuser::VK_F14 => KeyCode::Function(14),
|
|
winuser::VK_F15 => KeyCode::Function(15),
|
|
winuser::VK_F16 => KeyCode::Function(16),
|
|
winuser::VK_F17 => KeyCode::Function(17),
|
|
winuser::VK_F18 => KeyCode::Function(18),
|
|
winuser::VK_F19 => KeyCode::Function(19),
|
|
winuser::VK_F20 => KeyCode::Function(20),
|
|
winuser::VK_F21 => KeyCode::Function(21),
|
|
winuser::VK_F22 => KeyCode::Function(22),
|
|
winuser::VK_F23 => KeyCode::Function(23),
|
|
winuser::VK_F24 => KeyCode::Function(24),
|
|
winuser::VK_NUMLOCK => KeyCode::NumLock,
|
|
winuser::VK_SCROLL => KeyCode::ScrollLock,
|
|
winuser::VK_LSHIFT => KeyCode::LeftShift,
|
|
winuser::VK_RSHIFT => KeyCode::RightShift,
|
|
winuser::VK_LCONTROL => KeyCode::LeftControl,
|
|
winuser::VK_RCONTROL => KeyCode::RightControl,
|
|
winuser::VK_LMENU => KeyCode::LeftMenu,
|
|
winuser::VK_RMENU => KeyCode::RightMenu,
|
|
winuser::VK_BROWSER_BACK => KeyCode::BrowserBack,
|
|
winuser::VK_BROWSER_FORWARD => KeyCode::BrowserForward,
|
|
winuser::VK_BROWSER_REFRESH => KeyCode::BrowserRefresh,
|
|
winuser::VK_BROWSER_STOP => KeyCode::BrowserStop,
|
|
winuser::VK_BROWSER_SEARCH => KeyCode::BrowserSearch,
|
|
winuser::VK_BROWSER_FAVORITES => KeyCode::BrowserFavorites,
|
|
winuser::VK_BROWSER_HOME => KeyCode::BrowserHome,
|
|
winuser::VK_VOLUME_MUTE => KeyCode::VolumeMute,
|
|
winuser::VK_VOLUME_DOWN => KeyCode::VolumeDown,
|
|
winuser::VK_VOLUME_UP => KeyCode::VolumeUp,
|
|
winuser::VK_MEDIA_NEXT_TRACK => KeyCode::MediaNextTrack,
|
|
winuser::VK_MEDIA_PREV_TRACK => KeyCode::MediaPrevTrack,
|
|
winuser::VK_MEDIA_STOP => KeyCode::MediaStop,
|
|
winuser::VK_MEDIA_PLAY_PAUSE => KeyCode::MediaPlayPause,
|
|
_ => return,
|
|
},
|
|
};
|
|
let mut modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState);
|
|
|
|
let key_code = key_code.normalize_shift_to_upper_case(modifiers);
|
|
if let KeyCode::Char(c) = key_code {
|
|
if c.is_ascii_uppercase() {
|
|
modifiers.remove(Modifiers::SHIFT);
|
|
}
|
|
}
|
|
|
|
let input_event = InputEvent::Key(KeyEvent {
|
|
key: key_code,
|
|
modifiers,
|
|
});
|
|
for _ in 0..event.wRepeatCount {
|
|
callback(input_event.clone());
|
|
}
|
|
}
|
|
|
|
fn decode_mouse_record<F: FnMut(InputEvent)>(
|
|
&self,
|
|
event: &MOUSE_EVENT_RECORD,
|
|
callback: &mut F,
|
|
) {
|
|
use winapi::um::wincon::*;
|
|
let mut buttons = MouseButtons::NONE;
|
|
|
|
if (event.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0 {
|
|
buttons |= MouseButtons::LEFT;
|
|
}
|
|
if (event.dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0 {
|
|
buttons |= MouseButtons::RIGHT;
|
|
}
|
|
if (event.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0 {
|
|
buttons |= MouseButtons::MIDDLE;
|
|
}
|
|
|
|
let modifiers = modifiers_from_ctrl_key_state(event.dwControlKeyState);
|
|
|
|
if (event.dwEventFlags & MOUSE_WHEELED) != 0 {
|
|
buttons |= MouseButtons::VERT_WHEEL;
|
|
if (event.dwButtonState >> 8) != 0 {
|
|
buttons |= MouseButtons::WHEEL_POSITIVE;
|
|
}
|
|
} else if (event.dwEventFlags & MOUSE_HWHEELED) != 0 {
|
|
buttons |= MouseButtons::HORZ_WHEEL;
|
|
if (event.dwButtonState >> 8) != 0 {
|
|
buttons |= MouseButtons::WHEEL_POSITIVE;
|
|
}
|
|
}
|
|
|
|
let mouse = InputEvent::Mouse(MouseEvent {
|
|
x: event.dwMousePosition.X as u16,
|
|
y: event.dwMousePosition.Y as u16,
|
|
mouse_buttons: buttons,
|
|
modifiers,
|
|
});
|
|
|
|
if (event.dwEventFlags & DOUBLE_CLICK) != 0 {
|
|
callback(mouse.clone());
|
|
}
|
|
callback(mouse);
|
|
}
|
|
|
|
fn decode_resize_record<F: FnMut(InputEvent)>(
|
|
&self,
|
|
event: &WINDOW_BUFFER_SIZE_RECORD,
|
|
callback: &mut F,
|
|
) {
|
|
callback(InputEvent::Resized {
|
|
rows: event.dwSize.Y as usize,
|
|
cols: event.dwSize.X as usize,
|
|
});
|
|
}
|
|
|
|
pub fn decode_input_records<F: FnMut(InputEvent)>(
|
|
&mut self,
|
|
records: &[INPUT_RECORD],
|
|
callback: &mut F,
|
|
) {
|
|
for record in records {
|
|
match record.EventType {
|
|
KEY_EVENT => {
|
|
self.decode_key_record(unsafe { record.Event.KeyEvent() }, callback)
|
|
}
|
|
MOUSE_EVENT => {
|
|
self.decode_mouse_record(unsafe { record.Event.MouseEvent() }, callback)
|
|
}
|
|
WINDOW_BUFFER_SIZE_EVENT => self.decode_resize_record(
|
|
unsafe { record.Event.WindowBufferSizeEvent() },
|
|
callback,
|
|
),
|
|
_ => {}
|
|
}
|
|
}
|
|
self.process_bytes(callback, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for InputParser {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl InputParser {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
key_map: Self::build_basic_key_map(),
|
|
buf: ReadBuffer::new(),
|
|
state: InputState::Normal,
|
|
}
|
|
}
|
|
|
|
fn build_basic_key_map() -> KeyMap<InputEvent> {
|
|
let mut map = KeyMap::new();
|
|
|
|
let modifier_combos = &[
|
|
("", Modifiers::NONE),
|
|
(";1", Modifiers::NONE),
|
|
(";2", Modifiers::SHIFT),
|
|
(";3", Modifiers::ALT),
|
|
(";4", Modifiers::ALT | Modifiers::SHIFT),
|
|
(";5", Modifiers::CTRL),
|
|
(";6", Modifiers::CTRL | Modifiers::SHIFT),
|
|
(";7", Modifiers::CTRL | Modifiers::ALT),
|
|
(";8", Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT),
|
|
];
|
|
// Meta is theoretically a distinct modifier of its own, but modern systems don't
|
|
// have a dedicated Meta key and use the Alt/Option key instead. The mapping
|
|
// below is reproduced from the xterm documentation from a time where it was
|
|
// possible to hold both Alt and Meta down as modifiers. Since we define meta to
|
|
// ALT, the use of `meta | ALT` in the table below appears to be redundant,
|
|
// but makes it easier to see that the mapping matches xterm when viewing
|
|
// its documentation.
|
|
let meta = Modifiers::ALT;
|
|
let meta_modifier_combos = &[
|
|
(";9", meta),
|
|
(";10", meta | Modifiers::SHIFT),
|
|
(";11", meta | Modifiers::ALT),
|
|
(";12", meta | Modifiers::ALT | Modifiers::SHIFT),
|
|
(";13", meta | Modifiers::CTRL),
|
|
(";14", meta | Modifiers::CTRL | Modifiers::SHIFT),
|
|
(";15", meta | Modifiers::CTRL | Modifiers::ALT),
|
|
(
|
|
";16",
|
|
meta | Modifiers::CTRL | Modifiers::ALT | Modifiers::SHIFT,
|
|
),
|
|
];
|
|
|
|
let modifier_combos_including_meta =
|
|
|| modifier_combos.iter().chain(meta_modifier_combos.iter());
|
|
|
|
for alpha in b'A'..=b'Z' {
|
|
// Ctrl-[A..=Z] are sent as 1..=26
|
|
let ctrl = [alpha & 0x1f];
|
|
map.insert(
|
|
&ctrl,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Char(alpha as char),
|
|
modifiers: Modifiers::CTRL,
|
|
}),
|
|
);
|
|
|
|
// ALT A-Z is often sent with a leading ESC
|
|
let alt = [0x1b, alpha];
|
|
map.insert(
|
|
&alt,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Char(alpha as char),
|
|
modifiers: Modifiers::ALT,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// `CSI u` encodings for the ascii range;
|
|
// see http://www.leonerd.org.uk/hacks/fixterms/
|
|
for c in 0..=0x7fu8 {
|
|
for (suffix, modifiers) in modifier_combos {
|
|
let key = format!("\x1b[{}{}u", c, suffix);
|
|
map.insert(
|
|
key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Char(c as char),
|
|
modifiers: *modifiers,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Common arrow keys
|
|
for (keycode, dir) in &[
|
|
(KeyCode::UpArrow, b'A'),
|
|
(KeyCode::DownArrow, b'B'),
|
|
(KeyCode::RightArrow, b'C'),
|
|
(KeyCode::LeftArrow, b'D'),
|
|
(KeyCode::Home, b'H'),
|
|
(KeyCode::End, b'F'),
|
|
] {
|
|
// Arrow keys in normal mode encoded using CSI
|
|
let arrow = [0x1b, b'[', *dir];
|
|
map.insert(
|
|
&arrow,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
for (suffix, modifiers) in modifier_combos_including_meta() {
|
|
let key = format!("\x1b[1{}{}", suffix, *dir as char);
|
|
map.insert(
|
|
key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: *modifiers,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
for &(keycode, dir) in &[
|
|
(KeyCode::UpArrow, b'a'),
|
|
(KeyCode::DownArrow, b'b'),
|
|
(KeyCode::RightArrow, b'c'),
|
|
(KeyCode::LeftArrow, b'd'),
|
|
] {
|
|
// rxvt-specific modified arrows.
|
|
for &(seq, mods) in &[
|
|
([0x1b, b'[', dir], Modifiers::SHIFT),
|
|
([0x1b, b'O', dir], Modifiers::CTRL),
|
|
] {
|
|
map.insert(
|
|
&seq,
|
|
InputEvent::Key(KeyEvent {
|
|
key: keycode,
|
|
modifiers: mods,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
for (keycode, dir) in &[
|
|
(KeyCode::ApplicationUpArrow, b'A'),
|
|
(KeyCode::ApplicationDownArrow, b'B'),
|
|
(KeyCode::ApplicationRightArrow, b'C'),
|
|
(KeyCode::ApplicationLeftArrow, b'D'),
|
|
] {
|
|
// Arrow keys in application cursor mode encoded using SS3
|
|
let app = [0x1b, b'O', *dir];
|
|
map.insert(
|
|
&app,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
for (suffix, modifiers) in modifier_combos {
|
|
let key = format!("\x1bO1{}{}", suffix, *dir as char);
|
|
map.insert(
|
|
key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: *modifiers,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Function keys 1-4 with no modifiers encoded using SS3
|
|
for (keycode, c) in &[
|
|
(KeyCode::Function(1), b'P'),
|
|
(KeyCode::Function(2), b'Q'),
|
|
(KeyCode::Function(3), b'R'),
|
|
(KeyCode::Function(4), b'S'),
|
|
] {
|
|
let key = [0x1b, b'O', *c];
|
|
map.insert(
|
|
&key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Function keys 1-4 with modifiers
|
|
for (keycode, c) in &[
|
|
(KeyCode::Function(1), b'P'),
|
|
(KeyCode::Function(2), b'Q'),
|
|
(KeyCode::Function(3), b'R'),
|
|
(KeyCode::Function(4), b'S'),
|
|
] {
|
|
for (suffix, modifiers) in modifier_combos_including_meta() {
|
|
let key = format!("\x1b[1{suffix}{code}", code = *c as char, suffix = suffix);
|
|
map.insert(
|
|
key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: *modifiers,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Function keys with modifiers encoded using CSI.
|
|
// http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
|
|
for (range, offset) in &[
|
|
// F1-F5 encoded as 11-15
|
|
(1..=5, 10),
|
|
// F6-F10 encoded as 17-21
|
|
(6..=10, 11),
|
|
// F11-F14 encoded as 23-26
|
|
(11..=14, 12),
|
|
// F15-F16 encoded as 28-29
|
|
(15..=16, 13),
|
|
// F17-F20 encoded as 31-34
|
|
(17..=20, 14),
|
|
] {
|
|
for n in range.clone() {
|
|
for (suffix, modifiers) in modifier_combos_including_meta() {
|
|
let key = format!("\x1b[{code}{suffix}~", code = n + offset, suffix = suffix);
|
|
map.insert(
|
|
key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Function(n),
|
|
modifiers: *modifiers,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (keycode, c) in &[
|
|
(KeyCode::Insert, b'2'),
|
|
(KeyCode::Delete, b'3'),
|
|
(KeyCode::Home, b'1'),
|
|
(KeyCode::End, b'4'),
|
|
(KeyCode::PageUp, b'5'),
|
|
(KeyCode::PageDown, b'6'),
|
|
// rxvt
|
|
(KeyCode::Home, b'7'),
|
|
(KeyCode::End, b'8'),
|
|
] {
|
|
for (suffix, modifiers) in &[
|
|
(b'~', Modifiers::NONE),
|
|
(b'$', Modifiers::SHIFT),
|
|
(b'^', Modifiers::CTRL),
|
|
(b'@', Modifiers::SHIFT | Modifiers::CTRL),
|
|
] {
|
|
let key = [0x1b, b'[', *c, *suffix];
|
|
map.insert(
|
|
key,
|
|
InputEvent::Key(KeyEvent {
|
|
key: *keycode,
|
|
modifiers: *modifiers,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
map.insert(
|
|
&[0x7f],
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Backspace,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
|
|
map.insert(
|
|
&[0x8],
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Backspace,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
|
|
map.insert(
|
|
&[0x1b],
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Escape,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
|
|
map.insert(
|
|
&[b'\t'],
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Tab,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
|
|
map.insert(
|
|
&[b'\r'],
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Enter,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
map.insert(
|
|
&[b'\n'],
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Enter,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
|
|
map.insert(
|
|
b"\x1b[200~",
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::InternalPasteStart,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
map.insert(
|
|
b"\x1b[201~",
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::InternalPasteEnd,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
|
|
map
|
|
}
|
|
|
|
/// Returns the first char from a str and the length of that char
|
|
/// in *bytes*.
|
|
fn first_char_and_len(s: &str) -> (char, usize) {
|
|
let mut iter = s.chars();
|
|
let c = iter.next().unwrap();
|
|
(c, c.len_utf8())
|
|
}
|
|
|
|
/// This is a horrible function to pull off the first unicode character
|
|
/// from the sequence of bytes and return it and the remaining slice.
|
|
fn decode_one_char(bytes: &[u8]) -> Option<(char, usize)> {
|
|
// This has the potential to be an ugly hotspot since the complexity
|
|
// is a function of the length of the entire buffer rather than the length
|
|
// of the first char component. A simple mitigation might be to slice off
|
|
// the first 4 bytes. We pick 4 bytes because the docs for str::len_utf8()
|
|
// state that the maximum expansion for a `char` is 4 bytes.
|
|
let bytes = &bytes[..bytes.len().min(4)];
|
|
match std::str::from_utf8(bytes) {
|
|
Ok(s) => {
|
|
let (c, len) = Self::first_char_and_len(s);
|
|
Some((c, len))
|
|
}
|
|
Err(err) => {
|
|
let (valid, _after_valid) = bytes.split_at(err.valid_up_to());
|
|
if !valid.is_empty() {
|
|
let s = unsafe { std::str::from_utf8_unchecked(valid) };
|
|
let (c, len) = Self::first_char_and_len(s);
|
|
Some((c, len))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn dispatch_callback<F: FnMut(InputEvent)>(&mut self, mut callback: F, event: InputEvent) {
|
|
match (self.state, event) {
|
|
(
|
|
InputState::Normal,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::InternalPasteStart,
|
|
..
|
|
}),
|
|
) => {
|
|
self.state = InputState::Pasting(0);
|
|
}
|
|
(
|
|
InputState::EscapeMaybeAlt,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::InternalPasteStart,
|
|
..
|
|
}),
|
|
) => {
|
|
// The prior ESC was not part of an ALT sequence, so emit
|
|
// it before we start collecting for paste.
|
|
callback(InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Escape,
|
|
modifiers: Modifiers::NONE,
|
|
}));
|
|
self.state = InputState::Pasting(0);
|
|
}
|
|
(InputState::EscapeMaybeAlt, InputEvent::Key(KeyEvent { key, modifiers })) => {
|
|
// Treat this as ALT-key
|
|
self.state = InputState::Normal;
|
|
callback(InputEvent::Key(KeyEvent {
|
|
key,
|
|
modifiers: modifiers | Modifiers::ALT,
|
|
}));
|
|
}
|
|
(InputState::EscapeMaybeAlt, event) => {
|
|
// The prior ESC was not part of an ALT sequence, so emit
|
|
// both it and the current event
|
|
callback(InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Escape,
|
|
modifiers: Modifiers::NONE,
|
|
}));
|
|
callback(event);
|
|
}
|
|
(_, event) => callback(event),
|
|
}
|
|
}
|
|
|
|
fn process_bytes<F: FnMut(InputEvent)>(&mut self, mut callback: F, maybe_more: bool) {
|
|
while !self.buf.is_empty() {
|
|
match self.state {
|
|
InputState::Pasting(offset) => {
|
|
let end_paste = b"\x1b[201~";
|
|
if let Some(idx) = self.buf.find_subsequence(offset, end_paste) {
|
|
let pasted =
|
|
String::from_utf8_lossy(&self.buf.as_slice()[0..idx]).to_string();
|
|
self.buf.advance(pasted.len() + end_paste.len());
|
|
callback(InputEvent::Paste(pasted));
|
|
self.state = InputState::Normal;
|
|
} else {
|
|
self.state = InputState::Pasting(self.buf.len() - end_paste.len());
|
|
return;
|
|
}
|
|
}
|
|
InputState::EscapeMaybeAlt | InputState::Normal => {
|
|
if self.state == InputState::Normal && self.buf.as_slice()[0] == b'\x1b' {
|
|
// This feels a bit gross because we have two different parsers at play
|
|
// here. We want to re-use the escape sequence parser to crack the
|
|
// parameters out from things like mouse reports. The keymap tree doesn't
|
|
// know how to grok this.
|
|
let mut parser = Parser::new();
|
|
if let Some((Action::CSI(CSI::Mouse(mouse)), len)) =
|
|
parser.parse_first(self.buf.as_slice())
|
|
{
|
|
self.buf.advance(len);
|
|
|
|
match mouse {
|
|
MouseReport::SGR1006 {
|
|
x,
|
|
y,
|
|
button,
|
|
modifiers,
|
|
} => {
|
|
callback(InputEvent::Mouse(MouseEvent {
|
|
x,
|
|
y,
|
|
mouse_buttons: button.into(),
|
|
modifiers,
|
|
}));
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
match (self.key_map.lookup(self.buf.as_slice()), maybe_more) {
|
|
// If we got an unambiguous ESC and we have more data to
|
|
// follow, then this is likely the Meta version of the
|
|
// following keypress. Buffer up the escape key and
|
|
// consume it from the input. dispatch_callback() will
|
|
// emit either the ESC or the ALT modified following key.
|
|
(
|
|
Found::Exact(
|
|
len,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Escape,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
),
|
|
_,
|
|
) if self.state == InputState::Normal && self.buf.len() > len => {
|
|
self.state = InputState::EscapeMaybeAlt;
|
|
self.buf.advance(len);
|
|
}
|
|
(Found::Exact(len, event), _) | (Found::Ambiguous(len, event), false) => {
|
|
self.dispatch_callback(&mut callback, event.clone());
|
|
self.buf.advance(len);
|
|
}
|
|
(Found::Ambiguous(_, _), true) | (Found::NeedData, true) => {
|
|
return;
|
|
}
|
|
(Found::None, _) | (Found::NeedData, false) => {
|
|
// No pre-defined key, so pull out a unicode character
|
|
if let Some((c, len)) = Self::decode_one_char(self.buf.as_slice()) {
|
|
self.buf.advance(len);
|
|
self.dispatch_callback(
|
|
&mut callback,
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Char(c),
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
);
|
|
} else {
|
|
// We need more data to recognize the input, so
|
|
// yield the remainder of the slice
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Push a sequence of bytes into the parser.
|
|
/// Each time input is recognized, the provided `callback` will be passed
|
|
/// the decoded `InputEvent`.
|
|
/// If not enough data are available to fully decode a sequence, the
|
|
/// remaining data will be buffered until the next call.
|
|
/// The `maybe_more` flag controls how ambiguous partial sequences are
|
|
/// handled. The intent is that `maybe_more` should be set to true if
|
|
/// you believe that you will be able to provide more data momentarily.
|
|
/// This will cause the parser to defer judgement on partial prefix
|
|
/// matches. You should attempt to read and pass the new data in
|
|
/// immediately afterwards. If you have attempted a read and no data is
|
|
/// immediately available, you should follow up with a call to parse
|
|
/// with an empty slice and `maybe_more=false` to allow the partial
|
|
/// data to be recognized and processed.
|
|
pub fn parse<F: FnMut(InputEvent)>(&mut self, bytes: &[u8], callback: F, maybe_more: bool) {
|
|
self.buf.extend_with(bytes);
|
|
self.process_bytes(callback, maybe_more);
|
|
}
|
|
|
|
pub fn parse_as_vec(&mut self, bytes: &[u8]) -> Vec<InputEvent> {
|
|
let mut result = Vec::new();
|
|
self.parse(bytes, |event| result.push(event), false);
|
|
result
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
pub fn decode_input_records_as_vec(&mut self, records: &[INPUT_RECORD]) -> Vec<InputEvent> {
|
|
let mut result = Vec::new();
|
|
self.decode_input_records(records, &mut |event| result.push(event));
|
|
result
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn simple() {
|
|
let mut p = InputParser::new();
|
|
let inputs = p.parse_as_vec(b"hello");
|
|
assert_eq!(
|
|
vec![
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('h'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('e'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('l'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('l'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('o'),
|
|
}),
|
|
],
|
|
inputs
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn control_characters() {
|
|
let mut p = InputParser::new();
|
|
let inputs = p.parse_as_vec(b"\x03\x1bJ\x7f");
|
|
assert_eq!(
|
|
vec![
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::CTRL,
|
|
key: KeyCode::Char('C'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::ALT,
|
|
key: KeyCode::Char('J'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Backspace,
|
|
}),
|
|
],
|
|
inputs
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn arrow_keys() {
|
|
let mut p = InputParser::new();
|
|
let inputs = p.parse_as_vec(b"\x1bOA\x1bOB\x1bOC\x1bOD");
|
|
assert_eq!(
|
|
vec![
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::ApplicationUpArrow,
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::ApplicationDownArrow,
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::ApplicationRightArrow,
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::ApplicationLeftArrow,
|
|
}),
|
|
],
|
|
inputs
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn partial() {
|
|
let mut p = InputParser::new();
|
|
let mut inputs = Vec::new();
|
|
// Fragment this F-key sequence across two different pushes
|
|
p.parse(b"\x1b[11", |evt| inputs.push(evt), true);
|
|
p.parse(b"~", |evt| inputs.push(evt), true);
|
|
// make sure we recognize it as just the F-key
|
|
assert_eq!(
|
|
vec![InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Function(1),
|
|
})],
|
|
inputs
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn partial_ambig() {
|
|
let mut p = InputParser::new();
|
|
|
|
assert_eq!(
|
|
vec![InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Escape,
|
|
modifiers: Modifiers::NONE,
|
|
})],
|
|
p.parse_as_vec(b"\x1b")
|
|
);
|
|
|
|
let mut inputs = Vec::new();
|
|
// Fragment this F-key sequence across two different pushes
|
|
p.parse(b"\x1b[11", |evt| inputs.push(evt), true);
|
|
p.parse(b"", |evt| inputs.push(evt), false);
|
|
// make sure we recognize it as just the F-key
|
|
assert_eq!(
|
|
vec![
|
|
InputEvent::Key(KeyEvent {
|
|
key: KeyCode::Escape,
|
|
modifiers: Modifiers::NONE,
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('['),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('1'),
|
|
}),
|
|
InputEvent::Key(KeyEvent {
|
|
modifiers: Modifiers::NONE,
|
|
key: KeyCode::Char('1'),
|
|
}),
|
|
],
|
|
inputs
|
|
);
|
|
}
|
|
}
|