Input cleanup (#182)

- Removed input parsing WinAPI
This commit is contained in:
Timon 2019-07-25 15:57:05 +02:00 committed by GitHub
parent 1a60924abd
commit 762c3a9b8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 695 additions and 606 deletions

View File

@ -13,7 +13,7 @@ edition = "2018"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] } winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = "0.1.4" crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
[dependencies] [dependencies]
crossterm_utils = {path="../crossterm_utils"} crossterm_utils = {path="../crossterm_utils"}

View File

@ -13,7 +13,7 @@ edition = "2018"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["winnt", "winuser"] } 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] [target.'cfg(unix)'.dependencies]
libc = "0.2.51" libc = "0.2.51"

View File

@ -2,7 +2,7 @@
//! Like reading a line, reading a character and reading asynchronously. //! Like reading a line, reading a character and reading asynchronously.
use super::*; use super::*;
use std::{io, str}; use std::io;
/// Allows you to read user input. /// Allows you to read user input.
/// ///
@ -150,273 +150,3 @@ impl TerminalInput {
pub fn input() -> TerminalInput { pub fn input() -> TerminalInput {
TerminalInput::new() 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());
}
}

View File

@ -8,25 +8,27 @@ mod unix_input;
#[cfg(windows)] #[cfg(windows)]
mod windows_input; mod windows_input;
#[cfg(unix)]
pub use self::unix_input::AsyncReader;
#[cfg(unix)] #[cfg(unix)]
pub use self::unix_input::SyncReader; pub use self::unix_input::SyncReader;
#[cfg(unix)] #[cfg(unix)]
use self::unix_input::UnixInput; use self::unix_input::UnixInput;
#[cfg(windows)]
pub use self::windows_input::AsyncReader;
#[cfg(windows)] #[cfg(windows)]
pub use self::windows_input::SyncReader; pub use self::windows_input::SyncReader;
#[cfg(windows)] #[cfg(windows)]
use self::windows_input::WindowsInput; use self::windows_input::WindowsInput;
use self::input::parse_event;
pub use self::input::{input, TerminalInput}; pub use self::input::{input, TerminalInput};
use crossterm_utils::{ErrorKind, Result}; use crossterm_utils::Result;
use std::io; use std::io;
use std::sync::{mpsc, Arc}; use std::sync::{
mpsc::{Receiver, Sender},
use std::sync::atomic::{AtomicBool, Ordering}; Arc,
use std::sync::mpsc::{Receiver, Sender}; };
use std::thread;
/// This trait defines the actions that can be performed with the terminal input. /// 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 /// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill
@ -120,80 +122,3 @@ pub enum KeyEvent {
ShiftRight, ShiftRight,
ShiftLeft, 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();
}
}

View File

@ -2,10 +2,14 @@
use super::*; use super::*;
use crate::sys::unix::{get_tty, read_char_raw}; use crate::sys::unix::{get_tty, read_char_raw};
use crossterm_utils::{csi, write_cout, Result, ErrorKind};
use crossterm_utils::{csi, write_cout, Result};
use std::char; 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; 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 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. /// This type is an iterator, and can be used to iterate over input events.
@ -143,3 +224,273 @@ impl Iterator for SyncReader {
res 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());
}
}

View File

@ -15,11 +15,12 @@ use winapi::um::{
winuser::{ winuser::{
VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12, 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_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::time::Duration;
use std::{char, io, thread}; use std::{char, io, thread};
@ -62,7 +63,7 @@ impl ITerminalInput for WindowsInput {
fn read_async(&self) -> AsyncReader { fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { 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() { if event_tx.send(i).is_err() {
return; return;
} }
@ -78,11 +79,17 @@ impl ITerminalInput for WindowsInput {
fn read_until_async(&self, delimiter: u8) -> AsyncReader { fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for i in into_virtual_terminal_sequence().unwrap().1 { for event in read_input_events().unwrap().1 {
if i == delimiter || cancellation_token.load(Ordering::SeqCst) { if let InputEvent::Keyboard(KeyEvent::Char(key)) = event {
if (key as u8) == delimiter {
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return; return;
} else { } else {
if event_tx.send(i).is_err() { if event_tx.send(event).is_err() {
return; 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. /// 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; pub struct SyncReader;
@ -125,201 +132,291 @@ impl Iterator for SyncReader {
/// Read input from the user. /// Read input from the user.
/// ///
/// If there are no keys pressed, this will be a blocking call until there is one. /// 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. /// 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> { 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() { /// 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.
None => None, ///
Some(byte) => { /// **[SyncReader](./LINK)**
if let Ok(event) = parse_event(byte, &mut iterator) { /// 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.
Some(event) ///
} else { /// This type is an iterator, and could be used to iterate over input events.
None ///
} /// # 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" { extern "C" {
fn _getwche() -> INT; fn _getwche() -> INT;
fn _getwch() -> 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 /// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
fn into_virtual_terminal_sequence() -> Result<(u32, Vec<u8>)> { fn read_input_events() -> Result<(u32, Vec<InputEvent>)> {
let console = Console::from(Handle::current_in_handle()?); let console = Console::from(Handle::current_in_handle()?);
let mut vts: Vec<u8> = Vec::new();
let result = console.read_console_input()?; let result = console.read_console_input()?;
let mut input_events = Vec::with_capacity(result.0 as usize);
for input in result.1 { for input in result.1 {
unsafe { match input.event_type {
match input.event_type { InputEventType::KeyEvent => {
InputEventType::KeyEvent => { if let Ok(Some(event)) =
let key_event = KeyEventRecord::from(*input.event.KeyEvent()); handle_key_event(unsafe { 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 input_events.push(event)
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 => (),
} }
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, vts)); return Ok((result.0, input_events));
} }
fn handle_key_event(key_event: &KeyEventRecord, seq: &mut Vec<u8>) { fn handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<InputEvent>> {
match key_event.virtual_key_code as i32 { if let Some(event) = parse_mouse_event_record(&mouse_event) {
VK_SHIFT | VK_CONTROL | VK_MENU => { return Ok(Some(InputEvent::Mouse(event)));
// ignore SHIFT, CTRL, ALT standalone presses }
} Ok(None)
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'[');
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 // Modifier Keys (Ctrl, Shift) Support
let key_state = &key_event.control_key_state; let key_state = &key_event.control_key_state;
if key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED) { let ctrl_pressed = key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED);
seq.push(53); let shift_pressed = key_state.has_state(SHIFT_PRESSED);
} 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]); 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 => { VK_PRIOR | VK_NEXT => {
seq.push(b'\x1B'); if key_code == VK_PRIOR {
seq.push(b'['); Some(KeyEvent::PageUp)
seq.push([b'5', b'6'][(key_event.virtual_key_code - 0x21) as usize]); } else if key_code == VK_NEXT {
seq.push(b'~'); Some(KeyEvent::PageDown)
}
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 { } else {
seq.push(b'\t'); 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 // Modifier Keys (Ctrl, Alt, Shift) Support
// NOTE (@imdaveho): test to check if characters outside of let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
// alphabet or alphanumerics are supported
let character = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
if character < 255 { if character_raw < 255 {
let character = character as u8 as char; let character = character_raw as u8 as char;
let key_state = &key_event.control_key_state; let key_state = &key_event.control_key_state;
if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) { 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. // 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`. // The pressed command is stored in `virtual_key_code`.
let command = key_event.virtual_key_code as u8 as char; let command = key_event.virtual_key_code as u8 as char;
if (command).is_alphabetic() { if (command).is_alphabetic() {
seq.push(command as u8); Some(KeyEvent::Alt(command))
} else {
None
} }
} else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) { } else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
seq.push(character as u8); 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) { } else if key_state.has_state(SHIFT_PRESSED) {
// Shift + key press, essentially the same as single key press // Shift + key press, essentially the same as single key press
// Separating to be explicit about the Shift press. // Separating to be explicit about the Shift press.
seq.push(character as u8); if character == '\t' {
Some(KeyEvent::BackTab)
} else {
Some(KeyEvent::Char(character))
}
} else { } else {
seq.push(character as u8); Some(KeyEvent::Char(character))
} }
} else {
None
} }
} }
} }
} }
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 // 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 // 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 // mimicks the behavior; additionally, in xterm, mouse move is only handled when a
// mouse button is held down (ie. mouse drag) // mouse button is held down (ie. mouse drag)
let cxbs: Vec<u8> = let xpos = event.mouse_position.x + 1;
(event.mouse_position.x + 1) /* windows positions are 0 based and ansi codes 1. */ let ypos = event.mouse_position.y + 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();
// TODO (@imdaveho): check if linux only provides coords for visible terminal window vs the total buffer // 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 => { EventFlags::PressOrRelease => {
// Single click // Single click
match event.button_state { match event.button_state {
ButtonState::Release => { ButtonState::Release => Some(super::MouseEvent::Release(xpos as u16, ypos as u16)),
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::FromLeft1stButtonPressed => { ButtonState::FromLeft1stButtonPressed => {
// left click // left click
seq.append(&mut vec![b'\x1B', b'[', b'<', b'0', b';']); Some(super::MouseEvent::Press(
for x in cxbs { MouseButton::Left,
seq.push(x); xpos as u16,
} ypos as u16,
seq.push(b';'); ))
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} }
ButtonState::RightmostButtonPressed => { ButtonState::RightmostButtonPressed => {
// right click // right click
seq.append(&mut vec![b'\x1B', b'[', b'<', b'2', b';']); Some(super::MouseEvent::Press(
for x in cxbs { MouseButton::Right,
seq.push(x); xpos as u16,
} ypos as u16,
seq.push(b';'); ))
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} }
ButtonState::FromLeft2ndButtonPressed => { ButtonState::FromLeft2ndButtonPressed => {
// middle click // middle click
seq.append(&mut vec![b'\x1B', b'[', b'<', b'1', b';']); Some(super::MouseEvent::Press(
for x in cxbs { MouseButton::Middle,
seq.push(x); 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 => { EventFlags::MouseMoved => {
// Click + Move // Click + Move
// NOTE (@imdaveho) only register when mouse is not released // NOTE (@imdaveho) only register when mouse is not released
if event.button_state != ButtonState::Release { if event.button_state != ButtonState::Release {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b'2', b';']); Some(super::MouseEvent::Hold(xpos as u16, ypos as u16))
for x in cxbs {
seq.push(x);
}
seq.push(b';');
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} else { } else {
() None
} }
} }
EventFlags::MouseWheeled => { 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 // 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 `button_state` is negative then the wheel was rotated backward, toward the user.
if event.button_state != ButtonState::Negative { if event.button_state != ButtonState::Negative {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'4', b';']); Some(super::MouseEvent::Press(
for x in cxbs { MouseButton::WheelUp,
seq.push(x); xpos as u16,
} ypos as u16,
seq.push(b';'); ))
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} else { } else {
seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'5', b';']); Some(super::MouseEvent::Press(
for x in cxbs { MouseButton::WheelDown,
seq.push(x); xpos as u16,
} ypos as u16,
seq.push(b';'); ))
for y in cybs {
seq.push(y);
}
seq.push(b';');
seq.push(b'M');
} }
} }
EventFlags::DoubleClick => (), // NOTE (@imdaveho): double click not supported by unix terminals EventFlags::DoubleClick => None, // NOTE (@imdaveho): double click not supported by unix terminals
EventFlags::MouseHwheeled => (), // NOTE (@imdaveho): horizontal scroll not supported by unix terminals EventFlags::MouseHwheeled => None, // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
// TODO: Handle Ctrl + Mouse, Alt + Mouse, etc. // TODO: Handle Ctrl + Mouse, Alt + Mouse, etc.
}; }
} }

View File

@ -16,4 +16,4 @@ crossterm_utils = {path="../crossterm_utils"}
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["minwindef", "wincon"] } winapi = { version = "0.3.7", features = ["minwindef", "wincon"] }
crossterm_winapi = "0.1.4" crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}

View File

@ -13,7 +13,7 @@ edition = "2018"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["wincon"] } winapi = { version = "0.3.7", features = ["wincon"] }
crossterm_winapi = "0.1.4" crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
[dependencies] [dependencies]
crossterm_utils = {path="../crossterm_utils"} crossterm_utils = {path="../crossterm_utils"}

View File

@ -12,7 +12,7 @@ readme = "README.md"
edition = "2018" edition = "2018"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.1.4" crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"}
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2.51" libc = "0.2.51"

View File

@ -9,7 +9,7 @@ pub fn exit() {
pub fn get_terminal_size() -> (u16, u16) { pub fn get_terminal_size() -> (u16, u16) {
if let Ok(buffer) = ScreenBuffer::current() { if let Ok(buffer) = ScreenBuffer::current() {
let size = buffer.info().unwrap().terminal_size(); 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 { } else {
(0, 0) (0, 0)
} }

View File

@ -4,6 +4,7 @@
//! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions will use this implementation instead. //! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions will use this implementation instead.
use super::*; use super::*;
use crate::sys::winapi::get_terminal_size;
use crossterm_cursor::sys::winapi::Cursor; use crossterm_cursor::sys::winapi::Cursor;
use crossterm_utils::{ErrorKind, Result}; use crossterm_utils::{ErrorKind, Result};
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};

View File

@ -12,7 +12,7 @@ readme = "README.md"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.7", features = ["wincon"] } 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] [target.'cfg(unix)'.dependencies]
libc = "0.2.51" libc = "0.2.51"

View File

@ -1,11 +1,5 @@
use crate::{execute, impl_display, queue, write_cout, ErrorKind, Result}; use crate::{execute, impl_display, queue, write_cout, Result};
#[cfg(windows)]
use crate::supports_ansi;
use std::fmt::Display; use std::fmt::Display;
use std::fmt::{self, Error, Formatter};
use std::intrinsics::write_bytes;
use std::io::Write; use std::io::Write;
/// A command is an action that can be performed on the terminal. /// A command is an action that can be performed on the terminal.

View File

@ -1,6 +1,5 @@
//! Module containing error handling logic. //! Module containing error handling logic.
use command::Command;
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
io, io,

View File

@ -68,7 +68,7 @@ macro_rules! write_cout {
macro_rules! queue { macro_rules! queue {
($write:expr, $($command:expr), *) => ($write:expr, $($command:expr), *) =>
{{ {{
use $crate::{Command, write_cout}; use $crate::Command;
let mut error = None; let mut error = None;
$( $(
@ -104,7 +104,7 @@ macro_rules! queue {
}else { }else {
Ok(()) Ok(())
} }
}}; }}
} }
/// Execute one or more command(s) /// Execute one or more command(s)

View File

@ -2,6 +2,7 @@ use super::{is_true, Coord, Handle, HandleType, WindowPositions};
use std::io::{self, Error, Result}; use std::io::{self, Error, Result};
use std::str; use std::str;
use std::borrow::ToOwned;
use winapi::ctypes::c_void; use winapi::ctypes::c_void;
use winapi::shared::minwindef::DWORD; use winapi::shared::minwindef::DWORD;
use winapi::shared::ntdef::NULL; use winapi::shared::ntdef::NULL;
@ -13,6 +14,7 @@ use winapi::um::{
}, },
winnt::HANDLE, winnt::HANDLE,
}; };
use InputRecord; use InputRecord;
/// Could be used to do some basic things with the console. /// Could be used to do some basic things with the console.
@ -162,12 +164,26 @@ impl Console {
Ok(utf8.as_bytes().len()) Ok(utf8.as_bytes().len())
} }
pub fn read_console_input(&self) -> Result<(u32, Vec<InputRecord>)> { pub fn read_single_input_event(&self) -> Result<Option<InputRecord>> {
let mut buf_len: DWORD = 0; let mut buf_len = self.number_of_console_input_events()?;
if !is_true(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) }) {
return Err(Error::last_os_error()); // 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 // Fast-skipping all the code below if there is nothing to read at all
if buf_len == 0 { if buf_len == 0 {
return Ok((0, vec![])); 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 buf: Vec<INPUT_RECORD> = Vec::with_capacity(buf_len as usize);
let mut size = 0; 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 { 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()); return Err(Error::last_os_error());
} else { } else {
unsafe { unsafe {
buf.set_len(size as usize); buf.set_len(buf_len as usize);
} }
} }
Ok(( Ok((
size, buf_len,
buf[..(size as usize)] buf[..(buf_len as usize)]
.iter() .iter()
.map(|x| InputRecord::from(*x)) .map(|x| InputRecord::from(*x))
.collect::<Vec<InputRecord>>(), .collect::<Vec<InputRecord>>(),

View File

@ -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. /// 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) /// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/input-record-str)
#[derive(Clone)]
pub struct InputRecord { pub struct InputRecord {
/// A handle to the type of input event and the event record stored in the Event member. /// A handle to the type of input event and the event record stored in the Event member.
pub event_type: InputEventType, pub event_type: InputEventType,

View File

@ -127,6 +127,6 @@ pub fn read_synchronously() {
fn main() { fn main() {
// un-comment below and run with // un-comment below and run with
// `cargo run --example key_events`: // `cargo run --example key_events`:
read_synchronously(); // read_synchronously();
// read_asynchronously(); read_asynchronously();
} }

View File

@ -410,4 +410,6 @@ pub fn reset_fg_and_bg() {
println!("{}", Colored::Bg(Color::Reset)); println!("{}", Colored::Bg(Color::Reset));
} }
fn main() {print_all_foreground_colors_with_method()} fn main() {
print_all_foreground_colors_with_method()
}