parent
1a60924abd
commit
762c3a9b8e
@ -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"}
|
@ -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"
|
||||||
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 => {
|
||||||
let key_event = KeyEventRecord::from(*input.event.KeyEvent());
|
if let Ok(Some(event)) =
|
||||||
if key_event.key_down {
|
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
|
||||||
// NOTE (@imdaveho): only handle key down, this is because unix limits key events to key press
|
{
|
||||||
continue;
|
input_events.push(event)
|
||||||
}
|
}
|
||||||
handle_key_event(&key_event, &mut vts);
|
|
||||||
}
|
}
|
||||||
InputEventType::MouseEvent => {
|
InputEventType::MouseEvent => {
|
||||||
let mouse_event = MouseEvent::from(*input.event.MouseEvent());
|
if let Ok(Some(event)) =
|
||||||
// TODO: handle mouse events
|
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
|
||||||
handle_mouse_event(&mouse_event, &mut vts);
|
{
|
||||||
|
input_events.push(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// NOTE (@imdaveho): ignore below
|
// NOTE (@imdaveho): ignore below
|
||||||
InputEventType::WindowBufferSizeEvent => (),
|
InputEventType::WindowBufferSizeEvent => (), // TODO implement terminal resize event
|
||||||
InputEventType::FocusEvent => (),
|
InputEventType::FocusEvent => (),
|
||||||
InputEventType::MenuEvent => (),
|
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
|
|
||||||
}
|
}
|
||||||
VK_BACK => {
|
Ok(None)
|
||||||
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)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
VK_END | VK_HOME => {
|
VK_END | VK_HOME => {
|
||||||
seq.push(b'\x1B');
|
if key_code == VK_HOME {
|
||||||
seq.push(b'[');
|
Some(KeyEvent::Home)
|
||||||
seq.push([b'F', b'H'][(key_event.virtual_key_code - 0x23) as usize]);
|
} else if key_code == VK_END {
|
||||||
}
|
Some(KeyEvent::End)
|
||||||
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_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 {
|
} else {
|
||||||
seq.push(character as u8);
|
Some(KeyEvent::Char(character))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
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';');
|
_ => None,
|
||||||
for y in cybs {
|
|
||||||
seq.push(y);
|
|
||||||
}
|
|
||||||
seq.push(b';');
|
|
||||||
seq.push(b'M');
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"}
|
@ -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"}
|
@ -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"
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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};
|
||||||
|
@ -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"
|
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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>>(),
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user