|
|
@ -1,132 +1,128 @@
|
|
|
|
use crate::{error::Error, key, log::*};
|
|
|
|
use crate::{
|
|
|
|
|
|
|
|
error::{Error, InputError},
|
|
|
|
|
|
|
|
key,
|
|
|
|
|
|
|
|
log::*,
|
|
|
|
|
|
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
use anyhow::{Context, Result};
|
|
|
|
use macros::escapes;
|
|
|
|
use std::{
|
|
|
|
use windows::Win32::Foundation::HANDLE;
|
|
|
|
cell::{RefCell, RefMut},
|
|
|
|
use windows::Win32::System::Console;
|
|
|
|
collections::VecDeque,
|
|
|
|
|
|
|
|
fmt,
|
|
|
|
#[allow(dead_code)]
|
|
|
|
rc::Rc,
|
|
|
|
fn log_input_mode(mode: Console::CONSOLE_MODE) {
|
|
|
|
};
|
|
|
|
// Characters read by the ReadFile or ReadConsole function are written to the active screen
|
|
|
|
use windows::Win32::{Foundation::HANDLE, System::Console};
|
|
|
|
// buffer as they are typed into the console. This mode can be used only if the
|
|
|
|
|
|
|
|
// ENABLE_LINE_INPUT mode is also enabled.
|
|
|
|
|
|
|
|
if (mode & Console::ENABLE_ECHO_INPUT).0 > 0 {
|
|
|
|
|
|
|
|
debug!("Echo Input: Enabled");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
debug!("Echo Input: Disabled");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// When enabled, text entered in a console window will be inserted at the current cursor
|
|
|
|
/// retrieves a Windows Console handle using the Win32 apis
|
|
|
|
// location and all text following that location will not be overwritten. When disabled, all
|
|
|
|
fn stdin_handle() -> Result<HANDLE> {
|
|
|
|
// following text will be overwritten.
|
|
|
|
unsafe {
|
|
|
|
if (mode & Console::ENABLE_INSERT_MODE).0 > 0 {
|
|
|
|
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE)
|
|
|
|
debug!("Insert Mode: Enabled");
|
|
|
|
.context("unable to get stdin handle")?;
|
|
|
|
} else {
|
|
|
|
Ok(handle)
|
|
|
|
debug!("Insert Mode: Disabled");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The ReadFile or ReadConsole function returns only when a carriage return character is read.
|
|
|
|
|
|
|
|
// If this mode is disabled, the functions return when one or more characters are available.
|
|
|
|
|
|
|
|
if (mode & Console::ENABLE_LINE_INPUT).0 > 0 {
|
|
|
|
|
|
|
|
debug!("Line Input Mode: Enabled");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
debug!("Line Input Mode: Disabled");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If the mouse pointer is within the borders of the console window and the window has the
|
|
|
|
/// checks to see whether the raw underling console input record is the beginning of an escape
|
|
|
|
// keyboard focus, mouse events generated by mouse movement and button presses are placed in
|
|
|
|
/// sequence
|
|
|
|
// the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode
|
|
|
|
fn is_escape_start(record: &Console::INPUT_RECORD) -> bool {
|
|
|
|
// is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from
|
|
|
|
if record.EventType as u32 == Console::KEY_EVENT {
|
|
|
|
// the input buffer.
|
|
|
|
unsafe {
|
|
|
|
if (mode & Console::ENABLE_MOUSE_INPUT).0 > 0 {
|
|
|
|
let event = record.Event.KeyEvent;
|
|
|
|
debug!("Mouse Input: Enabled");
|
|
|
|
event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 27 && event.bKeyDown.as_bool()
|
|
|
|
} else {
|
|
|
|
|
|
|
|
debug!("Mouse Input: Disabled");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CTRL+C is processed by the system and is not placed in the input buffer. If the input buffer
|
|
|
|
|
|
|
|
// is being read by ReadFile or ReadConsole, other control keys are processed by the system and
|
|
|
|
|
|
|
|
// are not returned in the ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is
|
|
|
|
|
|
|
|
// also enabled, backspace, carriage return, and line feed characters are handled by the
|
|
|
|
|
|
|
|
// system.
|
|
|
|
|
|
|
|
if (mode & Console::ENABLE_PROCESSED_INPUT).0 > 0 {
|
|
|
|
|
|
|
|
debug!("Processed Input: Enabled");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
debug!("Processed Input: Disabled");
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This flag enables the user to use the mouse to select and edit text. To enable this mode,
|
|
|
|
|
|
|
|
// use ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS. To disable this mode, use
|
|
|
|
|
|
|
|
// ENABLE_EXTENDED_FLAGS without this flag.
|
|
|
|
|
|
|
|
if (mode & Console::ENABLE_QUICK_EDIT_MODE).0 > 0 {
|
|
|
|
|
|
|
|
debug!("Quick Edit Mode: Enabled");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
debug!("Quick Edit Mode: Disabled");
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// User interactions that change the size of the console screen buffer are reported in the
|
|
|
|
/// checks to see if a record is indicating the end of an escape sequence
|
|
|
|
// console's input buffer. Information about these events can be read from the input buffer by
|
|
|
|
fn is_escape_done(record: &Console::INPUT_RECORD) -> bool {
|
|
|
|
// applications using the ReadConsoleInput function, but not by those using ReadFile or
|
|
|
|
if record.EventType as u32 == Console::KEY_EVENT {
|
|
|
|
// ReadConsole.
|
|
|
|
unsafe {
|
|
|
|
if (mode & Console::ENABLE_WINDOW_INPUT).0 > 0 {
|
|
|
|
let event = record.Event.KeyEvent;
|
|
|
|
debug!("Window Input: Enabled");
|
|
|
|
event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 27 && !event.bKeyDown.as_bool()
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
debug!("Window Input: Disabled");
|
|
|
|
false
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Setting this flag directs the Virtual Terminal processing engine to convert user input
|
|
|
|
/// attempts to convert a record into a character that would appear in an escape sequence
|
|
|
|
// received by the console window into Console Virtual Terminal Sequences that can be retrieved
|
|
|
|
fn as_escape_character(record: &Console::INPUT_RECORD) -> Option<char> {
|
|
|
|
// by a supporting application through ReadFile or ReadConsole functions.
|
|
|
|
if record.EventType as u32 != Console::KEY_EVENT {
|
|
|
|
//
|
|
|
|
None
|
|
|
|
// The typical usage of this flag is intended in conjunction with
|
|
|
|
|
|
|
|
// ENABLE_VIRTUAL_TERMINAL_PROCESSING on the output handle to connect to an application that
|
|
|
|
|
|
|
|
// communicates exclusively via virtual terminal sequences.
|
|
|
|
|
|
|
|
if (mode & Console::ENABLE_VIRTUAL_TERMINAL_INPUT).0 > 0 {
|
|
|
|
|
|
|
|
debug!("Virtual Terminal Input: Enabled");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
debug!("Virtual Terminal Input: Disabled");
|
|
|
|
unsafe {
|
|
|
|
|
|
|
|
let n = record.Event.KeyEvent.uChar.UnicodeChar as u32;
|
|
|
|
|
|
|
|
let c = char::from_u32(n);
|
|
|
|
|
|
|
|
c
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn stdin_handle() -> Result<HANDLE> {
|
|
|
|
/// checks to see if a console record is indicating that the control key has been pressed or
|
|
|
|
|
|
|
|
/// depressed. Note that this has no analogue in the vt100 specification, this is purely a Windows
|
|
|
|
|
|
|
|
/// thing.
|
|
|
|
|
|
|
|
fn as_ctrl_toggle(record: &Console::INPUT_RECORD) -> Option<bool> {
|
|
|
|
|
|
|
|
if record.EventType as u32 == Console::KEY_EVENT {
|
|
|
|
unsafe {
|
|
|
|
unsafe {
|
|
|
|
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE)
|
|
|
|
let event = record.Event.KeyEvent;
|
|
|
|
.context("unable to get stdin handle")?;
|
|
|
|
if event.wVirtualKeyCode == 17 {
|
|
|
|
Ok(handle)
|
|
|
|
return Some(event.bKeyDown.as_bool());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// a handle to a terminal's stream of input events. This is analagous to the input portion of a
|
|
|
|
|
|
|
|
/// vt100 tty terminal.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// This implementation specifically is implementing the Windows API for Console input.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// In the Windows Console API, console inputs are communicated using struct values having type
|
|
|
|
|
|
|
|
/// [INPUT_RECORD](https://learn.microsoft.com/en-us/windows/console/input-record-str).
|
|
|
|
pub struct Reader {
|
|
|
|
pub struct Reader {
|
|
|
|
input: HANDLE,
|
|
|
|
input: HANDLE,
|
|
|
|
// this area in memory is where the Windows API will write events read off of the keyboard.
|
|
|
|
|
|
|
|
buf: [Console::INPUT_RECORD; 32],
|
|
|
|
|
|
|
|
// the number of valid input record items in the buf since last read
|
|
|
|
|
|
|
|
buf_len: u32,
|
|
|
|
|
|
|
|
// the position of our current indexer into the input record buffer
|
|
|
|
|
|
|
|
buf_idx: usize,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// this scratch buffer is used as a shared memory location for communicating with the kernel.
|
|
|
|
|
|
|
|
/// When we request input records, the windows kernel writes them into this buffer.
|
|
|
|
|
|
|
|
scratch: [Console::INPUT_RECORD; 32],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// A lookahead buffer of unprocessed events. If we request input events from Windows and there
|
|
|
|
|
|
|
|
/// are more than one event, we put the unprocessed events into this lookahead buffer to reduce
|
|
|
|
|
|
|
|
/// the number of syscalls we're making.
|
|
|
|
|
|
|
|
lookahead: VecDeque<Console::INPUT_RECORD>,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// whether or not the control key is pressed, i think?
|
|
|
|
ctrl: bool,
|
|
|
|
ctrl: bool,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) escapes: EscapeCursor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl Reader {
|
|
|
|
impl Reader {
|
|
|
|
pub fn new() -> Result<Self> {
|
|
|
|
pub fn new() -> Result<Self> {
|
|
|
|
|
|
|
|
let escapes = build_prefix_tree();
|
|
|
|
let v = Self {
|
|
|
|
let v = Self {
|
|
|
|
buf: [Console::INPUT_RECORD::default(); 32],
|
|
|
|
scratch: [Console::INPUT_RECORD::default(); 32],
|
|
|
|
buf_len: 0,
|
|
|
|
lookahead: VecDeque::new(),
|
|
|
|
buf_idx: 0,
|
|
|
|
|
|
|
|
input: stdin_handle()?,
|
|
|
|
input: stdin_handle()?,
|
|
|
|
ctrl: false,
|
|
|
|
ctrl: false,
|
|
|
|
|
|
|
|
escapes,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
v.reset()?;
|
|
|
|
v.reset()?;
|
|
|
|
Ok(v)
|
|
|
|
Ok(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn reset(&self) -> Result<()> {
|
|
|
|
pub fn reset(&self) -> Result<()> {
|
|
|
|
|
|
|
|
// https://learn.microsoft.com/en-us/windows/console/setconsolemode
|
|
|
|
let mut mode = Console::CONSOLE_MODE(0);
|
|
|
|
let mut mode = Console::CONSOLE_MODE(0);
|
|
|
|
unsafe {
|
|
|
|
unsafe {
|
|
|
|
Console::SetConsoleCP(65001);
|
|
|
|
// set the console's code page to 65001, which is the code page for utf-8
|
|
|
|
|
|
|
|
Error::check(Console::SetConsoleCP(65001))?;
|
|
|
|
|
|
|
|
|
|
|
|
let handle = stdin_handle()?;
|
|
|
|
// retrieve the current console mode
|
|
|
|
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
|
|
|
|
Error::check(Console::GetConsoleMode(self.input, &mut mode))?;
|
|
|
|
|
|
|
|
|
|
|
|
// allow terminal input characters
|
|
|
|
// allow terminal input characters
|
|
|
|
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
|
|
|
|
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
|
|
|
@ -143,202 +139,157 @@ impl Reader {
|
|
|
|
// enable mouse input
|
|
|
|
// enable mouse input
|
|
|
|
mode |= Console::ENABLE_MOUSE_INPUT;
|
|
|
|
mode |= Console::ENABLE_MOUSE_INPUT;
|
|
|
|
|
|
|
|
|
|
|
|
Error::check(Console::SetConsoleMode(handle, mode))?;
|
|
|
|
// enable reporting of window resize events
|
|
|
|
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
|
|
|
|
mode |= Console::ENABLE_WINDOW_INPUT;
|
|
|
|
// debug!("Stdin details:");
|
|
|
|
|
|
|
|
// log_input_mode(mode);
|
|
|
|
Error::check(Console::SetConsoleMode(self.input, mode))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn next(&mut self) -> Result<Event> {
|
|
|
|
pub fn next(&mut self) -> Result<Event> {
|
|
|
|
let rec = self.next_rec()?;
|
|
|
|
let record = self.next_record()?;
|
|
|
|
if rec.EventType as u32 == Console::KEY_EVENT {
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(ctrl_toggle) = as_ctrl_toggle(&record) {
|
|
|
|
|
|
|
|
self.ctrl = ctrl_toggle;
|
|
|
|
|
|
|
|
debug!("ctrl {on}", on = if self.ctrl { "ON" } else { "OFF" });
|
|
|
|
|
|
|
|
return self.next();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This is a little weird but on a vt100 terminal, when you held down control you could
|
|
|
|
|
|
|
|
// send ascii values directly. So ctrl-a is actually the ascii value of 1, which is the
|
|
|
|
|
|
|
|
// "start of heading" character, and ctrl-d is actually the ascii value of 4, which is the
|
|
|
|
|
|
|
|
// "end of transmission" character.
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Now the way that the Windows Console api works is that it sends events for both key down
|
|
|
|
|
|
|
|
// and key up. Here's the rub: the vt100 did not send any key up events. The vt100 didn't
|
|
|
|
|
|
|
|
// send anything at all if you tapped and released control, but the Windows Console api
|
|
|
|
|
|
|
|
// would send a ctrl key down and ctrl key up event.
|
|
|
|
|
|
|
|
if self.ctrl && record.EventType as u32 == Console::KEY_EVENT {
|
|
|
|
unsafe {
|
|
|
|
unsafe {
|
|
|
|
let event = rec.Event.KeyEvent;
|
|
|
|
let key_event = record.Event.KeyEvent;
|
|
|
|
if event.wVirtualKeyCode == 0 && event.uChar.UnicodeChar == 27 {
|
|
|
|
if key_event.bKeyDown.as_bool() {
|
|
|
|
return Ok(self.next_escape_sequence()?);
|
|
|
|
match ControlCharacter::try_from(key_event) {
|
|
|
|
|
|
|
|
Ok(c) => return Ok(Event::Control(c)),
|
|
|
|
|
|
|
|
Err(e) => warn!("{:?}", e),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if event.wVirtualKeyCode == 17 {
|
|
|
|
|
|
|
|
self.ctrl = event.bKeyDown.as_bool();
|
|
|
|
|
|
|
|
debug!("ctrl {}", event.bKeyDown.as_bool());
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(rec.into())
|
|
|
|
|
|
|
|
|
|
|
|
if is_escape_start(&record) {
|
|
|
|
|
|
|
|
let escape = self.next_escape_sequence()?;
|
|
|
|
|
|
|
|
return Ok(Event::Escape(escape));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn log_recs(&mut self) {
|
|
|
|
let event: Event = record.into();
|
|
|
|
debug!(" +---------------------+");
|
|
|
|
if matches!(event, Event::Key(key::Event { down: false, .. })) {
|
|
|
|
for i in 0..self.buf_len {
|
|
|
|
return self.next();
|
|
|
|
let rec = self.buf[i as usize];
|
|
|
|
|
|
|
|
let e: Event = rec.into();
|
|
|
|
|
|
|
|
match e {
|
|
|
|
|
|
|
|
Event::Key(k) => debug!(" | {} |", k),
|
|
|
|
|
|
|
|
_ => debug!(" | {:<14?} |", &e),
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(event)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
debug!(" +---------------------+");
|
|
|
|
|
|
|
|
|
|
|
|
/// reads the next INPUT_RECORD value from the underlying Windows Console file descriptor.
|
|
|
|
|
|
|
|
fn next_record(&mut self) -> Result<Console::INPUT_RECORD> {
|
|
|
|
|
|
|
|
if let Some(record) = self.lookahead.pop_front() {
|
|
|
|
|
|
|
|
return Ok(record);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn next_rec(&mut self) -> Result<Console::INPUT_RECORD> {
|
|
|
|
let mut num_read: u32 = 0;
|
|
|
|
if self.buf_idx as u32 >= self.buf_len {
|
|
|
|
|
|
|
|
|
|
|
|
// request records from windows
|
|
|
|
unsafe {
|
|
|
|
unsafe {
|
|
|
|
Error::check(Console::ReadConsoleInputA(
|
|
|
|
Error::check(Console::ReadConsoleInputA(
|
|
|
|
self.input,
|
|
|
|
self.input,
|
|
|
|
&mut self.buf,
|
|
|
|
&mut self.scratch,
|
|
|
|
&mut self.buf_len,
|
|
|
|
&mut num_read,
|
|
|
|
))?;
|
|
|
|
))?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
debug!("• {}", self.buf_len);
|
|
|
|
|
|
|
|
self.log_recs();
|
|
|
|
let records = &self.scratch[0..num_read as usize];
|
|
|
|
self.buf_idx = 0;
|
|
|
|
debug!("{num_read} records:");
|
|
|
|
}
|
|
|
|
for record in records {
|
|
|
|
|
|
|
|
self.lookahead.push_back(*record);
|
|
|
|
let rec = self.buf[self.buf_idx];
|
|
|
|
if record.EventType as u32 == Console::KEY_EVENT {
|
|
|
|
self.buf_idx += 1;
|
|
|
|
unsafe {
|
|
|
|
return Ok(rec);
|
|
|
|
let key: key::Event = record.Event.KeyEvent.clone().into();
|
|
|
|
}
|
|
|
|
debug!(" {key}");
|
|
|
|
|
|
|
|
|
|
|
|
fn next_escape_sequence(&mut self) -> Result<Event> {
|
|
|
|
|
|
|
|
match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'[' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'A' => Ok(Event::Up),
|
|
|
|
|
|
|
|
'B' => Ok(Event::Down),
|
|
|
|
|
|
|
|
'C' => Ok(Event::Right),
|
|
|
|
|
|
|
|
'D' => Ok(Event::Left),
|
|
|
|
|
|
|
|
'H' => Ok(Event::Home),
|
|
|
|
|
|
|
|
'F' => Ok(Event::End),
|
|
|
|
|
|
|
|
'1' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'3' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
';' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'5' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'u' => Ok(Event::Drop(String::from("13;5u"))),
|
|
|
|
|
|
|
|
e => Err(Error::input_error(format!(
|
|
|
|
|
|
|
|
"[13;5 unexpected escape char: {}",
|
|
|
|
|
|
|
|
e
|
|
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
.into()),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
e => Err(Error::input_error(format!(
|
|
|
|
|
|
|
|
"[13; unexpected escape char: {}",
|
|
|
|
|
|
|
|
e
|
|
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
.into()),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[13 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'5' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[15~ - F5"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[15 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'7' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[17~ - F6"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[17 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'8' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[18~ - F7"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[18 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'9' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[19~ - F8"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[19 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
e => {
|
|
|
|
|
|
|
|
Err(Error::input_error(format!("[1 unexpected escape char: {}", e)).into())
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
} else {
|
|
|
|
'2' => match self.next_escape_char()? {
|
|
|
|
debug!(" -");
|
|
|
|
'0' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[20~ - F9"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[20 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'1' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[21~ - F10"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[20 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'3' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[23~ - F11"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[23 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'4' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'~' => Ok(Event::Drop(String::from("[24~ - F12"))),
|
|
|
|
|
|
|
|
e => Err(
|
|
|
|
|
|
|
|
Error::input_error(format!("[24 unexpected escape char: {}", e)).into(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
e => {
|
|
|
|
|
|
|
|
Err(Error::input_error(format!("[2 unexpected escape char: {}", e)).into())
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
e => Err(Error::input_error(format!("[ unexpected escape char: {}", e)).into()),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
'O' => match self.next_escape_char()? {
|
|
|
|
|
|
|
|
'P' => Ok(Event::Drop(String::from("OP - F1"))),
|
|
|
|
|
|
|
|
'Q' => Ok(Event::Drop(String::from("OQ - F2"))),
|
|
|
|
|
|
|
|
'R' => Ok(Event::Drop(String::from("OR - F3"))),
|
|
|
|
|
|
|
|
'S' => Ok(Event::Drop(String::from("OS - F4"))),
|
|
|
|
|
|
|
|
e => Err(Error::input_error(format!("O unexpected escape char: {}", e)).into()),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
e => Err(Error::input_error(format!("unexpected escape char: {}", e)).into()),
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(self.lookahead.pop_front().unwrap())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn next_escape_char(&mut self) -> Result<char> {
|
|
|
|
fn next_escape_sequence(&mut self) -> Result<Escape> {
|
|
|
|
let rec = self.next_rec()?;
|
|
|
|
self.escapes.reset();
|
|
|
|
if rec.EventType as u32 != Console::KEY_EVENT {
|
|
|
|
loop {
|
|
|
|
Err(
|
|
|
|
let record = self.next_record()?;
|
|
|
|
Error::input_error("failed to read char in escape sequence: not a key event")
|
|
|
|
if is_escape_start(&record) {
|
|
|
|
.into(),
|
|
|
|
continue;
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if is_escape_done(&record) {
|
|
|
|
|
|
|
|
if self.escapes.is_at_root() {
|
|
|
|
|
|
|
|
return Ok(Escape::Empty);
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
unsafe {
|
|
|
|
panic!();
|
|
|
|
let n = rec.Event.KeyEvent.uChar.UnicodeChar as u32;
|
|
|
|
}
|
|
|
|
let c = char::from_u32(n);
|
|
|
|
}
|
|
|
|
c.ok_or_else(|| {
|
|
|
|
let c = as_escape_character(&record).ok_or(InputError::BadEscapeSequence)?;
|
|
|
|
let msg = format!("escape key value is not a valid unicode character: {}", n);
|
|
|
|
if let Some(escape) = self.escapes.step(c) {
|
|
|
|
Error::input_error(msg).into()
|
|
|
|
return Ok(escape);
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Event represents all of the events that may be seen as input by the shell process. Note that
|
|
|
|
|
|
|
|
/// the set of events described here includes both in-band and out of band events, which is because
|
|
|
|
|
|
|
|
/// that's how it works in the vt100 spec. It's a little funky but that's because it's reflecting a
|
|
|
|
|
|
|
|
/// spec from the 70's.
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Event {
|
|
|
|
pub enum Event {
|
|
|
|
|
|
|
|
/// The process has received focus
|
|
|
|
Focus(bool),
|
|
|
|
Focus(bool),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// This is a windows Menu event. This might be skippable?
|
|
|
|
Menu(u32),
|
|
|
|
Menu(u32),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// A Key press. Key events associate to keys on the keyboard that are typically in-band, that
|
|
|
|
|
|
|
|
/// is, they're associated with specific characters
|
|
|
|
Key(key::Event),
|
|
|
|
Key(key::Event),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// an event from the user's mouse, such as a mouse movement or click
|
|
|
|
Mouse { x: i16, y: i16 },
|
|
|
|
Mouse { x: i16, y: i16 },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// a resize event to inform us that the visual window of our process has changed
|
|
|
|
Size,
|
|
|
|
Size,
|
|
|
|
Left,
|
|
|
|
|
|
|
|
Right,
|
|
|
|
/// A decoded ANSI Escape Sequence. At the Terminal level, an Escape sequence is defined by a
|
|
|
|
Up,
|
|
|
|
/// string of characters instead of just one character. Escape Sequences are used to
|
|
|
|
Down,
|
|
|
|
/// communicate a variety of edit-related functionality such as navigation.
|
|
|
|
Home,
|
|
|
|
Escape(Escape),
|
|
|
|
End,
|
|
|
|
|
|
|
|
Drop(String),
|
|
|
|
/// An ASCII Controll Character.
|
|
|
|
|
|
|
|
Control(ControlCharacter),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const ALT_KEYS: u32 = 0x0002 | 0x0001;
|
|
|
|
impl fmt::Display for Event {
|
|
|
|
const CTRL_KEYS: u32 = 0x0008 | 0x0004;
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
const SHIFT_PRESSED: u32 = 0x0010;
|
|
|
|
use Event::*;
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
Key(k) => write!(f, "Event<Key {k}>"),
|
|
|
|
|
|
|
|
Control(c) => write!(f, "Event<Control {c:?}>"),
|
|
|
|
|
|
|
|
Escape(e) => write!(f, "Event<Escape {e:?}>"),
|
|
|
|
|
|
|
|
Focus(true) => write!(f, "Event<focus>"),
|
|
|
|
|
|
|
|
Focus(false) => write!(f, "Event<unfocus>"),
|
|
|
|
|
|
|
|
Menu(n) => write!(f, "Event<Menu {n}>"),
|
|
|
|
|
|
|
|
Mouse { x, y } => write!(f, "Event<Mouse {x},{y}>"),
|
|
|
|
|
|
|
|
Size => write!(f, "Event<Size>"),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<Console::INPUT_RECORD> for Event {
|
|
|
|
impl From<Console::INPUT_RECORD> for Event {
|
|
|
|
fn from(rec: Console::INPUT_RECORD) -> Self {
|
|
|
|
fn from(rec: Console::INPUT_RECORD) -> Self {
|
|
|
@ -353,24 +304,7 @@ impl From<Console::INPUT_RECORD> for Event {
|
|
|
|
let event = rec.Event.MenuEvent;
|
|
|
|
let event = rec.Event.MenuEvent;
|
|
|
|
Event::Menu(event.dwCommandId)
|
|
|
|
Event::Menu(event.dwCommandId)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Console::KEY_EVENT => {
|
|
|
|
Console::KEY_EVENT => unsafe { Event::Key(rec.Event.KeyEvent.clone().into()) },
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
|
|
let event = rec.Event.KeyEvent;
|
|
|
|
|
|
|
|
let mstate = event.dwControlKeyState;
|
|
|
|
|
|
|
|
let c = char::from_u32(event.uChar.UnicodeChar as u32).unwrap_or('💀');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Event::Key(key::Event {
|
|
|
|
|
|
|
|
down: event.bKeyDown.as_bool(),
|
|
|
|
|
|
|
|
repeats: event.wRepeatCount,
|
|
|
|
|
|
|
|
code: key::CODES[event.wVirtualKeyCode as usize],
|
|
|
|
|
|
|
|
alt: mstate & ALT_KEYS > 0,
|
|
|
|
|
|
|
|
ctrl: mstate & CTRL_KEYS > 0,
|
|
|
|
|
|
|
|
shift: mstate & SHIFT_PRESSED > 0,
|
|
|
|
|
|
|
|
char: c,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Console::MOUSE_EVENT => {
|
|
|
|
Console::MOUSE_EVENT => {
|
|
|
|
// OK I think it's safe to ignore these events since we're using the terminal
|
|
|
|
// OK I think it's safe to ignore these events since we're using the terminal
|
|
|
|
// escape sequences. I think we never see them because terminal.exe is going to
|
|
|
|
// escape sequences. I think we never see them because terminal.exe is going to
|
|
|
@ -395,6 +329,161 @@ impl From<Console::INPUT_RECORD> for Event {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
pub struct EscapeCursor {
|
|
|
|
|
|
|
|
target: Rc<EscapeNode>,
|
|
|
|
|
|
|
|
root: Rc<EscapeNode>,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
pub enum EscapeNode {
|
|
|
|
|
|
|
|
Root {
|
|
|
|
|
|
|
|
children: RefCell<Vec<Rc<EscapeNode>>>,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Nonterminal {
|
|
|
|
|
|
|
|
c: char,
|
|
|
|
|
|
|
|
children: RefCell<Vec<Rc<EscapeNode>>>,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Terminal {
|
|
|
|
|
|
|
|
c: char,
|
|
|
|
|
|
|
|
v: Escape,
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl EscapeNode {
|
|
|
|
|
|
|
|
pub fn new() -> EscapeCursor {
|
|
|
|
|
|
|
|
let root = Rc::new(EscapeNode::Root {
|
|
|
|
|
|
|
|
children: RefCell::new(Vec::new()),
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
EscapeCursor {
|
|
|
|
|
|
|
|
target: Rc::clone(&root),
|
|
|
|
|
|
|
|
root,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn char(&self) -> char {
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
EscapeNode::Nonterminal { c, .. } | EscapeNode::Terminal { c, .. } => *c,
|
|
|
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn children(&self) -> RefMut<Vec<Rc<EscapeNode>>> {
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
EscapeNode::Root { children } | EscapeNode::Nonterminal { children, .. } => {
|
|
|
|
|
|
|
|
children.borrow_mut()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn child(&self, c: char) -> Option<Rc<EscapeNode>> {
|
|
|
|
|
|
|
|
for child in self.children().iter_mut() {
|
|
|
|
|
|
|
|
if child.char() == c {
|
|
|
|
|
|
|
|
return Some(Rc::clone(child));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn child_nonterminal(&self, c: char) -> Rc<EscapeNode> {
|
|
|
|
|
|
|
|
for child in self.children().iter_mut() {
|
|
|
|
|
|
|
|
if child.char() == c {
|
|
|
|
|
|
|
|
return Rc::clone(child);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
let child = Rc::new(EscapeNode::Nonterminal {
|
|
|
|
|
|
|
|
c,
|
|
|
|
|
|
|
|
children: RefCell::new(Vec::new()),
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
self.children().push(Rc::clone(&child));
|
|
|
|
|
|
|
|
child
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn add_child_terminal(&self, c: char, v: Escape) {
|
|
|
|
|
|
|
|
for child in self.children().iter_mut() {
|
|
|
|
|
|
|
|
if child.char() == c {
|
|
|
|
|
|
|
|
panic!();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
let child = Rc::new(EscapeNode::Terminal { c, v });
|
|
|
|
|
|
|
|
self.children().push(child);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl EscapeCursor {
|
|
|
|
|
|
|
|
fn step(&mut self, c: char) -> Option<Escape> {
|
|
|
|
|
|
|
|
debug!("step: {c}");
|
|
|
|
|
|
|
|
let child = self.target.child(c).unwrap();
|
|
|
|
|
|
|
|
match child.as_ref() {
|
|
|
|
|
|
|
|
EscapeNode::Terminal { v, .. } => {
|
|
|
|
|
|
|
|
self.reset();
|
|
|
|
|
|
|
|
Some(*v)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
|
|
|
|
self.target = child;
|
|
|
|
|
|
|
|
None
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn add_step(&mut self, c: char) {
|
|
|
|
|
|
|
|
match self.target.child(c) {
|
|
|
|
|
|
|
|
Some(child) => self.target = child,
|
|
|
|
|
|
|
|
None => {
|
|
|
|
|
|
|
|
self.target = self.target.child_nonterminal(c);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn add_terminal(&mut self, c: char, v: Escape) {
|
|
|
|
|
|
|
|
self.target.add_child_terminal(c, v);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn reset(&mut self) {
|
|
|
|
|
|
|
|
self.target = Rc::clone(&self.root);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn is_at_root(&self) -> bool {
|
|
|
|
|
|
|
|
Rc::ptr_eq(&self.target, &self.root)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn insert(&mut self, sequence: &str, v: Escape) {
|
|
|
|
|
|
|
|
self.reset();
|
|
|
|
|
|
|
|
let mut chars = sequence.chars().peekable();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
|
|
|
let c = chars.next().unwrap();
|
|
|
|
|
|
|
|
if chars.peek().is_none() {
|
|
|
|
|
|
|
|
self.add_terminal(c, v);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
self.add_step(c);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
macro_rules! escapes {
|
|
|
|
|
|
|
|
($($sequence:literal $variant:tt)*) => {
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
|
|
|
pub enum Escape {
|
|
|
|
|
|
|
|
Empty,
|
|
|
|
|
|
|
|
$(
|
|
|
|
|
|
|
|
$variant,
|
|
|
|
|
|
|
|
)*
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn build_prefix_tree() -> EscapeCursor {
|
|
|
|
|
|
|
|
let mut tree = EscapeNode::new();
|
|
|
|
|
|
|
|
$(
|
|
|
|
|
|
|
|
let v = Escape::$variant;
|
|
|
|
|
|
|
|
tree.insert($sequence, v);
|
|
|
|
|
|
|
|
)*
|
|
|
|
|
|
|
|
tree.reset();
|
|
|
|
|
|
|
|
tree
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
escapes! {
|
|
|
|
escapes! {
|
|
|
|
"[A" Up
|
|
|
|
"[A" Up
|
|
|
|
"[B" Down
|
|
|
|
"[B" Down
|
|
|
@ -406,16 +495,16 @@ escapes! {
|
|
|
|
"[5~" PageUp
|
|
|
|
"[5~" PageUp
|
|
|
|
"[6~" PageDown
|
|
|
|
"[6~" PageDown
|
|
|
|
|
|
|
|
|
|
|
|
"[13;2u" Shift_Enter
|
|
|
|
"[13;2u" ShiftEnter
|
|
|
|
"[13;5u" Ctrl_Enter
|
|
|
|
"[13;5u" CtrlEnter
|
|
|
|
"[13;6u" Ctrl_Shift_Enter
|
|
|
|
"[13;6u" CtrlShiftEnter
|
|
|
|
|
|
|
|
|
|
|
|
"[1;2P" Shift_F1
|
|
|
|
"[1;2P" ShiftF1
|
|
|
|
"[1;2Q" Shift_F2
|
|
|
|
"[1;2Q" ShiftF2
|
|
|
|
"[1;5P" Ctrl_F1
|
|
|
|
"[1;5P" CtrlF1
|
|
|
|
"[1;6P" Ctrl_Shift_F1
|
|
|
|
"[1;6P" CtrlShiftF1
|
|
|
|
"[1;3P" Alt_F1
|
|
|
|
"[1;3P" AltF1
|
|
|
|
"[1;4P" Shift_Alt_F1
|
|
|
|
"[1;4P" ShiftAltF1
|
|
|
|
|
|
|
|
|
|
|
|
"OP" F1
|
|
|
|
"OP" F1
|
|
|
|
"OQ" F2
|
|
|
|
"OQ" F2
|
|
|
@ -423,10 +512,96 @@ escapes! {
|
|
|
|
"OS" F4
|
|
|
|
"OS" F4
|
|
|
|
|
|
|
|
|
|
|
|
"[15~" F5
|
|
|
|
"[15~" F5
|
|
|
|
"[15;2~" Shift_F5
|
|
|
|
"[15;2~" ShiftF5
|
|
|
|
"[17~" F6
|
|
|
|
"[17~" F6
|
|
|
|
"[18~" F7
|
|
|
|
"[18~" F7
|
|
|
|
"[19~" F8
|
|
|
|
"[19~" F8
|
|
|
|
|
|
|
|
"[20~" F9
|
|
|
|
"[24~" F12
|
|
|
|
"[24~" F12
|
|
|
|
"[53;5u" Ctrl_Shift_5
|
|
|
|
"[53;5u" CtrlShift5
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// a control character
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
|
|
pub enum ControlCharacter {
|
|
|
|
|
|
|
|
Null,
|
|
|
|
|
|
|
|
StartOfHeading,
|
|
|
|
|
|
|
|
StartOfText,
|
|
|
|
|
|
|
|
EndOfText,
|
|
|
|
|
|
|
|
EndOfTransmission,
|
|
|
|
|
|
|
|
Enquiry,
|
|
|
|
|
|
|
|
Acknowledge,
|
|
|
|
|
|
|
|
Bell,
|
|
|
|
|
|
|
|
Backspace,
|
|
|
|
|
|
|
|
Tab,
|
|
|
|
|
|
|
|
Linefeed,
|
|
|
|
|
|
|
|
VTab,
|
|
|
|
|
|
|
|
FormFeed,
|
|
|
|
|
|
|
|
CarriageReturn,
|
|
|
|
|
|
|
|
ShiftOut,
|
|
|
|
|
|
|
|
ShiftIn,
|
|
|
|
|
|
|
|
DataLinkEscape,
|
|
|
|
|
|
|
|
DeviceControl1,
|
|
|
|
|
|
|
|
DeviceControl2,
|
|
|
|
|
|
|
|
DeviceControl3,
|
|
|
|
|
|
|
|
DeviceControl4,
|
|
|
|
|
|
|
|
NegativeAcknowledge,
|
|
|
|
|
|
|
|
SynchronousIdle,
|
|
|
|
|
|
|
|
EndOfTransmissionBlock,
|
|
|
|
|
|
|
|
Cancel,
|
|
|
|
|
|
|
|
EndOfMedium,
|
|
|
|
|
|
|
|
Substitute,
|
|
|
|
|
|
|
|
Esc,
|
|
|
|
|
|
|
|
FileSeparator,
|
|
|
|
|
|
|
|
GroupSeparator,
|
|
|
|
|
|
|
|
RecordSeparator,
|
|
|
|
|
|
|
|
UnitSeparator,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl TryFrom<Console::KEY_EVENT_RECORD> for ControlCharacter {
|
|
|
|
|
|
|
|
type Error = InputError;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn try_from(v: Console::KEY_EVENT_RECORD) -> Result<Self, Self::Error> {
|
|
|
|
|
|
|
|
if !v.bKeyDown.as_bool() {
|
|
|
|
|
|
|
|
return Err(InputError::ControlCharactersOnlyGoDownNotUp);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
use ControlCharacter::*;
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
|
|
|
|
match v.uChar.AsciiChar.0 {
|
|
|
|
|
|
|
|
0 => Ok(Null),
|
|
|
|
|
|
|
|
1 => Ok(StartOfHeading),
|
|
|
|
|
|
|
|
2 => Ok(StartOfText),
|
|
|
|
|
|
|
|
3 => Ok(EndOfText),
|
|
|
|
|
|
|
|
4 => Ok(EndOfTransmission),
|
|
|
|
|
|
|
|
5 => Ok(Enquiry),
|
|
|
|
|
|
|
|
6 => Ok(Acknowledge),
|
|
|
|
|
|
|
|
7 => Ok(Bell),
|
|
|
|
|
|
|
|
8 => Ok(Backspace),
|
|
|
|
|
|
|
|
9 => Ok(Tab),
|
|
|
|
|
|
|
|
10 => Ok(Linefeed),
|
|
|
|
|
|
|
|
11 => Ok(VTab),
|
|
|
|
|
|
|
|
12 => Ok(FormFeed),
|
|
|
|
|
|
|
|
13 => Ok(CarriageReturn),
|
|
|
|
|
|
|
|
14 => Ok(ShiftOut),
|
|
|
|
|
|
|
|
15 => Ok(ShiftIn),
|
|
|
|
|
|
|
|
16 => Ok(DataLinkEscape),
|
|
|
|
|
|
|
|
17 => Ok(DeviceControl1),
|
|
|
|
|
|
|
|
18 => Ok(DeviceControl2),
|
|
|
|
|
|
|
|
19 => Ok(DeviceControl3),
|
|
|
|
|
|
|
|
20 => Ok(DeviceControl4),
|
|
|
|
|
|
|
|
21 => Ok(NegativeAcknowledge),
|
|
|
|
|
|
|
|
22 => Ok(SynchronousIdle),
|
|
|
|
|
|
|
|
23 => Ok(EndOfTransmissionBlock),
|
|
|
|
|
|
|
|
24 => Ok(Cancel),
|
|
|
|
|
|
|
|
25 => Ok(EndOfMedium),
|
|
|
|
|
|
|
|
26 => Ok(Substitute),
|
|
|
|
|
|
|
|
27 => Ok(Esc),
|
|
|
|
|
|
|
|
28 => Ok(FileSeparator),
|
|
|
|
|
|
|
|
29 => Ok(GroupSeparator),
|
|
|
|
|
|
|
|
30 => Ok(RecordSeparator),
|
|
|
|
|
|
|
|
31 => Ok(UnitSeparator),
|
|
|
|
|
|
|
|
n => Err(InputError::UnrecognizedControlCharacter(n)),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|