diff --git a/crossterm_cursor/Cargo.toml b/crossterm_cursor/Cargo.toml index f23711290..6508a3ddb 100644 --- a/crossterm_cursor/Cargo.toml +++ b/crossterm_cursor/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] } -crossterm_winapi = "0.1.4" +crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"} [dependencies] crossterm_utils = {path="../crossterm_utils"} \ No newline at end of file diff --git a/crossterm_input/Cargo.toml b/crossterm_input/Cargo.toml index 9cdf86c7c..9f16360dd 100644 --- a/crossterm_input/Cargo.toml +++ b/crossterm_input/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["winnt", "winuser"] } -crossterm_winapi = "0.1.4" +crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"} [target.'cfg(unix)'.dependencies] libc = "0.2.51" diff --git a/crossterm_input/src/input/input.rs b/crossterm_input/src/input/input.rs index ea8c74a6f..7b9f2780d 100644 --- a/crossterm_input/src/input/input.rs +++ b/crossterm_input/src/input/input.rs @@ -2,7 +2,7 @@ //! Like reading a line, reading a character and reading asynchronously. use super::*; -use std::{io, str}; +use std::io; /// Allows you to read user input. /// @@ -150,273 +150,3 @@ impl TerminalInput { pub fn input() -> TerminalInput { TerminalInput::new() } - -/// Parse an Event from `item` and possibly subsequent bytes through `iter`. -pub(crate) fn parse_event(item: u8, iter: &mut I) -> Result -where - I: Iterator, -{ - 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(iter: &mut I) -> InputEvent -where - I: Iterator, -{ - 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::().unwrap(); - let cx = nums.next().unwrap().parse::().unwrap(); - let cy = nums.next().unwrap().parse::().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 = 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 = 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(c: u8, iter: &mut I) -> Result -where - I: Iterator, -{ - 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()); - } -} diff --git a/crossterm_input/src/input/mod.rs b/crossterm_input/src/input/mod.rs index 4476b8ddf..80cfd0428 100644 --- a/crossterm_input/src/input/mod.rs +++ b/crossterm_input/src/input/mod.rs @@ -8,25 +8,27 @@ mod unix_input; #[cfg(windows)] mod windows_input; +#[cfg(unix)] +pub use self::unix_input::AsyncReader; #[cfg(unix)] pub use self::unix_input::SyncReader; #[cfg(unix)] use self::unix_input::UnixInput; +#[cfg(windows)] +pub use self::windows_input::AsyncReader; #[cfg(windows)] pub use self::windows_input::SyncReader; #[cfg(windows)] use self::windows_input::WindowsInput; -use self::input::parse_event; pub use self::input::{input, TerminalInput}; -use crossterm_utils::{ErrorKind, Result}; +use crossterm_utils::Result; use std::io; -use std::sync::{mpsc, Arc}; - -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc::{Receiver, Sender}; -use std::thread; +use std::sync::{ + mpsc::{Receiver, Sender}, + Arc, +}; /// This trait defines the actions that can be performed with the terminal input. /// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill @@ -120,80 +122,3 @@ pub enum KeyEvent { ShiftRight, ShiftLeft, } - -/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read. -/// -/// **[SyncReader](./LINK)** -/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read. -/// -/// This type is an iterator, and could be used to iterate over input events. -/// -/// # Remarks -/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope. -/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue. -pub struct AsyncReader { - event_rx: Receiver, - shutdown: Arc, -} - -impl AsyncReader { - /// Construct a new instance of the `AsyncReader`. - /// The reading will immediately start when calling this function. - pub fn new(function: Box, &Arc) + 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 { - 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(); - } -} diff --git a/crossterm_input/src/input/unix_input.rs b/crossterm_input/src/input/unix_input.rs index 480f60d7b..d0d1e66ab 100644 --- a/crossterm_input/src/input/unix_input.rs +++ b/crossterm_input/src/input/unix_input.rs @@ -2,10 +2,14 @@ use super::*; use crate::sys::unix::{get_tty, read_char_raw}; - -use crossterm_utils::{csi, write_cout, Result}; +use crossterm_utils::{csi, write_cout, Result, ErrorKind}; use std::char; -use std::io::{Read, Write}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::{ + io::{Read, Write}, + str, +}; +use std::{sync::mpsc, thread}; pub struct UnixInput; @@ -78,6 +82,83 @@ impl ITerminalInput for UnixInput { } } +/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read. +/// +/// **[SyncReader](./LINK)** +/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read. +/// +/// This type is an iterator, and could be used to iterate over input events. +/// +/// # Remarks +/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope. +/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue. +pub struct AsyncReader { + event_rx: Receiver, + shutdown: Arc, +} + +impl AsyncReader { + /// Construct a new instance of the `AsyncReader`. + /// The reading will immediately start when calling this function. + pub fn new(function: Box, &Arc) + 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 { + let mut iterator = self.event_rx.try_iter(); + + match iterator.next() { + Some(char_value) => { + if let Ok(char_value) = parse_event(char_value, &mut iterator) { + Some(char_value) + } else { + None + } + } + None => None, + } + } +} + +impl Drop for AsyncReader { + fn drop(&mut self) { + self.stop_reading(); + } +} + /// This type allows you to read input synchronously, which means that reading calls will block. /// /// This type is an iterator, and can be used to iterate over input events. @@ -143,3 +224,273 @@ impl Iterator for SyncReader { res } } + +/// Parse an Event from `item` and possibly subsequent bytes through `iter`. +pub(crate) fn parse_event(item: u8, iter: &mut I) -> Result +where + I: Iterator, +{ + 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(iter: &mut I) -> InputEvent +where + I: Iterator, +{ + 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::().unwrap(); + let cx = nums.next().unwrap().parse::().unwrap(); + let cy = nums.next().unwrap().parse::().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 = 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 = 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(c: u8, iter: &mut I) -> Result +where + I: Iterator, +{ + 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()); + } +} diff --git a/crossterm_input/src/input/windows_input.rs b/crossterm_input/src/input/windows_input.rs index 6d9ce0d1f..ba9cdc7d5 100644 --- a/crossterm_input/src/input/windows_input.rs +++ b/crossterm_input/src/input/windows_input.rs @@ -15,11 +15,12 @@ use winapi::um::{ winuser::{ VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12, VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT, - VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_TAB, VK_UP, + VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, }, }; -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; use std::time::Duration; use std::{char, io, thread}; @@ -62,7 +63,7 @@ impl ITerminalInput for WindowsInput { fn read_async(&self) -> AsyncReader { AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { - for i in into_virtual_terminal_sequence().unwrap().1 { + for i in read_input_events().unwrap().1 { if event_tx.send(i).is_err() { return; } @@ -78,11 +79,17 @@ impl ITerminalInput for WindowsInput { fn read_until_async(&self, delimiter: u8) -> AsyncReader { AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { - for i in into_virtual_terminal_sequence().unwrap().1 { - if i == delimiter || cancellation_token.load(Ordering::SeqCst) { + for event in read_input_events().unwrap().1 { + if let InputEvent::Keyboard(KeyEvent::Char(key)) = event { + if (key as u8) == delimiter { + return; + } + } + + if cancellation_token.load(Ordering::SeqCst) { return; } else { - if event_tx.send(i).is_err() { + if event_tx.send(event).is_err() { return; } } @@ -113,9 +120,9 @@ impl ITerminalInput for WindowsInput { } } -/// This type allows you to read input synchronously, which means that reading calls will block. +/// This type allows you to read input synchronously, which means that reading call will be blocking ones. /// -/// This type is an iterator, and can be used to iterate over input events. +/// This type is an iterator, and could be used to iterate over input events. /// /// If you don't want to block your calls use [AsyncReader](./LINK), which will read input on the background and queue it for you to read. pub struct SyncReader; @@ -125,201 +132,291 @@ impl Iterator for SyncReader { /// Read input from the user. /// - /// If there are no keys pressed, this will be a blocking call until there is one. - /// This will return `None` in case of a failure and `Some(InputEvent)` in case of an occurred input event. + /// If there are no keys pressed this will be a blocking call until there are. + /// This will return `None` in case of a failure and `Some(InputEvent) in case of an occurred input event.` fn next(&mut self) -> Option { - let mut iterator = into_virtual_terminal_sequence().unwrap().1.into_iter(); + read_single_event().unwrap() + } +} - match iterator.next() { - None => None, - Some(byte) => { - if let Ok(event) = parse_event(byte, &mut iterator) { - Some(event) - } else { - None - } - } +/// 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, + shutdown: Arc, +} + +impl AsyncReader { + /// Construct a new instance of the `AsyncReader`. + /// The reading will immediately start when calling this function. + pub fn new(function: Box, &Arc) + 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 { + let mut iterator = self.event_rx.try_iter(); + iterator.next() + } } extern "C" { fn _getwche() -> INT; - fn _getwch() -> INT; } -/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130 -fn into_virtual_terminal_sequence() -> Result<(u32, Vec)> { +fn read_single_event() -> Result> { let console = Console::from(Handle::current_in_handle()?); - let mut vts: Vec = Vec::new(); + let input = match console.read_single_input_event()? { + Some(event) => event, + None => return Ok(None), + }; + + match input.event_type { + InputEventType::KeyEvent => { + handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) }) + } + InputEventType::MouseEvent => { + handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) }) + } + // NOTE (@imdaveho): ignore below + InputEventType::WindowBufferSizeEvent => return Ok(None), // TODO implement terminal resize event + InputEventType::FocusEvent => Ok(None), + InputEventType::MenuEvent => Ok(None), + } +} + +/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130 +fn read_input_events() -> Result<(u32, Vec)> { + let console = Console::from(Handle::current_in_handle()?); let result = console.read_console_input()?; + let mut input_events = Vec::with_capacity(result.0 as usize); + for input in result.1 { - unsafe { - match input.event_type { - InputEventType::KeyEvent => { - let key_event = KeyEventRecord::from(*input.event.KeyEvent()); - if key_event.key_down { - // NOTE (@imdaveho): only handle key down, this is because unix limits key events to key press - continue; - } - handle_key_event(&key_event, &mut vts); + match input.event_type { + InputEventType::KeyEvent => { + if let Ok(Some(event)) = + handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) }) + { + input_events.push(event) } - InputEventType::MouseEvent => { - let mouse_event = MouseEvent::from(*input.event.MouseEvent()); - // TODO: handle mouse events - handle_mouse_event(&mouse_event, &mut vts); + } + 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 => (), - InputEventType::FocusEvent => (), - InputEventType::MenuEvent => (), } + // 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) { - match key_event.virtual_key_code as i32 { - VK_SHIFT | VK_CONTROL | VK_MENU => { - // ignore SHIFT, CTRL, ALT standalone presses - } - VK_BACK => { - seq.push(b'\x7F'); - } - VK_ESCAPE => { - seq.push(b'\x1B'); - } - VK_RETURN => { - seq.push(b'\n'); - } - VK_F1 | VK_F2 | VK_F3 | VK_F4 => { - // F1 - F4 are support by default VT100 - seq.push(b'\x1B'); - seq.push(b'O'); - seq.push([b'P', b'Q', b'R', b'S'][(key_event.virtual_key_code - 0x70) as usize]); - } - VK_F5 | VK_F6 | VK_F7 | VK_F8 => { - // NOTE: F Key Escape Codes: - // http://aperiodic.net/phil/archives/Geekery/term-function-keys.html - // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences - // F5 - F8 - seq.push(b'\x1B'); - seq.push(b'['); - seq.push(b'1'); - seq.push([b'5', b'7', b'8', b'9'][(key_event.virtual_key_code - 0x74) as usize]); - seq.push(b'~'); - } - VK_F9 | VK_F10 | VK_F11 | VK_F12 => { - seq.push(b'\x1B'); - seq.push(b'['); - seq.push(b'2'); - seq.push([b'0', b'1', b'3', b'4'][(key_event.virtual_key_code - 0x78) as usize]); - seq.push(b'~'); +fn handle_mouse_event(mouse_event: MouseEvent) -> Result> { + if let Some(event) = parse_mouse_event_record(&mouse_event) { + return Ok(Some(InputEvent::Mouse(event))); + } + Ok(None) +} + +fn handle_key_event(key_event: KeyEventRecord) -> Result> { + if key_event.key_down { + if let Some(event) = parse_key_event_record(&key_event) { + return Ok(Some(InputEvent::Keyboard(event))); } - VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => { - seq.push(b'\x1B'); - seq.push(b'['); + } + return Ok(None); +} + +fn parse_key_event_record(key_event: &KeyEventRecord) -> Option { + let key_code = key_event.virtual_key_code as i32; + match key_code { + VK_SHIFT | VK_CONTROL | VK_MENU => None, + VK_BACK => Some(KeyEvent::Backspace), + VK_ESCAPE => Some(KeyEvent::Esc), + VK_RETURN => Some(KeyEvent::Char('\n')), + VK_F1 | VK_F2 | VK_F3 | VK_F4 | VK_F5 | VK_F6 | VK_F7 | VK_F8 | VK_F9 | VK_F10 | VK_F11 + | VK_F12 => Some(KeyEvent::F((key_event.virtual_key_code - 111) as u8)), + VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => { // Modifier Keys (Ctrl, Shift) Support let key_state = &key_event.control_key_state; - if key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED) { - seq.push(53); - } else if key_state.has_state(SHIFT_PRESSED) { - seq.push(50); - } + let ctrl_pressed = key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED); + let shift_pressed = key_state.has_state(SHIFT_PRESSED); + + let event = match key_code { + VK_LEFT => { + if ctrl_pressed { + Some(KeyEvent::CtrlLeft) + } else if shift_pressed { + Some(KeyEvent::ShiftLeft) + } else { + Some(KeyEvent::Left) + } + } + VK_UP => { + if ctrl_pressed { + Some(KeyEvent::CtrlUp) + } else if shift_pressed { + Some(KeyEvent::ShiftUp) + } else { + Some(KeyEvent::Up) + } + } + VK_RIGHT => { + if ctrl_pressed { + Some(KeyEvent::CtrlRight) + } else if shift_pressed { + Some(KeyEvent::ShiftRight) + } else { + Some(KeyEvent::Right) + } + } + VK_DOWN => { + if ctrl_pressed { + Some(KeyEvent::CtrlDown) + } else if shift_pressed { + Some(KeyEvent::ShiftDown) + } else { + Some(KeyEvent::Down) + } + } + _ => None, + }; - seq.push([b'D', b'A', b'C', b'B'][(key_event.virtual_key_code - 0x25) as usize]); + event } VK_PRIOR | VK_NEXT => { - seq.push(b'\x1B'); - seq.push(b'['); - seq.push([b'5', b'6'][(key_event.virtual_key_code - 0x21) as usize]); - seq.push(b'~'); + if key_code == VK_PRIOR { + Some(KeyEvent::PageUp) + } else if key_code == VK_NEXT { + Some(KeyEvent::PageDown) + } else { + None + } } 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'); + if key_code == VK_HOME { + Some(KeyEvent::Home) + } else if key_code == VK_END { + Some(KeyEvent::End) } else { - seq.push(b'\t'); + None } } + VK_DELETE => Some(KeyEvent::Delete), + VK_INSERT => Some(KeyEvent::Insert), _ => { // Modifier Keys (Ctrl, Alt, Shift) Support - // NOTE (@imdaveho): test to check if characters outside of - // alphabet or alphanumerics are supported - let character = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) }; + let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) }; - if character < 255 { - let character = character as u8 as char; + if character_raw < 255 { + let character = character_raw as u8 as char; let key_state = &key_event.control_key_state; if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) { - seq.push(b'\x1B'); // If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command. // The pressed command is stored in `virtual_key_code`. let command = key_event.virtual_key_code as u8 as char; if (command).is_alphabetic() { - seq.push(command as u8); + Some(KeyEvent::Alt(command)) + } else { + None } } 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) { // Shift + key press, essentially the same as single key 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 { - seq.push(character as u8); + Some(KeyEvent::Char(character)) } + } else { + None } } } } -fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec) { +fn parse_mouse_event_record(event: &MouseEvent) -> Option { // NOTE (@imdaveho): xterm emulation takes the digits of the coords and passes them // individually as bytes into a buffer; the below cxbs and cybs replicates that and // mimicks the behavior; additionally, in xterm, mouse move is only handled when a // mouse button is held down (ie. mouse drag) - let cxbs: Vec = - (event.mouse_position.x + 1) /* windows positions are 0 based and ansi codes 1. */ - .to_string() - .chars() - .map(|d| d as u8) - .collect(); - let cybs: Vec = - (event.mouse_position.y + 1) /* windows positions are 0 based and ansi codes 1. */ - .to_string() - .chars() - .map(|d| d as u8) - .collect(); + let xpos = event.mouse_position.x + 1; + let ypos = event.mouse_position.y + 1; // TODO (@imdaveho): check if linux only provides coords for visible terminal window vs the total buffer @@ -327,76 +424,41 @@ fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec) { EventFlags::PressOrRelease => { // Single click match event.button_state { - ButtonState::Release => { - seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'm'); - } + ButtonState::Release => Some(super::MouseEvent::Release(xpos as u16, ypos as u16)), ButtonState::FromLeft1stButtonPressed => { // left click - seq.append(&mut vec![b'\x1B', b'[', b'<', b'0', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'M'); + Some(super::MouseEvent::Press( + MouseButton::Left, + xpos as u16, + ypos as u16, + )) } ButtonState::RightmostButtonPressed => { // right click - seq.append(&mut vec![b'\x1B', b'[', b'<', b'2', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'M'); + Some(super::MouseEvent::Press( + MouseButton::Right, + xpos as u16, + ypos as u16, + )) } ButtonState::FromLeft2ndButtonPressed => { // middle click - seq.append(&mut vec![b'\x1B', b'[', b'<', b'1', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'M'); + Some(super::MouseEvent::Press( + MouseButton::Middle, + xpos as u16, + ypos as u16, + )) } - _ => (), + _ => None, } } EventFlags::MouseMoved => { // Click + Move // NOTE (@imdaveho) only register when mouse is not released if event.button_state != ButtonState::Release { - seq.append(&mut vec![b'\x1B', b'[', b'<', b'3', b'2', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'M'); + Some(super::MouseEvent::Hold(xpos as u16, ypos as u16)) } else { - () + None } } EventFlags::MouseWheeled => { @@ -404,31 +466,21 @@ fn handle_mouse_event(event: &MouseEvent, seq: &mut Vec) { // NOTE (@imdaveho) from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str // if `button_state` is negative then the wheel was rotated backward, toward the user. if event.button_state != ButtonState::Negative { - seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'4', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'M'); + Some(super::MouseEvent::Press( + MouseButton::WheelUp, + xpos as u16, + ypos as u16, + )) } else { - seq.append(&mut vec![b'\x1B', b'[', b'<', b'6', b'5', b';']); - for x in cxbs { - seq.push(x); - } - seq.push(b';'); - for y in cybs { - seq.push(y); - } - seq.push(b';'); - seq.push(b'M'); + Some(super::MouseEvent::Press( + MouseButton::WheelDown, + xpos as u16, + ypos as u16, + )) } } - EventFlags::DoubleClick => (), // NOTE (@imdaveho): double click not supported by unix terminals - EventFlags::MouseHwheeled => (), // NOTE (@imdaveho): horizontal scroll not supported by unix terminals - // TODO: Handle Ctrl + Mouse, Alt + Mouse, etc. - }; + EventFlags::DoubleClick => None, // NOTE (@imdaveho): double click not supported by unix terminals + EventFlags::MouseHwheeled => None, // NOTE (@imdaveho): horizontal scroll not supported by unix terminals + // TODO: Handle Ctrl + Mouse, Alt + Mouse, etc. + } } diff --git a/crossterm_screen/Cargo.toml b/crossterm_screen/Cargo.toml index 15e046b08..795290b8d 100644 --- a/crossterm_screen/Cargo.toml +++ b/crossterm_screen/Cargo.toml @@ -16,4 +16,4 @@ crossterm_utils = {path="../crossterm_utils"} [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["minwindef", "wincon"] } -crossterm_winapi = "0.1.4" \ No newline at end of file +crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"} \ No newline at end of file diff --git a/crossterm_style/Cargo.toml b/crossterm_style/Cargo.toml index b4725b9e4..507b6671d 100644 --- a/crossterm_style/Cargo.toml +++ b/crossterm_style/Cargo.toml @@ -13,7 +13,7 @@ edition = "2018" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["wincon"] } -crossterm_winapi = "0.1.4" +crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"} [dependencies] crossterm_utils = {path="../crossterm_utils"} \ No newline at end of file diff --git a/crossterm_terminal/Cargo.toml b/crossterm_terminal/Cargo.toml index e38bbe945..5ca9ca6b4 100644 --- a/crossterm_terminal/Cargo.toml +++ b/crossterm_terminal/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" edition = "2018" [target.'cfg(windows)'.dependencies] -crossterm_winapi = "0.1.4" +crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"} [target.'cfg(unix)'.dependencies] libc = "0.2.51" diff --git a/crossterm_terminal/src/sys/winapi.rs b/crossterm_terminal/src/sys/winapi.rs index 4a2cedd1c..32ea08c03 100644 --- a/crossterm_terminal/src/sys/winapi.rs +++ b/crossterm_terminal/src/sys/winapi.rs @@ -9,7 +9,7 @@ pub fn exit() { pub fn get_terminal_size() -> (u16, u16) { if let Ok(buffer) = ScreenBuffer::current() { let size = buffer.info().unwrap().terminal_size(); - (size.width + 1, size.height + 1).into() + ((size.width + 1) as u16, (size.height + 1) as u16) } else { (0, 0) } diff --git a/crossterm_terminal/src/terminal/winapi_terminal.rs b/crossterm_terminal/src/terminal/winapi_terminal.rs index fe1f7cae7..c1f38232a 100644 --- a/crossterm_terminal/src/terminal/winapi_terminal.rs +++ b/crossterm_terminal/src/terminal/winapi_terminal.rs @@ -4,6 +4,7 @@ //! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions will use this implementation instead. use super::*; +use crate::sys::winapi::get_terminal_size; use crossterm_cursor::sys::winapi::Cursor; use crossterm_utils::{ErrorKind, Result}; use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; diff --git a/crossterm_utils/Cargo.toml b/crossterm_utils/Cargo.toml index 4d3174760..24087874b 100644 --- a/crossterm_utils/Cargo.toml +++ b/crossterm_utils/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.7", features = ["wincon"] } -crossterm_winapi = "0.1.4" +crossterm_winapi = { path="../crossterm_winapi", version = "0.1.4"} [target.'cfg(unix)'.dependencies] libc = "0.2.51" \ No newline at end of file diff --git a/crossterm_utils/src/command.rs b/crossterm_utils/src/command.rs index 5b30f1b57..820296100 100644 --- a/crossterm_utils/src/command.rs +++ b/crossterm_utils/src/command.rs @@ -1,11 +1,5 @@ -use crate::{execute, impl_display, queue, write_cout, ErrorKind, Result}; - -#[cfg(windows)] -use crate::supports_ansi; - +use crate::{execute, impl_display, queue, write_cout, Result}; use std::fmt::Display; -use std::fmt::{self, Error, Formatter}; -use std::intrinsics::write_bytes; use std::io::Write; /// A command is an action that can be performed on the terminal. diff --git a/crossterm_utils/src/error.rs b/crossterm_utils/src/error.rs index bf553e17e..80a3f0e7f 100644 --- a/crossterm_utils/src/error.rs +++ b/crossterm_utils/src/error.rs @@ -1,6 +1,5 @@ //! Module containing error handling logic. -use command::Command; use std::{ fmt::{self, Display, Formatter}, io, diff --git a/crossterm_utils/src/macros.rs b/crossterm_utils/src/macros.rs index 35a0f8319..2f8bcc823 100644 --- a/crossterm_utils/src/macros.rs +++ b/crossterm_utils/src/macros.rs @@ -68,7 +68,7 @@ macro_rules! write_cout { macro_rules! queue { ($write:expr, $($command:expr), *) => {{ - use $crate::{Command, write_cout}; + use $crate::Command; let mut error = None; $( @@ -104,7 +104,7 @@ macro_rules! queue { }else { Ok(()) } - }}; + }} } /// Execute one or more command(s) diff --git a/crossterm_winapi/src/console.rs b/crossterm_winapi/src/console.rs index bd3991a3a..8161aa6ef 100644 --- a/crossterm_winapi/src/console.rs +++ b/crossterm_winapi/src/console.rs @@ -2,6 +2,7 @@ use super::{is_true, Coord, Handle, HandleType, WindowPositions}; use std::io::{self, Error, Result}; use std::str; +use std::borrow::ToOwned; use winapi::ctypes::c_void; use winapi::shared::minwindef::DWORD; use winapi::shared::ntdef::NULL; @@ -13,6 +14,7 @@ use winapi::um::{ }, winnt::HANDLE, }; + use InputRecord; /// Could be used to do some basic things with the console. @@ -162,12 +164,26 @@ impl Console { Ok(utf8.as_bytes().len()) } - pub fn read_console_input(&self) -> Result<(u32, Vec)> { - let mut buf_len: DWORD = 0; - if !is_true(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) }) { - return Err(Error::last_os_error()); + pub fn read_single_input_event(&self) -> Result> { + let mut buf_len = self.number_of_console_input_events()?; + + // Fast-skipping all the code below if there is nothing to read at all + if buf_len == 0 { + return Ok(None); } + let mut buf: Vec = 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)> { + let mut buf_len = self.number_of_console_input_events()?; + // Fast-skipping all the code below if there is nothing to read at all if buf_len == 0 { return Ok((0, vec![])); @@ -176,19 +192,37 @@ impl Console { let mut buf: Vec = Vec::with_capacity(buf_len as usize); let mut size = 0; + self.read_input(&mut buf, buf_len, &mut size) + } + + pub fn number_of_console_input_events(&self) -> Result { + 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, + buf_len: u32, + bytes_written: &mut u32, + ) -> Result<(u32, Vec)> { if !is_true(unsafe { - ReadConsoleInputW(*self.handle, buf.as_mut_ptr(), buf_len, &mut size) + ReadConsoleInputW(*self.handle, buf.as_mut_ptr(), buf_len, bytes_written) }) { return Err(Error::last_os_error()); } else { unsafe { - buf.set_len(size as usize); + buf.set_len(buf_len as usize); } } Ok(( - size, - buf[..(size as usize)] + buf_len, + buf[..(buf_len as usize)] .iter() .map(|x| InputRecord::from(*x)) .collect::>(), diff --git a/crossterm_winapi/src/structs/input.rs b/crossterm_winapi/src/structs/input.rs index 2195c563b..fff209d17 100644 --- a/crossterm_winapi/src/structs/input.rs +++ b/crossterm_winapi/src/structs/input.rs @@ -165,6 +165,7 @@ impl From for EventFlags { /// These records can be read from the input buffer by using the `ReadConsoleInput` or `PeekConsoleInput` function, or written to the input buffer by using the `WriteConsoleInput` function. /// /// [Ms Docs](https://docs.microsoft.com/en-us/windows/console/input-record-str) +#[derive(Clone)] pub struct InputRecord { /// A handle to the type of input event and the event record stored in the Event member. pub event_type: InputEventType, diff --git a/examples/key_events.rs b/examples/key_events.rs index 71e201732..b3ebc799e 100644 --- a/examples/key_events.rs +++ b/examples/key_events.rs @@ -127,6 +127,6 @@ pub fn read_synchronously() { fn main() { // un-comment below and run with // `cargo run --example key_events`: - read_synchronously(); - // read_asynchronously(); + // read_synchronously(); + read_asynchronously(); } diff --git a/examples/style.rs b/examples/style.rs index ea524ae28..5d4e05681 100644 --- a/examples/style.rs +++ b/examples/style.rs @@ -410,4 +410,6 @@ pub fn reset_fg_and_bg() { println!("{}", Colored::Bg(Color::Reset)); } -fn main() {print_all_foreground_colors_with_method()} +fn main() { + print_all_foreground_colors_with_method() +}