Moved some files around (#342)

This commit is contained in:
Timon 2019-12-11 17:10:34 +01:00 committed by GitHub
parent 47e8366f2b
commit 71029c4a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1149 additions and 1150 deletions

View File

@ -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,

View File

@ -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 = {

View File

@ -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()
}
}

View File

@ -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;

View File

@ -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).

View File

@ -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<Option<(i16, i16)>> = Mutex::new(None);

View File

@ -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,
};

View File

@ -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,
};

View File

@ -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;

View File

@ -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<usize> {
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<FileDesc> {
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<Option<InputEvent>>
//
// 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<Option<InternalEvent>> {
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<Option<InternalEvent>> {
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<T>(iter: &mut dyn Iterator<Item = &str>) -> Result<T>
where
T: std::str::FromStr,
{
iter.next()
.ok_or_else(could_not_parse_event_error)?
.parse::<T>()
.map_err(|_| could_not_parse_event_error())
}
pub(crate) fn parse_csi_cursor_position(buffer: &[u8]) -> Result<Option<InternalEvent>> {
// 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::<u16>(&mut split)? - 1;
let x = next_parsed::<u16>(&mut split)? - 1;
Ok(Some(InternalEvent::CursorPosition(x, y)))
}
pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
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<Option<InternalEvent>> {
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::<u8>(&mut split)?;
if next_parsed::<u8>(&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<Option<InternalEvent>> {
// 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::<u16>(&mut split)?;
let cx = next_parsed::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&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<Option<InternalEvent>> {
// 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<Option<InternalEvent>> {
// 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::<u16>(&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::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&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<Option<char>> {
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;

View File

@ -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<usize> {
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<FileDesc> {
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))
}

706
src/event/sys/unix/parse.rs Normal file
View File

@ -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<Option<InputEvent>>
//
// 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<Option<InternalEvent>> {
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<Option<InternalEvent>> {
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<T>(iter: &mut dyn Iterator<Item = &str>) -> Result<T>
where
T: std::str::FromStr,
{
iter.next()
.ok_or_else(could_not_parse_event_error)?
.parse::<T>()
.map_err(|_| could_not_parse_event_error())
}
pub(crate) fn parse_csi_cursor_position(buffer: &[u8]) -> Result<Option<InternalEvent>> {
// 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::<u16>(&mut split)? - 1;
let x = next_parsed::<u16>(&mut split)? - 1;
Ok(Some(InternalEvent::CursorPosition(x, y)))
}
pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result<Option<InternalEvent>> {
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<Option<InternalEvent>> {
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::<u8>(&mut split)?;
if next_parsed::<u8>(&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<Option<InternalEvent>> {
// 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::<u16>(&mut split)?;
let cx = next_parsed::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&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<Option<InternalEvent>> {
// 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<Option<InternalEvent>> {
// 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::<u16>(&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::<u16>(&mut split)? - 1;
let cy = next_parsed::<u16>(&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<Option<char>> {
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());
}
}

View File

@ -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<Option<Event>> {
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<Option<Event>> {
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<ControlKeyState> 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<KeyEvent> {
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<i16> {
let window_size = ScreenBuffer::current()?.info()?.terminal_window();
Ok(y - window_size.top)
}
fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::event::MouseEvent>> {
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<WinApiPoll> {
Ok(WinApiPoll {})
}
#[cfg(feature = "event-stream")]
pub(crate) fn new() -> Result<WinApiPoll> {
Ok(WinApiPoll {
waker: Waker::new()?,
})
}
}
impl WinApiPoll {
pub fn poll(&mut self, timeout: Option<Duration>) -> Result<Option<bool>> {
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()
}
}

View File

@ -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<Option<Event>> {
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<Option<Event>> {
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<ControlKeyState> 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<KeyEvent> {
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<i16> {
let window_size = ScreenBuffer::current()?.info()?.terminal_window();
Ok(y - window_size.top)
}
fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::event::MouseEvent>> {
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
})
}

View File

@ -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<WinApiPoll> {
Ok(WinApiPoll {})
}
#[cfg(feature = "event-stream")]
pub(crate) fn new() -> Result<WinApiPoll> {
Ok(WinApiPoll {
waker: Waker::new()?,
})
}
}
impl WinApiPoll {
pub fn poll(&mut self, timeout: Option<Duration>) -> Result<Option<bool>> {
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()
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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::{

View File

@ -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};

View File

@ -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) {

View File

@ -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<Termios> {
fn set_terminal_attr(termios: &Termios) -> Result<bool> {
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
}
pub fn wrap_with_result(result: i32) -> Result<bool> {
if result == -1 {
Err(ErrorKind::IoError(io::Error::last_os_error()))
} else {
Ok(true)
}
}

View File

@ -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;

View File

@ -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;

View File

@ -1,5 +0,0 @@
#[cfg(windows)]
pub(crate) mod windows;
#[cfg(unix)]
pub(crate) mod unix;

View File

@ -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<bool> {
if result == -1 {
Err(ErrorKind::IoError(io::Error::last_os_error()))
} else {
Ok(true)
}
}

View File

@ -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(())
}