move input handling into a separate input module

parse-tree
Jordan Orelli 2 years ago
parent 0d64ff47c7
commit 5a74f9308b

@ -0,0 +1,161 @@
use anyhow::{Context, Result};
use crate::{error::Error, log::*};
use windows::Win32::System::Console;
use windows::Win32::Foundation::HANDLE;
fn log_input_mode(mode: Console::CONSOLE_MODE) {
// Characters read by the ReadFile or ReadConsole function are written to the active screen
// 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
// location and all text following that location will not be overwritten. When disabled, all
// following text will be overwritten.
if (mode & Console::ENABLE_INSERT_MODE).0 > 0 {
debug!("Insert Mode: Enabled");
} else {
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
// keyboard focus, mouse events generated by mouse movement and button presses are placed in
// the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode
// is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from
// the input buffer.
if (mode & Console::ENABLE_MOUSE_INPUT).0 > 0 {
debug!("Mouse Input: Enabled");
} 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 {
debug!("Processed Input: Disabled");
}
// 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
// console's input buffer. Information about these events can be read from the input buffer by
// applications using the ReadConsoleInput function, but not by those using ReadFile or
// ReadConsole.
if (mode & Console::ENABLE_WINDOW_INPUT).0 > 0 {
debug!("Window Input: Enabled");
} else {
debug!("Window Input: Disabled");
}
// Setting this flag directs the Virtual Terminal processing engine to convert user input
// received by the console window into Console Virtual Terminal Sequences that can be retrieved
// by a supporting application through ReadFile or ReadConsole functions.
//
// 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 {
debug!("Virtual Terminal Input: Disabled");
}
}
fn stdin_handle() -> Result<HANDLE> {
unsafe {
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE)
.context("unable to get stdin handle")?;
Ok(handle)
}
}
fn setup_stdin() -> Result<()> {
let mut mode = Console::CONSOLE_MODE(0);
unsafe {
Console::SetConsoleCP(65001);
let handle = stdin_handle()?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// allow terminal input characters
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
// disable automatic processing of CTRL+C, we'll handle it ourselves
mode &= !Console::ENABLE_PROCESSED_INPUT;
// disable line mode to get every input as its pressed
mode &= !Console::ENABLE_LINE_INPUT;
// disable automatic echoing of inputs
mode &= !Console::ENABLE_ECHO_INPUT;
// enable mouse input
mode |= Console::ENABLE_MOUSE_INPUT;
Error::check(Console::SetConsoleMode(handle, mode))?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
debug!("Stdin details:");
log_input_mode(mode);
}
Ok(())
}
pub struct Reader {
input: HANDLE,
// this area in memory is where the Windows API will write events read off of the keyboard.
buf: [Console::INPUT_RECORD; 100],
// 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,
}
impl Reader {
pub fn new() -> Result<Self> {
setup_stdin()?;
Ok(Self {
buf: [Console::INPUT_RECORD::default(); 100],
buf_len: 0,
buf_idx: 0,
input: stdin_handle()?,
})
}
pub fn next(&mut self) -> Result<Console::INPUT_RECORD> {
// All buffered items have been processed, ask the OS for new event records
if self.buf_idx as u32 >= self.buf_len {
unsafe {
Error::check(Console::ReadConsoleInputW(self.input, &mut self.buf, &mut self.buf_len))?;
}
self.buf_idx = 0;
}
let rec = self.buf[self.buf_idx];
self.buf_idx += 1;
return Ok(rec);
}
}

@ -1,6 +1,7 @@
mod error; mod error;
mod line; mod line;
mod log; mod log;
mod input;
mod prompt; mod prompt;
use line::Line; use line::Line;
@ -14,88 +15,6 @@ use anyhow::{Context, Result};
use crate::{error::Error, log::*}; use crate::{error::Error, log::*};
fn log_input_mode(mode: Console::CONSOLE_MODE) {
// Characters read by the ReadFile or ReadConsole function are written to the active screen
// 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
// location and all text following that location will not be overwritten. When disabled, all
// following text will be overwritten.
if (mode & Console::ENABLE_INSERT_MODE).0 > 0 {
debug!("Insert Mode: Enabled");
} else {
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
// keyboard focus, mouse events generated by mouse movement and button presses are placed in
// the input buffer. These events are discarded by ReadFile or ReadConsole, even when this mode
// is enabled. The ReadConsoleInput function can be used to read MOUSE_EVENT input records from
// the input buffer.
if (mode & Console::ENABLE_MOUSE_INPUT).0 > 0 {
debug!("Mouse Input: Enabled");
} 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 {
debug!("Processed Input: Disabled");
}
// 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
// console's input buffer. Information about these events can be read from the input buffer by
// applications using the ReadConsoleInput function, but not by those using ReadFile or
// ReadConsole.
if (mode & Console::ENABLE_WINDOW_INPUT).0 > 0 {
debug!("Window Input: Enabled");
} else {
debug!("Window Input: Disabled");
}
// Setting this flag directs the Virtual Terminal processing engine to convert user input
// received by the console window into Console Virtual Terminal Sequences that can be retrieved
// by a supporting application through ReadFile or ReadConsole functions.
//
// 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 {
debug!("Virtual Terminal Input: Disabled");
}
}
fn log_output_mode(mode: Console::CONSOLE_MODE) { fn log_output_mode(mode: Console::CONSOLE_MODE) {
// Characters written by the WriteFile or WriteConsole function or echoed by the ReadFile or // Characters written by the WriteFile or WriteConsole function or echoed by the ReadFile or
// ReadConsole function are parsed for ASCII control sequences, and the correct action is // ReadConsole function are parsed for ASCII control sequences, and the correct action is
@ -183,14 +102,6 @@ fn log_output_mode(mode: Console::CONSOLE_MODE) {
} }
} }
fn stdin_handle() -> Result<HANDLE> {
unsafe {
let handle = Console::GetStdHandle(Console::STD_INPUT_HANDLE)
.context("unable to get stdin handle")?;
Ok(handle)
}
}
pub fn stdout_handle() -> Result<HANDLE> { pub fn stdout_handle() -> Result<HANDLE> {
unsafe { unsafe {
let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE) let handle = Console::GetStdHandle(Console::STD_OUTPUT_HANDLE)
@ -199,37 +110,6 @@ pub fn stdout_handle() -> Result<HANDLE> {
} }
} }
fn setup_stdin() -> Result<()> {
let mut mode = Console::CONSOLE_MODE(0);
unsafe {
Console::SetConsoleCP(65001);
let handle = stdin_handle()?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
// allow terminal input characters
mode |= Console::ENABLE_VIRTUAL_TERMINAL_INPUT;
// disable automatic processing of CTRL+C, we'll handle it ourselves
mode &= !Console::ENABLE_PROCESSED_INPUT;
// disable line mode to get every input as its pressed
mode &= !Console::ENABLE_LINE_INPUT;
// disable automatic echoing of inputs
mode &= !Console::ENABLE_ECHO_INPUT;
// enable mouse input
mode |= Console::ENABLE_MOUSE_INPUT;
Error::check(Console::SetConsoleMode(handle, mode))?;
Error::check(Console::GetConsoleMode(handle, &mut mode))?;
debug!("Stdin details:");
log_input_mode(mode);
}
Ok(())
}
fn setup_stdout() -> Result<()> { fn setup_stdout() -> Result<()> {
unsafe { unsafe {
Console::SetConsoleOutputCP(65001); Console::SetConsoleOutputCP(65001);
@ -259,139 +139,131 @@ fn main() -> Result<()> {
} }
} }
setup_stdin()?;
setup_stdout()?; setup_stdout()?;
let stdin = stdin_handle()?;
let stdout = stdout_handle()?; let stdout = stdout_handle()?;
let mut buf = [Console::INPUT_RECORD::default(); 100];
let mut line = Line::new(); let mut line = Line::new();
let prompt = Prompt::new(); let prompt = Prompt::new();
let mut input = input::Reader::new()?;
prompt.print()?; prompt.print()?;
loop { loop {
let mut n: u32 = 0; let rec = input.next()?;
unsafe { debug!("Event Type: {}", rec.EventType);
Error::check(Console::ReadConsoleInputW(stdin, &mut buf, &mut n))?; match rec.EventType as u32 {
} Console::FOCUS_EVENT => {
let n = n as usize; // The Event member contains a FOCUS_EVENT_RECORD structure. These events are
for rec in &buf[0..n] { // used internally and should be ignored.
debug!("Event Type: {}", rec.EventType); unsafe {
match rec.EventType as u32 { let event = rec.Event.FocusEvent;
Console::FOCUS_EVENT => { debug!("Focus Event: {:?}", event);
// The Event member contains a FOCUS_EVENT_RECORD structure. These events are
// used internally and should be ignored.
unsafe {
let event = rec.Event.FocusEvent;
debug!("Focus Event: {:?}", event);
}
} }
Console::MENU_EVENT => { }
// The Event member contains a MENU_EVENT_RECORD structure. These events are Console::MENU_EVENT => {
// used internally and should be ignored. // The Event member contains a MENU_EVENT_RECORD structure. These events are
unsafe { // used internally and should be ignored.
let event = rec.Event.MenuEvent; unsafe {
debug!("Menu Event: {:?}", event); let event = rec.Event.MenuEvent;
} debug!("Menu Event: {:?}", event);
} }
Console::KEY_EVENT => { }
// The Event member contains a KEY_EVENT_RECORD structure with information Console::KEY_EVENT => {
// about a keyboard event. // The Event member contains a KEY_EVENT_RECORD structure with information
unsafe { // about a keyboard event.
let event = rec.Event.KeyEvent; unsafe {
let down = event.bKeyDown.as_bool(); let event = rec.Event.KeyEvent;
let repeats = event.wRepeatCount; let down = event.bKeyDown.as_bool();
let key_code = event.wVirtualKeyCode; let repeats = event.wRepeatCount;
let scan_code = event.wVirtualScanCode; let key_code = event.wVirtualKeyCode;
let c = event.uChar.UnicodeChar; let scan_code = event.wVirtualScanCode;
let modifiers = event.dwControlKeyState; let c = event.uChar.UnicodeChar;
// handle key presses on key up, not key down. This is a little weird but let modifiers = event.dwControlKeyState;
// it's sending both down and up events when you hit the key anyway. I // handle key presses on key up, not key down. This is a little weird but
// dunno if this is right. But for some reason, the down events don't have // it's sending both down and up events when you hit the key anyway. I
// the modifiers // dunno if this is right. But for some reason, the down events don't have
if !down { // the modifiers
// Carriage Return (Enter key) if !down {
if c == 13 { // Carriage Return (Enter key)
let mask = Console::LEFT_ALT_PRESSED if c == 13 {
| Console::RIGHT_ALT_PRESSED let mask = Console::LEFT_ALT_PRESSED
| Console::LEFT_CTRL_PRESSED | Console::RIGHT_ALT_PRESSED
| Console::RIGHT_CTRL_PRESSED | Console::LEFT_CTRL_PRESSED
| Console::SHIFT_PRESSED; | Console::RIGHT_CTRL_PRESSED
if modifiers & mask == 0 { | Console::SHIFT_PRESSED;
Error::check(Console::WriteConsoleA( if modifiers & mask == 0 {
stdout, Error::check(Console::WriteConsoleA(
"\r\n".as_bytes(), stdout,
None, "\r\n".as_bytes(),
None, None,
))?; None,
line.print()?; ))?;
Error::check(Console::WriteConsoleA( line.print()?;
stdout,
"\r\n".as_bytes(),
None,
None,
))?;
prompt.print()?;
line.clear();
}
} else if key_code == 68
&& (modifiers & Console::LEFT_CTRL_PRESSED
== Console::LEFT_CTRL_PRESSED)
{
// this is CTRL+D
CloseHandle(stdout);
return Ok(());
} else if key_code == 74
&& (modifiers & Console::LEFT_CTRL_PRESSED
== Console::LEFT_CTRL_PRESSED)
{
// red bullet
let text = "\x1b[31m\u{2022}\x1b[0m";
Error::check(Console::WriteConsoleA( Error::check(Console::WriteConsoleA(
stdout, stdout,
text.as_bytes(), "\r\n".as_bytes(),
None, None,
None, None,
))?; ))?;
} else { prompt.print()?;
if let Some(c) = char::from_u32(event.uChar.UnicodeChar as u32) { line.clear();
if !c.is_control() { }
let mut buf = [0 as u8; 8]; } else if key_code == 68
let s = c.encode_utf8(&mut buf); && (modifiers & Console::LEFT_CTRL_PRESSED
s.chars().for_each(|c| line.append(c)); == Console::LEFT_CTRL_PRESSED)
Error::check(Console::WriteConsoleW( {
stdout, // this is CTRL+D
s.as_bytes(), CloseHandle(stdout);
None, return Ok(());
None, } else if key_code == 74
))?; && (modifiers & Console::LEFT_CTRL_PRESSED
} == Console::LEFT_CTRL_PRESSED)
{
// red bullet
let text = "\x1b[31m\u{2022}\x1b[0m";
Error::check(Console::WriteConsoleA(
stdout,
text.as_bytes(),
None,
None,
))?;
} else {
if let Some(c) = char::from_u32(event.uChar.UnicodeChar as u32) {
if !c.is_control() {
let mut buf = [0 as u8; 8];
let s = c.encode_utf8(&mut buf);
s.chars().for_each(|c| line.append(c));
Error::check(Console::WriteConsoleW(
stdout,
s.as_bytes(),
None,
None,
))?;
} }
} }
} }
debug!(
"Key Event: down: {down:?} repeats: {repeats} key-code: {key_code} scan-code: {scan_code} char: {c} modifiers: {modifiers}"
);
} }
debug!(
"Key Event: down: {down:?} repeats: {repeats} key-code: {key_code} scan-code: {scan_code} char: {c} modifiers: {modifiers}"
);
} }
Console::MOUSE_EVENT => { }
// The Event member contains a MOUSE_EVENT_RECORD structure with information Console::MOUSE_EVENT => {
// about a mouse movement or button press event. // The Event member contains a MOUSE_EVENT_RECORD structure with information
unsafe { // about a mouse movement or button press event.
let event = rec.Event.MouseEvent; unsafe {
debug!("Mouse Event: {:?}", event); let event = rec.Event.MouseEvent;
} debug!("Mouse Event: {:?}", event);
} }
Console::WINDOW_BUFFER_SIZE_EVENT => { }
// The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with Console::WINDOW_BUFFER_SIZE_EVENT => {
// information about the new size of the console screen buffer. // The Event member contains a WINDOW_BUFFER_SIZE_RECORD structure with
unsafe { // information about the new size of the console screen buffer.
let event = rec.Event.WindowBufferSizeEvent; unsafe {
debug!("Window Buffer Event: {:?}", event); let event = rec.Event.WindowBufferSizeEvent;
} debug!("Window Buffer Event: {:?}", event);
} }
_ => {}
} }
_ => {}
} }
} }
} }

Loading…
Cancel
Save