From 71029c4a8719ce08a2fa487d9403610ec48acf42 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 11 Dec 2019 17:10:34 +0100 Subject: [PATCH] Moved some files around (#342) --- examples/stderr.rs | 5 +- src/{utils/functions.rs => ansi_support.rs} | 33 +- src/{utils => }/command.rs | 2 +- src/cursor.rs | 4 +- src/cursor/sys/unix.rs | 2 +- src/cursor/sys/windows.rs | 2 +- src/{utils => }/error.rs | 0 src/event/source/unix.rs | 5 +- src/event/source/windows.rs | 4 +- src/event/sys.rs | 4 +- src/event/sys/unix.rs | 793 +------------------- src/event/sys/unix/file_descriptor.rs | 81 ++ src/event/sys/unix/parse.rs | 706 +++++++++++++++++ src/event/sys/windows.rs | 279 +------ src/event/sys/windows/parse.rs | 188 +++++ src/event/sys/windows/poll.rs | 89 +++ src/lib.rs | 13 +- src/{utils => }/macros.rs | 4 +- src/style.rs | 2 +- src/style/sys/windows.rs | 2 +- src/terminal.rs | 4 +- src/terminal/sys/unix.rs | 12 +- src/terminal/sys/windows.rs | 2 +- src/utils.rs | 13 - src/utils/sys.rs | 5 - src/utils/sys/unix.rs | 13 - src/utils/sys/windows.rs | 32 - 27 files changed, 1149 insertions(+), 1150 deletions(-) rename src/{utils/functions.rs => ansi_support.rs} (62%) rename src/{utils => }/command.rs (99%) rename src/{utils => }/error.rs (100%) create mode 100644 src/event/sys/unix/file_descriptor.rs create mode 100644 src/event/sys/unix/parse.rs create mode 100644 src/event/sys/windows/parse.rs create mode 100644 src/event/sys/windows/poll.rs rename src/{utils => }/macros.rs (98%) delete mode 100644 src/utils.rs delete mode 100644 src/utils/sys.rs delete mode 100644 src/utils/sys/unix.rs delete mode 100644 src/utils/sys/windows.rs diff --git a/examples/stderr.rs b/examples/stderr.rs index f0cef54..87993c0 100644 --- a/examples/stderr.rs +++ b/examples/stderr.rs @@ -8,10 +8,11 @@ use std::io::{stderr, Write}; -use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::{ cursor::{Hide, MoveTo, Show}, - event, execute, queue, + event, + event::{Event, KeyCode, KeyEvent}, + execute, queue, style::Print, terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, Result, diff --git a/src/utils/functions.rs b/src/ansi_support.rs similarity index 62% rename from src/utils/functions.rs rename to src/ansi_support.rs index 2a806a2..026fba5 100644 --- a/src/utils/functions.rs +++ b/src/ansi_support.rs @@ -1,6 +1,37 @@ +use crossterm_winapi::ConsoleMode; +use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; + use lazy_static::lazy_static; -use super::sys::windows::set_virtual_terminal_processing; +use crate::Result; + +/// Toggle virtual terminal processing. +/// +/// This method attempts to toggle virtual terminal processing for this +/// console. If there was a problem toggling it, then an error returned. +/// On success, the caller may assume that toggling it was successful. +/// +/// When virtual terminal processing is enabled, characters emitted to the +/// console are parsed for VT100 and similar control character sequences +/// that control color and other similar operations. +pub(crate) fn set_virtual_terminal_processing(yes: bool) -> Result<()> { + let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + let console_mode = ConsoleMode::new()?; + let old_mode = console_mode.mode()?; + + let new_mode = if yes { + old_mode | mask + } else { + old_mode & !mask + }; + + if old_mode != new_mode { + console_mode.set_mode(new_mode)?; + } + + Ok(()) +} lazy_static! { static ref SUPPORTS_ANSI_ESCAPE_CODES: bool = { diff --git a/src/utils/command.rs b/src/command.rs similarity index 99% rename from src/utils/command.rs rename to src/command.rs index 049174f..590a3d8 100644 --- a/src/utils/command.rs +++ b/src/command.rs @@ -35,7 +35,7 @@ pub trait Command { /// can be found [here](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences). #[cfg(windows)] fn is_ansi_code_supported(&self) -> bool { - super::functions::supports_ansi() + super::ansi_support::supports_ansi() } } diff --git a/src/cursor.rs b/src/cursor.rs index b681c97..d898fd3 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -45,8 +45,8 @@ pub use sys::position; #[cfg(windows)] -use crate::utils::Result; -use crate::{impl_display, utils::Command}; +use crate::Result; +use crate::{impl_display, Command}; mod ansi; pub(crate) mod sys; diff --git a/src/cursor/sys/unix.rs b/src/cursor/sys/unix.rs index 9cd16d4..5468f30 100644 --- a/src/cursor/sys/unix.rs +++ b/src/cursor/sys/unix.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent}, terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled}, - utils::Result, + Result, }; /// Returns the cursor position (column, row). diff --git a/src/cursor/sys/windows.rs b/src/cursor/sys/windows.rs index bd965ab..6396565 100644 --- a/src/cursor/sys/windows.rs +++ b/src/cursor/sys/windows.rs @@ -10,7 +10,7 @@ use winapi::{ use lazy_static::lazy_static; -use crate::utils::Result; +use crate::Result; lazy_static! { static ref SAVED_CURSOR_POS: Mutex> = Mutex::new(None); diff --git a/src/utils/error.rs b/src/error.rs similarity index 100% rename from src/utils/error.rs rename to src/error.rs diff --git a/src/event/source/unix.rs b/src/event/source/unix.rs index c1a97d2..71d5b03 100644 --- a/src/event/source/unix.rs +++ b/src/event/source/unix.rs @@ -8,7 +8,10 @@ use crate::Result; use super::super::sys::Waker; use super::super::{ source::EventSource, - sys::unix::{parse_event, tty_fd, FileDesc}, + sys::unix::{ + file_descriptor::{tty_fd, FileDesc}, + parse::parse_event, + }, timeout::PollTimeout, Event, InternalEvent, }; diff --git a/src/event/source/windows.rs b/src/event/source/windows.rs index 0218b72..3491acf 100644 --- a/src/event/source/windows.rs +++ b/src/event/source/windows.rs @@ -2,13 +2,13 @@ use std::time::Duration; use crossterm_winapi::{Console, Handle, InputEventType, KeyEventRecord, MouseEvent}; -use crate::event::{sys::windows::WinApiPoll, Event}; +use crate::event::{sys::windows::poll::WinApiPoll, Event}; #[cfg(feature = "event-stream")] use super::super::sys::Waker; use super::super::{ source::EventSource, - sys::windows::{handle_key_event, handle_mouse_event}, + sys::windows::parse::{handle_key_event, handle_mouse_event}, timeout::PollTimeout, InternalEvent, Result, }; diff --git a/src/event/sys.rs b/src/event/sys.rs index 85ffa92..bd79307 100644 --- a/src/event/sys.rs +++ b/src/event/sys.rs @@ -1,7 +1,7 @@ #[cfg(all(unix, feature = "event-stream"))] -pub(crate) use unix::Waker; +pub(crate) use unix::waker::Waker; #[cfg(all(windows, feature = "event-stream"))] -pub(crate) use windows::Waker; +pub(crate) use windows::waker::Waker; #[cfg(unix)] pub(crate) mod unix; diff --git a/src/event/sys/unix.rs b/src/event/sys/unix.rs index b36bfe9..19321f6 100644 --- a/src/event/sys/unix.rs +++ b/src/event/sys/unix.rs @@ -1,792 +1,5 @@ -use std::{ - fs, io, - os::unix::io::{IntoRawFd, RawFd}, -}; - -use libc::size_t; - #[cfg(feature = "event-stream")] -pub(crate) use waker::Waker; +pub(crate) mod waker; -use crate::{ - event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent}, - ErrorKind, Result, -}; - -use super::super::InternalEvent; - -#[cfg(feature = "event-stream")] -mod waker; - -/// A file descriptor wrapper. -/// -/// It allows to retrieve raw file descriptor, write to the file descriptor and -/// mainly it closes the file descriptor once dropped. -pub struct FileDesc { - fd: RawFd, - close_on_drop: bool, -} - -impl FileDesc { - /// Constructs a new `FileDesc` with the given `RawFd`. - /// - /// # Arguments - /// - /// * `fd` - raw file descriptor - /// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped - pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc { - FileDesc { fd, close_on_drop } - } - - pub fn read(&self, buffer: &mut [u8], size: usize) -> Result { - let result = unsafe { - libc::read( - self.fd, - buffer.as_mut_ptr() as *mut libc::c_void, - size as size_t, - ) as isize - }; - - if result < 0 { - Err(ErrorKind::IoError(io::Error::last_os_error())) - } else { - Ok(result as usize) - } - } - - /// Returns the underlying file descriptor. - pub fn raw_fd(&self) -> RawFd { - self.fd - } -} - -impl Drop for FileDesc { - fn drop(&mut self) { - if self.close_on_drop { - // Note that errors are ignored when closing a file descriptor. The - // reason for this is that if an error occurs we don't actually know if - // the file descriptor was closed or not, and if we retried (for - // something like EINTR), we might close another valid file descriptor - // opened after we closed ours. - let _ = unsafe { libc::close(self.fd) }; - } - } -} - -/// Creates a file descriptor pointing to the standard input or `/dev/tty`. -pub fn tty_fd() -> Result { - let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { - (libc::STDIN_FILENO, false) - } else { - ( - fs::OpenOptions::new() - .read(true) - .write(true) - .open("/dev/tty")? - .into_raw_fd(), - true, - ) - }; - - Ok(FileDesc::new(fd, close_on_drop)) -} - -// -// Event parsing -// -// This code (& previous one) are kind of ugly. We have to think about this, -// because it's really not maintainable, no tests, etc. -// -// Every fn returns Result> -// -// Ok(None) -> wait for more bytes -// Err(_) -> failed to parse event, clear the buffer -// Ok(Some(event)) -> we have event, clear the buffer -// - -fn could_not_parse_event_error() -> ErrorKind { - ErrorKind::IoError(io::Error::new( - io::ErrorKind::Other, - "Could not parse an event.", - )) -} - -pub(crate) fn parse_event(buffer: &[u8], input_available: bool) -> Result> { - if buffer.is_empty() { - return Ok(None); - } - - match buffer[0] { - b'\x1B' => { - if buffer.len() == 1 { - if input_available { - // Possible Esc sequence - Ok(None) - } else { - Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))) - } - } else { - match buffer[1] { - b'O' => { - if buffer.len() == 2 { - Ok(None) - } else { - match buffer[2] { - // F1-F4 - val @ b'P'..=b'S' => Ok(Some(InternalEvent::Event(Event::Key( - KeyCode::F(1 + val - b'P').into(), - )))), - _ => Err(could_not_parse_event_error()), - } - } - } - b'[' => parse_csi(buffer), - b'\x1B' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))), - _ => parse_utf8_char(&buffer[1..]).map(|maybe_char| { - maybe_char - .map(KeyCode::Char) - .map(|code| KeyEvent::new(code, KeyModifiers::ALT)) - .map(Event::Key) - .map(InternalEvent::Event) - }), - } - } - } - b'\r' | b'\n' => Ok(Some(InternalEvent::Event(Event::Key( - KeyCode::Enter.into(), - )))), - b'\t' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into())))), - b'\x7F' => Ok(Some(InternalEvent::Event(Event::Key( - KeyCode::Backspace.into(), - )))), - c @ b'\x01'..=b'\x1A' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( - KeyCode::Char((c as u8 - 0x1 + b'a') as char), - KeyModifiers::CONTROL, - ))))), - c @ b'\x1C'..=b'\x1F' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( - KeyCode::Char((c as u8 - 0x1C + b'4') as char), - KeyModifiers::CONTROL, - ))))), - b'\0' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Null.into())))), - _ => parse_utf8_char(buffer).map(|maybe_char| { - maybe_char - .map(KeyCode::Char) - .map(Into::into) - .map(Event::Key) - .map(InternalEvent::Event) - }), - } -} - -pub(crate) fn parse_csi(buffer: &[u8]) -> Result> { - assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ - - if buffer.len() == 2 { - return Ok(None); - } - - let input_event = match buffer[2] { - b'[' => { - if buffer.len() == 3 { - None - } else { - match buffer[3] { - // NOTE (@imdaveho): cannot find when this occurs; - // having another '[' after ESC[ not a likely scenario - val @ b'A'..=b'E' => Some(Event::Key(KeyCode::F(1 + val - b'A').into())), - _ => return Err(could_not_parse_event_error()), - } - } - } - b'D' => Some(Event::Key(KeyCode::Left.into())), - b'C' => Some(Event::Key(KeyCode::Right.into())), - b'A' => Some(Event::Key(KeyCode::Up.into())), - b'B' => Some(Event::Key(KeyCode::Down.into())), - b'H' => Some(Event::Key(KeyCode::Home.into())), - b'F' => Some(Event::Key(KeyCode::End.into())), - b'Z' => Some(Event::Key(KeyCode::BackTab.into())), - b'M' => return parse_csi_x10_mouse(buffer), - b'<' => return parse_csi_xterm_mouse(buffer), - b'0'..=b'9' => { - // Numbered escape code. - if buffer.len() == 3 { - None - } else { - // The final byte of a CSI sequence can be in the range 64-126, so - // let's keep reading anything else. - let last_byte = *buffer.last().unwrap(); - if last_byte < 64 || last_byte > 126 { - None - } else { - match buffer[buffer.len() - 1] { - b'M' => return parse_csi_rxvt_mouse(buffer), - b'~' => return parse_csi_special_key_code(buffer), - b'R' => return parse_csi_cursor_position(buffer), - _ => return parse_csi_modifier_key_code(buffer), - } - } - } - } - _ => return Err(could_not_parse_event_error()), - }; - - Ok(input_event.map(InternalEvent::Event)) -} - -pub(crate) fn next_parsed(iter: &mut dyn Iterator) -> Result -where - T: std::str::FromStr, -{ - iter.next() - .ok_or_else(could_not_parse_event_error)? - .parse::() - .map_err(|_| could_not_parse_event_error()) -} - -pub(crate) fn parse_csi_cursor_position(buffer: &[u8]) -> Result> { - // ESC [ Cy ; Cx R - // Cy - cursor row number (starting from 1) - // Cx - cursor column number (starting from 1) - assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ - assert!(buffer.ends_with(&[b'R'])); - - let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) - .map_err(|_| could_not_parse_event_error())?; - - let mut split = s.split(';'); - - let y = next_parsed::(&mut split)? - 1; - let x = next_parsed::(&mut split)? - 1; - - Ok(Some(InternalEvent::CursorPosition(x, y))) -} - -pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result> { - assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ - - let modifier = buffer[buffer.len() - 2]; - let key = buffer[buffer.len() - 1]; - - let input_event = match (modifier, key) { - (53, 65) => Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL)), - (53, 66) => Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL)), - (53, 67) => Event::Key(KeyEvent::new(KeyCode::Right, KeyModifiers::CONTROL)), - (53, 68) => Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::CONTROL)), - (50, 65) => Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::SHIFT)), - (50, 66) => Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::SHIFT)), - (50, 67) => Event::Key(KeyEvent::new(KeyCode::Right, KeyModifiers::SHIFT)), - (50, 68) => Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT)), - _ => return Err(could_not_parse_event_error()), - }; - - Ok(Some(InternalEvent::Event(input_event))) -} - -pub(crate) fn parse_csi_special_key_code(buffer: &[u8]) -> Result> { - assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ - assert!(buffer.ends_with(&[b'~'])); - - let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) - .map_err(|_| could_not_parse_event_error())?; - let mut split = s.split(';'); - - // This CSI sequence can be a list of semicolon-separated numbers. - let first = next_parsed::(&mut split)?; - - if next_parsed::(&mut split).is_ok() { - // TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete) - return Err(could_not_parse_event_error()); - } - - let input_event = match first { - 1 | 7 => Event::Key(KeyCode::Home.into()), - 2 => Event::Key(KeyCode::Insert.into()), - 3 => Event::Key(KeyCode::Delete.into()), - 4 | 8 => Event::Key(KeyCode::End.into()), - 5 => Event::Key(KeyCode::PageUp.into()), - 6 => Event::Key(KeyCode::PageDown.into()), - v @ 11..=15 => Event::Key(KeyCode::F(v - 10).into()), - v @ 17..=21 => Event::Key(KeyCode::F(v - 11).into()), - v @ 23..=24 => Event::Key(KeyCode::F(v - 12).into()), - _ => return Err(could_not_parse_event_error()), - }; - - Ok(Some(InternalEvent::Event(input_event))) -} - -pub(crate) fn parse_csi_rxvt_mouse(buffer: &[u8]) -> Result> { - // rxvt mouse encoding: - // ESC [ Cb ; Cx ; Cy ; M - - assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ - assert!(buffer.ends_with(&[b'M'])); - - let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) - .map_err(|_| could_not_parse_event_error())?; - let mut split = s.split(';'); - - let cb = next_parsed::(&mut split)?; - let cx = next_parsed::(&mut split)? - 1; - let cy = next_parsed::(&mut split)? - 1; - - let mut modifiers = KeyModifiers::empty(); - - if cb & 0b0000_0100 == 0b0000_0100 { - modifiers |= KeyModifiers::SHIFT; - } - - if cb & 0b0000_1000 == 0b0000_1000 { - modifiers |= KeyModifiers::ALT; - } - - if cb & 0b0001_0000 == 0b0001_0000 { - modifiers |= KeyModifiers::CONTROL; - } - - let event = if cb & 0b0110_0000 == 0b0110_0000 { - if cb & 0b0000_0001 == 0b0000_0001 { - MouseEvent::ScrollDown(cx, cy, modifiers) - } else { - MouseEvent::ScrollUp(cx, cy, modifiers) - } - } else { - let drag = cb & 0b0100_0000 == 0b0100_0000; - - match (cb & 0b0000_0011, drag) { - (0b0000_0000, false) => MouseEvent::Down(MouseButton::Left, cx, cy, modifiers), - (0b0000_0010, false) => MouseEvent::Down(MouseButton::Right, cx, cy, modifiers), - (0b0000_0001, false) => MouseEvent::Down(MouseButton::Middle, cx, cy, modifiers), - - (0b0000_0000, true) => MouseEvent::Drag(MouseButton::Left, cx, cy, modifiers), - (0b0000_0010, true) => MouseEvent::Drag(MouseButton::Right, cx, cy, modifiers), - (0b0000_0001, true) => MouseEvent::Drag(MouseButton::Middle, cx, cy, modifiers), - - (0b0000_0011, false) => MouseEvent::Up(MouseButton::Left, cx, cy, modifiers), - - _ => return Err(could_not_parse_event_error()), - } - }; - - Ok(Some(InternalEvent::Event(Event::Mouse(event)))) -} - -pub(crate) fn parse_csi_x10_mouse(buffer: &[u8]) -> Result> { - // X10 emulation mouse encoding: ESC [ M CB Cx Cy (6 characters only). - // NOTE (@imdaveho): cannot find documentation on this - - assert!(buffer.starts_with(&[b'\x1B', b'[', b'M'])); // ESC [ M - - if buffer.len() < 6 { - return Ok(None); - } - - let cb = buffer[3] - 0x30; - // See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking - // The upper left character position on the terminal is denoted as 1,1. - // Subtract 1 to keep it synced with cursor - let cx = u16::from(buffer[4].saturating_sub(32)) - 1; - let cy = u16::from(buffer[5].saturating_sub(32)) - 1; - - let mut modifiers = KeyModifiers::empty(); - - if cb & 0b0000_0100 == 0b0000_0100 { - modifiers |= KeyModifiers::SHIFT; - } - - if cb & 0b0000_1000 == 0b0000_1000 { - modifiers |= KeyModifiers::ALT; - } - - if cb & 0b0001_0000 == 0b0001_0000 { - modifiers |= KeyModifiers::CONTROL; - } - - let mouse_input_event = match cb & 0b0000_0011 { - 0 => { - if cb & 0b0100_0000 == 0b0100_0000 { - MouseEvent::ScrollUp(cx, cy, modifiers) - } else { - MouseEvent::Down(MouseButton::Left, cx, cy, modifiers) - } - } - 1 => { - if cb & 0b0100_0000 == 0b0100_0000 { - MouseEvent::ScrollDown(cx, cy, modifiers) - } else { - MouseEvent::Down(MouseButton::Middle, cx, cy, modifiers) - } - } - 2 => MouseEvent::Down(MouseButton::Right, cx, cy, modifiers), - 3 => MouseEvent::Up(MouseButton::Left, cx, cy, modifiers), - _ => return Err(could_not_parse_event_error()), - }; - - Ok(Some(InternalEvent::Event(Event::Mouse(mouse_input_event)))) -} - -pub(crate) fn parse_csi_xterm_mouse(buffer: &[u8]) -> Result> { - // ESC [ < Cb ; Cx ; Cy (;) (M or m) - - assert!(buffer.starts_with(&[b'\x1B', b'[', b'<'])); // ESC [ < - - if !buffer.ends_with(&[b'm']) && !buffer.ends_with(&[b'M']) { - return Ok(None); - } - - let s = std::str::from_utf8(&buffer[3..buffer.len() - 1]) - .map_err(|_| could_not_parse_event_error())?; - let mut split = s.split(';'); - - let cb = next_parsed::(&mut split)?; - - // See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking - // The upper left character position on the terminal is denoted as 1,1. - // Subtract 1 to keep it synced with cursor - let cx = next_parsed::(&mut split)? - 1; - let cy = next_parsed::(&mut split)? - 1; - - let mut modifiers = KeyModifiers::empty(); - - if cb & 0b0000_0100 == 0b0000_0100 { - modifiers |= KeyModifiers::SHIFT; - } - - if cb & 0b0000_1000 == 0b0000_1000 { - modifiers |= KeyModifiers::ALT; - } - - if cb & 0b0001_0000 == 0b0001_0000 { - modifiers |= KeyModifiers::CONTROL; - } - - let event = if cb & 0b0100_0000 == 0b0100_0000 { - if cb & 0b0000_0001 == 0b0000_0001 { - MouseEvent::ScrollDown(cx, cy, modifiers) - } else { - MouseEvent::ScrollUp(cx, cy, modifiers) - } - } else { - let up = match buffer.last().unwrap() { - b'm' => true, - b'M' => false, - _ => return Err(could_not_parse_event_error()), - }; - - let drag = cb & 0b0010_0000 == 0b0010_0000; - - match (cb & 0b0000_0011, up, drag) { - (0, true, _) => MouseEvent::Up(MouseButton::Left, cx, cy, modifiers), - (0, false, false) => MouseEvent::Down(MouseButton::Left, cx, cy, modifiers), - (0, false, true) => MouseEvent::Drag(MouseButton::Left, cx, cy, modifiers), - (1, true, _) => MouseEvent::Up(MouseButton::Middle, cx, cy, modifiers), - (1, false, false) => MouseEvent::Down(MouseButton::Middle, cx, cy, modifiers), - (1, false, true) => MouseEvent::Drag(MouseButton::Middle, cx, cy, modifiers), - (2, true, _) => MouseEvent::Up(MouseButton::Right, cx, cy, modifiers), - (2, false, false) => MouseEvent::Down(MouseButton::Right, cx, cy, modifiers), - (2, false, true) => MouseEvent::Drag(MouseButton::Right, cx, cy, modifiers), - _ => return Err(could_not_parse_event_error()), - } - }; - - Ok(Some(InternalEvent::Event(Event::Mouse(event)))) -} - -pub(crate) fn parse_utf8_char(buffer: &[u8]) -> Result> { - match std::str::from_utf8(buffer) { - Ok(s) => { - let ch = s.chars().next().ok_or_else(could_not_parse_event_error)?; - - Ok(Some(ch)) - } - Err(_) => { - // from_utf8 failed, but we have to check if we need more bytes for code point - // and if all the bytes we have no are valid - - let required_bytes = match buffer[0] { - // https://en.wikipedia.org/wiki/UTF-8#Description - (0x00..=0x7F) => 1, // 0xxxxxxx - (0xC0..=0xDF) => 2, // 110xxxxx 10xxxxxx - (0xE0..=0xEF) => 3, // 1110xxxx 10xxxxxx 10xxxxxx - (0xF0..=0xF7) => 4, // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - (0x80..=0xBF) | (0xF8..=0xFF) => return Err(could_not_parse_event_error()), - }; - - // More than 1 byte, check them for 10xxxxxx pattern - if required_bytes > 1 && buffer.len() > 1 { - for byte in &buffer[1..] { - if byte & !0b0011_1111 != 0b1000_0000 { - return Err(could_not_parse_event_error()); - } - } - } - - if buffer.len() < required_bytes { - // All bytes looks good so far, but we need more of them - Ok(None) - } else { - Err(could_not_parse_event_error()) - } - } - } -} - -#[cfg(test)] -mod tests { - use crate::event::{KeyModifiers, MouseButton, MouseEvent}; - - use super::*; - - #[test] - fn test_esc_key() { - assert_eq!( - parse_event("\x1B".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into()))), - ); - } - - #[test] - fn test_possible_esc_sequence() { - assert_eq!(parse_event("\x1B".as_bytes(), true).unwrap(), None,); - } - - #[test] - fn test_alt_key() { - assert_eq!( - parse_event("\x1Bc".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyEvent::new( - KeyCode::Char('c'), - KeyModifiers::ALT - )))), - ); - } - - #[test] - fn test_parse_event_subsequent_calls() { - // The main purpose of this test is to check if we're passing - // correct slice to other parse_ functions. - - // parse_csi_cursor_position - assert_eq!( - parse_event("\x1B[20;10R".as_bytes(), false).unwrap(), - Some(InternalEvent::CursorPosition(9, 19)) - ); - - // parse_csi - assert_eq!( - parse_event("\x1B[D".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))), - ); - - // parse_csi_modifier_key_code - assert_eq!( - parse_event("\x1B[2D".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyEvent::new( - KeyCode::Left, - KeyModifiers::SHIFT - )))) - ); - - // parse_csi_special_key_code - assert_eq!( - parse_event("\x1B[3~".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Delete.into()))), - ); - - // parse_csi_rxvt_mouse - assert_eq!( - parse_event("\x1B[32;30;40;M".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 29, - 39, - KeyModifiers::empty(), - )))) - ); - - // parse_csi_x10_mouse - assert_eq!( - parse_event("\x1B[M0\x60\x70".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 63, - 79, - KeyModifiers::empty(), - )))) - ); - - // parse_csi_xterm_mouse - assert_eq!( - parse_event("\x1B[<0;20;10;M".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 19, - 9, - KeyModifiers::empty(), - )))) - ); - - // parse_utf8_char - assert_eq!( - parse_event("Ž".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Char('Ž').into()))), - ); - } - - #[test] - fn test_parse_event() { - assert_eq!( - parse_event("\t".as_bytes(), false).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into()))), - ); - } - - #[test] - fn test_parse_csi_cursor_position() { - assert_eq!( - parse_csi_cursor_position("\x1B[20;10R".as_bytes()).unwrap(), - Some(InternalEvent::CursorPosition(9, 19)) - ); - } - - #[test] - fn test_parse_csi() { - assert_eq!( - parse_csi("\x1B[D".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))), - ); - } - - #[test] - fn test_parse_csi_modifier_key_code() { - assert_eq!( - parse_csi_modifier_key_code("\x1B[2D".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyEvent::new( - KeyCode::Left, - KeyModifiers::SHIFT - )))), - ); - } - - #[test] - fn test_parse_csi_special_key_code() { - assert_eq!( - parse_csi_special_key_code("\x1B[3~".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Key(KeyCode::Delete.into()))), - ); - } - - #[test] - fn test_parse_csi_special_key_code_multiple_values_not_supported() { - assert!(parse_csi_special_key_code("\x1B[3;2~".as_bytes()).is_err()); - } - - #[test] - fn test_parse_csi_rxvt_mouse() { - assert_eq!( - parse_csi_rxvt_mouse("\x1B[32;30;40;M".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 29, - 39, - KeyModifiers::empty(), - )))) - ); - } - - #[test] - fn test_parse_csi_x10_mouse() { - assert_eq!( - parse_csi_x10_mouse("\x1B[M0\x60\x70".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 63, - 79, - KeyModifiers::empty(), - )))) - ); - } - - #[test] - fn test_parse_csi_xterm_mouse() { - assert_eq!( - parse_csi_xterm_mouse("\x1B[<0;20;10;M".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 19, - 9, - KeyModifiers::empty(), - )))) - ); - assert_eq!( - parse_csi_xterm_mouse("\x1B[<0;20;10M".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( - MouseButton::Left, - 19, - 9, - KeyModifiers::empty(), - )))) - ); - assert_eq!( - parse_csi_xterm_mouse("\x1B[<0;20;10;m".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Up( - MouseButton::Left, - 19, - 9, - KeyModifiers::empty(), - )))) - ); - assert_eq!( - parse_csi_xterm_mouse("\x1B[<0;20;10m".as_bytes()).unwrap(), - Some(InternalEvent::Event(Event::Mouse(MouseEvent::Up( - MouseButton::Left, - 19, - 9, - KeyModifiers::empty(), - )))) - ); - } - - #[test] - fn test_utf8() { - // https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805 - - // 'Valid ASCII' => "a", - assert_eq!(parse_utf8_char("a".as_bytes()).unwrap(), Some('a'),); - - // 'Valid 2 Octet Sequence' => "\xc3\xb1", - assert_eq!(parse_utf8_char(&[0xC3, 0xB1]).unwrap(), Some('ñ'),); - - // 'Invalid 2 Octet Sequence' => "\xc3\x28", - assert!(parse_utf8_char(&[0xC3, 0x28]).is_err()); - - // 'Invalid Sequence Identifier' => "\xa0\xa1", - assert!(parse_utf8_char(&[0xA0, 0xA1]).is_err()); - - // 'Valid 3 Octet Sequence' => "\xe2\x82\xa1", - assert_eq!( - parse_utf8_char(&[0xE2, 0x81, 0xA1]).unwrap(), - Some('\u{2061}'), - ); - - // 'Invalid 3 Octet Sequence (in 2nd Octet)' => "\xe2\x28\xa1", - assert!(parse_utf8_char(&[0xE2, 0x28, 0xA1]).is_err()); - - // 'Invalid 3 Octet Sequence (in 3rd Octet)' => "\xe2\x82\x28", - assert!(parse_utf8_char(&[0xE2, 0x82, 0x28]).is_err()); - - // 'Valid 4 Octet Sequence' => "\xf0\x90\x8c\xbc", - assert_eq!( - parse_utf8_char(&[0xF0, 0x90, 0x8C, 0xBC]).unwrap(), - Some('𐌼'), - ); - - // 'Invalid 4 Octet Sequence (in 2nd Octet)' => "\xf0\x28\x8c\xbc", - assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0xBC]).is_err()); - - // 'Invalid 4 Octet Sequence (in 3rd Octet)' => "\xf0\x90\x28\xbc", - assert!(parse_utf8_char(&[0xF0, 0x90, 0x28, 0xBC]).is_err()); - - // 'Invalid 4 Octet Sequence (in 4th Octet)' => "\xf0\x28\x8c\x28", - assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0x28]).is_err()); - } -} +pub(crate) mod file_descriptor; +pub(crate) mod parse; diff --git a/src/event/sys/unix/file_descriptor.rs b/src/event/sys/unix/file_descriptor.rs new file mode 100644 index 0000000..239af6e --- /dev/null +++ b/src/event/sys/unix/file_descriptor.rs @@ -0,0 +1,81 @@ +use std::{ + fs, io, + os::unix::io::{IntoRawFd, RawFd}, +}; + +use libc::size_t; + +use crate::{ErrorKind, Result}; + +/// A file descriptor wrapper. +/// +/// It allows to retrieve raw file descriptor, write to the file descriptor and +/// mainly it closes the file descriptor once dropped. +pub struct FileDesc { + fd: RawFd, + close_on_drop: bool, +} + +impl FileDesc { + /// Constructs a new `FileDesc` with the given `RawFd`. + /// + /// # Arguments + /// + /// * `fd` - raw file descriptor + /// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped + pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc { + FileDesc { fd, close_on_drop } + } + + pub fn read(&self, buffer: &mut [u8], size: usize) -> Result { + let result = unsafe { + libc::read( + self.fd, + buffer.as_mut_ptr() as *mut libc::c_void, + size as size_t, + ) as isize + }; + + if result < 0 { + Err(ErrorKind::IoError(io::Error::last_os_error())) + } else { + Ok(result as usize) + } + } + + /// Returns the underlying file descriptor. + pub fn raw_fd(&self) -> RawFd { + self.fd + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + if self.close_on_drop { + // Note that errors are ignored when closing a file descriptor. The + // reason for this is that if an error occurs we don't actually know if + // the file descriptor was closed or not, and if we retried (for + // something like EINTR), we might close another valid file descriptor + // opened after we closed ours. + let _ = unsafe { libc::close(self.fd) }; + } + } +} + +/// Creates a file descriptor pointing to the standard input or `/dev/tty`. +pub fn tty_fd() -> Result { + let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { + (libc::STDIN_FILENO, false) + } else { + ( + fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty")? + .into_raw_fd(), + true, + ) + }; + + Ok(FileDesc::new(fd, close_on_drop)) +} diff --git a/src/event/sys/unix/parse.rs b/src/event/sys/unix/parse.rs new file mode 100644 index 0000000..6318466 --- /dev/null +++ b/src/event/sys/unix/parse.rs @@ -0,0 +1,706 @@ +use crate::{ + event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent}, + ErrorKind, Result, +}; + +use super::super::super::InternalEvent; +use std::io; + +// Event parsing +// +// This code (& previous one) are kind of ugly. We have to think about this, +// because it's really not maintainable, no tests, etc. +// +// Every fn returns Result> +// +// Ok(None) -> wait for more bytes +// Err(_) -> failed to parse event, clear the buffer +// Ok(Some(event)) -> we have event, clear the buffer +// + +fn could_not_parse_event_error() -> ErrorKind { + ErrorKind::IoError(io::Error::new( + io::ErrorKind::Other, + "Could not parse an event.", + )) +} + +pub(crate) fn parse_event(buffer: &[u8], input_available: bool) -> Result> { + if buffer.is_empty() { + return Ok(None); + } + + match buffer[0] { + b'\x1B' => { + if buffer.len() == 1 { + if input_available { + // Possible Esc sequence + Ok(None) + } else { + Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))) + } + } else { + match buffer[1] { + b'O' => { + if buffer.len() == 2 { + Ok(None) + } else { + match buffer[2] { + // F1-F4 + val @ b'P'..=b'S' => Ok(Some(InternalEvent::Event(Event::Key( + KeyCode::F(1 + val - b'P').into(), + )))), + _ => Err(could_not_parse_event_error()), + } + } + } + b'[' => parse_csi(buffer), + b'\x1B' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))), + _ => parse_utf8_char(&buffer[1..]).map(|maybe_char| { + maybe_char + .map(KeyCode::Char) + .map(|code| KeyEvent::new(code, KeyModifiers::ALT)) + .map(Event::Key) + .map(InternalEvent::Event) + }), + } + } + } + b'\r' | b'\n' => Ok(Some(InternalEvent::Event(Event::Key( + KeyCode::Enter.into(), + )))), + b'\t' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into())))), + b'\x7F' => Ok(Some(InternalEvent::Event(Event::Key( + KeyCode::Backspace.into(), + )))), + c @ b'\x01'..=b'\x1A' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Char((c as u8 - 0x1 + b'a') as char), + KeyModifiers::CONTROL, + ))))), + c @ b'\x1C'..=b'\x1F' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Char((c as u8 - 0x1C + b'4') as char), + KeyModifiers::CONTROL, + ))))), + b'\0' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Null.into())))), + _ => parse_utf8_char(buffer).map(|maybe_char| { + maybe_char + .map(KeyCode::Char) + .map(Into::into) + .map(Event::Key) + .map(InternalEvent::Event) + }), + } +} + +pub(crate) fn parse_csi(buffer: &[u8]) -> Result> { + assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ + + if buffer.len() == 2 { + return Ok(None); + } + + let input_event = match buffer[2] { + b'[' => { + if buffer.len() == 3 { + None + } else { + match buffer[3] { + // NOTE (@imdaveho): cannot find when this occurs; + // having another '[' after ESC[ not a likely scenario + val @ b'A'..=b'E' => Some(Event::Key(KeyCode::F(1 + val - b'A').into())), + _ => return Err(could_not_parse_event_error()), + } + } + } + b'D' => Some(Event::Key(KeyCode::Left.into())), + b'C' => Some(Event::Key(KeyCode::Right.into())), + b'A' => Some(Event::Key(KeyCode::Up.into())), + b'B' => Some(Event::Key(KeyCode::Down.into())), + b'H' => Some(Event::Key(KeyCode::Home.into())), + b'F' => Some(Event::Key(KeyCode::End.into())), + b'Z' => Some(Event::Key(KeyCode::BackTab.into())), + b'M' => return parse_csi_x10_mouse(buffer), + b'<' => return parse_csi_xterm_mouse(buffer), + b'0'..=b'9' => { + // Numbered escape code. + if buffer.len() == 3 { + None + } else { + // The final byte of a CSI sequence can be in the range 64-126, so + // let's keep reading anything else. + let last_byte = *buffer.last().unwrap(); + if last_byte < 64 || last_byte > 126 { + None + } else { + match buffer[buffer.len() - 1] { + b'M' => return parse_csi_rxvt_mouse(buffer), + b'~' => return parse_csi_special_key_code(buffer), + b'R' => return parse_csi_cursor_position(buffer), + _ => return parse_csi_modifier_key_code(buffer), + } + } + } + } + _ => return Err(could_not_parse_event_error()), + }; + + Ok(input_event.map(InternalEvent::Event)) +} + +pub(crate) fn next_parsed(iter: &mut dyn Iterator) -> Result +where + T: std::str::FromStr, +{ + iter.next() + .ok_or_else(could_not_parse_event_error)? + .parse::() + .map_err(|_| could_not_parse_event_error()) +} + +pub(crate) fn parse_csi_cursor_position(buffer: &[u8]) -> Result> { + // ESC [ Cy ; Cx R + // Cy - cursor row number (starting from 1) + // Cx - cursor column number (starting from 1) + assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ + assert!(buffer.ends_with(&[b'R'])); + + let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) + .map_err(|_| could_not_parse_event_error())?; + + let mut split = s.split(';'); + + let y = next_parsed::(&mut split)? - 1; + let x = next_parsed::(&mut split)? - 1; + + Ok(Some(InternalEvent::CursorPosition(x, y))) +} + +pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result> { + assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ + + let modifier = buffer[buffer.len() - 2]; + let key = buffer[buffer.len() - 1]; + + let input_event = match (modifier, key) { + (53, 65) => Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::CONTROL)), + (53, 66) => Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::CONTROL)), + (53, 67) => Event::Key(KeyEvent::new(KeyCode::Right, KeyModifiers::CONTROL)), + (53, 68) => Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::CONTROL)), + (50, 65) => Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::SHIFT)), + (50, 66) => Event::Key(KeyEvent::new(KeyCode::Down, KeyModifiers::SHIFT)), + (50, 67) => Event::Key(KeyEvent::new(KeyCode::Right, KeyModifiers::SHIFT)), + (50, 68) => Event::Key(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT)), + _ => return Err(could_not_parse_event_error()), + }; + + Ok(Some(InternalEvent::Event(input_event))) +} + +pub(crate) fn parse_csi_special_key_code(buffer: &[u8]) -> Result> { + assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ + assert!(buffer.ends_with(&[b'~'])); + + let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) + .map_err(|_| could_not_parse_event_error())?; + let mut split = s.split(';'); + + // This CSI sequence can be a list of semicolon-separated numbers. + let first = next_parsed::(&mut split)?; + + if next_parsed::(&mut split).is_ok() { + // TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete) + return Err(could_not_parse_event_error()); + } + + let input_event = match first { + 1 | 7 => Event::Key(KeyCode::Home.into()), + 2 => Event::Key(KeyCode::Insert.into()), + 3 => Event::Key(KeyCode::Delete.into()), + 4 | 8 => Event::Key(KeyCode::End.into()), + 5 => Event::Key(KeyCode::PageUp.into()), + 6 => Event::Key(KeyCode::PageDown.into()), + v @ 11..=15 => Event::Key(KeyCode::F(v - 10).into()), + v @ 17..=21 => Event::Key(KeyCode::F(v - 11).into()), + v @ 23..=24 => Event::Key(KeyCode::F(v - 12).into()), + _ => return Err(could_not_parse_event_error()), + }; + + Ok(Some(InternalEvent::Event(input_event))) +} + +pub(crate) fn parse_csi_rxvt_mouse(buffer: &[u8]) -> Result> { + // rxvt mouse encoding: + // ESC [ Cb ; Cx ; Cy ; M + + assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ + assert!(buffer.ends_with(&[b'M'])); + + let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) + .map_err(|_| could_not_parse_event_error())?; + let mut split = s.split(';'); + + let cb = next_parsed::(&mut split)?; + let cx = next_parsed::(&mut split)? - 1; + let cy = next_parsed::(&mut split)? - 1; + + let mut modifiers = KeyModifiers::empty(); + + if cb & 0b0000_0100 == 0b0000_0100 { + modifiers |= KeyModifiers::SHIFT; + } + + if cb & 0b0000_1000 == 0b0000_1000 { + modifiers |= KeyModifiers::ALT; + } + + if cb & 0b0001_0000 == 0b0001_0000 { + modifiers |= KeyModifiers::CONTROL; + } + + let event = if cb & 0b0110_0000 == 0b0110_0000 { + if cb & 0b0000_0001 == 0b0000_0001 { + MouseEvent::ScrollDown(cx, cy, modifiers) + } else { + MouseEvent::ScrollUp(cx, cy, modifiers) + } + } else { + let drag = cb & 0b0100_0000 == 0b0100_0000; + + match (cb & 0b0000_0011, drag) { + (0b0000_0000, false) => MouseEvent::Down(MouseButton::Left, cx, cy, modifiers), + (0b0000_0010, false) => MouseEvent::Down(MouseButton::Right, cx, cy, modifiers), + (0b0000_0001, false) => MouseEvent::Down(MouseButton::Middle, cx, cy, modifiers), + + (0b0000_0000, true) => MouseEvent::Drag(MouseButton::Left, cx, cy, modifiers), + (0b0000_0010, true) => MouseEvent::Drag(MouseButton::Right, cx, cy, modifiers), + (0b0000_0001, true) => MouseEvent::Drag(MouseButton::Middle, cx, cy, modifiers), + + (0b0000_0011, false) => MouseEvent::Up(MouseButton::Left, cx, cy, modifiers), + + _ => return Err(could_not_parse_event_error()), + } + }; + + Ok(Some(InternalEvent::Event(Event::Mouse(event)))) +} + +pub(crate) fn parse_csi_x10_mouse(buffer: &[u8]) -> Result> { + // X10 emulation mouse encoding: ESC [ M CB Cx Cy (6 characters only). + // NOTE (@imdaveho): cannot find documentation on this + + assert!(buffer.starts_with(&[b'\x1B', b'[', b'M'])); // ESC [ M + + if buffer.len() < 6 { + return Ok(None); + } + + let cb = buffer[3] - 0x30; + // See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking + // The upper left character position on the terminal is denoted as 1,1. + // Subtract 1 to keep it synced with cursor + let cx = u16::from(buffer[4].saturating_sub(32)) - 1; + let cy = u16::from(buffer[5].saturating_sub(32)) - 1; + + let mut modifiers = KeyModifiers::empty(); + + if cb & 0b0000_0100 == 0b0000_0100 { + modifiers |= KeyModifiers::SHIFT; + } + + if cb & 0b0000_1000 == 0b0000_1000 { + modifiers |= KeyModifiers::ALT; + } + + if cb & 0b0001_0000 == 0b0001_0000 { + modifiers |= KeyModifiers::CONTROL; + } + + let mouse_input_event = match cb & 0b0000_0011 { + 0 => { + if cb & 0b0100_0000 == 0b0100_0000 { + MouseEvent::ScrollUp(cx, cy, modifiers) + } else { + MouseEvent::Down(MouseButton::Left, cx, cy, modifiers) + } + } + 1 => { + if cb & 0b0100_0000 == 0b0100_0000 { + MouseEvent::ScrollDown(cx, cy, modifiers) + } else { + MouseEvent::Down(MouseButton::Middle, cx, cy, modifiers) + } + } + 2 => MouseEvent::Down(MouseButton::Right, cx, cy, modifiers), + 3 => MouseEvent::Up(MouseButton::Left, cx, cy, modifiers), + _ => return Err(could_not_parse_event_error()), + }; + + Ok(Some(InternalEvent::Event(Event::Mouse(mouse_input_event)))) +} + +pub(crate) fn parse_csi_xterm_mouse(buffer: &[u8]) -> Result> { + // ESC [ < Cb ; Cx ; Cy (;) (M or m) + + assert!(buffer.starts_with(&[b'\x1B', b'[', b'<'])); // ESC [ < + + if !buffer.ends_with(&[b'm']) && !buffer.ends_with(&[b'M']) { + return Ok(None); + } + + let s = std::str::from_utf8(&buffer[3..buffer.len() - 1]) + .map_err(|_| could_not_parse_event_error())?; + let mut split = s.split(';'); + + let cb = next_parsed::(&mut split)?; + + // See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking + // The upper left character position on the terminal is denoted as 1,1. + // Subtract 1 to keep it synced with cursor + let cx = next_parsed::(&mut split)? - 1; + let cy = next_parsed::(&mut split)? - 1; + + let mut modifiers = KeyModifiers::empty(); + + if cb & 0b0000_0100 == 0b0000_0100 { + modifiers |= KeyModifiers::SHIFT; + } + + if cb & 0b0000_1000 == 0b0000_1000 { + modifiers |= KeyModifiers::ALT; + } + + if cb & 0b0001_0000 == 0b0001_0000 { + modifiers |= KeyModifiers::CONTROL; + } + + let event = if cb & 0b0100_0000 == 0b0100_0000 { + if cb & 0b0000_0001 == 0b0000_0001 { + MouseEvent::ScrollDown(cx, cy, modifiers) + } else { + MouseEvent::ScrollUp(cx, cy, modifiers) + } + } else { + let up = match buffer.last().unwrap() { + b'm' => true, + b'M' => false, + _ => return Err(could_not_parse_event_error()), + }; + + let drag = cb & 0b0010_0000 == 0b0010_0000; + + match (cb & 0b0000_0011, up, drag) { + (0, true, _) => MouseEvent::Up(MouseButton::Left, cx, cy, modifiers), + (0, false, false) => MouseEvent::Down(MouseButton::Left, cx, cy, modifiers), + (0, false, true) => MouseEvent::Drag(MouseButton::Left, cx, cy, modifiers), + (1, true, _) => MouseEvent::Up(MouseButton::Middle, cx, cy, modifiers), + (1, false, false) => MouseEvent::Down(MouseButton::Middle, cx, cy, modifiers), + (1, false, true) => MouseEvent::Drag(MouseButton::Middle, cx, cy, modifiers), + (2, true, _) => MouseEvent::Up(MouseButton::Right, cx, cy, modifiers), + (2, false, false) => MouseEvent::Down(MouseButton::Right, cx, cy, modifiers), + (2, false, true) => MouseEvent::Drag(MouseButton::Right, cx, cy, modifiers), + _ => return Err(could_not_parse_event_error()), + } + }; + + Ok(Some(InternalEvent::Event(Event::Mouse(event)))) +} + +pub(crate) fn parse_utf8_char(buffer: &[u8]) -> Result> { + match std::str::from_utf8(buffer) { + Ok(s) => { + let ch = s.chars().next().ok_or_else(could_not_parse_event_error)?; + + Ok(Some(ch)) + } + Err(_) => { + // from_utf8 failed, but we have to check if we need more bytes for code point + // and if all the bytes we have no are valid + + let required_bytes = match buffer[0] { + // https://en.wikipedia.org/wiki/UTF-8#Description + (0x00..=0x7F) => 1, // 0xxxxxxx + (0xC0..=0xDF) => 2, // 110xxxxx 10xxxxxx + (0xE0..=0xEF) => 3, // 1110xxxx 10xxxxxx 10xxxxxx + (0xF0..=0xF7) => 4, // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + (0x80..=0xBF) | (0xF8..=0xFF) => return Err(could_not_parse_event_error()), + }; + + // More than 1 byte, check them for 10xxxxxx pattern + if required_bytes > 1 && buffer.len() > 1 { + for byte in &buffer[1..] { + if byte & !0b0011_1111 != 0b1000_0000 { + return Err(could_not_parse_event_error()); + } + } + } + + if buffer.len() < required_bytes { + // All bytes looks good so far, but we need more of them + Ok(None) + } else { + Err(could_not_parse_event_error()) + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::event::{KeyModifiers, MouseButton, MouseEvent}; + + use super::*; + + #[test] + fn test_esc_key() { + assert_eq!( + parse_event("\x1B".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into()))), + ); + } + + #[test] + fn test_possible_esc_sequence() { + assert_eq!(parse_event("\x1B".as_bytes(), true).unwrap(), None,); + } + + #[test] + fn test_alt_key() { + assert_eq!( + parse_event("\x1Bc".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Char('c'), + KeyModifiers::ALT + )))), + ); + } + + #[test] + fn test_parse_event_subsequent_calls() { + // The main purpose of this test is to check if we're passing + // correct slice to other parse_ functions. + + // parse_csi_cursor_position + assert_eq!( + parse_event("\x1B[20;10R".as_bytes(), false).unwrap(), + Some(InternalEvent::CursorPosition(9, 19)) + ); + + // parse_csi + assert_eq!( + parse_event("\x1B[D".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))), + ); + + // parse_csi_modifier_key_code + assert_eq!( + parse_event("\x1B[2D".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Left, + KeyModifiers::SHIFT + )))) + ); + + // parse_csi_special_key_code + assert_eq!( + parse_event("\x1B[3~".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Delete.into()))), + ); + + // parse_csi_rxvt_mouse + assert_eq!( + parse_event("\x1B[32;30;40;M".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 29, + 39, + KeyModifiers::empty(), + )))) + ); + + // parse_csi_x10_mouse + assert_eq!( + parse_event("\x1B[M0\x60\x70".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 63, + 79, + KeyModifiers::empty(), + )))) + ); + + // parse_csi_xterm_mouse + assert_eq!( + parse_event("\x1B[<0;20;10;M".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 19, + 9, + KeyModifiers::empty(), + )))) + ); + + // parse_utf8_char + assert_eq!( + parse_event("Ž".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Char('Ž').into()))), + ); + } + + #[test] + fn test_parse_event() { + assert_eq!( + parse_event("\t".as_bytes(), false).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into()))), + ); + } + + #[test] + fn test_parse_csi_cursor_position() { + assert_eq!( + parse_csi_cursor_position("\x1B[20;10R".as_bytes()).unwrap(), + Some(InternalEvent::CursorPosition(9, 19)) + ); + } + + #[test] + fn test_parse_csi() { + assert_eq!( + parse_csi("\x1B[D".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))), + ); + } + + #[test] + fn test_parse_csi_modifier_key_code() { + assert_eq!( + parse_csi_modifier_key_code("\x1B[2D".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyEvent::new( + KeyCode::Left, + KeyModifiers::SHIFT + )))), + ); + } + + #[test] + fn test_parse_csi_special_key_code() { + assert_eq!( + parse_csi_special_key_code("\x1B[3~".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Key(KeyCode::Delete.into()))), + ); + } + + #[test] + fn test_parse_csi_special_key_code_multiple_values_not_supported() { + assert!(parse_csi_special_key_code("\x1B[3;2~".as_bytes()).is_err()); + } + + #[test] + fn test_parse_csi_rxvt_mouse() { + assert_eq!( + parse_csi_rxvt_mouse("\x1B[32;30;40;M".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 29, + 39, + KeyModifiers::empty(), + )))) + ); + } + + #[test] + fn test_parse_csi_x10_mouse() { + assert_eq!( + parse_csi_x10_mouse("\x1B[M0\x60\x70".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 63, + 79, + KeyModifiers::empty(), + )))) + ); + } + + #[test] + fn test_parse_csi_xterm_mouse() { + assert_eq!( + parse_csi_xterm_mouse("\x1B[<0;20;10;M".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 19, + 9, + KeyModifiers::empty(), + )))) + ); + assert_eq!( + parse_csi_xterm_mouse("\x1B[<0;20;10M".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Down( + MouseButton::Left, + 19, + 9, + KeyModifiers::empty(), + )))) + ); + assert_eq!( + parse_csi_xterm_mouse("\x1B[<0;20;10;m".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Up( + MouseButton::Left, + 19, + 9, + KeyModifiers::empty(), + )))) + ); + assert_eq!( + parse_csi_xterm_mouse("\x1B[<0;20;10m".as_bytes()).unwrap(), + Some(InternalEvent::Event(Event::Mouse(MouseEvent::Up( + MouseButton::Left, + 19, + 9, + KeyModifiers::empty(), + )))) + ); + } + + #[test] + fn test_utf8() { + // https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805 + + // 'Valid ASCII' => "a", + assert_eq!(parse_utf8_char("a".as_bytes()).unwrap(), Some('a'),); + + // 'Valid 2 Octet Sequence' => "\xc3\xb1", + assert_eq!(parse_utf8_char(&[0xC3, 0xB1]).unwrap(), Some('ñ'),); + + // 'Invalid 2 Octet Sequence' => "\xc3\x28", + assert!(parse_utf8_char(&[0xC3, 0x28]).is_err()); + + // 'Invalid Sequence Identifier' => "\xa0\xa1", + assert!(parse_utf8_char(&[0xA0, 0xA1]).is_err()); + + // 'Valid 3 Octet Sequence' => "\xe2\x82\xa1", + assert_eq!( + parse_utf8_char(&[0xE2, 0x81, 0xA1]).unwrap(), + Some('\u{2061}'), + ); + + // 'Invalid 3 Octet Sequence (in 2nd Octet)' => "\xe2\x28\xa1", + assert!(parse_utf8_char(&[0xE2, 0x28, 0xA1]).is_err()); + + // 'Invalid 3 Octet Sequence (in 3rd Octet)' => "\xe2\x82\x28", + assert!(parse_utf8_char(&[0xE2, 0x82, 0x28]).is_err()); + + // 'Valid 4 Octet Sequence' => "\xf0\x90\x8c\xbc", + assert_eq!( + parse_utf8_char(&[0xF0, 0x90, 0x8C, 0xBC]).unwrap(), + Some('𐌼'), + ); + + // 'Invalid 4 Octet Sequence (in 2nd Octet)' => "\xf0\x28\x8c\xbc", + assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0xBC]).is_err()); + + // 'Invalid 4 Octet Sequence (in 3rd Octet)' => "\xf0\x90\x28\xbc", + assert!(parse_utf8_char(&[0xF0, 0x90, 0x28, 0xBC]).is_err()); + + // 'Invalid 4 Octet Sequence (in 4th Octet)' => "\xf0\x28\x8c\x28", + assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0x28]).is_err()); + } +} diff --git a/src/event/sys/windows.rs b/src/event/sys/windows.rs index 824b0f7..d0fa1dd 100644 --- a/src/event/sys/windows.rs +++ b/src/event/sys/windows.rs @@ -1,37 +1,18 @@ //! This is a WINDOWS specific implementation for input related action. -use std::{io, sync::Mutex, time::Duration}; +use std::sync::Mutex; -use crossterm_winapi::{ - ConsoleMode, ControlKeyState, EventFlags, Handle, KeyEventRecord, MouseEvent, ScreenBuffer, -}; -use winapi::{ - shared::winerror::WAIT_TIMEOUT, - um::{ - synchapi::WaitForMultipleObjects, - winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0}, - wincon::{ - LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, - SHIFT_PRESSED, - }, - winuser::{ - VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME, - VK_INSERT, VK_LEFT, VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, - }, - }, -}; +use crossterm_winapi::{ConsoleMode, Handle}; use lazy_static::lazy_static; -#[cfg(feature = "event-stream")] -pub(crate) use waker::Waker; -use crate::{ - event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton}, - Result, -}; +use crate::Result; #[cfg(feature = "event-stream")] -mod waker; +pub(crate) mod waker; + +pub(crate) mod parse; +pub(crate) mod poll; const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008; @@ -70,249 +51,3 @@ pub(crate) fn disable_mouse_capture() -> Result<()> { mode.set_mode(original_console_mode())?; Ok(()) } - -pub(crate) fn handle_mouse_event(mouse_event: MouseEvent) -> Result> { - if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event) { - return Ok(Some(Event::Mouse(event))); - } - Ok(None) -} - -pub(crate) 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(Event::Key(event))); - } - } - - Ok(None) -} - -impl From for KeyModifiers { - fn from(state: ControlKeyState) -> Self { - let shift = state.has_state(SHIFT_PRESSED); - let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); - let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); - - let mut modifier = KeyModifiers::empty(); - - if shift { - modifier |= KeyModifiers::SHIFT; - } - if control { - modifier |= KeyModifiers::CONTROL; - } - if alt { - modifier |= KeyModifiers::ALT; - } - - modifier - } -} - -fn parse_key_event_record(key_event: &KeyEventRecord) -> Option { - let modifiers = KeyModifiers::from(key_event.control_key_state); - - let key_code = key_event.virtual_key_code as i32; - - let parse_result = match key_code { - VK_SHIFT | VK_CONTROL | VK_MENU => None, - VK_BACK => Some(KeyCode::Backspace), - VK_ESCAPE => Some(KeyCode::Esc), - VK_RETURN => Some(KeyCode::Enter), - VK_F1..=VK_F24 => Some(KeyCode::F((key_event.virtual_key_code - 111) as u8)), - VK_LEFT => Some(KeyCode::Left), - VK_UP => Some(KeyCode::Up), - VK_RIGHT => Some(KeyCode::Right), - VK_DOWN => Some(KeyCode::Down), - VK_PRIOR => Some(KeyCode::PageUp), - VK_NEXT => Some(KeyCode::PageDown), - VK_HOME => Some(KeyCode::Home), - VK_END => Some(KeyCode::End), - VK_DELETE => Some(KeyCode::Delete), - VK_INSERT => Some(KeyCode::Insert), - _ => { - // Modifier Keys (Ctrl, Alt, Shift) Support - let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) }; - - if character_raw < 255 { - let mut character = character_raw as u8 as char; - - if modifiers.contains(KeyModifiers::ALT) { - // 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() { - character = command; - } else { - return None; - } - } else if modifiers.contains(KeyModifiers::CONTROL) { - // we need to do some parsing - character = match character_raw as u8 { - c @ b'\x01'..=b'\x1A' => (c as u8 - 0x1 + b'a') as char, - c @ b'\x1C'..=b'\x1F' => (c as u8 - 0x1C + b'4') as char, - _ => return None, - } - } - - if modifiers.contains(KeyModifiers::SHIFT) && character == '\t' { - Some(KeyCode::BackTab) - } else if character == '\t' { - Some(KeyCode::Tab) - } else { - Some(KeyCode::Char(character)) - } - } else { - None - } - } - }; - - if let Some(key_code) = parse_result { - return Some(KeyEvent::new(key_code, modifiers)); - } - - None -} - -// The 'y' position of a mouse event or resize event is not relative to the window but absolute to screen buffer. -// This means that when the mouse cursor is at the top left it will be x: 0, y: 2295 (e.g. y = number of cells conting from the absolute buffer height) instead of relative x: 0, y: 0 to the window. -pub fn parse_relative_y(y: i16) -> Result { - let window_size = ScreenBuffer::current()?.info()?.terminal_window(); - Ok(y - window_size.top) -} - -fn parse_mouse_event_record(event: &MouseEvent) -> Result> { - let modifiers = KeyModifiers::from(event.control_key_state); - - let xpos = event.mouse_position.x as u16; - let ypos = parse_relative_y(event.mouse_position.y)? as u16; - - let button_state = event.button_state; - let button = if button_state.right_button() { - MouseButton::Right - } else if button_state.middle_button() { - MouseButton::Middle - } else { - MouseButton::Left - }; - - Ok(match event.event_flags { - EventFlags::PressOrRelease => { - if button_state.release_button() { - // in order to read the up button type, we have to check the last down input record. - Some(crate::event::MouseEvent::Up( - MouseButton::Left, - xpos, - ypos, - modifiers, - )) - } else { - Some(crate::event::MouseEvent::Down( - button, xpos, ypos, modifiers, - )) - } - } - EventFlags::MouseMoved => { - // Click + Move - // Only register when mouse is not released - // because unix systems share this behaviour. - if !button_state.release_button() { - Some(crate::event::MouseEvent::Drag( - button, xpos, ypos, modifiers, - )) - } else { - None - } - } - EventFlags::MouseWheeled => { - // Vertical scroll - // 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.scroll_down() { - Some(crate::event::MouseEvent::ScrollDown(xpos, ypos, modifiers)) - } else if button_state.scroll_up() { - Some(crate::event::MouseEvent::ScrollUp(xpos, ypos, modifiers)) - } else { - None - } - } - EventFlags::DoubleClick => None, // double click not supported by unix terminals - EventFlags::MouseHwheeled => None, // horizontal scroll not supported by unix terminals - }) -} - -pub(crate) struct WinApiPoll { - #[cfg(feature = "event-stream")] - waker: Waker, -} - -impl WinApiPoll { - #[cfg(not(feature = "event-stream"))] - pub(crate) fn new() -> Result { - Ok(WinApiPoll {}) - } - - #[cfg(feature = "event-stream")] - pub(crate) fn new() -> Result { - Ok(WinApiPoll { - waker: Waker::new()?, - }) - } -} - -impl WinApiPoll { - pub fn poll(&mut self, timeout: Option) -> Result> { - let dw_millis = if let Some(duration) = timeout { - duration.as_millis() as u32 - } else { - INFINITE - }; - - let console_handle = Handle::current_in_handle()?; - - #[cfg(feature = "event-stream")] - let semaphore = self.waker.semaphore(); - #[cfg(feature = "event-stream")] - let handles = &[*console_handle, **semaphore.handle()]; - #[cfg(not(feature = "event-stream"))] - let handles = &[*console_handle]; - - let output = - unsafe { WaitForMultipleObjects(handles.len() as u32, handles.as_ptr(), 0, dw_millis) }; - - match output { - output if output == WAIT_OBJECT_0 => { - // input handle triggered - Ok(Some(true)) - } - #[cfg(feature = "event-stream")] - output if output == WAIT_OBJECT_0 + 1 => { - // semaphore handle triggered - let _ = self.waker.reset(); - Err(io::Error::new( - io::ErrorKind::Interrupted, - "Poll operation was woken up by `Waker::wake`", - ) - .into()) - } - WAIT_TIMEOUT | WAIT_ABANDONED_0 => { - // timeout elapsed - Ok(None) - } - WAIT_FAILED => Err(io::Error::last_os_error().into()), - _ => Err(io::Error::new( - io::ErrorKind::Other, - "WaitForMultipleObjects returned unexpected result.", - ) - .into()), - } - } - - #[cfg(feature = "event-stream")] - pub fn waker(&self) -> Waker { - self.waker.clone() - } -} diff --git a/src/event/sys/windows/parse.rs b/src/event/sys/windows/parse.rs new file mode 100644 index 0000000..f6d906b --- /dev/null +++ b/src/event/sys/windows/parse.rs @@ -0,0 +1,188 @@ +use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, MouseEvent, ScreenBuffer}; +use winapi::um::{ + wincon::{ + LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED, + }, + winuser::{ + VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME, + VK_INSERT, VK_LEFT, VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, + }, +}; + +use crate::{ + event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton}, + Result, +}; + +pub(crate) fn handle_mouse_event(mouse_event: MouseEvent) -> Result> { + if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event) { + return Ok(Some(Event::Mouse(event))); + } + Ok(None) +} + +pub(crate) 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(Event::Key(event))); + } + } + + Ok(None) +} + +impl From for KeyModifiers { + fn from(state: ControlKeyState) -> Self { + let shift = state.has_state(SHIFT_PRESSED); + let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); + let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); + + let mut modifier = KeyModifiers::empty(); + + if shift { + modifier |= KeyModifiers::SHIFT; + } + if control { + modifier |= KeyModifiers::CONTROL; + } + if alt { + modifier |= KeyModifiers::ALT; + } + + modifier + } +} + +fn parse_key_event_record(key_event: &KeyEventRecord) -> Option { + let modifiers = KeyModifiers::from(key_event.control_key_state); + + let key_code = key_event.virtual_key_code as i32; + + let parse_result = match key_code { + VK_SHIFT | VK_CONTROL | VK_MENU => None, + VK_BACK => Some(KeyCode::Backspace), + VK_ESCAPE => Some(KeyCode::Esc), + VK_RETURN => Some(KeyCode::Enter), + VK_F1..=VK_F24 => Some(KeyCode::F((key_event.virtual_key_code - 111) as u8)), + VK_LEFT => Some(KeyCode::Left), + VK_UP => Some(KeyCode::Up), + VK_RIGHT => Some(KeyCode::Right), + VK_DOWN => Some(KeyCode::Down), + VK_PRIOR => Some(KeyCode::PageUp), + VK_NEXT => Some(KeyCode::PageDown), + VK_HOME => Some(KeyCode::Home), + VK_END => Some(KeyCode::End), + VK_DELETE => Some(KeyCode::Delete), + VK_INSERT => Some(KeyCode::Insert), + _ => { + // Modifier Keys (Ctrl, Alt, Shift) Support + let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) }; + + if character_raw < 255 { + let mut character = character_raw as u8 as char; + + if modifiers.contains(KeyModifiers::ALT) { + // 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() { + character = command; + } else { + return None; + } + } else if modifiers.contains(KeyModifiers::CONTROL) { + // we need to do some parsing + character = match character_raw as u8 { + c @ b'\x01'..=b'\x1A' => (c as u8 - 0x1 + b'a') as char, + c @ b'\x1C'..=b'\x1F' => (c as u8 - 0x1C + b'4') as char, + _ => return None, + } + } + + if modifiers.contains(KeyModifiers::SHIFT) && character == '\t' { + Some(KeyCode::BackTab) + } else if character == '\t' { + Some(KeyCode::Tab) + } else { + Some(KeyCode::Char(character)) + } + } else { + None + } + } + }; + + if let Some(key_code) = parse_result { + return Some(KeyEvent::new(key_code, modifiers)); + } + + None +} + +// The 'y' position of a mouse event or resize event is not relative to the window but absolute to screen buffer. +// This means that when the mouse cursor is at the top left it will be x: 0, y: 2295 (e.g. y = number of cells conting from the absolute buffer height) instead of relative x: 0, y: 0 to the window. +pub fn parse_relative_y(y: i16) -> Result { + let window_size = ScreenBuffer::current()?.info()?.terminal_window(); + Ok(y - window_size.top) +} + +fn parse_mouse_event_record(event: &MouseEvent) -> Result> { + let modifiers = KeyModifiers::from(event.control_key_state); + + let xpos = event.mouse_position.x as u16; + let ypos = parse_relative_y(event.mouse_position.y)? as u16; + + let button_state = event.button_state; + let button = if button_state.right_button() { + MouseButton::Right + } else if button_state.middle_button() { + MouseButton::Middle + } else { + MouseButton::Left + }; + + Ok(match event.event_flags { + EventFlags::PressOrRelease => { + if button_state.release_button() { + // in order to read the up button type, we have to check the last down input record. + Some(crate::event::MouseEvent::Up( + MouseButton::Left, + xpos, + ypos, + modifiers, + )) + } else { + Some(crate::event::MouseEvent::Down( + button, xpos, ypos, modifiers, + )) + } + } + EventFlags::MouseMoved => { + // Click + Move + // Only register when mouse is not released + // because unix systems share this behaviour. + if !button_state.release_button() { + Some(crate::event::MouseEvent::Drag( + button, xpos, ypos, modifiers, + )) + } else { + None + } + } + EventFlags::MouseWheeled => { + // Vertical scroll + // 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.scroll_down() { + Some(crate::event::MouseEvent::ScrollDown(xpos, ypos, modifiers)) + } else if button_state.scroll_up() { + Some(crate::event::MouseEvent::ScrollUp(xpos, ypos, modifiers)) + } else { + None + } + } + EventFlags::DoubleClick => None, // double click not supported by unix terminals + EventFlags::MouseHwheeled => None, // horizontal scroll not supported by unix terminals + }) +} diff --git a/src/event/sys/windows/poll.rs b/src/event/sys/windows/poll.rs new file mode 100644 index 0000000..2e9acc7 --- /dev/null +++ b/src/event/sys/windows/poll.rs @@ -0,0 +1,89 @@ +use std::io; +use std::time::Duration; + +use crossterm_winapi::Handle; +use winapi::{ + shared::winerror::WAIT_TIMEOUT, + um::{ + synchapi::WaitForMultipleObjects, + winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0}, + }, +}; + +use crate::Result; + +#[cfg(feature = "event-stream")] +pub(crate) use super::waker::Waker; + +pub(crate) struct WinApiPoll { + #[cfg(feature = "event-stream")] + waker: Waker, +} + +impl WinApiPoll { + #[cfg(not(feature = "event-stream"))] + pub(crate) fn new() -> Result { + Ok(WinApiPoll {}) + } + + #[cfg(feature = "event-stream")] + pub(crate) fn new() -> Result { + Ok(WinApiPoll { + waker: Waker::new()?, + }) + } +} + +impl WinApiPoll { + pub fn poll(&mut self, timeout: Option) -> Result> { + let dw_millis = if let Some(duration) = timeout { + duration.as_millis() as u32 + } else { + INFINITE + }; + + let console_handle = Handle::current_in_handle()?; + + #[cfg(feature = "event-stream")] + let semaphore = self.waker.semaphore(); + #[cfg(feature = "event-stream")] + let handles = &[*console_handle, **semaphore.handle()]; + #[cfg(not(feature = "event-stream"))] + let handles = &[*console_handle]; + + let output = + unsafe { WaitForMultipleObjects(handles.len() as u32, handles.as_ptr(), 0, dw_millis) }; + + match output { + output if output == WAIT_OBJECT_0 => { + // input handle triggered + Ok(Some(true)) + } + #[cfg(feature = "event-stream")] + output if output == WAIT_OBJECT_0 + 1 => { + // semaphore handle triggered + let _ = self.waker.reset(); + Err(io::Error::new( + io::ErrorKind::Interrupted, + "Poll operation was woken up by `Waker::wake`", + ) + .into()) + } + WAIT_TIMEOUT | WAIT_ABANDONED_0 => { + // timeout elapsed + Ok(None) + } + WAIT_FAILED => Err(io::Error::last_os_error().into()), + _ => Err(io::Error::new( + io::ErrorKind::Other, + "WaitForMultipleObjects returned unexpected result.", + ) + .into()), + } + } + + #[cfg(feature = "event-stream")] + pub fn waker(&self) -> Waker { + self.waker.clone() + } +} diff --git a/src/lib.rs b/src/lib.rs index 1335210..1131dc1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -227,7 +227,10 @@ //! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html //! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush -pub use utils::{Command, ErrorKind, ExecutableCommand, QueueableCommand, Result}; +pub use crate::{ + command::{Command, ExecutableCommand, QueueableCommand}, + error::{ErrorKind, Result}, +}; /// A module to work with the terminal cursor pub mod cursor; @@ -237,5 +240,9 @@ pub mod event; pub mod style; /// A module to work with the terminal. pub mod terminal; -/// Shared utilities. -pub mod utils; + +#[cfg(windows)] +pub(crate) mod ansi_support; +mod command; +mod error; +pub(crate) mod macros; diff --git a/src/utils/macros.rs b/src/macros.rs similarity index 98% rename from src/utils/macros.rs rename to src/macros.rs index ef10f68..9377865 100644 --- a/src/utils/macros.rs +++ b/src/macros.rs @@ -208,9 +208,9 @@ macro_rules! impl_from { mod tests { use std::io::{stdout, Write}; - use crate::utils::command::Command; + use crate::command::Command; #[cfg(windows)] - use crate::utils::error::ErrorKind; + use crate::error::ErrorKind; pub struct FakeCommand; diff --git a/src/style.rs b/src/style.rs index 0d761a0..4972fff 100644 --- a/src/style.rs +++ b/src/style.rs @@ -114,7 +114,7 @@ use std::{env, fmt::Display}; #[cfg(windows)] use crate::Result; -use crate::{impl_display, utils::Command}; +use crate::{impl_display, Command}; pub(crate) use self::enums::Colored; pub use self::{ diff --git a/src/style/sys/windows.rs b/src/style/sys/windows.rs index 9765a2a..562618a 100644 --- a/src/style/sys/windows.rs +++ b/src/style/sys/windows.rs @@ -5,7 +5,7 @@ use winapi::um::wincon; use lazy_static::lazy_static; -use crate::utils::Result; +use crate::Result; use super::super::{Color, Colored}; diff --git a/src/terminal.rs b/src/terminal.rs index 99e0839..a32be19 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -87,7 +87,7 @@ use crossterm_winapi::{Handle, ScreenBuffer}; use serde::{Deserialize, Serialize}; #[doc(no_inline)] -use crate::utils::Command; +use crate::Command; use crate::{impl_display, Result}; mod ansi; @@ -345,7 +345,7 @@ mod tests { #[cfg(windows)] { if cfg!(target_os = "windows") { - use crate::utils::sys::windows::set_virtual_terminal_processing; + use crate::ansi_support::set_virtual_terminal_processing; // if it is not listed we should try with WinApi to check if we do support ANSI-codes. match set_virtual_terminal_processing(true) { diff --git a/src/terminal/sys/unix.rs b/src/terminal/sys/unix.rs index fb8cc43..e571d49 100644 --- a/src/terminal/sys/unix.rs +++ b/src/terminal/sys/unix.rs @@ -1,5 +1,5 @@ //! UNIX related logic for terminal manipulation. -use std::{mem, process, sync::Mutex}; +use std::{io, mem, process, sync::Mutex}; use libc::{ cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDIN_FILENO, @@ -8,7 +8,7 @@ use libc::{ use lazy_static::lazy_static; -use crate::utils::{sys::unix::wrap_with_result, Result}; +use crate::error::{ErrorKind, Result}; lazy_static! { // Some(Termios) -> we're in the raw mode and this is the previous mode @@ -120,3 +120,11 @@ fn get_terminal_attr() -> Result { fn set_terminal_attr(termios: &Termios) -> Result { wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) }) } + +pub fn wrap_with_result(result: i32) -> Result { + if result == -1 { + Err(ErrorKind::IoError(io::Error::last_os_error())) + } else { + Ok(true) + } +} diff --git a/src/terminal/sys/windows.rs b/src/terminal/sys/windows.rs index 0f73d03..99eae0b 100644 --- a/src/terminal/sys/windows.rs +++ b/src/terminal/sys/windows.rs @@ -5,7 +5,7 @@ use winapi::{ um::wincon::{ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT}, }; -use crate::{cursor, terminal::ClearType, utils::Result, ErrorKind}; +use crate::{cursor, terminal::ClearType, ErrorKind, Result}; const RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT; diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 9ed8073..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! # Utils - -pub use self::{ - command::{Command, ExecutableCommand, QueueableCommand}, - error::{ErrorKind, Result}, -}; - -mod command; -mod error; -#[cfg(windows)] -pub(crate) mod functions; -pub(crate) mod macros; -pub(crate) mod sys; diff --git a/src/utils/sys.rs b/src/utils/sys.rs deleted file mode 100644 index d98e2d9..0000000 --- a/src/utils/sys.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(windows)] -pub(crate) mod windows; - -#[cfg(unix)] -pub(crate) mod unix; diff --git a/src/utils/sys/unix.rs b/src/utils/sys/unix.rs deleted file mode 100644 index 50e49fb..0000000 --- a/src/utils/sys/unix.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! This module contains all `unix` specific terminal related logic. - -use std::io; - -use super::super::error::{ErrorKind, Result}; - -pub fn wrap_with_result(result: i32) -> Result { - if result == -1 { - Err(ErrorKind::IoError(io::Error::last_os_error())) - } else { - Ok(true) - } -} diff --git a/src/utils/sys/windows.rs b/src/utils/sys/windows.rs deleted file mode 100644 index 53fb204..0000000 --- a/src/utils/sys/windows.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crossterm_winapi::ConsoleMode; -use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; - -use crate::Result; - -/// Toggle virtual terminal processing. -/// -/// This method attempts to toggle virtual terminal processing for this -/// console. If there was a problem toggling it, then an error returned. -/// On success, the caller may assume that toggling it was successful. -/// -/// When virtual terminal processing is enabled, characters emitted to the -/// console are parsed for VT100 and similar control character sequences -/// that control color and other similar operations. -pub(crate) fn set_virtual_terminal_processing(yes: bool) -> Result<()> { - let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; - - let console_mode = ConsoleMode::new()?; - let old_mode = console_mode.mode()?; - - let new_mode = if yes { - old_mode | mask - } else { - old_mode & !mask - }; - - if old_mode != new_mode { - console_mode.set_mode(new_mode)?; - } - - Ok(()) -}