//! # Event | |
//! | |
//! The `event` module provides the functionality to read keyboard, mouse and terminal resize events. | |
//! | |
//! * The [`read`](fn.read.html) function returns an [`Event`](enum.Event.html) immediately | |
//! (if available) or blocks until an [`Event`](enum.Event.html) is available. | |
//! | |
//! * The [`poll`](fn.poll.html) function allows you to check if there is or isn't an [`Event`](enum.Event.html) available | |
//! within the given period of time. In other words - if subsequent call to the [`read`](fn.read.html) | |
//! function will block or not. | |
//! | |
//! It's **not allowed** to call these functions from different threads or combine them with the | |
//! [`EventStream`](struct.EventStream.html). You're allowed to either: | |
//! | |
//! * use the [`read`](fn.read.html) & [`poll`](fn.poll.html) functions on any, but same, thread | |
//! * or the [`EventStream`](struct.EventStream.html). | |
//! | |
//! **Make sure to enable raw mode in order for keyboard events to work properly** | |
//! | |
//! ## Mouse Events | |
//! | |
//! Mouse events are not enabled by default. You have to enable them with the | |
//! [`EnableMouseCapture`](struct.EnableMouseCapture.html) command. See [Command API](../index.html#command-api) | |
//! for more information. | |
//! | |
//! ## Examples | |
//! | |
//! Blocking read: | |
//! | |
//! ```no_run | |
//! use crossterm::event::{read, Event}; | |
//! | |
//! fn print_events() -> crossterm::Result<()> { | |
//! loop { | |
//! // `read()` blocks until an `Event` is available | |
//! match read()? { | |
//! Event::Key(event) => println!("{:?}", event), | |
//! Event::Mouse(event) => println!("{:?}", event), | |
//! Event::Resize(width, height) => println!("New size {}x{}", width, height), | |
//! } | |
//! } | |
//! Ok(()) | |
//! } | |
//! ``` | |
//! | |
//! Non-blocking read: | |
//! | |
//! ```no_run | |
//! use std::time::Duration; | |
//! | |
//! use crossterm::event::{poll, read, Event}; | |
//! | |
//! fn print_events() -> crossterm::Result<()> { | |
//! loop { | |
//! // `poll()` waits for an `Event` for a given time period | |
//! if poll(Duration::from_millis(500))? { | |
//! // It's guaranteed that the `read()` won't block when the `poll()` | |
//! // function returns `true` | |
//! match read()? { | |
//! Event::Key(event) => println!("{:?}", event), | |
//! Event::Mouse(event) => println!("{:?}", event), | |
//! Event::Resize(width, height) => println!("New size {}x{}", width, height), | |
//! } | |
//! } else { | |
//! // Timeout expired and no `Event` is available | |
//! } | |
//! } | |
//! Ok(()) | |
//! } | |
//! ``` | |
//! | |
//! Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder for more of | |
//! them (`event-*`). | |
use std::fmt; | |
use std::time::Duration; | |
use parking_lot::RwLock; | |
#[cfg(feature = "serde")] | |
use serde::{Deserialize, Serialize}; | |
use bitflags::bitflags; | |
use lazy_static::lazy_static; | |
use crate::{Command, Result}; | |
use filter::{EventFilter, Filter}; | |
#[cfg(feature = "event-stream")] | |
pub use stream::EventStream; | |
use timeout::PollTimeout; | |
mod ansi; | |
pub(crate) mod filter; | |
mod read; | |
mod source; | |
#[cfg(feature = "event-stream")] | |
mod stream; | |
pub(crate) mod sys; | |
mod timeout; | |
lazy_static! { | |
/// Static instance of `InternalEventReader`. | |
/// This needs to be static because there can be one event reader. | |
static ref INTERNAL_EVENT_READER: RwLock<read::InternalEventReader> = RwLock::new(read::InternalEventReader::default()); | |
} | |
/// Checks if there is an [`Event`](enum.Event.html) available. | |
/// | |
/// Returns `Ok(true)` if an [`Event`](enum.Event.html) is available otherwise it returns `Ok(false)`. | |
/// | |
/// `Ok(true)` guarantees that subsequent call to the [`read`](fn.read.html) function | |
/// wont block. | |
/// | |
/// # Arguments | |
/// | |
/// * `timeout` - maximum waiting time for event availability | |
/// | |
/// # Examples | |
/// | |
/// Return immediately: | |
/// | |
/// ```no_run | |
/// use std::time::Duration; | |
/// | |
/// use crossterm::{event::poll, Result}; | |
/// | |
/// fn is_event_available() -> Result<bool> { | |
/// // Zero duration says that the `poll` function must return immediately | |
/// // with an `Event` availability information | |
/// poll(Duration::from_secs(0)) | |
/// } | |
/// ``` | |
/// | |
/// Wait up to 100ms: | |
/// | |
/// ```no_run | |
/// use std::time::Duration; | |
/// | |
/// use crossterm::{event::poll, Result}; | |
/// | |
/// fn is_event_available() -> Result<bool> { | |
/// // Wait for an `Event` availability for 100ms. It returns immediately | |
/// // if an `Event` is/becomes available. | |
/// poll(Duration::from_millis(100)) | |
/// } | |
/// ``` | |
pub fn poll(timeout: Duration) -> Result<bool> { | |
poll_internal(Some(timeout), &EventFilter) | |
} | |
/// Reads a single [`Event`](enum.Event.html). | |
/// | |
/// This function blocks until an [`Event`](enum.Event.html) is available. Combine it with the | |
/// [`poll`](fn.poll.html) function to get non-blocking reads. | |
/// | |
/// # Examples | |
/// | |
/// Blocking read: | |
/// | |
/// ```no_run | |
/// use crossterm::{event::read, Result}; | |
/// | |
/// fn print_events() -> Result<bool> { | |
/// loop { | |
/// // Blocks until an `Event` is available | |
/// println!("{:?}", read()?); | |
/// } | |
/// } | |
/// ``` | |
/// | |
/// Non-blocking read: | |
/// | |
/// ```no_run | |
/// use std::time::Duration; | |
/// | |
/// use crossterm::{event::{read, poll}, Result}; | |
/// | |
/// fn print_events() -> Result<bool> { | |
/// loop { | |
/// if poll(Duration::from_millis(100))? { | |
/// // It's guaranteed that `read` wont block, because `poll` returned | |
/// // `Ok(true)`. | |
/// println!("{:?}", read()?); | |
/// } else { | |
/// // Timeout expired, no `Event` is available | |
/// } | |
/// } | |
/// } | |
/// ``` | |
pub fn read() -> Result<Event> { | |
match read_internal(&EventFilter)? { | |
InternalEvent::Event(event) => Ok(event), | |
#[cfg(unix)] | |
_ => unreachable!(), | |
} | |
} | |
/// Polls to check if there are any `InternalEvent`s that can be read within the given duration. | |
pub(crate) fn poll_internal<F>(timeout: Option<Duration>, filter: &F) -> Result<bool> | |
where | |
F: Filter, | |
{ | |
let (mut reader, timeout) = if let Some(timeout) = timeout { | |
let poll_timeout = PollTimeout::new(Some(timeout)); | |
if let Some(reader) = INTERNAL_EVENT_READER.try_write_for(timeout) { | |
(reader, poll_timeout.leftover()) | |
} else { | |
return Ok(false); | |
} | |
} else { | |
(INTERNAL_EVENT_READER.write(), None) | |
}; | |
reader.poll(timeout, filter) | |
} | |
/// Reads a single `InternalEvent`. | |
pub(crate) fn read_internal<F>(filter: &F) -> Result<InternalEvent> | |
where | |
F: Filter, | |
{ | |
let mut reader = INTERNAL_EVENT_READER.write(); | |
reader.read(filter) | |
} | |
/// A command that enables mouse event capturing. | |
/// | |
/// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html). | |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
pub struct EnableMouseCapture; | |
impl Command for EnableMouseCapture { | |
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { | |
f.write_str(ansi::ENABLE_MOUSE_MODE_CSI_SEQUENCE) | |
} | |
#[cfg(windows)] | |
fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> { | |
sys::windows::enable_mouse_capture() | |
} | |
#[cfg(windows)] | |
fn is_ansi_code_supported(&self) -> bool { | |
false | |
} | |
} | |
/// A command that disables mouse event capturing. | |
/// | |
/// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html). | |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
pub struct DisableMouseCapture; | |
impl Command for DisableMouseCapture { | |
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { | |
f.write_str(ansi::DISABLE_MOUSE_MODE_CSI_SEQUENCE) | |
} | |
#[cfg(windows)] | |
fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> { | |
sys::windows::disable_mouse_capture() | |
} | |
#[cfg(windows)] | |
fn is_ansi_code_supported(&self) -> bool { | |
false | |
} | |
} | |
/// Represents an event. | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] | |
pub enum Event { | |
/// A single key event with additional pressed modifiers. | |
Key(KeyEvent), | |
/// A single mouse event with additional pressed modifiers. | |
Mouse(MouseEvent), | |
/// An resize event with new dimensions after resize (columns, rows). | |
/// **Note** that resize events can be occur in batches. | |
Resize(u16, u16), | |
} | |
/// Represents a mouse event. | |
/// | |
/// # Platform-specific Notes | |
/// | |
/// ## Mouse Buttons | |
/// | |
/// Some platforms/terminals do not report mouse button for the | |
/// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left` | |
/// is returned if we don't know which button was used. | |
/// | |
/// ## Key Modifiers | |
/// | |
/// Some platforms/terminals does not report all key modifiers | |
/// combinations for all mouse event types. For example - macOS reports | |
/// `Ctrl` + left mouse button click as a right mouse button click. | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] | |
pub struct MouseEvent { | |
/// The kind of mouse event that was caused. | |
pub kind: MouseEventKind, | |
/// The column that the event occurred on. | |
pub column: u16, | |
/// The row that the event occurred on. | |
pub row: u16, | |
/// The key modifiers active when the event occurred. | |
pub modifiers: KeyModifiers, | |
} | |
/// A mouse event kind. | |
/// | |
/// # Platform-specific Notes | |
/// | |
/// ## Mouse Buttons | |
/// | |
/// Some platforms/terminals do not report mouse button for the | |
/// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left` | |
/// is returned if we don't know which button was used. | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] | |
pub enum MouseEventKind { | |
/// Pressed mouse button. Contains the button that was pressed. | |
Down(MouseButton), | |
/// Released mouse button. Contains the button that was released. | |
Up(MouseButton), | |
/// Moved the mouse cursor while pressing the contained mouse button. | |
Drag(MouseButton), | |
/// Moved the mouse cursor while not pressing a mouse button. | |
Moved, | |
/// Scrolled mouse wheel downwards (towards the user). | |
ScrollDown, | |
/// Scrolled mouse wheel upwards (away from the user). | |
ScrollUp, | |
} | |
/// Represents a mouse button. | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] | |
pub enum MouseButton { | |
/// Left mouse button. | |
Left, | |
/// Right mouse button. | |
Right, | |
/// Middle mouse button. | |
Middle, | |
} | |
bitflags! { | |
/// Represents key modifiers (shift, control, alt). | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
pub struct KeyModifiers: u8 { | |
const SHIFT = 0b0000_0001; | |
const CONTROL = 0b0000_0010; | |
const ALT = 0b0000_0100; | |
const NONE = 0b0000_0000; | |
} | |
} | |
/// Represents a key event. | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] | |
pub struct KeyEvent { | |
/// The key itself. | |
pub code: KeyCode, | |
/// Additional key modifiers. | |
pub modifiers: KeyModifiers, | |
} | |
impl KeyEvent { | |
pub fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent { | |
KeyEvent { code, modifiers } | |
} | |
} | |
impl From<KeyCode> for KeyEvent { | |
fn from(code: KeyCode) -> Self { | |
KeyEvent { | |
code, | |
modifiers: KeyModifiers::empty(), | |
} | |
} | |
} | |
/// Represents a key. | |
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] | |
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
pub enum KeyCode { | |
/// Backspace key. | |
Backspace, | |
/// Enter key. | |
Enter, | |
/// Left arrow key. | |
Left, | |
/// Right arrow key. | |
Right, | |
/// Up arrow key. | |
Up, | |
/// Down arrow key. | |
Down, | |
/// Home key. | |
Home, | |
/// End key. | |
End, | |
/// Page up key. | |
PageUp, | |
/// Page dow key. | |
PageDown, | |
/// Tab key. | |
Tab, | |
/// Shift + Tab key. | |
BackTab, | |
/// Delete key. | |
Delete, | |
/// Insert key. | |
Insert, | |
/// F key. | |
/// | |
/// `KeyEvent::F(1)` represents F1 key, etc. | |
F(u8), | |
/// A character. | |
/// | |
/// `KeyEvent::Char('c')` represents `c` character, etc. | |
Char(char), | |
/// Null. | |
Null, | |
/// Escape key. | |
Esc, | |
} | |
/// An internal event. | |
/// | |
/// Encapsulates publicly available `Event` with additional internal | |
/// events that shouldn't be publicly available to the crate users. | |
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Eq)] | |
pub(crate) enum InternalEvent { | |
/// An event. | |
Event(Event), | |
/// A cursor position (`col`, `row`). | |
#[cfg(unix)] | |
CursorPosition(u16, u16), | |
} |