parent
1a60924abd
commit
762c3a9b8e
@ -13,7 +13,7 @@ edition = "2018"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] }
|
||||
crossterm_winapi = "0.1.4"
|
||||
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
|
||||
|
||||
[dependencies]
|
||||
crossterm_utils = {path="../crossterm_utils"}
|
@ -13,7 +13,7 @@ edition = "2018"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["winnt", "winuser"] }
|
||||
crossterm_winapi = "0.1.4"
|
||||
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.51"
|
||||
|
@ -2,7 +2,7 @@
|
||||
//! Like reading a line, reading a character and reading asynchronously.
|
||||
|
||||
use super::*;
|
||||
use std::{io, str};
|
||||
use std::io;
|
||||
|
||||
/// Allows you to read user input.
|
||||
///
|
||||
@ -150,273 +150,3 @@ impl TerminalInput {
|
||||
pub fn input() -> TerminalInput {
|
||||
TerminalInput::new()
|
||||
}
|
||||
|
||||
/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
|
||||
pub(crate) fn parse_event<I>(item: u8, iter: &mut I) -> Result<InputEvent>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let error = ErrorKind::IoError(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Could not parse an event",
|
||||
));
|
||||
let input_event = match item {
|
||||
b'\x1B' => {
|
||||
let a = iter.next();
|
||||
// This is an escape character, leading a control sequence.
|
||||
match a {
|
||||
Some(b'O') => {
|
||||
match iter.next() {
|
||||
// F1-F4
|
||||
Some(val @ b'P'...b'S') => {
|
||||
InputEvent::Keyboard(KeyEvent::F(1 + val - b'P'))
|
||||
}
|
||||
_ => return Err(error),
|
||||
}
|
||||
}
|
||||
Some(b'[') => {
|
||||
// This is a CSI sequence.
|
||||
parse_csi(iter)
|
||||
}
|
||||
Some(b'\x1B') => InputEvent::Keyboard(KeyEvent::Esc),
|
||||
Some(c) => {
|
||||
let ch = parse_utf8_char(c, iter);
|
||||
InputEvent::Keyboard(KeyEvent::Alt(ch?))
|
||||
}
|
||||
None => InputEvent::Keyboard(KeyEvent::Esc),
|
||||
}
|
||||
}
|
||||
b'\n' | b'\r' => InputEvent::Keyboard(KeyEvent::Char('\n')),
|
||||
b'\t' => InputEvent::Keyboard(KeyEvent::Char('\t')),
|
||||
b'\x7F' => InputEvent::Keyboard(KeyEvent::Backspace),
|
||||
c @ b'\x01'...b'\x1A' => {
|
||||
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
|
||||
}
|
||||
c @ b'\x1C'...b'\x1F' => {
|
||||
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
|
||||
}
|
||||
b'\0' => InputEvent::Keyboard(KeyEvent::Null),
|
||||
c => {
|
||||
let ch = parse_utf8_char(c, iter);
|
||||
InputEvent::Keyboard(KeyEvent::Char(ch?))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(input_event)
|
||||
}
|
||||
|
||||
/// Parses a CSI sequence, just after reading ^[
|
||||
/// Returns Event::Unknown if an unrecognized sequence is found.
|
||||
/// Most of this parsing code is been taken over from 'termion`.
|
||||
fn parse_csi<I>(iter: &mut I) -> InputEvent
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
match iter.next() {
|
||||
Some(b'[') => match iter.next() {
|
||||
// NOTE (@imdaveho): cannot find when this occurs;
|
||||
// having another '[' after ESC[ not a likely scenario
|
||||
Some(val @ b'A'...b'E') => InputEvent::Keyboard(KeyEvent::F(1 + val - b'A')),
|
||||
_ => InputEvent::Unknown,
|
||||
},
|
||||
Some(b'D') => InputEvent::Keyboard(KeyEvent::Left),
|
||||
Some(b'C') => InputEvent::Keyboard(KeyEvent::Right),
|
||||
Some(b'A') => InputEvent::Keyboard(KeyEvent::Up),
|
||||
Some(b'B') => InputEvent::Keyboard(KeyEvent::Down),
|
||||
Some(b'H') => InputEvent::Keyboard(KeyEvent::Home),
|
||||
Some(b'F') => InputEvent::Keyboard(KeyEvent::End),
|
||||
Some(b'Z') => InputEvent::Keyboard(KeyEvent::BackTab),
|
||||
Some(b'M') => {
|
||||
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
|
||||
// NOTE (@imdaveho): cannot find documentation on this
|
||||
let mut next = || iter.next().unwrap();
|
||||
|
||||
let cb = next() as i8 - 32;
|
||||
// (1, 1) are the coords for upper left.
|
||||
let cx = next().saturating_sub(32) as u16;
|
||||
let cy = next().saturating_sub(32) as u16;
|
||||
|
||||
InputEvent::Mouse(match cb & 0b11 {
|
||||
0 => {
|
||||
if cb & 0x40 != 0 {
|
||||
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
|
||||
} else {
|
||||
MouseEvent::Press(MouseButton::Left, cx, cy)
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
if cb & 0x40 != 0 {
|
||||
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
|
||||
} else {
|
||||
MouseEvent::Press(MouseButton::Middle, cx, cy)
|
||||
}
|
||||
}
|
||||
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
|
||||
3 => MouseEvent::Release(cx, cy),
|
||||
_ => MouseEvent::Unknown,
|
||||
})
|
||||
}
|
||||
Some(b'<') => {
|
||||
// xterm mouse handling:
|
||||
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
|
||||
let mut buf = Vec::new();
|
||||
let mut c = iter.next().unwrap();
|
||||
while match c {
|
||||
b'm' | b'M' => false,
|
||||
_ => true,
|
||||
} {
|
||||
buf.push(c);
|
||||
c = iter.next().unwrap();
|
||||
}
|
||||
let str_buf = String::from_utf8(buf).unwrap();
|
||||
let nums = &mut str_buf.split(';');
|
||||
|
||||
let cb = nums.next().unwrap().parse::<u16>().unwrap();
|
||||
let cx = nums.next().unwrap().parse::<u16>().unwrap();
|
||||
let cy = nums.next().unwrap().parse::<u16>().unwrap();
|
||||
|
||||
match cb {
|
||||
0...2 | 64...65 => {
|
||||
let button = match cb {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Middle,
|
||||
2 => MouseButton::Right,
|
||||
64 => MouseButton::WheelUp,
|
||||
65 => MouseButton::WheelDown,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match c {
|
||||
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
|
||||
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
|
||||
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
Some(c @ b'0'...b'9') => {
|
||||
// Numbered escape code.
|
||||
let mut buf = Vec::new();
|
||||
buf.push(c);
|
||||
let mut character = iter.next().unwrap();
|
||||
|
||||
// The final byte of a CSI sequence can be in the range 64-126, so
|
||||
// let's keep reading anything else.
|
||||
while character < 64 || character > 126 {
|
||||
buf.push(character);
|
||||
character = iter.next().unwrap();
|
||||
}
|
||||
|
||||
match character {
|
||||
// rxvt mouse encoding:
|
||||
// ESC [ Cb ; Cx ; Cy ; M
|
||||
b'M' => {
|
||||
let str_buf = String::from_utf8(buf).unwrap();
|
||||
|
||||
let nums: Vec<u16> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
|
||||
|
||||
let cb = nums[0];
|
||||
let cx = nums[1];
|
||||
let cy = nums[2];
|
||||
|
||||
let event = match cb {
|
||||
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
|
||||
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
|
||||
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
|
||||
35 => MouseEvent::Release(cx, cy),
|
||||
64 => MouseEvent::Hold(cx, cy),
|
||||
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
|
||||
_ => MouseEvent::Unknown,
|
||||
};
|
||||
|
||||
InputEvent::Mouse(event)
|
||||
}
|
||||
// Special key code.
|
||||
b'~' => {
|
||||
let str_buf = String::from_utf8(buf).unwrap();
|
||||
|
||||
// This CSI sequence can be a list of semicolon-separated numbers.
|
||||
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
|
||||
|
||||
if nums.is_empty() {
|
||||
return InputEvent::Unknown;
|
||||
}
|
||||
|
||||
// TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete)
|
||||
if nums.len() > 1 {
|
||||
return InputEvent::Unknown;
|
||||
}
|
||||
|
||||
match nums[0] {
|
||||
1 | 7 => InputEvent::Keyboard(KeyEvent::Home),
|
||||
2 => InputEvent::Keyboard(KeyEvent::Insert),
|
||||
3 => InputEvent::Keyboard(KeyEvent::Delete),
|
||||
4 | 8 => InputEvent::Keyboard(KeyEvent::End),
|
||||
5 => InputEvent::Keyboard(KeyEvent::PageUp),
|
||||
6 => InputEvent::Keyboard(KeyEvent::PageDown),
|
||||
v @ 11...15 => InputEvent::Keyboard(KeyEvent::F(v - 10)),
|
||||
v @ 17...21 => InputEvent::Keyboard(KeyEvent::F(v - 11)),
|
||||
v @ 23...24 => InputEvent::Keyboard(KeyEvent::F(v - 12)),
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
e => match (buf.last().unwrap(), e) {
|
||||
(53, 65) => InputEvent::Keyboard(KeyEvent::CtrlUp),
|
||||
(53, 66) => InputEvent::Keyboard(KeyEvent::CtrlDown),
|
||||
(53, 67) => InputEvent::Keyboard(KeyEvent::CtrlRight),
|
||||
(53, 68) => InputEvent::Keyboard(KeyEvent::CtrlLeft),
|
||||
(50, 65) => InputEvent::Keyboard(KeyEvent::ShiftUp),
|
||||
(50, 66) => InputEvent::Keyboard(KeyEvent::ShiftDown),
|
||||
(50, 67) => InputEvent::Keyboard(KeyEvent::ShiftRight),
|
||||
(50, 68) => InputEvent::Keyboard(KeyEvent::ShiftLeft),
|
||||
_ => InputEvent::Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
|
||||
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let error = Err(ErrorKind::IoError(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Input character is not valid UTF-8",
|
||||
)));
|
||||
|
||||
if c.is_ascii() {
|
||||
Ok(c as char)
|
||||
} else {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(c);
|
||||
|
||||
while let Some(next) = iter.next() {
|
||||
bytes.push(next);
|
||||
if let Ok(st) = str::from_utf8(&bytes) {
|
||||
return Ok(st.chars().next().unwrap());
|
||||
}
|
||||
if bytes.len() >= 4 {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_parse_utf8() {
|
||||
let st = "abcéŷ¤£€ù%323";
|
||||
let ref mut bytes = st.bytes().map(|x| Ok(x));
|
||||
let chars = st.chars();
|
||||
for c in chars {
|
||||
let b = bytes.next().unwrap().unwrap();
|
||||
assert_eq!(c, parse_utf8_char(b, bytes).unwrap());
|
||||
}
|
||||
}
|
||||
|
@ -8,25 +8,27 @@ mod unix_input;
|
||||
#[cfg(windows)]
|
||||
mod windows_input;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use self::unix_input::AsyncReader;
|
||||
#[cfg(unix)]
|
||||
pub use self::unix_input::SyncReader;
|
||||
#[cfg(unix)]
|
||||
use self::unix_input::UnixInput;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub use self::windows_input::AsyncReader;
|
||||
#[cfg(windows)]
|
||||
pub use self::windows_input::SyncReader;
|
||||
#[cfg(windows)]
|
||||
use self::windows_input::WindowsInput;
|
||||
|
||||
use self::input::parse_event;
|
||||
pub use self::input::{input, TerminalInput};
|
||||
use crossterm_utils::{ErrorKind, Result};
|
||||
use crossterm_utils::Result;
|
||||
use std::io;
|
||||
use std::sync::{mpsc, Arc};
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::thread;
|
||||
use std::sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
Arc,
|
||||
};
|
||||
|
||||
/// This trait defines the actions that can be performed with the terminal input.
|
||||
/// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill
|
||||
@ -120,80 +122,3 @@ pub enum KeyEvent {
|
||||
ShiftRight,
|
||||
ShiftLeft,
|
||||
}
|
||||
|
||||
/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read.
|
||||
///
|
||||
/// **[SyncReader](./LINK)**
|
||||
/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read.
|
||||
///
|
||||
/// This type is an iterator, and could be used to iterate over input events.
|
||||
///
|
||||
/// # Remarks
|
||||
/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope.
|
||||
/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue.
|
||||
pub struct AsyncReader {
|
||||
event_rx: Receiver<u8>,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl AsyncReader {
|
||||
/// Construct a new instance of the `AsyncReader`.
|
||||
/// The reading will immediately start when calling this function.
|
||||
pub fn new(function: Box<Fn(&Sender<u8>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
|
||||
let shutdown_handle = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let (event_tx, event_rx) = mpsc::channel();
|
||||
let thread_shutdown = shutdown_handle.clone();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
function(&event_tx, &thread_shutdown);
|
||||
});
|
||||
|
||||
AsyncReader {
|
||||
event_rx,
|
||||
shutdown: shutdown_handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the input event reading.
|
||||
///
|
||||
/// You don't necessarily have to call this function because it will automatically be called when this reader goes out of scope.
|
||||
///
|
||||
/// # Remarks
|
||||
/// - Background thread will be closed.
|
||||
/// - This will consume the handle you won't be able to restart the reading with this handle, create a new `AsyncReader` instead.
|
||||
pub fn stop_reading(&mut self) {
|
||||
self.shutdown.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for AsyncReader {
|
||||
type Item = InputEvent;
|
||||
|
||||
/// Check if there are input events to read.
|
||||
///
|
||||
/// It will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
|
||||
///
|
||||
/// # Remark
|
||||
/// - This is **not** a blocking call.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut iterator = self.event_rx.try_iter();
|
||||
|
||||
match iterator.next() {
|
||||
Some(char_value) => {
|
||||
if let Ok(char_value) = parse_event(char_value, &mut iterator) {
|
||||
Some(char_value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AsyncReader {
|
||||
fn drop(&mut self) {
|
||||
self.stop_reading();
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,14 @@
|
||||
|
||||
use super::*;
|
||||
use crate::sys::unix::{get_tty, read_char_raw};
|
||||
|
||||
use crossterm_utils::{csi, write_cout, Result};
|
||||
use crossterm_utils::{csi, write_cout, Result, ErrorKind};
|
||||
use std::char;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
str,
|
||||
};
|
||||
use std::{sync::mpsc, thread};
|
||||
|
||||
pub struct UnixInput;
|
||||
|
||||
@ -78,6 +82,83 @@ impl ITerminalInput for UnixInput {
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read.
|
||||
///
|
||||
/// **[SyncReader](./LINK)**
|
||||
/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read.
|
||||
///
|
||||
/// This type is an iterator, and could be used to iterate over input events.
|
||||
///
|
||||
/// # Remarks
|
||||
/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope.
|
||||
/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue.
|
||||
pub struct AsyncReader {
|
||||
event_rx: Receiver<u8>,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl AsyncReader {
|
||||
/// Construct a new instance of the `AsyncReader`.
|
||||
/// The reading will immediately start when calling this function.
|
||||
pub fn new(function: Box<Fn(&Sender<u8>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
|
||||
let shutdown_handle = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let (event_tx, event_rx) = mpsc::channel();
|
||||
let thread_shutdown = shutdown_handle.clone();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
function(&event_tx, &thread_shutdown);
|
||||
});
|
||||
|
||||
AsyncReader {
|
||||
event_rx,
|
||||
shutdown: shutdown_handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the input event reading.
|
||||
///
|
||||
/// You don't necessarily have to call this function because it will automatically be called when this reader goes out of scope.
|
||||
///
|
||||
/// # Remarks
|
||||
/// - Background thread will be closed.
|
||||
/// - This will consume the handle you won't be able to restart the reading with this handle, create a new `AsyncReader` instead.
|
||||
pub fn stop_reading(&mut self) {
|
||||
self.shutdown.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for AsyncReader {
|
||||
type Item = InputEvent;
|
||||
|
||||
/// Check if there are input events to read.
|
||||
///
|
||||
/// It will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
|
||||
///
|
||||
/// # Remark
|
||||
/// - This is **not** a blocking call.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut iterator = self.event_rx.try_iter();
|
||||
|
||||
match iterator.next() {
|
||||
Some(char_value) => {
|
||||
if let Ok(char_value) = parse_event(char_value, &mut iterator) {
|
||||
Some(char_value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AsyncReader {
|
||||
fn drop(&mut self) {
|
||||
self.stop_reading();
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows you to read input synchronously, which means that reading calls will block.
|
||||
///
|
||||
/// This type is an iterator, and can be used to iterate over input events.
|
||||
@ -143,3 +224,273 @@ impl Iterator for SyncReader {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
|
||||
pub(crate) fn parse_event<I>(item: u8, iter: &mut I) -> Result<InputEvent>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let error = ErrorKind::IoError(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Could not parse an event",
|
||||
));
|
||||
let input_event = match item {
|
||||
b'\x1B' => {
|
||||
let a = iter.next();
|
||||
// This is an escape character, leading a control sequence.
|
||||
match a {
|
||||
Some(b'O') => {
|
||||
match iter.next() {
|
||||
// F1-F4
|
||||
Some(val @ b'P'...b'S') => {
|
||||
InputEvent::Keyboard(KeyEvent::F(1 + val - b'P'))
|
||||
}
|
||||
_ => return Err(error),
|
||||
}
|
||||
}
|
||||
Some(b'[') => {
|
||||
// This is a CSI sequence.
|
||||
parse_csi(iter)
|
||||
}
|
||||
Some(b'\x1B') => InputEvent::Keyboard(KeyEvent::Esc),
|
||||
Some(c) => {
|
||||
let ch = parse_utf8_char(c, iter);
|
||||
InputEvent::Keyboard(KeyEvent::Alt(ch?))
|
||||
}
|
||||
None => InputEvent::Keyboard(KeyEvent::Esc),
|
||||
}
|
||||
}
|
||||
b'\n' | b'\r' => InputEvent::Keyboard(KeyEvent::Char('\n')),
|
||||
b'\t' => InputEvent::Keyboard(KeyEvent::Char('\t')),
|
||||
b'\x7F' => InputEvent::Keyboard(KeyEvent::Backspace),
|
||||
c @ b'\x01'...b'\x1A' => {
|
||||
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
|
||||
}
|
||||
c @ b'\x1C'...b'\x1F' => {
|
||||
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
|
||||
}
|
||||
b'\0' => InputEvent::Keyboard(KeyEvent::Null),
|
||||
c => {
|
||||
let ch = parse_utf8_char(c, iter);
|
||||
InputEvent::Keyboard(KeyEvent::Char(ch?))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(input_event)
|
||||
}
|
||||
|
||||
/// Parses a CSI sequence, just after reading ^[
|
||||
/// Returns Event::Unknown if an unrecognized sequence is found.
|
||||
/// Most of this parsing code is been taken over from 'termion`.
|
||||
fn parse_csi<I>(iter: &mut I) -> InputEvent
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
match iter.next() {
|
||||
Some(b'[') => match iter.next() {
|
||||
// NOTE (@imdaveho): cannot find when this occurs;
|
||||
// having another '[' after ESC[ not a likely scenario
|
||||
Some(val @ b'A'...b'E') => InputEvent::Keyboard(KeyEvent::F(1 + val - b'A')),
|
||||
_ => InputEvent::Unknown,
|
||||
},
|
||||
Some(b'D') => InputEvent::Keyboard(KeyEvent::Left),
|
||||
Some(b'C') => InputEvent::Keyboard(KeyEvent::Right),
|
||||
Some(b'A') => InputEvent::Keyboard(KeyEvent::Up),
|
||||
Some(b'B') => InputEvent::Keyboard(KeyEvent::Down),
|
||||
Some(b'H') => InputEvent::Keyboard(KeyEvent::Home),
|
||||
Some(b'F') => InputEvent::Keyboard(KeyEvent::End),
|
||||
Some(b'Z') => InputEvent::Keyboard(KeyEvent::BackTab),
|
||||
Some(b'M') => {
|
||||
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
|
||||
// NOTE (@imdaveho): cannot find documentation on this
|
||||
let mut next = || iter.next().unwrap();
|
||||
|
||||
let cb = next() as i8 - 32;
|
||||
// (1, 1) are the coords for upper left.
|
||||
let cx = next().saturating_sub(32) as u16;
|
||||
let cy = next().saturating_sub(32) as u16;
|
||||
|
||||
InputEvent::Mouse(match cb & 0b11 {
|
||||
0 => {
|
||||
if cb & 0x40 != 0 {
|
||||
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
|
||||
} else {
|
||||
MouseEvent::Press(MouseButton::Left, cx, cy)
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
if cb & 0x40 != 0 {
|
||||
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
|
||||
} else {
|
||||
MouseEvent::Press(MouseButton::Middle, cx, cy)
|
||||
}
|
||||
}
|
||||
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
|
||||
3 => MouseEvent::Release(cx, cy),
|
||||
_ => MouseEvent::Unknown,
|
||||
})
|
||||
}
|
||||
Some(b'<') => {
|
||||
// xterm mouse handling:
|
||||
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
|
||||
let mut buf = Vec::new();
|
||||
let mut c = iter.next().unwrap();
|
||||
while match c {
|
||||
b'm' | b'M' => false,
|
||||
_ => true,
|
||||
} {
|
||||
buf.push(c);
|
||||
c = iter.next().unwrap();
|
||||
}
|
||||
let str_buf = String::from_utf8(buf).unwrap();
|
||||
let nums = &mut str_buf.split(';');
|
||||
|
||||
let cb = nums.next().unwrap().parse::<u16>().unwrap();
|
||||
let cx = nums.next().unwrap().parse::<u16>().unwrap();
|
||||
let cy = nums.next().unwrap().parse::<u16>().unwrap();
|
||||
|
||||
match cb {
|
||||
0...2 | 64...65 => {
|
||||
let button = match cb {
|
||||
0 => MouseButton::Left,
|
||||
1 => MouseButton::Middle,
|
||||
2 => MouseButton::Right,
|
||||
64 => MouseButton::WheelUp,
|
||||
65 => MouseButton::WheelDown,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match c {
|
||||
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
|
||||
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
|
||||
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
Some(c @ b'0'...b'9') => {
|
||||
// Numbered escape code.
|
||||
let mut buf = Vec::new();
|
||||
buf.push(c);
|
||||
let mut character = iter.next().unwrap();
|
||||
|
||||
// The final byte of a CSI sequence can be in the range 64-126, so
|
||||
// let's keep reading anything else.
|
||||
while character < 64 || character > 126 {
|
||||
buf.push(character);
|
||||
character = iter.next().unwrap();
|
||||
}
|
||||
|
||||
match character {
|
||||
// rxvt mouse encoding:
|
||||
// ESC [ Cb ; Cx ; Cy ; M
|
||||
b'M' => {
|
||||
let str_buf = String::from_utf8(buf).unwrap();
|
||||
|
||||
let nums: Vec<u16> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
|
||||
|
||||
let cb = nums[0];
|
||||
let cx = nums[1];
|
||||
let cy = nums[2];
|
||||
|
||||
let event = match cb {
|
||||
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
|
||||
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
|
||||
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
|
||||
35 => MouseEvent::Release(cx, cy),
|
||||
64 => MouseEvent::Hold(cx, cy),
|
||||
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
|
||||
_ => MouseEvent::Unknown,
|
||||
};
|
||||
|
||||
InputEvent::Mouse(event)
|
||||
}
|
||||
// Special key code.
|
||||
b'~' => {
|
||||
let str_buf = String::from_utf8(buf).unwrap();
|
||||
|
||||
// This CSI sequence can be a list of semicolon-separated numbers.
|
||||
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
|
||||
|
||||
if nums.is_empty() {
|
||||
return InputEvent::Unknown;
|
||||
}
|
||||
|
||||
// TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete)
|
||||
if nums.len() > 1 {
|
||||
return InputEvent::Unknown;
|
||||
}
|
||||
|
||||
match nums[0] {
|
||||
1 | 7 => InputEvent::Keyboard(KeyEvent::Home),
|
||||
2 => InputEvent::Keyboard(KeyEvent::Insert),
|
||||
3 => InputEvent::Keyboard(KeyEvent::Delete),
|
||||
4 | 8 => InputEvent::Keyboard(KeyEvent::End),
|
||||
5 => InputEvent::Keyboard(KeyEvent::PageUp),
|
||||
6 => InputEvent::Keyboard(KeyEvent::PageDown),
|
||||
v @ 11...15 => InputEvent::Keyboard(KeyEvent::F(v - 10)),
|
||||
v @ 17...21 => InputEvent::Keyboard(KeyEvent::F(v - 11)),
|
||||
v @ 23...24 => InputEvent::Keyboard(KeyEvent::F(v - 12)),
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
e => match (buf.last().unwrap(), e) {
|
||||
(53, 65) => InputEvent::Keyboard(KeyEvent::CtrlUp),
|
||||
(53, 66) => InputEvent::Keyboard(KeyEvent::CtrlDown),
|
||||
(53, 67) => InputEvent::Keyboard(KeyEvent::CtrlRight),
|
||||
(53, 68) => InputEvent::Keyboard(KeyEvent::CtrlLeft),
|
||||
(50, 65) => InputEvent::Keyboard(KeyEvent::ShiftUp),
|
||||
(50, 66) => InputEvent::Keyboard(KeyEvent::ShiftDown),
|
||||
(50, 67) => InputEvent::Keyboard(KeyEvent::ShiftRight),
|
||||
(50, 68) => InputEvent::Keyboard(KeyEvent::ShiftLeft),
|
||||
_ => InputEvent::Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => InputEvent::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
|
||||
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char>
|
||||
where
|
||||
I: Iterator<Item = u8>,
|
||||
{
|
||||
let error = Err(ErrorKind::IoError(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Input character is not valid UTF-8",
|
||||
)));
|
||||
|
||||
if c.is_ascii() {
|
||||
Ok(c as char)
|
||||
} else {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.push(c);
|
||||
|
||||
while let Some(next) = iter.next() {
|
||||
bytes.push(next);
|
||||
if let Ok(st) = str::from_utf8(&bytes) {
|
||||
return Ok(st.chars().next().unwrap());
|
||||
}
|
||||
if bytes.len() >= 4 {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_parse_utf8() {
|
||||
let st = "abcéŷ¤£€ù%323";
|
||||
let ref mut bytes = st.bytes().map(|x| Ok(x));
|
||||
let chars = st.chars();
|
||||
for c in chars {
|
||||
let b = bytes.next().unwrap().unwrap();
|
||||
assert_eq!(c, parse_utf8_char(b, bytes).unwrap());
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ use winapi::um::{
|
||||
winuser::{
|
||||
VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12,
|
||||
VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT,
|
||||
VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_TAB, VK_UP,
|
||||
VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP,
|
||||
},
|
||||
};
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use std::{char, io, thread};
|
||||
|
||||
@ -62,7 +63,7 @@ impl ITerminalInput for WindowsInput {
|
||||
|
||||
fn read_async(&self) -> AsyncReader {
|
||||
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
|
||||
for i in into_virtual_terminal_sequence().unwrap().1 {
|
||||
for i in read_input_events().unwrap().1 {
|
||||
if event_tx.send(i).is_err() {
|
||||
return;
|
||||
}
|
||||
@ -78,11 +79,17 @@ impl ITerminalInput for WindowsInput {
|
||||
|
||||
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
|
||||
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
|
||||
for i in into_virtual_terminal_sequence().unwrap().1 {
|
||||
if i == delimiter || cancellation_token.load(Ordering::SeqCst) {
|
||||
for event in read_input_events().unwrap().1 {
|
||||
if let InputEvent::Keyboard(KeyEvent::Char(key)) = event {
|
||||
if (key as u8) == delimiter {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if cancellation_token.load(Ordering::SeqCst) {
|
||||
return;
|
||||
} else {
|
||||
if event_tx.send(i).is_err() {
|
||||
if event_tx.send(event).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -113,9 +120,9 @@ impl ITerminalInput for WindowsInput {
|
||||
}
|
||||
}
|
||||
|
||||
/// This type allows you to read input synchronously, which means that reading calls will block.
|
||||
/// This type allows you to read input synchronously, which means that reading call will be blocking ones.
|
||||
///
|
||||
/// This type is an iterator, and can be used to iterate over input events.
|
||||
/// This type is an iterator, and could be used to iterate over input events.
|
||||
///
|
||||
/// If you don't want to block your calls use [AsyncReader](./LINK), which will read input on the background and queue it for you to read.
|
||||
pub struct SyncReader;
|
||||
@ -125,201 +132,291 @@ impl Iterator for SyncReader {
|
||||
|
||||
/// Read input from the user.
|
||||
///
|
||||
/// If there are no keys pressed, this will be a blocking call until there is one.
|
||||
/// This will return `None` in case of a failure and `Some(InputEvent)` in case of an occurred input event.
|
||||
/// If there are no keys pressed this will be a blocking call until there are.
|
||||
/// This will return `None` in case of a failure and `Some(InputEvent) in case of an occurred input event.`
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut iterator = into_virtual_terminal_sequence().unwrap().1.into_iter();
|
||||
read_single_event().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
match iterator.next() {
|
||||
None => None,
|
||||
Some(byte) => {
|
||||
if let Ok(event) = parse_event(byte, &mut iterator) {
|
||||
Some(event)
|
||||
/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read.
|
||||
///
|
||||
/// **[SyncReader](./LINK)**
|
||||
/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read.
|
||||
///
|
||||
/// This type is an iterator, and could be used to iterate over input events.
|
||||
///
|
||||
/// # Remarks
|
||||
/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope.
|
||||
/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue.
|
||||
pub struct AsyncReader {
|
||||
event_rx: Receiver<InputEvent>,
|
||||
shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl AsyncReader {
|
||||
/// Construct a new instance of the `AsyncReader`.
|
||||
/// The reading will immediately start when calling this function.
|
||||
pub fn new(function: Box<Fn(&Sender<InputEvent>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
|
||||
let shutdown_handle = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let (event_tx, event_rx) = mpsc::channel();
|
||||
let thread_shutdown = shutdown_handle.clone();
|
||||
|
||||
thread::spawn(move || loop {
|
||||
function(&event_tx, &thread_shutdown);
|
||||
});
|
||||
|
||||
AsyncReader {
|
||||
event_rx,
|
||||
shutdown: shutdown_handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop the input event reading.
|
||||
///
|
||||
/// You don't necessarily have to call this function because it will automatically be called when this reader goes out of scope.
|
||||
///
|
||||
/// # Remarks
|
||||
/// - Background thread will be closed.
|
||||
/// - This will consume the handle you won't be able to restart the reading with this handle, create a new `AsyncReader` instead.
|
||||
pub fn stop_reading(&mut self) {
|
||||
self.shutdown.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AsyncReader {
|
||||
fn drop(&mut self) {
|
||||
self.stop_reading();
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for AsyncReader {
|
||||
type Item = InputEvent;
|
||||
|
||||
/// Check if there are input events to read.
|
||||
///
|
||||
/// It will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
|
||||
///
|
||||
/// # Remark
|
||||
/// - This is **not** a blocking call.
|
||||
/// - When calling this method to fast after each other the reader might not have read a full byte sequence of some pressed key.
|
||||
/// Make sure that you have some delay of a few ms when calling this method.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut iterator = self.event_rx.try_iter();
|
||||
iterator.next()
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn _getwche() -> INT;
|
||||
}
|
||||
|
||||
fn read_single_event() -> Result<Option<InputEvent>> {
|
||||
let console = Console::from(Handle::current_in_handle()?);
|
||||
|
||||
let input = match console.read_single_input_event()? {
|
||||
Some(event) => event,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
match input.event_type {
|
||||
InputEventType::KeyEvent => {
|
||||
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
|
||||
}
|
||||
InputEventType::MouseEvent => {
|
||||
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
|
||||
}
|
||||
// NOTE (@imdaveho): ignore below
|
||||
InputEventType::WindowBufferSizeEvent => return Ok(None), // TODO implement terminal resize event
|
||||
InputEventType::FocusEvent => Ok(None),
|
||||
InputEventType::MenuEvent => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
|
||||
fn read_input_events() -> Result<(u32, Vec<InputEvent>)> {
|
||||
let console = Console::from(Handle::current_in_handle()?);
|
||||
|
||||
let result = console.read_console_input()?;
|
||||
|
||||
let mut input_events = Vec::with_capacity(result.0 as usize);
|
||||
|
||||
for input in result.1 {
|
||||
match input.event_type {
|
||||
InputEventType::KeyEvent => {
|
||||
if let Ok(Some(event)) =
|
||||
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
|
||||
{
|
||||
input_events.push(event)
|
||||
}
|
||||
}
|
||||
InputEventType::MouseEvent => {
|
||||
if let Ok(Some(event)) =
|
||||
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
|
||||
{
|
||||
input_events.push(event)
|
||||
}
|
||||
}
|
||||
// NOTE (@imdaveho): ignore below
|
||||
InputEventType::WindowBufferSizeEvent => (), // TODO implement terminal resize event
|
||||
InputEventType::FocusEvent => (),
|
||||
InputEventType::MenuEvent => (),
|
||||
}
|
||||
}
|
||||
|
||||
return Ok((result.0, input_events));
|
||||
}
|
||||
|
||||
fn handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<InputEvent>> {
|
||||
if let Some(event) = parse_mouse_event_record(&mouse_event) {
|
||||
return Ok(Some(InputEvent::Mouse(event)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(key_event: KeyEventRecord) -> Result<Option<InputEvent>> {
|
||||
if key_event.key_down {
|
||||
if let Some(event) = parse_key_event_record(&key_event) {
|
||||
return Ok(Some(InputEvent::Keyboard(event)));
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
|
||||
let key_code = key_event.virtual_key_code as i32;
|
||||
match key_code {
|
||||
VK_SHIFT | VK_CONTROL | VK_MENU => None,
|
||||
VK_BACK => Some(KeyEvent::Backspace),
|
||||
VK_ESCAPE => Some(KeyEvent::Esc),
|
||||
VK_RETURN => Some(KeyEvent::Char('\n')),
|
||||
VK_F1 | VK_F2 | VK_F3 | VK_F4 | VK_F5 | VK_F6 | VK_F7 | VK_F8 | VK_F9 | VK_F10 | VK_F11
|
||||
| VK_F12 => Some(KeyEvent::F((key_event.virtual_key_code - 111) as u8)),
|
||||
VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => {
|
||||
// Modifier Keys (Ctrl, Shift) Support
|
||||
let key_state = &key_event.control_key_state;
|
||||
let ctrl_pressed = key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED);
|
||||
let shift_pressed = key_state.has_state(SHIFT_PRESSED);
|
||||
|
||||
let event = match key_code {
|
||||
VK_LEFT => {
|
||||
if ctrl_pressed {
|
||||
Some(KeyEvent::CtrlLeft)
|
||||
} else if shift_pressed {
|
||||
Some(KeyEvent::ShiftLeft)
|
||||
} else {
|
||||
Some(KeyEvent::Left)
|
||||
}
|
||||
}
|
||||
VK_UP => {
|
||||
if ctrl_pressed {
|
||||
Some(KeyEvent::CtrlUp)
|
||||
} else if shift_pressed {
|
||||
Some(KeyEvent::ShiftUp)
|
||||
} else {
|
||||
Some(KeyEvent::Up)
|
||||
}
|
||||
}
|
||||
VK_RIGHT => {
|
||||
if ctrl_pressed {
|
||||
Some(KeyEvent::CtrlRight)
|
||||
} else if shift_pressed {
|
||||
Some(KeyEvent::ShiftRight)
|
||||
} else {
|
||||
Some(KeyEvent::Right)
|
||||
}
|
||||
}
|
||||
VK_DOWN => {
|
||||
if ctrl_pressed {
|
||||
Some(KeyEvent::CtrlDown)
|
||||
} else if shift_pressed {
|
||||
Some(KeyEvent::ShiftDown)
|
||||
} else {
|
||||
Some(KeyEvent::Down)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
event
|
||||
}
|
||||
VK_PRIOR | VK_NEXT => {
|
||||
if key_code == VK_PRIOR {
|
||||
Some(KeyEvent::PageUp)
|
||||
} else if key_code == VK_NEXT {
|
||||
Some(KeyEvent::PageDown)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VK_END | VK_HOME => {
|
||||
if key_code == VK_HOME {
|
||||
Some(KeyEvent::Home)
|
||||
} else if key_code == VK_END {
|
||||
Some(KeyEvent::End)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
VK_DELETE => Some(KeyEvent::Delete),
|
||||
VK_INSERT => Some(KeyEvent::Insert),
|
||||
_ => {
|
||||
// Modifier Keys (Ctrl, Alt, Shift) Support
|
||||
let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
|
||||
|
||||
if character_raw < 255 {
|
||||
let character = character_raw as u8 as char;
|
||||
|
||||
let key_state = &key_event.control_key_state;
|
||||
|
||||
if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) {
|
||||
// If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
|
||||
// The pressed command is stored in `virtual_key_code`.
|
||||
let command = key_event.virtual_key_code as u8 as char;
|
||||
|
||||
if (command).is_alphabetic() {
|
||||
Some(KeyEvent::Alt(command))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
|
||||
match character_raw as u8 {
|
||||
c @ b'\x01'...b'\x1A' => {
|
||||
Some(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
|
||||
}
|
||||
c @ b'\x1C'...b'\x1F' => {
|
||||
Some(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else if key_state.has_state(SHIFT_PRESSED) {
|
||||
// Shift + key press, essentially the same as single key press
|
||||
// Separating to be explicit about the Shift press.
|
||||
if character == '\t' {
|
||||
Some(KeyEvent::BackTab)
|
||||
} else {
|
||||
Some(KeyEvent::Char(character))
|
||||
}
|
||||
} else {
|
||||
Some(KeyEvent::Char(character))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn _getwche() -> INT;
|
||||
fn _getwch() -> INT;
|
||||
}
|
||||
|
||||
/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
|
||||
fn into_virtual_terminal_sequence() -> Result<(u32, Vec<u8>)> {
|
||||
let console = Console::from(Handle::current_in_handle()?);
|
||||
|
||||
let mut vts: Vec<u8> = Vec::new();
|
||||
|
||||
let result = console.read_console_input()?;
|
||||
|
||||
for input in result.1 {
|
||||
unsafe {
|
||||
match input.event_type {
|
||||
InputEventType::KeyEvent => {
|
||||
let key_event = KeyEventRecord::from(*input.event.KeyEvent());
|
||||
if key_event.key_down {
|
||||
// NOTE (@imdaveho): only handle key down, this is because unix limits key events to key press
|
||||
continue;
|
||||
}
|
||||
handle_key_event(&key_event, &mut vts);
|
||||
}
|
||||
InputEventType::MouseEvent => {
|
||||
let mouse_event = MouseEvent::from(*input.event.MouseEvent());
|
||||
// TODO: handle mouse events
|
||||
handle_mouse_event(&mouse_event, &mut vts);
|
||||
}
|
||||
// NOTE (@imdaveho): ignore below
|
||||
InputEventType::WindowBufferSizeEvent => (),
|
||||
InputEventType::FocusEvent => (),
|
||||
InputEventType::MenuEvent => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok((result.0, vts));
|
||||
}
|
||||
|
||||
fn handle_key_event(key_event: &KeyEventRecord, seq: &mut Vec<u8>) {
|
||||
match key_event.virtual_key_code as i32 {
|
||||
VK_SHIFT | VK_CONTROL | VK_MENU => {
|
||||
// ignore SHIFT, CTRL, ALT standalone presses
|
||||
}
|
||||
VK_BACK => {
|
||||
seq.push(b'\x7F');
|
||||
}
|
||||
VK_ESCAPE => {
|
||||
seq.push(b'\x1B');
|
||||
}
|
||||
VK_RETURN => {
|
||||
seq.push(b'\n');
|
||||
}
|
||||
VK_F1 | VK_F2 | VK_F3 | VK_F4 => {
|
||||
// F1 - F4 are support by default VT100
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'O');
|
||||
seq.push([b'P', b'Q', b'R', b'S'][(key_event.virtual_key_code - 0x70) as usize]);
|
||||
}
|
||||
VK_F5 | VK_F6 | VK_F7 | VK_F8 => {
|
||||
// NOTE: F Key Escape Codes:
|
||||
// http://aperiodic.net/phil/archives/Geekery/term-function-keys.html
|
||||
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
|
||||
// F5 - F8
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push(b'1');
|
||||
seq.push([b'5', b'7', b'8', b'9'][(key_event.virtual_key_code - 0x74) as usize]);
|
||||
seq.push(b'~');
|
||||
}
|
||||
VK_F9 | VK_F10 | VK_F11 | VK_F12 => {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push(b'2');
|
||||
seq.push([b'0', b'1', b'3', b'4'][(key_event.virtual_key_code - 0x78) as usize]);
|
||||
seq.push(b'~');
|
||||
}
|
||||
VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
|
||||
// Modifier Keys (Ctrl, Shift) Support
|
||||
let key_state = &key_event.control_key_state;
|
||||
if key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED) {
|
||||
seq.push(53);
|
||||
} else if key_state.has_state(SHIFT_PRESSED) {
|
||||
seq.push(50);
|
||||
}
|
||||
|
||||
seq.push([b'D', b'A', b'C', b'B'][(key_event.virtual_key_code - 0x25) as usize]);
|
||||
}
|
||||
VK_PRIOR | VK_NEXT => {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push([b'5', b'6'][(key_event.virtual_key_code - 0x21) as usize]);
|
||||
seq.push(b'~');
|
||||
}
|
||||
VK_END | VK_HOME => {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push([b'F', b'H'][(key_event.virtual_key_code - 0x23) as usize]);
|
||||
}
|
||||
VK_DELETE => {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push([b'2', b'3'][(key_event.virtual_key_code - 0x2D) as usize]);
|
||||
seq.push(b'~');
|
||||
}
|
||||
VK_INSERT => {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push(b'2');
|
||||
seq.push(b'~');
|
||||
}
|
||||
VK_TAB => {
|
||||
let key_state = &key_event.control_key_state;
|
||||
if key_state.has_state(SHIFT_PRESSED) {
|
||||
seq.push(b'\x1B');
|
||||
seq.push(b'[');
|
||||
seq.push(b'Z');
|
||||
} else {
|
||||
seq.push(b'\t');
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Modifier Keys (Ctrl, Alt, Shift) Support
|
||||
// NOTE (@imdaveho): test to check if characters outside of
|
||||
// alphabet or alphanumerics are supported
|
||||
let character = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
|
||||
|
||||
if character < 255 {
|
||||
let character = character as u8 as char;
|
||||
|
||||
let key_state = &key_event.control_key_state;
|
||||
|
||||
if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) {
|
||||
seq.push(b'\x1B');
|
||||
// If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
|
||||
// The pressed command is stored in `virtual_key_code`.
|
||||
let command = key_event.virtual_key_code as u8 as char;
|
||||
|
||||
if (command).is_alphabetic() {
|
||||
seq.push(command as u8);
|
||||
}
|
||||
} else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
|
||||
seq.push(character as u8);
|
||||
} else if key_state.has_state(SHIFT_PRESSED) {
|
||||
// Shift + key press, essentially the same as single key press
|
||||
// Separating to be explicit about the Shift press.
|
||||
seq.push(character as u8);
|
||||
} else {
|
||||
seq.push(character as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec<u8>) {
|
||||
fn parse_mouse_event_record(event: &MouseEvent) -> Option<super::MouseEvent> {
|
||||
// NOTE (@imdaveho): xterm emulation takes the digits of the coords and passes them
|
||||
// individually as bytes into a buffer; the below cxbs and cybs replicates that and
|
||||
// mimicks the behavior; additionally, in xterm, mouse move is only handled when a
|
||||
// mouse button is held down (ie. mouse drag)
|
||||
|
||||
let cxbs: Vec<u8> =
|
||||
(event.mouse_position.x + 1) /* windows positions are 0 based and ansi codes 1. */
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|d| d as u8)
|
||||
.collect();
|
||||
let cybs: Vec<u8> =
|
||||
(event.mouse_position.y + 1) /* windows positions are 0 based and ansi codes 1. */
|
||||
.to_string()
|
||||
.chars()
|
||||
.map(|d| d as u8)
|
||||
.collect();
|
||||
let xpos = event.mouse_position.x + 1;
|
||||
let ypos = event.mouse_position.y + 1;
|
||||
|
||||
// TODO (@imdaveho): check if linux only provides coords for visible terminal window vs the total buffer
|
||||
|
||||
@ -327,76 +424,41 @@ fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec<u8>) {
|
||||
EventFlags::PressOrRelease => {
|
||||
// Single click
|
||||
match event.button_state {
|
||||
ButtonState::Release => {
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'm');
|
||||
}
|
||||
ButtonState::Release => Some(super::MouseEvent::Release(xpos as u16, ypos as u16)),
|
||||
ButtonState::FromLeft1stButtonPressed => {
|
||||
// left click
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'0', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'M');
|
||||
Some(super::MouseEvent::Press(
|
||||
MouseButton::Left,
|
||||
xpos as u16,
|
||||
ypos as u16,
|
||||
))
|
||||
}
|
||||
ButtonState::RightmostButtonPressed => {
|
||||
// right click
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'2', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'M');
|
||||
Some(super::MouseEvent::Press(
|
||||
MouseButton::Right,
|
||||
xpos as u16,
|
||||
ypos as u16,
|
||||
))
|
||||
}
|
||||
ButtonState::FromLeft2ndButtonPressed => {
|
||||
// middle click
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'1', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
Some(super::MouseEvent::Press(
|
||||
MouseButton::Middle,
|
||||
xpos as u16,
|
||||
ypos as u16,
|
||||
))
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'M');
|
||||
}
|
||||
_ => (),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
EventFlags::MouseMoved => {
|
||||
// Click + Move
|
||||
// NOTE (@imdaveho) only register when mouse is not released
|
||||
if event.button_state != ButtonState::Release {
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b'2', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'M');
|
||||
Some(super::MouseEvent::Hold(xpos as u16, ypos as u16))
|
||||
} else {
|
||||
()
|
||||
None
|
||||
}
|
||||
}
|
||||
EventFlags::MouseWheeled => {
|
||||
@ -404,31 +466,21 @@ fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec<u8>) {
|
||||
// NOTE (@imdaveho) from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
|
||||
// if `button_state` is negative then the wheel was rotated backward, toward the user.
|
||||
if event.button_state != ButtonState::Negative {
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'4', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'M');
|
||||
Some(super::MouseEvent::Press(
|
||||
MouseButton::WheelUp,
|
||||
xpos as u16,
|
||||
ypos as u16,
|
||||
))
|
||||
} else {
|
||||
seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'5', b';']);
|
||||
for x in cxbs {
|
||||
seq.push(x);
|
||||
}
|
||||
seq.push(b';');
|
||||
for y in cybs {
|
||||
seq.push(y);
|
||||
}
|
||||
seq.push(b';');
|
||||
seq.push(b'M');
|
||||
Some(super::MouseEvent::Press(
|
||||
MouseButton::WheelDown,
|
||||
xpos as u16,
|
||||
ypos as u16,
|
||||
))
|
||||
}
|
||||
}
|
||||
EventFlags::DoubleClick => (), // NOTE (@imdaveho): double click not supported by unix terminals
|
||||
EventFlags::MouseHwheeled => (), // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
|
||||
EventFlags::DoubleClick => None, // NOTE (@imdaveho): double click not supported by unix terminals
|
||||
EventFlags::MouseHwheeled => None, // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
|
||||
// TODO: Handle Ctrl + Mouse, Alt + Mouse, etc.
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -16,4 +16,4 @@ crossterm_utils = {path="../crossterm_utils"}
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["minwindef", "wincon"] }
|
||||
crossterm_winapi = "0.1.4"
|
||||
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
|
@ -13,7 +13,7 @@ edition = "2018"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["wincon"] }
|
||||
crossterm_winapi = "0.1.4"
|
||||
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
|
||||
|
||||
[dependencies]
|
||||
crossterm_utils = {path="../crossterm_utils"}
|
@ -12,7 +12,7 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
crossterm_winapi = "0.1.4"
|
||||
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.51"
|
||||
|
@ -9,7 +9,7 @@ pub fn exit() {
|
||||
pub fn get_terminal_size() -> (u16, u16) {
|
||||
if let Ok(buffer) = ScreenBuffer::current() {
|
||||
let size = buffer.info().unwrap().terminal_size();
|
||||
(size.width + 1, size.height + 1).into()
|
||||
((size.width + 1) as u16, (size.height + 1) as u16)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
//! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions will use this implementation instead.
|
||||
|
||||
use super::*;
|
||||
use crate::sys::winapi::get_terminal_size;
|
||||
use crossterm_cursor::sys::winapi::Cursor;
|
||||
use crossterm_utils::{ErrorKind, Result};
|
||||
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};
|
||||
|
@ -12,7 +12,7 @@ readme = "README.md"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.7", features = ["wincon"] }
|
||||
crossterm_winapi = "0.1.4"
|
||||
crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2.51"
|
@ -1,11 +1,5 @@
|
||||
use crate::{execute, impl_display, queue, write_cout, ErrorKind, Result};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::supports_ansi;
|
||||
|
||||
use crate::{execute, impl_display, queue, write_cout, Result};
|
||||
use std::fmt::Display;
|
||||
use std::fmt::{self, Error, Formatter};
|
||||
use std::intrinsics::write_bytes;
|
||||
use std::io::Write;
|
||||
|
||||
/// A command is an action that can be performed on the terminal.
|
||||
|
@ -1,6 +1,5 @@
|
||||
//! Module containing error handling logic.
|
||||
|
||||
use command::Command;
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io,
|
||||
|
@ -68,7 +68,7 @@ macro_rules! write_cout {
|
||||
macro_rules! queue {
|
||||
($write:expr, $($command:expr), *) =>
|
||||
{{
|
||||
use $crate::{Command, write_cout};
|
||||
use $crate::Command;
|
||||
let mut error = None;
|
||||
|
||||
$(
|
||||
@ -104,7 +104,7 @@ macro_rules! queue {
|
||||
}else {
|
||||
Ok(())
|
||||
}
|
||||
}};
|
||||
}}
|
||||
}
|
||||
|
||||
/// Execute one or more command(s)
|
||||
|
@ -2,6 +2,7 @@ use super::{is_true, Coord, Handle, HandleType, WindowPositions};
|
||||
use std::io::{self, Error, Result};
|
||||
use std::str;
|
||||
|
||||
use std::borrow::ToOwned;
|
||||
use winapi::ctypes::c_void;
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::shared::ntdef::NULL;
|
||||
@ -13,6 +14,7 @@ use winapi::um::{
|
||||
},
|
||||
winnt::HANDLE,
|
||||
};
|
||||
|
||||
use InputRecord;
|
||||
|
||||
/// Could be used to do some basic things with the console.
|
||||
@ -162,12 +164,26 @@ impl Console {
|
||||
Ok(utf8.as_bytes().len())
|
||||
}
|
||||
|
||||
pub fn read_console_input(&self) -> Result<(u32, Vec<InputRecord>)> {
|
||||
let mut buf_len: DWORD = 0;
|
||||
if !is_true(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) }) {
|
||||
return Err(Error::last_os_error());
|
||||
pub fn read_single_input_event(&self) -> Result<Option<InputRecord>> {
|
||||
let mut buf_len = self.number_of_console_input_events()?;
|
||||
|
||||
// Fast-skipping all the code below if there is nothing to read at all
|
||||
if buf_len == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut buf: Vec<INPUT_RECORD> = Vec::with_capacity(1);
|
||||
let mut size = 0;
|
||||
|
||||
let a = self.read_input(&mut buf, 1, &mut size)?.1[0].to_owned();
|
||||
|
||||
// read single input event
|
||||
Ok(Some(a))
|
||||
}
|
||||
|
||||
pub fn read_console_input(&self) -> Result<(u32, Vec<InputRecord>)> {
|
||||
let mut buf_len = self.number_of_console_input_events()?;
|
||||
|
||||
// Fast-skipping all the code below if there is nothing to read at all
|
||||
if buf_len == 0 {
|
||||
return Ok((0, vec![]));
|
||||
@ -176,19 +192,37 @@ impl Console {
|
||||
let mut buf: Vec<INPUT_RECORD> = Vec::with_capacity(buf_len as usize);
|
||||
let mut size = 0;
|
||||
|
||||
self.read_input(&mut buf, buf_len, &mut size)
|
||||
}
|
||||
|
||||
pub fn number_of_console_input_events(&self) -> Result<u32> {
|
||||
let mut buf_len: DWORD = 0;
|
||||
if !is_true(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) }) {
|
||||
return Err(Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(buf_len)
|
||||
}
|
||||
|
||||
fn read_input(
|
||||
&self,
|
||||
buf: &mut Vec<INPUT_RECORD>,
|
||||
buf_len: u32,
|
||||
bytes_written: &mut u32,
|
||||
) -> Result<(u32, Vec<InputRecord>)> {
|
||||
if !is_true(unsafe {
|
||||
ReadConsoleInputW(*self.handle, buf.as_mut_ptr(), buf_len, &mut size)
|
||||
ReadConsoleInputW(*self.handle, buf.as_mut_ptr(), buf_len, bytes_written)
|
||||
}) {
|
||||
return Err(Error::last_os_error());
|
||||
} else {
|
||||
unsafe {
|
||||
buf.set_len(size as usize);
|
||||
buf.set_len(buf_len as usize);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
size,
|
||||
buf[..(size as usize)]
|
||||
buf_len,
|
||||
buf[..(buf_len as usize)]
|
||||
.iter()
|
||||
.map(|x| InputRecord::from(*x))
|
||||
.collect::<Vec<InputRecord>>(),
|
||||
|
@ -165,6 +165,7 @@ impl From<DWORD> for EventFlags {
|
||||
/// These records can be read from the input buffer by using the `ReadConsoleInput` or `PeekConsoleInput` function, or written to the input buffer by using the `WriteConsoleInput` function.
|
||||
///
|
||||
/// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/input-record-str)
|
||||
#[derive(Clone)]
|
||||
pub struct InputRecord {
|
||||
/// A handle to the type of input event and the event record stored in the Event member.
|
||||
pub event_type: InputEventType,
|
||||
|
@ -127,6 +127,6 @@ pub fn read_synchronously() {
|
||||
fn main() {
|
||||
// un-comment below and run with
|
||||
// `cargo run --example key_events`:
|
||||
read_synchronously();
|
||||
// read_asynchronously();
|
||||
// read_synchronously();
|
||||
read_asynchronously();
|
||||
}
|
||||
|
@ -410,4 +410,6 @@ pub fn reset_fg_and_bg() {
|
||||
println!("{}", Colored::Bg(Color::Reset));
|
||||
}
|
||||
|
||||
fn main() {print_all_foreground_colors_with_method()}
|
||||
fn main() {
|
||||
print_all_foreground_colors_with_method()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user