Moved some files around (#342)
This commit is contained in:
parent
47e8366f2b
commit
71029c4a87
@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
use std::io::{stderr, Write};
|
use std::io::{stderr, Write};
|
||||||
|
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent};
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::{Hide, MoveTo, Show},
|
cursor::{Hide, MoveTo, Show},
|
||||||
event, execute, queue,
|
event,
|
||||||
|
event::{Event, KeyCode, KeyEvent},
|
||||||
|
execute, queue,
|
||||||
style::Print,
|
style::Print,
|
||||||
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
Result,
|
Result,
|
||||||
|
@ -1,6 +1,37 @@
|
|||||||
|
use crossterm_winapi::ConsoleMode;
|
||||||
|
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
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! {
|
lazy_static! {
|
||||||
static ref SUPPORTS_ANSI_ESCAPE_CODES: bool = {
|
static ref SUPPORTS_ANSI_ESCAPE_CODES: bool = {
|
@ -35,7 +35,7 @@ pub trait Command {
|
|||||||
/// can be found [here](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences).
|
/// can be found [here](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences).
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn is_ansi_code_supported(&self) -> bool {
|
fn is_ansi_code_supported(&self) -> bool {
|
||||||
super::functions::supports_ansi()
|
super::ansi_support::supports_ansi()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +45,8 @@
|
|||||||
pub use sys::position;
|
pub use sys::position;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::utils::Result;
|
use crate::Result;
|
||||||
use crate::{impl_display, utils::Command};
|
use crate::{impl_display, Command};
|
||||||
|
|
||||||
mod ansi;
|
mod ansi;
|
||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent},
|
event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent},
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled},
|
terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled},
|
||||||
utils::Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Returns the cursor position (column, row).
|
/// Returns the cursor position (column, row).
|
||||||
|
@ -10,7 +10,7 @@ use winapi::{
|
|||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::utils::Result;
|
use crate::Result;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SAVED_CURSOR_POS: Mutex<Option<(i16, i16)>> = Mutex::new(None);
|
static ref SAVED_CURSOR_POS: Mutex<Option<(i16, i16)>> = Mutex::new(None);
|
||||||
|
@ -8,7 +8,10 @@ use crate::Result;
|
|||||||
use super::super::sys::Waker;
|
use super::super::sys::Waker;
|
||||||
use super::super::{
|
use super::super::{
|
||||||
source::EventSource,
|
source::EventSource,
|
||||||
sys::unix::{parse_event, tty_fd, FileDesc},
|
sys::unix::{
|
||||||
|
file_descriptor::{tty_fd, FileDesc},
|
||||||
|
parse::parse_event,
|
||||||
|
},
|
||||||
timeout::PollTimeout,
|
timeout::PollTimeout,
|
||||||
Event, InternalEvent,
|
Event, InternalEvent,
|
||||||
};
|
};
|
||||||
|
@ -2,13 +2,13 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use crossterm_winapi::{Console, Handle, InputEventType, KeyEventRecord, MouseEvent};
|
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")]
|
#[cfg(feature = "event-stream")]
|
||||||
use super::super::sys::Waker;
|
use super::super::sys::Waker;
|
||||||
use super::super::{
|
use super::super::{
|
||||||
source::EventSource,
|
source::EventSource,
|
||||||
sys::windows::{handle_key_event, handle_mouse_event},
|
sys::windows::parse::{handle_key_event, handle_mouse_event},
|
||||||
timeout::PollTimeout,
|
timeout::PollTimeout,
|
||||||
InternalEvent, Result,
|
InternalEvent, Result,
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#[cfg(all(unix, feature = "event-stream"))]
|
#[cfg(all(unix, feature = "event-stream"))]
|
||||||
pub(crate) use unix::Waker;
|
pub(crate) use unix::waker::Waker;
|
||||||
#[cfg(all(windows, feature = "event-stream"))]
|
#[cfg(all(windows, feature = "event-stream"))]
|
||||||
pub(crate) use windows::Waker;
|
pub(crate) use windows::waker::Waker;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) mod unix;
|
pub(crate) mod unix;
|
||||||
|
@ -1,792 +1,5 @@
|
|||||||
use std::{
|
|
||||||
fs, io,
|
|
||||||
os::unix::io::{IntoRawFd, RawFd},
|
|
||||||
};
|
|
||||||
|
|
||||||
use libc::size_t;
|
|
||||||
|
|
||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
pub(crate) use waker::Waker;
|
pub(crate) mod waker;
|
||||||
|
|
||||||
use crate::{
|
pub(crate) mod file_descriptor;
|
||||||
event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent},
|
pub(crate) mod parse;
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
81
src/event/sys/unix/file_descriptor.rs
Normal file
81
src/event/sys/unix/file_descriptor.rs
Normal 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
706
src/event/sys/unix/parse.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +1,18 @@
|
|||||||
//! This is a WINDOWS specific implementation for input related action.
|
//! This is a WINDOWS specific implementation for input related action.
|
||||||
|
|
||||||
use std::{io, sync::Mutex, time::Duration};
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crossterm_winapi::{
|
use crossterm_winapi::{ConsoleMode, Handle};
|
||||||
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 lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
#[cfg(feature = "event-stream")]
|
|
||||||
pub(crate) use waker::Waker;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::Result;
|
||||||
event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton},
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "event-stream")]
|
#[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;
|
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())?;
|
mode.set_mode(original_console_mode())?;
|
||||||
Ok(())
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
188
src/event/sys/windows/parse.rs
Normal file
188
src/event/sys/windows/parse.rs
Normal 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
|
||||||
|
})
|
||||||
|
}
|
89
src/event/sys/windows/poll.rs
Normal file
89
src/event/sys/windows/poll.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
13
src/lib.rs
13
src/lib.rs
@ -227,7 +227,10 @@
|
|||||||
//! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html
|
//! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html
|
||||||
//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
|
//! [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
|
/// A module to work with the terminal cursor
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
@ -237,5 +240,9 @@ pub mod event;
|
|||||||
pub mod style;
|
pub mod style;
|
||||||
/// A module to work with the terminal.
|
/// A module to work with the terminal.
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
/// Shared utilities.
|
|
||||||
pub mod utils;
|
#[cfg(windows)]
|
||||||
|
pub(crate) mod ansi_support;
|
||||||
|
mod command;
|
||||||
|
mod error;
|
||||||
|
pub(crate) mod macros;
|
||||||
|
@ -208,9 +208,9 @@ macro_rules! impl_from {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::io::{stdout, Write};
|
use std::io::{stdout, Write};
|
||||||
|
|
||||||
use crate::utils::command::Command;
|
use crate::command::Command;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::utils::error::ErrorKind;
|
use crate::error::ErrorKind;
|
||||||
|
|
||||||
pub struct FakeCommand;
|
pub struct FakeCommand;
|
||||||
|
|
@ -114,7 +114,7 @@ use std::{env, fmt::Display};
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{impl_display, utils::Command};
|
use crate::{impl_display, Command};
|
||||||
|
|
||||||
pub(crate) use self::enums::Colored;
|
pub(crate) use self::enums::Colored;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
@ -5,7 +5,7 @@ use winapi::um::wincon;
|
|||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::utils::Result;
|
use crate::Result;
|
||||||
|
|
||||||
use super::super::{Color, Colored};
|
use super::super::{Color, Colored};
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ use crossterm_winapi::{Handle, ScreenBuffer};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
use crate::utils::Command;
|
use crate::Command;
|
||||||
use crate::{impl_display, Result};
|
use crate::{impl_display, Result};
|
||||||
|
|
||||||
mod ansi;
|
mod ansi;
|
||||||
@ -345,7 +345,7 @@ mod tests {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
if cfg!(target_os = "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.
|
// if it is not listed we should try with WinApi to check if we do support ANSI-codes.
|
||||||
match set_virtual_terminal_processing(true) {
|
match set_virtual_terminal_processing(true) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//! UNIX related logic for terminal manipulation.
|
//! UNIX related logic for terminal manipulation.
|
||||||
use std::{mem, process, sync::Mutex};
|
use std::{io, mem, process, sync::Mutex};
|
||||||
|
|
||||||
use libc::{
|
use libc::{
|
||||||
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDIN_FILENO,
|
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDIN_FILENO,
|
||||||
@ -8,7 +8,7 @@ use libc::{
|
|||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::utils::{sys::unix::wrap_with_result, Result};
|
use crate::error::{ErrorKind, Result};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// Some(Termios) -> we're in the raw mode and this is the previous mode
|
// 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> {
|
fn set_terminal_attr(termios: &Termios) -> Result<bool> {
|
||||||
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@ use winapi::{
|
|||||||
um::wincon::{ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT},
|
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;
|
const RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
|
||||||
|
|
||||||
|
13
src/utils.rs
13
src/utils.rs
@ -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;
|
|
@ -1,5 +0,0 @@
|
|||||||
#[cfg(windows)]
|
|
||||||
pub(crate) mod windows;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub(crate) mod unix;
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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(())
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user