fix: refresh diff colors on terminal theme changes
This commit is contained in:
Vendored
+115
@@ -0,0 +1,115 @@
|
||||
use crate::event::InternalEvent;
|
||||
|
||||
/// Interface for filtering an `InternalEvent`.
|
||||
pub(crate) trait Filter: Send + Sync + 'static {
|
||||
/// Returns whether the given event fulfills the filter.
|
||||
fn eval(&self, event: &InternalEvent) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct CursorPositionFilter;
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Filter for CursorPositionFilter {
|
||||
fn eval(&self, event: &InternalEvent) -> bool {
|
||||
matches!(*event, InternalEvent::CursorPosition(_, _))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct KeyboardEnhancementFlagsFilter;
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Filter for KeyboardEnhancementFlagsFilter {
|
||||
fn eval(&self, event: &InternalEvent) -> bool {
|
||||
// This filter checks for either a KeyboardEnhancementFlags response or
|
||||
// a PrimaryDeviceAttributes response. If we receive the PrimaryDeviceAttributes
|
||||
// response but not KeyboardEnhancementFlags, the terminal does not support
|
||||
// progressive keyboard enhancement.
|
||||
matches!(
|
||||
*event,
|
||||
InternalEvent::KeyboardEnhancementFlags(_) | InternalEvent::PrimaryDeviceAttributes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PrimaryDeviceAttributesFilter;
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Filter for PrimaryDeviceAttributesFilter {
|
||||
fn eval(&self, event: &InternalEvent) -> bool {
|
||||
matches!(*event, InternalEvent::PrimaryDeviceAttributes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct EventFilter;
|
||||
|
||||
impl Filter for EventFilter {
|
||||
#[cfg(unix)]
|
||||
fn eval(&self, event: &InternalEvent) -> bool {
|
||||
matches!(*event, InternalEvent::Event(_))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn eval(&self, _: &InternalEvent) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(unix)]
|
||||
mod tests {
|
||||
use super::{
|
||||
super::Event, CursorPositionFilter, EventFilter, Filter, InternalEvent,
|
||||
KeyboardEnhancementFlagsFilter, PrimaryDeviceAttributesFilter,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InternalEventFilter;
|
||||
|
||||
impl Filter for InternalEventFilter {
|
||||
fn eval(&self, _: &InternalEvent) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cursor_position_filter_filters_cursor_position() {
|
||||
assert!(!CursorPositionFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
|
||||
assert!(CursorPositionFilter.eval(&InternalEvent::CursorPosition(0, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyboard_enhancement_status_filter_filters_keyboard_enhancement_status() {
|
||||
assert!(!KeyboardEnhancementFlagsFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
|
||||
assert!(
|
||||
KeyboardEnhancementFlagsFilter.eval(&InternalEvent::KeyboardEnhancementFlags(
|
||||
crate::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||
))
|
||||
);
|
||||
assert!(KeyboardEnhancementFlagsFilter.eval(&InternalEvent::PrimaryDeviceAttributes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_primary_device_attributes_filter_filters_primary_device_attributes() {
|
||||
assert!(!PrimaryDeviceAttributesFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
|
||||
assert!(PrimaryDeviceAttributesFilter.eval(&InternalEvent::PrimaryDeviceAttributes));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_event_filter_filters_events() {
|
||||
assert!(EventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
|
||||
assert!(!EventFilter.eval(&InternalEvent::CursorPosition(0, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_event_filter_filters_internal_events() {
|
||||
assert!(InternalEventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
|
||||
assert!(InternalEventFilter.eval(&InternalEvent::CursorPosition(0, 0)));
|
||||
}
|
||||
}
|
||||
Vendored
+436
@@ -0,0 +1,436 @@
|
||||
use std::{collections::vec_deque::VecDeque, io, time::Duration};
|
||||
|
||||
#[cfg(unix)]
|
||||
use crate::event::source::unix::UnixInternalEventSource;
|
||||
#[cfg(windows)]
|
||||
use crate::event::source::windows::WindowsEventSource;
|
||||
#[cfg(feature = "event-stream")]
|
||||
use crate::event::sys::Waker;
|
||||
use crate::event::{filter::Filter, source::EventSource, timeout::PollTimeout, InternalEvent};
|
||||
|
||||
/// Can be used to read `InternalEvent`s.
|
||||
pub(crate) struct InternalEventReader {
|
||||
events: VecDeque<InternalEvent>,
|
||||
source: Option<Box<dyn EventSource>>,
|
||||
skipped_events: Vec<InternalEvent>,
|
||||
}
|
||||
|
||||
impl Default for InternalEventReader {
|
||||
fn default() -> Self {
|
||||
#[cfg(windows)]
|
||||
let source = WindowsEventSource::new();
|
||||
#[cfg(unix)]
|
||||
let source = UnixInternalEventSource::new();
|
||||
|
||||
let source = source.ok().map(|x| Box::new(x) as Box<dyn EventSource>);
|
||||
|
||||
InternalEventReader {
|
||||
source,
|
||||
events: VecDeque::with_capacity(32),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalEventReader {
|
||||
/// Returns a `Waker` allowing to wake/force the `poll` method to return `Ok(false)`.
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub(crate) fn waker(&self) -> Waker {
|
||||
self.source.as_ref().expect("reader source not set").waker()
|
||||
}
|
||||
|
||||
pub(crate) fn poll<F>(&mut self, timeout: Option<Duration>, filter: &F) -> io::Result<bool>
|
||||
where
|
||||
F: Filter,
|
||||
{
|
||||
for event in &self.events {
|
||||
if filter.eval(event) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
let event_source = match self.source.as_mut() {
|
||||
Some(source) => source,
|
||||
None => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Failed to initialize input reader",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let poll_timeout = PollTimeout::new(timeout);
|
||||
|
||||
loop {
|
||||
let maybe_event = match event_source.try_read(poll_timeout.leftover()) {
|
||||
Ok(None) => None,
|
||||
Ok(Some(event)) => {
|
||||
if filter.eval(&event) {
|
||||
Some(event)
|
||||
} else {
|
||||
self.skipped_events.push(event);
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
if poll_timeout.elapsed() || maybe_event.is_some() {
|
||||
self.events.extend(self.skipped_events.drain(..));
|
||||
|
||||
if let Some(event) = maybe_event {
|
||||
self.events.push_front(event);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read<F>(&mut self, filter: &F) -> io::Result<InternalEvent>
|
||||
where
|
||||
F: Filter,
|
||||
{
|
||||
let mut skipped_events = VecDeque::new();
|
||||
|
||||
loop {
|
||||
while let Some(event) = self.events.pop_front() {
|
||||
if filter.eval(&event) {
|
||||
while let Some(event) = skipped_events.pop_front() {
|
||||
self.events.push_back(event);
|
||||
}
|
||||
|
||||
return Ok(event);
|
||||
} else {
|
||||
// We can not directly write events back to `self.events`.
|
||||
// If we did, we would put our self's into an endless loop
|
||||
// that would enqueue -> dequeue -> enqueue etc.
|
||||
// This happens because `poll` in this function will always return true if there are events in it's.
|
||||
// And because we just put the non-fulfilling event there this is going to be the case.
|
||||
// Instead we can store them into the temporary buffer,
|
||||
// and then when the filter is fulfilled write all events back in order.
|
||||
skipped_events.push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.poll(None, filter)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io;
|
||||
use std::{collections::VecDeque, time::Duration};
|
||||
|
||||
#[cfg(unix)]
|
||||
use super::super::filter::CursorPositionFilter;
|
||||
use super::{super::Event, EventSource, Filter, InternalEvent, InternalEventReader};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InternalEventFilter;
|
||||
|
||||
impl Filter for InternalEventFilter {
|
||||
fn eval(&self, _: &InternalEvent) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_fails_without_event_source() {
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: None,
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert!(reader.poll(None, &InternalEventFilter).is_err());
|
||||
assert!(reader
|
||||
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
|
||||
.is_err());
|
||||
assert!(reader
|
||||
.poll(Some(Duration::from_secs(10)), &InternalEventFilter)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_returns_true_for_matching_event_in_queue_at_front() {
|
||||
let mut reader = InternalEventReader {
|
||||
events: vec![InternalEvent::Event(Event::Resize(10, 10))].into(),
|
||||
source: None,
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert!(reader.poll(None, &InternalEventFilter).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_poll_returns_true_for_matching_event_in_queue_at_back() {
|
||||
let mut reader = InternalEventReader {
|
||||
events: vec![
|
||||
InternalEvent::Event(Event::Resize(10, 10)),
|
||||
InternalEvent::CursorPosition(10, 20),
|
||||
]
|
||||
.into(),
|
||||
source: None,
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert!(reader.poll(None, &CursorPositionFilter).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_returns_matching_event_in_queue_at_front() {
|
||||
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: vec![EVENT].into(),
|
||||
source: None,
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_read_returns_matching_event_in_queue_at_back() {
|
||||
const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: vec![InternalEvent::Event(Event::Resize(10, 10)), CURSOR_EVENT].into(),
|
||||
source: None,
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_read_does_not_consume_skipped_event() {
|
||||
const SKIPPED_EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: vec![SKIPPED_EVENT, CURSOR_EVENT].into(),
|
||||
source: None,
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT);
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), SKIPPED_EVENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_timeouts_if_source_has_no_events() {
|
||||
let source = FakeSource::default();
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert!(!reader
|
||||
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_returns_true_if_source_has_at_least_one_event() {
|
||||
let source = FakeSource::with_events(&[InternalEvent::Event(Event::Resize(10, 10))]);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert!(reader.poll(None, &InternalEventFilter).unwrap());
|
||||
assert!(reader
|
||||
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reads_returns_event_if_source_has_at_least_one_event() {
|
||||
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
|
||||
let source = FakeSource::with_events(&[EVENT]);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_returns_events_if_source_has_events() {
|
||||
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
|
||||
let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_returns_false_after_all_source_events_are_consumed() {
|
||||
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
|
||||
let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert!(!reader
|
||||
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_propagates_error() {
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(FakeSource::new(&[]))),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
reader
|
||||
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
|
||||
.err()
|
||||
.map(|e| format!("{:?}", &e.kind())),
|
||||
Some(format!("{:?}", io::ErrorKind::Other))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_propagates_error() {
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(FakeSource::new(&[]))),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
reader
|
||||
.read(&InternalEventFilter)
|
||||
.err()
|
||||
.map(|e| format!("{:?}", &e.kind())),
|
||||
Some(format!("{:?}", io::ErrorKind::Other))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_poll_continues_after_error() {
|
||||
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
|
||||
let source = FakeSource::new(&[EVENT, EVENT]);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert!(reader.read(&InternalEventFilter).is_err());
|
||||
assert!(reader
|
||||
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
|
||||
.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_continues_after_error() {
|
||||
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
|
||||
|
||||
let source = FakeSource::new(&[EVENT, EVENT]);
|
||||
|
||||
let mut reader = InternalEventReader {
|
||||
events: VecDeque::new(),
|
||||
source: Some(Box::new(source)),
|
||||
skipped_events: Vec::with_capacity(32),
|
||||
};
|
||||
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
assert!(reader.read(&InternalEventFilter).is_err());
|
||||
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeSource {
|
||||
events: VecDeque<InternalEvent>,
|
||||
error: Option<io::Error>,
|
||||
}
|
||||
|
||||
impl FakeSource {
|
||||
fn new(events: &[InternalEvent]) -> FakeSource {
|
||||
FakeSource {
|
||||
events: events.to_vec().into(),
|
||||
error: Some(io::Error::new(io::ErrorKind::Other, "")),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_events(events: &[InternalEvent]) -> FakeSource {
|
||||
FakeSource {
|
||||
events: events.to_vec().into(),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for FakeSource {
|
||||
fn try_read(&mut self, _timeout: Option<Duration>) -> io::Result<Option<InternalEvent>> {
|
||||
// Return error if set in case there's just one remaining event
|
||||
if self.events.len() == 1 {
|
||||
if let Some(error) = self.error.take() {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Return all events from the queue
|
||||
if let Some(event) = self.events.pop_front() {
|
||||
return Ok(Some(event));
|
||||
}
|
||||
|
||||
// Return error if there're no more events
|
||||
if let Some(error) = self.error.take() {
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// Timeout
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
fn waker(&self) -> super::super::sys::Waker {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
use std::{io, time::Duration};
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
use super::sys::Waker;
|
||||
use super::InternalEvent;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) mod unix;
|
||||
#[cfg(windows)]
|
||||
pub(crate) mod windows;
|
||||
|
||||
/// An interface for trying to read an `InternalEvent` within an optional `Duration`.
|
||||
pub(crate) trait EventSource: Sync + Send {
|
||||
/// Tries to read an `InternalEvent` within the given duration.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `timeout` - `None` block indefinitely until an event is available, `Some(duration)` blocks
|
||||
/// for the given timeout
|
||||
///
|
||||
/// Returns `Ok(None)` if there's no event available and timeout expires.
|
||||
fn try_read(&mut self, timeout: Option<Duration>) -> io::Result<Option<InternalEvent>>;
|
||||
|
||||
/// Returns a `Waker` allowing to wake/force the `try_read` method to return `Ok(None)`.
|
||||
#[cfg(feature = "event-stream")]
|
||||
fn waker(&self) -> Waker;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#[cfg(feature = "use-dev-tty")]
|
||||
pub(crate) mod tty;
|
||||
|
||||
#[cfg(not(feature = "use-dev-tty"))]
|
||||
pub(crate) mod mio;
|
||||
|
||||
#[cfg(feature = "use-dev-tty")]
|
||||
pub(crate) use self::tty::UnixInternalEventSource;
|
||||
|
||||
#[cfg(not(feature = "use-dev-tty"))]
|
||||
pub(crate) use self::mio::UnixInternalEventSource;
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
use std::{collections::VecDeque, io, time::Duration};
|
||||
|
||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
|
||||
use signal_hook_mio::v1_0::Signals;
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
use crate::event::sys::Waker;
|
||||
use crate::event::{
|
||||
source::EventSource, sys::unix::parse::parse_event, timeout::PollTimeout, Event, InternalEvent,
|
||||
};
|
||||
use crate::terminal::sys::file_descriptor::{tty_fd, FileDesc};
|
||||
|
||||
// Tokens to identify file descriptor
|
||||
const TTY_TOKEN: Token = Token(0);
|
||||
const SIGNAL_TOKEN: Token = Token(1);
|
||||
#[cfg(feature = "event-stream")]
|
||||
const WAKE_TOKEN: Token = Token(2);
|
||||
|
||||
// I (@zrzka) wasn't able to read more than 1_022 bytes when testing
|
||||
// reading on macOS/Linux -> we don't need bigger buffer and 1k of bytes
|
||||
// is enough.
|
||||
const TTY_BUFFER_SIZE: usize = 1_024;
|
||||
|
||||
pub(crate) struct UnixInternalEventSource {
|
||||
poll: Poll,
|
||||
events: Events,
|
||||
parser: Parser,
|
||||
tty_buffer: [u8; TTY_BUFFER_SIZE],
|
||||
tty_fd: FileDesc<'static>,
|
||||
signals: Signals,
|
||||
#[cfg(feature = "event-stream")]
|
||||
waker: Waker,
|
||||
}
|
||||
|
||||
impl UnixInternalEventSource {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
UnixInternalEventSource::from_file_descriptor(tty_fd()?)
|
||||
}
|
||||
|
||||
pub(crate) fn from_file_descriptor(input_fd: FileDesc<'static>) -> io::Result<Self> {
|
||||
let poll = Poll::new()?;
|
||||
let registry = poll.registry();
|
||||
|
||||
let tty_raw_fd = input_fd.raw_fd();
|
||||
let mut tty_ev = SourceFd(&tty_raw_fd);
|
||||
registry.register(&mut tty_ev, TTY_TOKEN, Interest::READABLE)?;
|
||||
|
||||
let mut signals = Signals::new([signal_hook::consts::SIGWINCH])?;
|
||||
registry.register(&mut signals, SIGNAL_TOKEN, Interest::READABLE)?;
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
let waker = Waker::new(registry, WAKE_TOKEN)?;
|
||||
|
||||
Ok(UnixInternalEventSource {
|
||||
poll,
|
||||
events: Events::with_capacity(3),
|
||||
parser: Parser::default(),
|
||||
tty_buffer: [0u8; TTY_BUFFER_SIZE],
|
||||
tty_fd: input_fd,
|
||||
signals,
|
||||
#[cfg(feature = "event-stream")]
|
||||
waker,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for UnixInternalEventSource {
|
||||
fn try_read(&mut self, timeout: Option<Duration>) -> io::Result<Option<InternalEvent>> {
|
||||
if let Some(event) = self.parser.next() {
|
||||
return Ok(Some(event));
|
||||
}
|
||||
|
||||
let timeout = PollTimeout::new(timeout);
|
||||
|
||||
loop {
|
||||
if let Err(e) = self.poll.poll(&mut self.events, timeout.leftover()) {
|
||||
// Mio will throw an interrupted error in case of cursor position retrieval. We need to retry until it succeeds.
|
||||
// Previous versions of Mio (< 0.7) would automatically retry the poll call if it was interrupted (if EINTR was returned).
|
||||
// https://docs.rs/mio/0.7.0/mio/struct.Poll.html#notes
|
||||
if e.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
if self.events.is_empty() {
|
||||
// No readiness events = timeout
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
for token in self.events.iter().map(|x| x.token()) {
|
||||
match token {
|
||||
TTY_TOKEN => {
|
||||
loop {
|
||||
match self.tty_fd.read(&mut self.tty_buffer) {
|
||||
Ok(read_count) => {
|
||||
if read_count > 0 {
|
||||
self.parser.advance(
|
||||
&self.tty_buffer[..read_count],
|
||||
read_count == TTY_BUFFER_SIZE,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// No more data to read at the moment. We will receive another event
|
||||
if e.kind() == io::ErrorKind::WouldBlock {
|
||||
break;
|
||||
}
|
||||
// once more data is available to read.
|
||||
else if e.kind() == io::ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(event) = self.parser.next() {
|
||||
return Ok(Some(event));
|
||||
}
|
||||
}
|
||||
}
|
||||
SIGNAL_TOKEN => {
|
||||
if self.signals.pending().next() == Some(signal_hook::consts::SIGWINCH) {
|
||||
// TODO Should we remove tput?
|
||||
//
|
||||
// This can take a really long time, because terminal::size can
|
||||
// launch new process (tput) and then it parses its output. It's
|
||||
// not a really long time from the absolute time point of view, but
|
||||
// it's a really long time from the mio, async-std/tokio executor, ...
|
||||
// point of view.
|
||||
let new_size = crate::terminal::size()?;
|
||||
return Ok(Some(InternalEvent::Event(Event::Resize(
|
||||
new_size.0, new_size.1,
|
||||
))));
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "event-stream")]
|
||||
WAKE_TOKEN => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Interrupted,
|
||||
"Poll operation was woken up by `Waker::wake`",
|
||||
));
|
||||
}
|
||||
_ => unreachable!("Synchronize Evented handle registration & token handling"),
|
||||
}
|
||||
}
|
||||
|
||||
// Processing above can take some time, check if timeout expired
|
||||
if timeout.elapsed() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
fn waker(&self) -> Waker {
|
||||
self.waker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Following `Parser` structure exists for two reasons:
|
||||
//
|
||||
// * mimic anes Parser interface
|
||||
// * move the advancing, parsing, ... stuff out of the `try_read` method
|
||||
//
|
||||
#[derive(Debug)]
|
||||
struct Parser {
|
||||
buffer: Vec<u8>,
|
||||
internal_events: VecDeque<InternalEvent>,
|
||||
}
|
||||
|
||||
impl Default for Parser {
|
||||
fn default() -> Self {
|
||||
Parser {
|
||||
// This buffer is used for -> 1 <- ANSI escape sequence. Are we
|
||||
// aware of any ANSI escape sequence that is bigger? Can we make
|
||||
// it smaller?
|
||||
//
|
||||
// Probably not worth spending more time on this as "there's a plan"
|
||||
// to use the anes crate parser.
|
||||
buffer: Vec::with_capacity(256),
|
||||
// TTY_BUFFER_SIZE is 1_024 bytes. How many ANSI escape sequences can
|
||||
// fit? What is an average sequence length? Let's guess here
|
||||
// and say that the average ANSI escape sequence length is 8 bytes. Thus
|
||||
// the buffer size should be 1024/8=128 to avoid additional allocations
|
||||
// when processing large amounts of data.
|
||||
//
|
||||
// There's no need to make it bigger, because when you look at the `try_read`
|
||||
// method implementation, all events are consumed before the next TTY_BUFFER
|
||||
// is processed -> events pushed.
|
||||
internal_events: VecDeque::with_capacity(128),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn advance(&mut self, buffer: &[u8], more: bool) {
|
||||
for (idx, byte) in buffer.iter().enumerate() {
|
||||
let more = idx + 1 < buffer.len() || more;
|
||||
|
||||
self.buffer.push(*byte);
|
||||
|
||||
match parse_event(&self.buffer, more) {
|
||||
Ok(Some(ie)) => {
|
||||
self.internal_events.push_back(ie);
|
||||
self.buffer.clear();
|
||||
}
|
||||
Ok(None) => {
|
||||
// Event can't be parsed, because we don't have enough bytes for
|
||||
// the current sequence. Keep the buffer and process next bytes.
|
||||
}
|
||||
Err(_) => {
|
||||
// Event can't be parsed (not enough parameters, parameter is not a number, ...).
|
||||
// Clear the buffer and continue with another sequence.
|
||||
self.buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Parser {
|
||||
type Item = InternalEvent;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.internal_events.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Parser;
|
||||
use crate::event::{Event, InternalEvent, KeyCode};
|
||||
|
||||
#[test]
|
||||
fn color_scheme_notifications_preserve_following_keys() {
|
||||
for report in [b"\x1B[?997;1n", b"\x1B[?997;2n"] {
|
||||
for split in 1..report.len() {
|
||||
let mut parser = Parser::default();
|
||||
parser.advance(&report[..split], true);
|
||||
assert_eq!(parser.next(), None);
|
||||
parser.advance(&report[split..], false);
|
||||
parser.advance(b"x", false);
|
||||
assert_eq!(
|
||||
parser.next(),
|
||||
Some(InternalEvent::Event(Event::ColorSchemeChanged))
|
||||
);
|
||||
assert_eq!(
|
||||
parser.next(),
|
||||
Some(InternalEvent::Event(Event::Key(KeyCode::Char('x').into())))
|
||||
);
|
||||
assert_eq!(parser.next(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_color_scheme_notifications_do_not_trap_keys() {
|
||||
for report in [
|
||||
b"\x1B[?997n".as_slice(),
|
||||
b"\x1B[?997;0n",
|
||||
b"\x1B[?997;3n",
|
||||
b"\x1B[?997;01n",
|
||||
b"\x1B[?997;1;2n",
|
||||
b"\x1B[?997;1A",
|
||||
] {
|
||||
let mut parser = Parser::default();
|
||||
parser.advance(report, false);
|
||||
assert_eq!(parser.next(), None);
|
||||
parser.advance(b"x", false);
|
||||
assert_eq!(
|
||||
parser.next(),
|
||||
Some(InternalEvent::Event(Event::Key(KeyCode::Char('x').into())))
|
||||
);
|
||||
assert_eq!(parser.next(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
#[cfg(feature = "libc")]
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::{collections::VecDeque, io, os::unix::net::UnixStream, time::Duration};
|
||||
|
||||
#[cfg(not(feature = "libc"))]
|
||||
use rustix::fd::{AsFd, AsRawFd};
|
||||
|
||||
use signal_hook::low_level::pipe;
|
||||
|
||||
use crate::event::timeout::PollTimeout;
|
||||
use crate::event::Event;
|
||||
use filedescriptor::{poll, pollfd, POLLIN};
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
use crate::event::sys::Waker;
|
||||
use crate::event::{source::EventSource, sys::unix::parse::parse_event, InternalEvent};
|
||||
use crate::terminal::sys::file_descriptor::{tty_fd, FileDesc};
|
||||
|
||||
/// Holds a prototypical Waker and a receiver we can wait on when doing select().
|
||||
#[cfg(feature = "event-stream")]
|
||||
struct WakePipe {
|
||||
receiver: UnixStream,
|
||||
waker: Waker,
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
impl WakePipe {
|
||||
fn new() -> io::Result<Self> {
|
||||
let (receiver, sender) = nonblocking_unix_pair()?;
|
||||
Ok(WakePipe {
|
||||
receiver,
|
||||
waker: Waker::new(sender),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// I (@zrzka) wasn't able to read more than 1_022 bytes when testing
|
||||
// reading on macOS/Linux -> we don't need bigger buffer and 1k of bytes
|
||||
// is enough.
|
||||
const TTY_BUFFER_SIZE: usize = 1_024;
|
||||
|
||||
pub(crate) struct UnixInternalEventSource {
|
||||
parser: Parser,
|
||||
tty_buffer: [u8; TTY_BUFFER_SIZE],
|
||||
tty: FileDesc<'static>,
|
||||
winch_signal_receiver: UnixStream,
|
||||
#[cfg(feature = "event-stream")]
|
||||
wake_pipe: WakePipe,
|
||||
}
|
||||
|
||||
fn nonblocking_unix_pair() -> io::Result<(UnixStream, UnixStream)> {
|
||||
let (receiver, sender) = UnixStream::pair()?;
|
||||
receiver.set_nonblocking(true)?;
|
||||
sender.set_nonblocking(true)?;
|
||||
Ok((receiver, sender))
|
||||
}
|
||||
|
||||
impl UnixInternalEventSource {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
UnixInternalEventSource::from_file_descriptor(tty_fd()?)
|
||||
}
|
||||
|
||||
pub(crate) fn from_file_descriptor(input_fd: FileDesc<'static>) -> io::Result<Self> {
|
||||
Ok(UnixInternalEventSource {
|
||||
parser: Parser::default(),
|
||||
tty_buffer: [0u8; TTY_BUFFER_SIZE],
|
||||
tty: input_fd,
|
||||
winch_signal_receiver: {
|
||||
let (receiver, sender) = nonblocking_unix_pair()?;
|
||||
// Unregistering is unnecessary because EventSource is a singleton
|
||||
#[cfg(feature = "libc")]
|
||||
pipe::register(libc::SIGWINCH, sender)?;
|
||||
#[cfg(not(feature = "libc"))]
|
||||
pipe::register(rustix::process::Signal::WINCH.as_raw(), sender)?;
|
||||
receiver
|
||||
},
|
||||
#[cfg(feature = "event-stream")]
|
||||
wake_pipe: WakePipe::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// read_complete reads from a non-blocking file descriptor
|
||||
/// until the buffer is full or it would block.
|
||||
///
|
||||
/// Similar to `std::io::Read::read_to_end`, except this function
|
||||
/// only fills the given buffer and does not read beyond that.
|
||||
fn read_complete(fd: &FileDesc, buf: &mut [u8]) -> io::Result<usize> {
|
||||
loop {
|
||||
match fd.read(buf) {
|
||||
Ok(x) => return Ok(x),
|
||||
Err(e) => match e.kind() {
|
||||
io::ErrorKind::WouldBlock => return Ok(0),
|
||||
io::ErrorKind::Interrupted => continue,
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for UnixInternalEventSource {
|
||||
fn try_read(&mut self, timeout: Option<Duration>) -> io::Result<Option<InternalEvent>> {
|
||||
let timeout = PollTimeout::new(timeout);
|
||||
|
||||
fn make_pollfd<F: AsRawFd>(fd: &F) -> pollfd {
|
||||
pollfd {
|
||||
fd: fd.as_raw_fd(),
|
||||
events: POLLIN,
|
||||
revents: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "event-stream"))]
|
||||
let mut fds = [
|
||||
make_pollfd(&self.tty),
|
||||
make_pollfd(&self.winch_signal_receiver),
|
||||
];
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
let mut fds = [
|
||||
make_pollfd(&self.tty),
|
||||
make_pollfd(&self.winch_signal_receiver),
|
||||
make_pollfd(&self.wake_pipe.receiver),
|
||||
];
|
||||
|
||||
while timeout.leftover().map_or(true, |t| !t.is_zero()) {
|
||||
// check if there are buffered events from the last read
|
||||
if let Some(event) = self.parser.next() {
|
||||
return Ok(Some(event));
|
||||
}
|
||||
match poll(&mut fds, timeout.leftover()) {
|
||||
Err(filedescriptor::Error::Poll(e)) | Err(filedescriptor::Error::Io(e)) => {
|
||||
match e.kind() {
|
||||
// retry on EINTR
|
||||
io::ErrorKind::Interrupted => continue,
|
||||
_ => return Err(e),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("got unexpected error while polling: {:?}", e),
|
||||
))
|
||||
}
|
||||
Ok(_) => (),
|
||||
};
|
||||
if fds[0].revents & POLLIN != 0 {
|
||||
loop {
|
||||
let read_count = read_complete(&self.tty, &mut self.tty_buffer)?;
|
||||
if read_count > 0 {
|
||||
self.parser.advance(
|
||||
&self.tty_buffer[..read_count],
|
||||
read_count == TTY_BUFFER_SIZE,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(event) = self.parser.next() {
|
||||
return Ok(Some(event));
|
||||
}
|
||||
|
||||
if read_count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if fds[1].revents & POLLIN != 0 {
|
||||
#[cfg(feature = "libc")]
|
||||
let fd = FileDesc::new(self.winch_signal_receiver.as_raw_fd(), false);
|
||||
#[cfg(not(feature = "libc"))]
|
||||
let fd = FileDesc::Borrowed(self.winch_signal_receiver.as_fd());
|
||||
// drain the pipe
|
||||
while read_complete(&fd, &mut [0; 1024])? != 0 {}
|
||||
// TODO Should we remove tput?
|
||||
//
|
||||
// This can take a really long time, because terminal::size can
|
||||
// launch new process (tput) and then it parses its output. It's
|
||||
// not a really long time from the absolute time point of view, but
|
||||
// it's a really long time from the mio, async-std/tokio executor, ...
|
||||
// point of view.
|
||||
let new_size = crate::terminal::size()?;
|
||||
return Ok(Some(InternalEvent::Event(Event::Resize(
|
||||
new_size.0, new_size.1,
|
||||
))));
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
if fds[2].revents & POLLIN != 0 {
|
||||
#[cfg(feature = "libc")]
|
||||
let fd = FileDesc::new(self.wake_pipe.receiver.as_raw_fd(), false);
|
||||
#[cfg(not(feature = "libc"))]
|
||||
let fd = FileDesc::Borrowed(self.wake_pipe.receiver.as_fd());
|
||||
// drain the pipe
|
||||
while read_complete(&fd, &mut [0; 1024])? != 0 {}
|
||||
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Interrupted,
|
||||
"Poll operation was woken up by `Waker::wake`",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
fn waker(&self) -> Waker {
|
||||
self.wake_pipe.waker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Following `Parser` structure exists for two reasons:
|
||||
//
|
||||
// * mimic anes Parser interface
|
||||
// * move the advancing, parsing, ... stuff out of the `try_read` method
|
||||
//
|
||||
#[derive(Debug)]
|
||||
struct Parser {
|
||||
buffer: Vec<u8>,
|
||||
internal_events: VecDeque<InternalEvent>,
|
||||
}
|
||||
|
||||
impl Default for Parser {
|
||||
fn default() -> Self {
|
||||
Parser {
|
||||
// This buffer is used for -> 1 <- ANSI escape sequence. Are we
|
||||
// aware of any ANSI escape sequence that is bigger? Can we make
|
||||
// it smaller?
|
||||
//
|
||||
// Probably not worth spending more time on this as "there's a plan"
|
||||
// to use the anes crate parser.
|
||||
buffer: Vec::with_capacity(256),
|
||||
// TTY_BUFFER_SIZE is 1_024 bytes. How many ANSI escape sequences can
|
||||
// fit? What is an average sequence length? Let's guess here
|
||||
// and say that the average ANSI escape sequence length is 8 bytes. Thus
|
||||
// the buffer size should be 1024/8=128 to avoid additional allocations
|
||||
// when processing large amounts of data.
|
||||
//
|
||||
// There's no need to make it bigger, because when you look at the `try_read`
|
||||
// method implementation, all events are consumed before the next TTY_BUFFER
|
||||
// is processed -> events pushed.
|
||||
internal_events: VecDeque::with_capacity(128),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn advance(&mut self, buffer: &[u8], more: bool) {
|
||||
for (idx, byte) in buffer.iter().enumerate() {
|
||||
let more = idx + 1 < buffer.len() || more;
|
||||
|
||||
self.buffer.push(*byte);
|
||||
|
||||
match parse_event(&self.buffer, more) {
|
||||
Ok(Some(ie)) => {
|
||||
self.internal_events.push_back(ie);
|
||||
self.buffer.clear();
|
||||
}
|
||||
Ok(None) => {
|
||||
// Event can't be parsed, because we don't have enough bytes for
|
||||
// the current sequence. Keep the buffer and process next bytes.
|
||||
}
|
||||
Err(_) => {
|
||||
// Event can't be parsed (not enough parameters, parameter is not a number, ...).
|
||||
// Clear the buffer and continue with another sequence.
|
||||
self.buffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Parser {
|
||||
type Item = InternalEvent;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.internal_events.pop_front()
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use crossterm_winapi::{Console, Handle, InputRecord};
|
||||
|
||||
use crate::event::{
|
||||
sys::windows::{parse::MouseButtonsPressed, poll::WinApiPoll},
|
||||
Event,
|
||||
};
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
use crate::event::sys::Waker;
|
||||
use crate::event::{
|
||||
source::EventSource,
|
||||
sys::windows::parse::{handle_key_event, handle_mouse_event},
|
||||
timeout::PollTimeout,
|
||||
InternalEvent,
|
||||
};
|
||||
|
||||
pub(crate) struct WindowsEventSource {
|
||||
console: Console,
|
||||
poll: WinApiPoll,
|
||||
surrogate_buffer: Option<u16>,
|
||||
mouse_buttons_pressed: MouseButtonsPressed,
|
||||
}
|
||||
|
||||
impl WindowsEventSource {
|
||||
pub(crate) fn new() -> std::io::Result<WindowsEventSource> {
|
||||
let console = Console::from(Handle::current_in_handle()?);
|
||||
Ok(WindowsEventSource {
|
||||
console,
|
||||
|
||||
#[cfg(not(feature = "event-stream"))]
|
||||
poll: WinApiPoll::new(),
|
||||
#[cfg(feature = "event-stream")]
|
||||
poll: WinApiPoll::new()?,
|
||||
|
||||
surrogate_buffer: None,
|
||||
mouse_buttons_pressed: MouseButtonsPressed::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for WindowsEventSource {
|
||||
fn try_read(&mut self, timeout: Option<Duration>) -> std::io::Result<Option<InternalEvent>> {
|
||||
let poll_timeout = PollTimeout::new(timeout);
|
||||
|
||||
loop {
|
||||
if let Some(event_ready) = self.poll.poll(poll_timeout.leftover())? {
|
||||
let number = self.console.number_of_console_input_events()?;
|
||||
if event_ready && number != 0 {
|
||||
let event = match self.console.read_single_input_event()? {
|
||||
InputRecord::KeyEvent(record) => {
|
||||
handle_key_event(record, &mut self.surrogate_buffer)
|
||||
}
|
||||
InputRecord::MouseEvent(record) => {
|
||||
let mouse_event =
|
||||
handle_mouse_event(record, &self.mouse_buttons_pressed);
|
||||
self.mouse_buttons_pressed = MouseButtonsPressed {
|
||||
left: record.button_state.left_button(),
|
||||
right: record.button_state.right_button(),
|
||||
middle: record.button_state.middle_button(),
|
||||
};
|
||||
|
||||
mouse_event
|
||||
}
|
||||
InputRecord::WindowBufferSizeEvent(record) => {
|
||||
// windows starts counting at 0, unix at 1, add one to replicate unix behaviour.
|
||||
Some(Event::Resize(
|
||||
(record.size.x as i32 + 1) as u16,
|
||||
(record.size.y as i32 + 1) as u16,
|
||||
))
|
||||
}
|
||||
InputRecord::FocusEvent(record) => {
|
||||
let event = if record.set_focus {
|
||||
Event::FocusGained
|
||||
} else {
|
||||
Event::FocusLost
|
||||
};
|
||||
Some(event)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(event) = event {
|
||||
return Ok(Some(InternalEvent::Event(event)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if poll_timeout.elapsed() {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
fn waker(&self) -> Waker {
|
||||
self.poll.waker()
|
||||
}
|
||||
}
|
||||
Vendored
+146
@@ -0,0 +1,146 @@
|
||||
use std::{
|
||||
io,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{self, SyncSender},
|
||||
Arc,
|
||||
},
|
||||
task::{Context, Poll},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use futures_core::stream::Stream;
|
||||
|
||||
use crate::event::{
|
||||
filter::EventFilter, lock_internal_event_reader, poll_internal, read_internal, sys::Waker,
|
||||
Event, InternalEvent,
|
||||
};
|
||||
|
||||
/// A stream of `Result<Event>`.
|
||||
///
|
||||
/// **This type is not available by default. You have to use the `event-stream` feature flag
|
||||
/// to make it available.**
|
||||
///
|
||||
/// It implements the [Stream](futures_core::stream::Stream)
|
||||
/// trait and allows you to receive [`Event`]s with [`async-std`](https://crates.io/crates/async-std)
|
||||
/// or [`tokio`](https://crates.io/crates/tokio) crates.
|
||||
///
|
||||
/// Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder to see how to use
|
||||
/// it (`event-stream-*`).
|
||||
#[derive(Debug)]
|
||||
pub struct EventStream {
|
||||
poll_internal_waker: Waker,
|
||||
stream_wake_task_executed: Arc<AtomicBool>,
|
||||
stream_wake_task_should_shutdown: Arc<AtomicBool>,
|
||||
task_sender: SyncSender<Task>,
|
||||
}
|
||||
|
||||
impl Default for EventStream {
|
||||
fn default() -> Self {
|
||||
let (task_sender, receiver) = mpsc::sync_channel::<Task>(1);
|
||||
|
||||
thread::spawn(move || {
|
||||
while let Ok(task) = receiver.recv() {
|
||||
loop {
|
||||
if let Ok(true) = poll_internal(None, &EventFilter) {
|
||||
break;
|
||||
}
|
||||
|
||||
if task.stream_wake_task_should_shutdown.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
task.stream_wake_task_executed
|
||||
.store(false, Ordering::SeqCst);
|
||||
task.stream_waker.wake();
|
||||
}
|
||||
});
|
||||
|
||||
EventStream {
|
||||
poll_internal_waker: lock_internal_event_reader().waker(),
|
||||
stream_wake_task_executed: Arc::new(AtomicBool::new(false)),
|
||||
stream_wake_task_should_shutdown: Arc::new(AtomicBool::new(false)),
|
||||
task_sender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventStream {
|
||||
/// Constructs a new instance of `EventStream`.
|
||||
pub fn new() -> EventStream {
|
||||
EventStream::default()
|
||||
}
|
||||
}
|
||||
|
||||
struct Task {
|
||||
stream_waker: std::task::Waker,
|
||||
stream_wake_task_executed: Arc<AtomicBool>,
|
||||
stream_wake_task_should_shutdown: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
// Note to future me
|
||||
//
|
||||
// We need two wakers in order to implement EventStream correctly.
|
||||
//
|
||||
// 1. futures::Stream waker
|
||||
//
|
||||
// Stream::poll_next can return Poll::Pending which means that there's no
|
||||
// event available. We are going to spawn a thread with the
|
||||
// poll_internal(None, &EventFilter) call. This call blocks until an
|
||||
// event is available and then we have to wake up the executor with notification
|
||||
// that the task can be resumed.
|
||||
//
|
||||
// 2. poll_internal waker
|
||||
//
|
||||
// There's no event available, Poll::Pending was returned, stream waker thread
|
||||
// is up and sitting in the poll_internal. User wants to drop the EventStream.
|
||||
// We have to wake up the poll_internal (force it to return Ok(false)) and quit
|
||||
// the thread before we drop.
|
||||
impl Stream for EventStream {
|
||||
type Item = io::Result<Event>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let result = match poll_internal(Some(Duration::from_secs(0)), &EventFilter) {
|
||||
Ok(true) => match read_internal(&EventFilter) {
|
||||
Ok(InternalEvent::Event(event)) => Poll::Ready(Some(Ok(event))),
|
||||
Err(e) => Poll::Ready(Some(Err(e))),
|
||||
#[cfg(unix)]
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Ok(false) => {
|
||||
if !self
|
||||
.stream_wake_task_executed
|
||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
||||
// https://github.com/rust-lang/rust/issues/80486#issuecomment-752244166
|
||||
.unwrap_or_else(|x| x)
|
||||
{
|
||||
let stream_waker = cx.waker().clone();
|
||||
let stream_wake_task_executed = self.stream_wake_task_executed.clone();
|
||||
let stream_wake_task_should_shutdown =
|
||||
self.stream_wake_task_should_shutdown.clone();
|
||||
|
||||
stream_wake_task_should_shutdown.store(false, Ordering::SeqCst);
|
||||
|
||||
let _ = self.task_sender.send(Task {
|
||||
stream_waker,
|
||||
stream_wake_task_executed,
|
||||
stream_wake_task_should_shutdown,
|
||||
});
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
Err(e) => Poll::Ready(Some(Err(e))),
|
||||
};
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventStream {
|
||||
fn drop(&mut self) {
|
||||
self.stream_wake_task_should_shutdown
|
||||
.store(true, Ordering::SeqCst);
|
||||
let _ = self.poll_internal_waker.wake();
|
||||
}
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
#[cfg(all(unix, feature = "event-stream"))]
|
||||
pub(crate) use unix::waker::Waker;
|
||||
#[cfg(all(windows, feature = "event-stream"))]
|
||||
pub(crate) use windows::waker::Waker;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) mod unix;
|
||||
#[cfg(windows)]
|
||||
pub(crate) mod windows;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub(crate) mod waker;
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
pub(crate) mod parse;
|
||||
+1514
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
#[cfg(feature = "use-dev-tty")]
|
||||
pub(crate) mod tty;
|
||||
|
||||
#[cfg(not(feature = "use-dev-tty"))]
|
||||
pub(crate) mod mio;
|
||||
|
||||
#[cfg(feature = "use-dev-tty")]
|
||||
pub(crate) use self::tty::Waker;
|
||||
|
||||
#[cfg(not(feature = "use-dev-tty"))]
|
||||
pub(crate) use self::mio::Waker;
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use ::mio::{Registry, Token};
|
||||
|
||||
/// Allows to wake up the `mio::Poll::poll()` method.
|
||||
/// This type wraps `mio::Waker`, for more information see its documentation.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Waker {
|
||||
inner: Arc<Mutex<::mio::Waker>>,
|
||||
}
|
||||
|
||||
impl Waker {
|
||||
/// Create a new `Waker`.
|
||||
pub(crate) fn new(registry: &Registry, waker_token: Token) -> std::io::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: Arc::new(Mutex::new(mio::Waker::new(registry, waker_token)?)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Wake up the [`Poll`] associated with this `Waker`.
|
||||
///
|
||||
/// Readiness is set to `Ready::readable()`.
|
||||
pub(crate) fn wake(&self) -> std::io::Result<()> {
|
||||
self.inner.lock().unwrap().wake()
|
||||
}
|
||||
|
||||
/// Resets the state so the same waker can be reused.
|
||||
///
|
||||
/// This function is not impl
|
||||
#[allow(dead_code, clippy::clippy::unnecessary_wraps)]
|
||||
pub(crate) fn reset(&self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
os::unix::net::UnixStream,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
/// Allows to wake up the EventSource::try_read() method.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Waker {
|
||||
inner: Arc<Mutex<UnixStream>>,
|
||||
}
|
||||
|
||||
impl Waker {
|
||||
/// Create a new `Waker`.
|
||||
pub(crate) fn new(writer: UnixStream) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(writer)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Wake up the [`Poll`] associated with this `Waker`.
|
||||
///
|
||||
/// Readiness is set to `Ready::readable()`.
|
||||
pub(crate) fn wake(&self) -> io::Result<()> {
|
||||
self.inner.lock().unwrap().write_all(&[0])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//! This is a WINDOWS specific implementation for input related action.
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use crossterm_winapi::{ConsoleMode, Handle};
|
||||
|
||||
pub(crate) mod parse;
|
||||
pub(crate) mod poll;
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub(crate) mod waker;
|
||||
|
||||
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
||||
|
||||
/// This is a either `u64::MAX` if it's uninitialized or a valid `u32` that stores the original
|
||||
/// console mode if it's initialized.
|
||||
static ORIGINAL_CONSOLE_MODE: AtomicU64 = AtomicU64::new(u64::MAX);
|
||||
|
||||
/// Initializes the default console color. It will will be skipped if it has already been initialized.
|
||||
fn init_original_console_mode(original_mode: u32) {
|
||||
let _ = ORIGINAL_CONSOLE_MODE.compare_exchange(
|
||||
u64::MAX,
|
||||
u64::from(original_mode),
|
||||
Ordering::Relaxed,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic.
|
||||
fn original_console_mode() -> std::io::Result<u32> {
|
||||
u32::try_from(ORIGINAL_CONSOLE_MODE.load(Ordering::Relaxed))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Initial console modes not set"))
|
||||
}
|
||||
|
||||
pub(crate) fn enable_mouse_capture() -> std::io::Result<()> {
|
||||
let mode = ConsoleMode::from(Handle::current_in_handle()?);
|
||||
init_original_console_mode(mode.mode()?);
|
||||
mode.set_mode(ENABLE_MOUSE_MODE)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn disable_mouse_capture() -> std::io::Result<()> {
|
||||
let mode = ConsoleMode::from(Handle::current_in_handle()?);
|
||||
mode.set_mode(original_console_mode()?)?;
|
||||
Ok(())
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, ScreenBuffer};
|
||||
use winapi::um::{
|
||||
wincon::{
|
||||
CAPSLOCK_ON, LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
|
||||
SHIFT_PRESSED,
|
||||
},
|
||||
winuser::{
|
||||
GetForegroundWindow, GetKeyboardLayout, GetWindowThreadProcessId, ToUnicodeEx, 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_NUMPAD0, VK_NUMPAD9, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT,
|
||||
VK_TAB, VK_UP,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::event::{
|
||||
Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MouseButtonsPressed {
|
||||
pub(crate) left: bool,
|
||||
pub(crate) right: bool,
|
||||
pub(crate) middle: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn handle_mouse_event(
|
||||
mouse_event: crossterm_winapi::MouseEvent,
|
||||
buttons_pressed: &MouseButtonsPressed,
|
||||
) -> Option<Event> {
|
||||
if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event, buttons_pressed) {
|
||||
return Some(Event::Mouse(event));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
enum WindowsKeyEvent {
|
||||
KeyEvent(KeyEvent),
|
||||
Surrogate(u16),
|
||||
}
|
||||
|
||||
pub(crate) fn handle_key_event(
|
||||
key_event: KeyEventRecord,
|
||||
surrogate_buffer: &mut Option<u16>,
|
||||
) -> Option<Event> {
|
||||
let windows_key_event = parse_key_event_record(&key_event)?;
|
||||
match windows_key_event {
|
||||
WindowsKeyEvent::KeyEvent(key_event) => {
|
||||
// Discard any buffered surrogate value if another valid key event comes before the
|
||||
// next surrogate value.
|
||||
*surrogate_buffer = None;
|
||||
Some(Event::Key(key_event))
|
||||
}
|
||||
WindowsKeyEvent::Surrogate(new_surrogate) => {
|
||||
let ch = handle_surrogate(surrogate_buffer, new_surrogate)?;
|
||||
let modifiers = KeyModifiers::from(&key_event.control_key_state);
|
||||
let key_event = KeyEvent::new(KeyCode::Char(ch), modifiers);
|
||||
Some(Event::Key(key_event))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_surrogate(surrogate_buffer: &mut Option<u16>, new_surrogate: u16) -> Option<char> {
|
||||
match *surrogate_buffer {
|
||||
Some(buffered_surrogate) => {
|
||||
*surrogate_buffer = None;
|
||||
std::char::decode_utf16([buffered_surrogate, new_surrogate])
|
||||
.next()
|
||||
.unwrap()
|
||||
.ok()
|
||||
}
|
||||
None => {
|
||||
*surrogate_buffer = Some(new_surrogate);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
enum CharCase {
|
||||
LowerCase,
|
||||
UpperCase,
|
||||
}
|
||||
|
||||
fn try_ensure_char_case(ch: char, desired_case: CharCase) -> char {
|
||||
match desired_case {
|
||||
CharCase::LowerCase if ch.is_uppercase() => {
|
||||
let mut iter = ch.to_lowercase();
|
||||
// Unwrap is safe; iterator yields one or more chars.
|
||||
let ch_lower = iter.next().unwrap();
|
||||
if iter.next().is_none() {
|
||||
ch_lower
|
||||
} else {
|
||||
ch
|
||||
}
|
||||
}
|
||||
CharCase::UpperCase if ch.is_lowercase() => {
|
||||
let mut iter = ch.to_uppercase();
|
||||
// Unwrap is safe; iterator yields one or more chars.
|
||||
let ch_upper = iter.next().unwrap();
|
||||
if iter.next().is_none() {
|
||||
ch_upper
|
||||
} else {
|
||||
ch
|
||||
}
|
||||
}
|
||||
_ => ch,
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to return the character for a key event accounting for the user's keyboard layout.
|
||||
// The returned character (if any) is capitalized (if applicable) based on shift and capslock state.
|
||||
// Returns None if the key doesn't map to a character or if it is a dead key.
|
||||
// We use the *currently* active keyboard layout (if it can be determined). This layout may not
|
||||
// correspond to the keyboard layout that was active when the user typed their input, since console
|
||||
// applications get their input asynchronously from the terminal. By the time a console application
|
||||
// can process a key input, the user may have changed the active layout. In this case, the character
|
||||
// returned might not correspond to what the user expects, but there is no way for a console
|
||||
// application to know what the keyboard layout actually was for a key event, so this is our best
|
||||
// effort. If a console application processes input in a timely fashion, then it is unlikely that a
|
||||
// user has time to change their keyboard layout before a key event is processed.
|
||||
fn get_char_for_key(key_event: &KeyEventRecord) -> Option<char> {
|
||||
let virtual_key_code = key_event.virtual_key_code as u32;
|
||||
let virtual_scan_code = key_event.virtual_scan_code as u32;
|
||||
let key_state = [0u8; 256];
|
||||
let mut utf16_buf = [0u16, 16];
|
||||
let dont_change_kernel_keyboard_state = 0x4;
|
||||
|
||||
// Best-effort attempt at determining the currently active keyboard layout.
|
||||
// At the time of writing, this works for a console application running in Windows Terminal, but
|
||||
// doesn't work under a Conhost terminal. For Conhost, the window handle returned by
|
||||
// GetForegroundWindow() does not appear to actually be the foreground window which has the
|
||||
// keyboard layout associated with it (or perhaps it is, but also has special protection that
|
||||
// doesn't allow us to query it).
|
||||
// When this determination fails, the returned keyboard layout handle will be null, which is an
|
||||
// acceptable input for ToUnicodeEx, as that argument is optional. In this case ToUnicodeEx
|
||||
// appears to use the keyboard layout associated with the current thread, which will be the
|
||||
// layout that was inherited when the console application started (or possibly when the current
|
||||
// thread was spawned). This is then unfortunately not updated when the user changes their
|
||||
// keyboard layout in the terminal, but it's what we get.
|
||||
let active_keyboard_layout = unsafe {
|
||||
let foreground_window = GetForegroundWindow();
|
||||
let foreground_thread = GetWindowThreadProcessId(foreground_window, std::ptr::null_mut());
|
||||
GetKeyboardLayout(foreground_thread)
|
||||
};
|
||||
|
||||
let ret = unsafe {
|
||||
ToUnicodeEx(
|
||||
virtual_key_code,
|
||||
virtual_scan_code,
|
||||
key_state.as_ptr(),
|
||||
utf16_buf.as_mut_ptr(),
|
||||
utf16_buf.len() as i32,
|
||||
dont_change_kernel_keyboard_state,
|
||||
active_keyboard_layout,
|
||||
)
|
||||
};
|
||||
|
||||
// -1 indicates a dead key.
|
||||
// 0 indicates no character for this key.
|
||||
if ret < 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut ch_iter = std::char::decode_utf16(utf16_buf.into_iter().take(ret as usize));
|
||||
let mut ch = ch_iter.next()?.ok()?;
|
||||
if ch_iter.next().is_some() {
|
||||
// Key doesn't map to a single char.
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_shift_pressed = key_event.control_key_state.has_state(SHIFT_PRESSED);
|
||||
let is_capslock_on = key_event.control_key_state.has_state(CAPSLOCK_ON);
|
||||
let desired_case = if is_shift_pressed ^ is_capslock_on {
|
||||
CharCase::UpperCase
|
||||
} else {
|
||||
CharCase::LowerCase
|
||||
};
|
||||
ch = try_ensure_char_case(ch, desired_case);
|
||||
Some(ch)
|
||||
}
|
||||
|
||||
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<WindowsKeyEvent> {
|
||||
let modifiers = KeyModifiers::from(&key_event.control_key_state);
|
||||
let virtual_key_code = key_event.virtual_key_code as i32;
|
||||
|
||||
// We normally ignore all key release events, but we will make an exception for an Alt key
|
||||
// release if it carries a u_char value, as this indicates an Alt code.
|
||||
let is_alt_code = virtual_key_code == VK_MENU && !key_event.key_down && key_event.u_char != 0;
|
||||
if is_alt_code {
|
||||
let utf16 = key_event.u_char;
|
||||
match utf16 {
|
||||
surrogate @ 0xD800..=0xDFFF => {
|
||||
return Some(WindowsKeyEvent::Surrogate(surrogate));
|
||||
}
|
||||
unicode_scalar_value => {
|
||||
// Unwrap is safe: We tested for surrogate values above and those are the only
|
||||
// u16 values that are invalid when directly interpreted as unicode scalar
|
||||
// values.
|
||||
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
|
||||
let key_code = KeyCode::Char(ch);
|
||||
let kind = if key_event.key_down {
|
||||
KeyEventKind::Press
|
||||
} else {
|
||||
KeyEventKind::Release
|
||||
};
|
||||
let key_event = KeyEvent::new_with_kind(key_code, modifiers, kind);
|
||||
return Some(WindowsKeyEvent::KeyEvent(key_event));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't generate events for numpad key presses when they're producing Alt codes.
|
||||
let is_numpad_numeric_key = (VK_NUMPAD0..=VK_NUMPAD9).contains(&virtual_key_code);
|
||||
let is_only_alt_modifier = modifiers.contains(KeyModifiers::ALT)
|
||||
&& !modifiers.contains(KeyModifiers::SHIFT | KeyModifiers::CONTROL);
|
||||
if is_only_alt_modifier && is_numpad_numeric_key {
|
||||
return None;
|
||||
}
|
||||
|
||||
let parse_result = match virtual_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),
|
||||
VK_TAB if modifiers.contains(KeyModifiers::SHIFT) => Some(KeyCode::BackTab),
|
||||
VK_TAB => Some(KeyCode::Tab),
|
||||
_ => {
|
||||
let utf16 = key_event.u_char;
|
||||
match utf16 {
|
||||
0x00..=0x1f => {
|
||||
// Some key combinations generate either no u_char value or generate control
|
||||
// codes. To deliver back a KeyCode::Char(...) event we want to know which
|
||||
// character the key normally maps to on the user's keyboard layout.
|
||||
// The keys that intentionally generate control codes (ESC, ENTER, TAB, etc.)
|
||||
// are handled by their virtual key codes above.
|
||||
get_char_for_key(key_event).map(KeyCode::Char)
|
||||
}
|
||||
surrogate @ 0xD800..=0xDFFF => {
|
||||
return Some(WindowsKeyEvent::Surrogate(surrogate));
|
||||
}
|
||||
unicode_scalar_value => {
|
||||
// Unwrap is safe: We tested for surrogate values above and those are the only
|
||||
// u16 values that are invalid when directly interpreted as unicode scalar
|
||||
// values.
|
||||
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
|
||||
Some(KeyCode::Char(ch))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(key_code) = parse_result {
|
||||
let kind = if key_event.key_down {
|
||||
KeyEventKind::Press
|
||||
} else {
|
||||
KeyEventKind::Release
|
||||
};
|
||||
let key_event = KeyEvent::new_with_kind(key_code, modifiers, kind);
|
||||
return Some(WindowsKeyEvent::KeyEvent(key_event));
|
||||
}
|
||||
|
||||
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) -> std::io::Result<i16> {
|
||||
let window_size = ScreenBuffer::current()?.info()?.terminal_window();
|
||||
Ok(y - window_size.top)
|
||||
}
|
||||
|
||||
fn parse_mouse_event_record(
|
||||
event: &crossterm_winapi::MouseEvent,
|
||||
buttons_pressed: &MouseButtonsPressed,
|
||||
) -> std::io::Result<Option<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 kind = match event.event_flags {
|
||||
EventFlags::PressOrRelease | EventFlags::DoubleClick => {
|
||||
if button_state.left_button() && !buttons_pressed.left {
|
||||
Some(MouseEventKind::Down(MouseButton::Left))
|
||||
} else if !button_state.left_button() && buttons_pressed.left {
|
||||
Some(MouseEventKind::Up(MouseButton::Left))
|
||||
} else if button_state.right_button() && !buttons_pressed.right {
|
||||
Some(MouseEventKind::Down(MouseButton::Right))
|
||||
} else if !button_state.right_button() && buttons_pressed.right {
|
||||
Some(MouseEventKind::Up(MouseButton::Right))
|
||||
} else if button_state.middle_button() && !buttons_pressed.middle {
|
||||
Some(MouseEventKind::Down(MouseButton::Middle))
|
||||
} else if !button_state.middle_button() && buttons_pressed.middle {
|
||||
Some(MouseEventKind::Up(MouseButton::Middle))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
EventFlags::MouseMoved => {
|
||||
let button = if button_state.right_button() {
|
||||
MouseButton::Right
|
||||
} else if button_state.middle_button() {
|
||||
MouseButton::Middle
|
||||
} else {
|
||||
MouseButton::Left
|
||||
};
|
||||
if button_state.release_button() {
|
||||
Some(MouseEventKind::Moved)
|
||||
} else {
|
||||
Some(MouseEventKind::Drag(button))
|
||||
}
|
||||
}
|
||||
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(MouseEventKind::ScrollDown)
|
||||
} else if button_state.scroll_up() {
|
||||
Some(MouseEventKind::ScrollUp)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
EventFlags::MouseHwheeled => {
|
||||
if button_state.scroll_left() {
|
||||
Some(MouseEventKind::ScrollLeft)
|
||||
} else if button_state.scroll_right() {
|
||||
Some(MouseEventKind::ScrollRight)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(kind.map(|kind| MouseEvent {
|
||||
kind,
|
||||
column: xpos,
|
||||
row: ypos,
|
||||
modifiers,
|
||||
}))
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
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},
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub(crate) use super::waker::Waker;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WinApiPoll {
|
||||
#[cfg(feature = "event-stream")]
|
||||
waker: Waker,
|
||||
}
|
||||
|
||||
impl WinApiPoll {
|
||||
#[cfg(not(feature = "event-stream"))]
|
||||
pub(crate) fn new() -> WinApiPoll {
|
||||
WinApiPoll {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub(crate) fn new() -> std::io::Result<WinApiPoll> {
|
||||
Ok(WinApiPoll {
|
||||
waker: Waker::new()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WinApiPoll {
|
||||
pub fn poll(&mut self, timeout: Option<Duration>) -> std::io::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`",
|
||||
))
|
||||
}
|
||||
WAIT_TIMEOUT | WAIT_ABANDONED_0 => {
|
||||
// timeout elapsed
|
||||
Ok(None)
|
||||
}
|
||||
WAIT_FAILED => Err(io::Error::last_os_error()),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"WaitForMultipleObjects returned unexpected result.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub fn waker(&self) -> Waker {
|
||||
self.waker.clone()
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crossterm_winapi::Semaphore;
|
||||
|
||||
/// Allows to wake up the `WinApiPoll::poll()` method.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Waker {
|
||||
inner: Arc<Mutex<Semaphore>>,
|
||||
}
|
||||
|
||||
impl Waker {
|
||||
/// Creates a new waker.
|
||||
///
|
||||
/// `Waker` is based on the `Semaphore`. You have to use the semaphore
|
||||
/// handle along with the `WaitForMultipleObjects`.
|
||||
pub(crate) fn new() -> std::io::Result<Self> {
|
||||
let inner = Semaphore::new()?;
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(Mutex::new(inner)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Wakes the `WaitForMultipleObjects`.
|
||||
pub(crate) fn wake(&self) -> std::io::Result<()> {
|
||||
self.inner.lock().unwrap().release()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replaces the current semaphore with a new one allowing us to reuse the same `Waker`.
|
||||
pub(crate) fn reset(&self) -> std::io::Result<()> {
|
||||
*self.inner.lock().unwrap() = Semaphore::new()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the semaphore associated with the waker.
|
||||
pub(crate) fn semaphore(&self) -> Semaphore {
|
||||
self.inner.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Keeps track of the elapsed time since the moment the polling started.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PollTimeout {
|
||||
timeout: Option<Duration>,
|
||||
start: Instant,
|
||||
}
|
||||
|
||||
impl PollTimeout {
|
||||
/// Constructs a new `PollTimeout` with the given optional `Duration`.
|
||||
pub fn new(timeout: Option<Duration>) -> PollTimeout {
|
||||
PollTimeout {
|
||||
timeout,
|
||||
start: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the timeout has elapsed.
|
||||
///
|
||||
/// It always returns `false` if the initial timeout was set to `None`.
|
||||
pub fn elapsed(&self) -> bool {
|
||||
self.timeout
|
||||
.map(|timeout| self.start.elapsed() >= timeout)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns the timeout leftover (initial timeout duration - elapsed duration).
|
||||
pub fn leftover(&self) -> Option<Duration> {
|
||||
self.timeout.map(|timeout| {
|
||||
let elapsed = self.start.elapsed();
|
||||
|
||||
if elapsed >= timeout {
|
||||
Duration::from_secs(0)
|
||||
} else {
|
||||
timeout - elapsed
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::PollTimeout;
|
||||
|
||||
#[test]
|
||||
pub fn test_timeout_without_duration_does_not_have_leftover() {
|
||||
let timeout = PollTimeout::new(None);
|
||||
assert_eq!(timeout.leftover(), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_timeout_without_duration_never_elapses() {
|
||||
let timeout = PollTimeout::new(None);
|
||||
assert!(!timeout.elapsed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_timeout_elapses() {
|
||||
const TIMEOUT_MILLIS: u64 = 100;
|
||||
|
||||
let timeout = PollTimeout {
|
||||
timeout: Some(Duration::from_millis(TIMEOUT_MILLIS)),
|
||||
start: Instant::now() - Duration::from_millis(2 * TIMEOUT_MILLIS),
|
||||
};
|
||||
|
||||
assert!(timeout.elapsed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_elapsed_timeout_has_zero_leftover() {
|
||||
const TIMEOUT_MILLIS: u64 = 100;
|
||||
|
||||
let timeout = PollTimeout {
|
||||
timeout: Some(Duration::from_millis(TIMEOUT_MILLIS)),
|
||||
start: Instant::now() - Duration::from_millis(2 * TIMEOUT_MILLIS),
|
||||
};
|
||||
|
||||
assert!(timeout.elapsed());
|
||||
assert_eq!(timeout.leftover(), Some(Duration::from_millis(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_not_elapsed_timeout_has_positive_leftover() {
|
||||
let timeout = PollTimeout::new(Some(Duration::from_secs(60)));
|
||||
|
||||
assert!(!timeout.elapsed());
|
||||
assert!(timeout.leftover().unwrap() > Duration::from_secs(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user