mirror of
https://github.com/crossterm-rs/crossterm.git
synced 2025-10-02 23:36:06 +00:00
Make the events module optional feature
This commit is contained in:
parent
b354b4cc34
commit
2b398e2bed
10
Cargo.toml
10
Cargo.toml
@ -26,12 +26,12 @@ all-features = true
|
|||||||
# Features
|
# Features
|
||||||
#
|
#
|
||||||
[features]
|
[features]
|
||||||
default = ["bracketed-paste", "windows"]
|
default = ["bracketed-paste", "windows", "events"]
|
||||||
windows = ["winapi", "crossterm_winapi"]
|
windows = ["winapi", "crossterm_winapi"]
|
||||||
bracketed-paste = []
|
bracketed-paste = []
|
||||||
event-stream = ["futures-core"]
|
event-stream = ["futures-core"]
|
||||||
use-dev-tty = ["filedescriptor"]
|
use-dev-tty = ["filedescriptor"]
|
||||||
|
events = ["mio", "signal-hook", "signal-hook-mio"]
|
||||||
#
|
#
|
||||||
# Shared dependencies
|
# Shared dependencies
|
||||||
#
|
#
|
||||||
@ -59,10 +59,10 @@ crossterm_winapi = { version = "0.9", optional = true }
|
|||||||
#
|
#
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
signal-hook = { version = "0.3.13" }
|
signal-hook = { version = "0.3.13", optional = true }
|
||||||
filedescriptor = { version = "0.8", optional = true }
|
filedescriptor = { version = "0.8", optional = true }
|
||||||
mio = { version = "0.8", features = ["os-poll"] }
|
mio = { version = "0.8", features = ["os-poll"], optional = true }
|
||||||
signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"] }
|
signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"], optional = true }
|
||||||
|
|
||||||
#
|
#
|
||||||
# Dev dependencies (examples, ...)
|
# Dev dependencies (examples, ...)
|
||||||
|
12
README.md
12
README.md
@ -144,6 +144,12 @@ features = ["event-stream"]
|
|||||||
|:---------------|:---------------------------------------------|
|
|:---------------|:---------------------------------------------|
|
||||||
| `event-stream` | `futures::Stream` producing `Result<Event>`. |
|
| `event-stream` | `futures::Stream` producing `Result<Event>`. |
|
||||||
| `serde` | (De)serializing of events. |
|
| `serde` | (De)serializing of events. |
|
||||||
|
| `events` | Reading input/system events (enabled by default) |
|
||||||
|
| `filedescriptor` | Use raw filedescriptor for all events rather then mio dependency |
|
||||||
|
|
||||||
|
|
||||||
|
To use crossterm as a very tin layer you can disable the `events` feature or use `filedescriptor` feature.
|
||||||
|
This can disable `mio` / `signal-hook` / `signal-hook-mio` dependencies.
|
||||||
|
|
||||||
### Dependency Justification
|
### Dependency Justification
|
||||||
|
|
||||||
@ -151,9 +157,9 @@ features = ["event-stream"]
|
|||||||
|:---------------|:---------------------------------------------------------------------------------|:--------------------------------------|
|
|:---------------|:---------------------------------------------------------------------------------|:--------------------------------------|
|
||||||
| `bitflags` | `KeyModifiers`, those are differ based on input. | always |
|
| `bitflags` | `KeyModifiers`, those are differ based on input. | always |
|
||||||
| `parking_lot` | locking `RwLock`s with a timeout, const mutexes. | always |
|
| `parking_lot` | locking `RwLock`s with a timeout, const mutexes. | always |
|
||||||
| `libc` | UNIX terminal_size/raw modes/set_title and several other low level functionality. | UNIX only |
|
| `libc` | UNIX terminal_size/raw modes/set_title and several other low level functionality. | optional (`events` feature), UNIX only |
|
||||||
| `Mio` | event readiness polling, waking up poller | UNIX only |
|
| `Mio` | event readiness polling, waking up poller | optional (`events` feature), UNIX only |
|
||||||
| `signal-hook` | signal-hook is used to handle terminal resize SIGNAL with Mio. | UNIX only |
|
| `signal-hook` | signal-hook is used to handle terminal resize SIGNAL with Mio. | optional (`events` feature),UNIX only |
|
||||||
| `winapi` | Used for low-level windows system calls which ANSI codes can't replace | windows only |
|
| `winapi` | Used for low-level windows system calls which ANSI codes can't replace | windows only |
|
||||||
| `futures-core` | For async stream of events | only with `event-stream` feature flag |
|
| `futures-core` | For async stream of events | only with `event-stream` feature flag |
|
||||||
| `serde` | ***ser***ializing and ***de***serializing of events | only with `serde` feature flag |
|
| `serde` | ***ser***ializing and ***de***serializing of events | only with `serde` feature flag |
|
||||||
|
@ -48,8 +48,6 @@ use std::fmt;
|
|||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{csi, impl_display, Command};
|
use crate::{csi, impl_display, Command};
|
||||||
|
|
||||||
pub use sys::position;
|
|
||||||
|
|
||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
|
|
||||||
/// A command that moves the terminal cursor to the given position (column, row).
|
/// A command that moves the terminal cursor to the given position (column, row).
|
||||||
@ -432,7 +430,7 @@ mod tests {
|
|||||||
use crate::execute;
|
use crate::execute;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition,
|
sys::position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test is disabled, because it's failing on Travis
|
// Test is disabled, because it's failing on Travis
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! This module provides platform related functions.
|
//! This module provides platform related functions.
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub use self::unix::position;
|
pub use self::unix::position;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub use self::windows::position;
|
pub use self::windows::position;
|
||||||
@ -14,4 +15,5 @@ pub(crate) use self::windows::{
|
|||||||
pub(crate) mod windows;
|
pub(crate) mod windows;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub(crate) mod unix;
|
pub(crate) mod unix;
|
||||||
|
764
src/event.rs
764
src/event.rs
@ -81,166 +81,31 @@
|
|||||||
//! them (`event-*`).
|
//! them (`event-*`).
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
#[cfg(windows)]
|
|
||||||
use std::io;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use bitflags::bitflags;
|
|
||||||
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{csi, Command, Result};
|
use crate::{csi, Command};
|
||||||
use filter::{EventFilter, Filter};
|
|
||||||
use read::InternalEventReader;
|
|
||||||
#[cfg(feature = "event-stream")]
|
|
||||||
pub use stream::EventStream;
|
|
||||||
use timeout::PollTimeout;
|
|
||||||
|
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub(crate) mod filter;
|
pub(crate) mod filter;
|
||||||
|
#[cfg(feature = "events")]
|
||||||
mod read;
|
mod read;
|
||||||
|
#[cfg(feature = "events")]
|
||||||
mod source;
|
mod source;
|
||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
|
#[cfg(feature = "events")]
|
||||||
mod stream;
|
mod stream;
|
||||||
pub(crate) mod sys;
|
#[cfg(feature = "events")]
|
||||||
mod timeout;
|
mod timeout;
|
||||||
|
|
||||||
/// Static instance of `InternalEventReader`.
|
#[cfg(feature = "events")]
|
||||||
/// This needs to be static because there can be one event reader.
|
mod events_api;
|
||||||
static INTERNAL_EVENT_READER: Mutex<Option<InternalEventReader>> = parking_lot::const_mutex(None);
|
|
||||||
|
|
||||||
fn lock_internal_event_reader() -> MappedMutexGuard<'static, InternalEventReader> {
|
#[cfg(feature = "events")]
|
||||||
MutexGuard::map(INTERNAL_EVENT_READER.lock(), |reader| {
|
pub use events_api::*;
|
||||||
reader.get_or_insert_with(InternalEventReader::default)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn try_lock_internal_event_reader_for(
|
|
||||||
duration: Duration,
|
|
||||||
) -> Option<MappedMutexGuard<'static, InternalEventReader>> {
|
|
||||||
Some(MutexGuard::map(
|
|
||||||
INTERNAL_EVENT_READER.try_lock_for(duration)?,
|
|
||||||
|reader| reader.get_or_insert_with(InternalEventReader::default),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if there is an [`Event`](enum.Event.html) available.
|
pub(crate) mod sys;
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
/// won't 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` won't 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) = try_lock_internal_event_reader_for(timeout) {
|
|
||||||
(reader, poll_timeout.leftover())
|
|
||||||
} else {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(lock_internal_event_reader(), 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 = lock_internal_event_reader();
|
|
||||||
reader.read(filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A command that enables mouse event capturing.
|
/// A command that enables mouse event capturing.
|
||||||
///
|
///
|
||||||
@ -265,7 +130,7 @@ impl Command for EnableMouseCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
fn execute_winapi(&self) -> crate::Result<()> {
|
||||||
sys::windows::enable_mouse_capture()
|
sys::windows::enable_mouse_capture()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,7 +159,7 @@ impl Command for DisableMouseCapture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
fn execute_winapi(&self) -> crate::Result<()> {
|
||||||
sys::windows::disable_mouse_capture()
|
sys::windows::disable_mouse_capture()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,119 +169,6 @@ impl Command for DisableMouseCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
/// Represents special flags that tell compatible terminals to add extra information to keyboard events.
|
|
||||||
///
|
|
||||||
/// See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement> for more information.
|
|
||||||
///
|
|
||||||
/// Alternate keys and Unicode codepoints are not yet supported by crossterm.
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct KeyboardEnhancementFlags: u8 {
|
|
||||||
/// Represent Escape and modified keys using CSI-u sequences, so they can be unambiguously
|
|
||||||
/// read.
|
|
||||||
const DISAMBIGUATE_ESCAPE_CODES = 0b0000_0001;
|
|
||||||
/// Add extra events with [`KeyEvent.kind`] set to [`KeyEventKind::Repeat`] or
|
|
||||||
/// [`KeyEventKind::Release`] when keys are autorepeated or released.
|
|
||||||
const REPORT_EVENT_TYPES = 0b0000_0010;
|
|
||||||
// Send [alternate keycodes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#key-codes)
|
|
||||||
// in addition to the base keycode. The alternate keycode overrides the base keycode in
|
|
||||||
// resulting `KeyEvent`s.
|
|
||||||
const REPORT_ALTERNATE_KEYS = 0b0000_0100;
|
|
||||||
/// Represent all keyboard events as CSI-u sequences. This is required to get repeat/release
|
|
||||||
/// events for plain-text keys.
|
|
||||||
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b0000_1000;
|
|
||||||
// Send the Unicode codepoint as well as the keycode.
|
|
||||||
//
|
|
||||||
// *Note*: this is not yet supported by crossterm.
|
|
||||||
// const REPORT_ASSOCIATED_TEXT = 0b0001_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A command that enables the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), which adds extra information to keyboard events and removes ambiguity for modifier keys.
|
|
||||||
///
|
|
||||||
/// It should be paired with [`PopKeyboardEnhancementFlags`] at the end of execution.
|
|
||||||
///
|
|
||||||
/// Example usage:
|
|
||||||
/// ```no_run
|
|
||||||
/// use std::io::{Write, stdout};
|
|
||||||
/// use crossterm::execute;
|
|
||||||
/// use crossterm::event::{
|
|
||||||
/// KeyboardEnhancementFlags,
|
|
||||||
/// PushKeyboardEnhancementFlags,
|
|
||||||
/// PopKeyboardEnhancementFlags
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let mut stdout = stdout();
|
|
||||||
///
|
|
||||||
/// execute!(
|
|
||||||
/// stdout,
|
|
||||||
/// PushKeyboardEnhancementFlags(
|
|
||||||
/// KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
|
||||||
/// )
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// // ...
|
|
||||||
///
|
|
||||||
/// execute!(stdout, PopKeyboardEnhancementFlags);
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Note that, currently, only the following support this protocol:
|
|
||||||
/// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
|
|
||||||
/// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
|
|
||||||
/// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
|
|
||||||
/// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
|
|
||||||
/// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
|
|
||||||
/// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
|
|
||||||
/// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct PushKeyboardEnhancementFlags(pub KeyboardEnhancementFlags);
|
|
||||||
|
|
||||||
impl Command for PushKeyboardEnhancementFlags {
|
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
|
||||||
write!(f, "{}{}u", csi!(">"), self.0.bits())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Unsupported,
|
|
||||||
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn is_ansi_code_supported(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A command that disables extra kinds of keyboard events.
|
|
||||||
///
|
|
||||||
/// Specifically, it pops one level of keyboard enhancement flags.
|
|
||||||
///
|
|
||||||
/// See [`PushKeyboardEnhancementFlags`] and <https://sw.kovidgoyal.net/kitty/keyboard-protocol/> for more information.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct PopKeyboardEnhancementFlags;
|
|
||||||
|
|
||||||
impl Command for PopKeyboardEnhancementFlags {
|
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
|
||||||
f.write_str(csi!("<1u"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::Unsupported,
|
|
||||||
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn is_ansi_code_supported(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A command that enables focus event emission.
|
/// A command that enables focus event emission.
|
||||||
///
|
///
|
||||||
/// It should be paired with [`DisableFocusChange`] at the end of execution.
|
/// It should be paired with [`DisableFocusChange`] at the end of execution.
|
||||||
@ -431,7 +183,7 @@ impl Command for EnableFocusChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
fn execute_winapi(&self) -> crate::Result<()> {
|
||||||
// Focus events are always enabled on Windows
|
// Focus events are always enabled on Windows
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -447,7 +199,7 @@ impl Command for DisableFocusChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self) -> Result<()> {
|
fn execute_winapi(&self) -> crate::Result<()> {
|
||||||
// Focus events can't be disabled on Windows
|
// Focus events can't be disabled on Windows
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -494,489 +246,3 @@ impl Command for DisableBracketedPaste {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an event.
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[cfg_attr(not(feature = "bracketed-paste"), derive(Copy))]
|
|
||||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
|
|
||||||
pub enum Event {
|
|
||||||
/// The terminal gained focus
|
|
||||||
FocusGained,
|
|
||||||
/// The terminal lost focus
|
|
||||||
FocusLost,
|
|
||||||
/// A single key event with additional pressed modifiers.
|
|
||||||
Key(KeyEvent),
|
|
||||||
/// A single mouse event with additional pressed modifiers.
|
|
||||||
Mouse(MouseEvent),
|
|
||||||
/// A string that was pasted into the terminal. Only emitted if bracketed paste has been
|
|
||||||
/// enabled.
|
|
||||||
#[cfg(feature = "bracketed-paste")]
|
|
||||||
Paste(String),
|
|
||||||
/// An resize event with new dimensions after resize (columns, rows).
|
|
||||||
/// **Note** that resize events can 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, etc.).
|
|
||||||
///
|
|
||||||
/// **Note:** `SUPER`, `HYPER`, and `META` can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct KeyModifiers: u8 {
|
|
||||||
const SHIFT = 0b0000_0001;
|
|
||||||
const CONTROL = 0b0000_0010;
|
|
||||||
const ALT = 0b0000_0100;
|
|
||||||
const SUPER = 0b0000_1000;
|
|
||||||
const HYPER = 0b0001_0000;
|
|
||||||
const META = 0b0010_0000;
|
|
||||||
const NONE = 0b0000_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a keyboard event kind.
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
|
||||||
pub enum KeyEventKind {
|
|
||||||
Press,
|
|
||||||
Repeat,
|
|
||||||
Release,
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
/// Represents extra state about the key event.
|
|
||||||
///
|
|
||||||
/// **Note:** This state can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct KeyEventState: u8 {
|
|
||||||
/// The key event origins from the keypad.
|
|
||||||
const KEYPAD = 0b0000_0001;
|
|
||||||
/// Caps Lock was enabled for this key event.
|
|
||||||
///
|
|
||||||
/// **Note:** this is set for the initial press of Caps Lock itself.
|
|
||||||
const CAPS_LOCK = 0b0000_1000;
|
|
||||||
/// Num Lock was enabled for this key event.
|
|
||||||
///
|
|
||||||
/// **Note:** this is set for the initial press of Num Lock itself.
|
|
||||||
const NUM_LOCK = 0b0000_1000;
|
|
||||||
const NONE = 0b0000_0000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a key event.
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
#[derive(Debug, PartialOrd, Clone, Copy)]
|
|
||||||
pub struct KeyEvent {
|
|
||||||
/// The key itself.
|
|
||||||
pub code: KeyCode,
|
|
||||||
/// Additional key modifiers.
|
|
||||||
pub modifiers: KeyModifiers,
|
|
||||||
/// Kind of event.
|
|
||||||
///
|
|
||||||
/// Only set if:
|
|
||||||
/// - Unix: [`KeyboardEnhancementFlags::REPORT_EVENT_TYPES`] has been enabled with [`PushKeyboardEnhancementFlags`].
|
|
||||||
/// - Windows: always
|
|
||||||
pub kind: KeyEventKind,
|
|
||||||
/// Keyboard state.
|
|
||||||
///
|
|
||||||
/// Only set if [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
pub state: KeyEventState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyEvent {
|
|
||||||
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
|
|
||||||
KeyEvent {
|
|
||||||
code,
|
|
||||||
modifiers,
|
|
||||||
kind: KeyEventKind::Press,
|
|
||||||
state: KeyEventState::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn new_with_kind(
|
|
||||||
code: KeyCode,
|
|
||||||
modifiers: KeyModifiers,
|
|
||||||
kind: KeyEventKind,
|
|
||||||
) -> KeyEvent {
|
|
||||||
KeyEvent {
|
|
||||||
code,
|
|
||||||
modifiers,
|
|
||||||
kind,
|
|
||||||
state: KeyEventState::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn new_with_kind_and_state(
|
|
||||||
code: KeyCode,
|
|
||||||
modifiers: KeyModifiers,
|
|
||||||
kind: KeyEventKind,
|
|
||||||
state: KeyEventState,
|
|
||||||
) -> KeyEvent {
|
|
||||||
KeyEvent {
|
|
||||||
code,
|
|
||||||
modifiers,
|
|
||||||
kind,
|
|
||||||
state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// modifies the KeyEvent,
|
|
||||||
// so that KeyModifiers::SHIFT is present iff
|
|
||||||
// an uppercase char is present.
|
|
||||||
fn normalize_case(mut self) -> KeyEvent {
|
|
||||||
let c = match self.code {
|
|
||||||
KeyCode::Char(c) => c,
|
|
||||||
_ => return self,
|
|
||||||
};
|
|
||||||
|
|
||||||
if c.is_ascii_uppercase() {
|
|
||||||
self.modifiers.insert(KeyModifiers::SHIFT);
|
|
||||||
} else if self.modifiers.contains(KeyModifiers::SHIFT) {
|
|
||||||
self.code = KeyCode::Char(c.to_ascii_uppercase())
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KeyCode> for KeyEvent {
|
|
||||||
fn from(code: KeyCode) -> Self {
|
|
||||||
KeyEvent {
|
|
||||||
code,
|
|
||||||
modifiers: KeyModifiers::empty(),
|
|
||||||
kind: KeyEventKind::Press,
|
|
||||||
state: KeyEventState::empty(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for KeyEvent {
|
|
||||||
fn eq(&self, other: &KeyEvent) -> bool {
|
|
||||||
let KeyEvent {
|
|
||||||
code: lhs_code,
|
|
||||||
modifiers: lhs_modifiers,
|
|
||||||
kind: lhs_kind,
|
|
||||||
state: lhs_state,
|
|
||||||
} = self.normalize_case();
|
|
||||||
let KeyEvent {
|
|
||||||
code: rhs_code,
|
|
||||||
modifiers: rhs_modifiers,
|
|
||||||
kind: rhs_kind,
|
|
||||||
state: rhs_state,
|
|
||||||
} = other.normalize_case();
|
|
||||||
(lhs_code == rhs_code)
|
|
||||||
&& (lhs_modifiers == rhs_modifiers)
|
|
||||||
&& (lhs_kind == rhs_kind)
|
|
||||||
&& (lhs_state == rhs_state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for KeyEvent {}
|
|
||||||
|
|
||||||
impl Hash for KeyEvent {
|
|
||||||
fn hash<H: Hasher>(&self, hash_state: &mut H) {
|
|
||||||
let KeyEvent {
|
|
||||||
code,
|
|
||||||
modifiers,
|
|
||||||
kind,
|
|
||||||
state,
|
|
||||||
} = self.normalize_case();
|
|
||||||
code.hash(hash_state);
|
|
||||||
modifiers.hash(hash_state);
|
|
||||||
kind.hash(hash_state);
|
|
||||||
state.hash(hash_state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a media key (as part of [`KeyCode::Media`]).
|
|
||||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum MediaKeyCode {
|
|
||||||
/// Play media key.
|
|
||||||
Play,
|
|
||||||
/// Pause media key.
|
|
||||||
Pause,
|
|
||||||
/// Play/Pause media key.
|
|
||||||
PlayPause,
|
|
||||||
/// Reverse media key.
|
|
||||||
Reverse,
|
|
||||||
/// Stop media key.
|
|
||||||
Stop,
|
|
||||||
/// Fast-forward media key.
|
|
||||||
FastForward,
|
|
||||||
/// Rewind media key.
|
|
||||||
Rewind,
|
|
||||||
/// Next-track media key.
|
|
||||||
TrackNext,
|
|
||||||
/// Previous-track media key.
|
|
||||||
TrackPrevious,
|
|
||||||
/// Record media key.
|
|
||||||
Record,
|
|
||||||
/// Lower-volume media key.
|
|
||||||
LowerVolume,
|
|
||||||
/// Raise-volume media key.
|
|
||||||
RaiseVolume,
|
|
||||||
/// Mute media key.
|
|
||||||
MuteVolume,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a modifier key (as part of [`KeyCode::Modifier`]).
|
|
||||||
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum ModifierKeyCode {
|
|
||||||
/// Left Shift key.
|
|
||||||
LeftShift,
|
|
||||||
/// Left Control key.
|
|
||||||
LeftControl,
|
|
||||||
/// Left Alt key.
|
|
||||||
LeftAlt,
|
|
||||||
/// Left Super key.
|
|
||||||
LeftSuper,
|
|
||||||
/// Left Hyper key.
|
|
||||||
LeftHyper,
|
|
||||||
/// Left Meta key.
|
|
||||||
LeftMeta,
|
|
||||||
/// Right Shift key.
|
|
||||||
RightShift,
|
|
||||||
/// Right Control key.
|
|
||||||
RightControl,
|
|
||||||
/// Right Alt key.
|
|
||||||
RightAlt,
|
|
||||||
/// Right Super key.
|
|
||||||
RightSuper,
|
|
||||||
/// Right Hyper key.
|
|
||||||
RightHyper,
|
|
||||||
/// Right Meta key.
|
|
||||||
RightMeta,
|
|
||||||
/// Iso Level3 Shift key.
|
|
||||||
IsoLevel3Shift,
|
|
||||||
/// Iso Level5 Shift key.
|
|
||||||
IsoLevel5Shift,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 down key.
|
|
||||||
PageDown,
|
|
||||||
/// Tab key.
|
|
||||||
Tab,
|
|
||||||
/// Shift + Tab key.
|
|
||||||
BackTab,
|
|
||||||
/// Delete key.
|
|
||||||
Delete,
|
|
||||||
/// Insert key.
|
|
||||||
Insert,
|
|
||||||
/// F key.
|
|
||||||
///
|
|
||||||
/// `KeyCode::F(1)` represents F1 key, etc.
|
|
||||||
F(u8),
|
|
||||||
/// A character.
|
|
||||||
///
|
|
||||||
/// `KeyCode::Char('c')` represents `c` character, etc.
|
|
||||||
Char(char),
|
|
||||||
/// Null.
|
|
||||||
Null,
|
|
||||||
/// Escape key.
|
|
||||||
Esc,
|
|
||||||
/// Caps Lock key.
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
CapsLock,
|
|
||||||
/// Scroll Lock key.
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
ScrollLock,
|
|
||||||
/// Num Lock key.
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
NumLock,
|
|
||||||
/// Print Screen key.
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
PrintScreen,
|
|
||||||
/// Pause key.
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
Pause,
|
|
||||||
/// Menu key.
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
Menu,
|
|
||||||
/// The "Begin" key (often mapped to the 5 key when Num Lock is turned on).
|
|
||||||
///
|
|
||||||
/// **Note:** this key can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
KeypadBegin,
|
|
||||||
/// A media key.
|
|
||||||
///
|
|
||||||
/// **Note:** these keys can only be read if
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
Media(MediaKeyCode),
|
|
||||||
/// A modifier key.
|
|
||||||
///
|
|
||||||
/// **Note:** these keys can only be read if **both**
|
|
||||||
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] and
|
|
||||||
/// [`KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES`] have been enabled with
|
|
||||||
/// [`PushKeyboardEnhancementFlags`].
|
|
||||||
Modifier(ModifierKeyCode),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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),
|
|
||||||
/// The progressive keyboard enhancement flags enabled by the terminal.
|
|
||||||
#[cfg(unix)]
|
|
||||||
KeyboardEnhancementFlags(KeyboardEnhancementFlags),
|
|
||||||
/// Attributes and architectural class of the terminal.
|
|
||||||
#[cfg(unix)]
|
|
||||||
PrimaryDeviceAttributes,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
use super::{KeyCode, KeyEvent, KeyModifiers};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_equality() {
|
|
||||||
let lowercase_d_with_shift = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT);
|
|
||||||
let uppercase_d_with_shift = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT);
|
|
||||||
let uppercase_d = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE);
|
|
||||||
assert_eq!(lowercase_d_with_shift, uppercase_d_with_shift);
|
|
||||||
assert_eq!(uppercase_d, uppercase_d_with_shift);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_hash() {
|
|
||||||
let lowercase_d_with_shift_hash = {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT).hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
};
|
|
||||||
let uppercase_d_with_shift_hash = {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT).hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
};
|
|
||||||
let uppercase_d_hash = {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE).hash(&mut hasher);
|
|
||||||
hasher.finish()
|
|
||||||
};
|
|
||||||
assert_eq!(lowercase_d_with_shift_hash, uppercase_d_with_shift_hash);
|
|
||||||
assert_eq!(uppercase_d_hash, uppercase_d_with_shift_hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
755
src/event/events_api.rs
Normal file
755
src/event/events_api.rs
Normal file
@ -0,0 +1,755 @@
|
|||||||
|
use std::{fmt, time::Duration};
|
||||||
|
|
||||||
|
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
|
||||||
|
|
||||||
|
#[cfg(feature = "event-stream")]
|
||||||
|
pub use stream::EventStream;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
filter::{EventFilter, Filter},
|
||||||
|
read::InternalEventReader,
|
||||||
|
timeout::PollTimeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{csi, Command, Result};
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
/// Static instance of `InternalEventReader`.
|
||||||
|
/// This needs to be static because there can be one event reader.
|
||||||
|
static INTERNAL_EVENT_READER: Mutex<Option<InternalEventReader>> = parking_lot::const_mutex(None);
|
||||||
|
|
||||||
|
fn lock_internal_event_reader() -> MappedMutexGuard<'static, InternalEventReader> {
|
||||||
|
MutexGuard::map(INTERNAL_EVENT_READER.lock(), |reader| {
|
||||||
|
reader.get_or_insert_with(InternalEventReader::default)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn try_lock_internal_event_reader_for(
|
||||||
|
duration: Duration,
|
||||||
|
) -> Option<MappedMutexGuard<'static, InternalEventReader>> {
|
||||||
|
Some(MutexGuard::map(
|
||||||
|
INTERNAL_EVENT_READER.try_lock_for(duration)?,
|
||||||
|
|reader| reader.get_or_insert_with(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
|
||||||
|
/// won't 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` won't 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) = try_lock_internal_event_reader_for(timeout) {
|
||||||
|
(reader, poll_timeout.leftover())
|
||||||
|
} else {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(lock_internal_event_reader(), 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 = lock_internal_event_reader();
|
||||||
|
reader.read(filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Represents special flags that tell compatible terminals to add extra information to keyboard events.
|
||||||
|
///
|
||||||
|
/// See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement> for more information.
|
||||||
|
///
|
||||||
|
/// Alternate keys and Unicode codepoints are not yet supported by crossterm.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct KeyboardEnhancementFlags: u8 {
|
||||||
|
/// Represent Escape and modified keys using CSI-u sequences, so they can be unambiguously
|
||||||
|
/// read.
|
||||||
|
const DISAMBIGUATE_ESCAPE_CODES = 0b0000_0001;
|
||||||
|
/// Add extra events with [`KeyEvent.kind`] set to [`KeyEventKind::Repeat`] or
|
||||||
|
/// [`KeyEventKind::Release`] when keys are autorepeated or released.
|
||||||
|
const REPORT_EVENT_TYPES = 0b0000_0010;
|
||||||
|
// Send [alternate keycodes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#key-codes)
|
||||||
|
// in addition to the base keycode. The alternate keycode overrides the base keycode in
|
||||||
|
// resulting `KeyEvent`s.
|
||||||
|
const REPORT_ALTERNATE_KEYS = 0b0000_0100;
|
||||||
|
/// Represent all keyboard events as CSI-u sequences. This is required to get repeat/release
|
||||||
|
/// events for plain-text keys.
|
||||||
|
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b0000_1000;
|
||||||
|
// Send the Unicode codepoint as well as the keycode.
|
||||||
|
//
|
||||||
|
// *Note*: this is not yet supported by crossterm.
|
||||||
|
// const REPORT_ASSOCIATED_TEXT = 0b0001_0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A command that enables the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), which adds extra information to keyboard events and removes ambiguity for modifier keys.
|
||||||
|
///
|
||||||
|
/// It should be paired with [`PopKeyboardEnhancementFlags`] at the end of execution.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```no_run
|
||||||
|
/// use std::io::{Write, stdout};
|
||||||
|
/// use crossterm::execute;
|
||||||
|
/// use crossterm::event::{
|
||||||
|
/// KeyboardEnhancementFlags,
|
||||||
|
/// PushKeyboardEnhancementFlags,
|
||||||
|
/// PopKeyboardEnhancementFlags
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// let mut stdout = stdout();
|
||||||
|
///
|
||||||
|
/// execute!(
|
||||||
|
/// stdout,
|
||||||
|
/// PushKeyboardEnhancementFlags(
|
||||||
|
/// KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||||
|
/// )
|
||||||
|
/// );
|
||||||
|
///
|
||||||
|
/// // ...
|
||||||
|
///
|
||||||
|
/// execute!(stdout, PopKeyboardEnhancementFlags);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that, currently, only the following support this protocol:
|
||||||
|
/// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
|
||||||
|
/// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
|
||||||
|
/// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
|
||||||
|
/// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
|
||||||
|
/// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
|
||||||
|
/// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
|
||||||
|
/// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct PushKeyboardEnhancementFlags(pub KeyboardEnhancementFlags);
|
||||||
|
|
||||||
|
impl Command for PushKeyboardEnhancementFlags {
|
||||||
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
|
write!(f, "{}{}u", csi!(">"), self.0.bits())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execute_winapi(&self) -> Result<()> {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Unsupported,
|
||||||
|
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn is_ansi_code_supported(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A command that disables extra kinds of keyboard events.
|
||||||
|
///
|
||||||
|
/// Specifically, it pops one level of keyboard enhancement flags.
|
||||||
|
///
|
||||||
|
/// See [`PushKeyboardEnhancementFlags`] and <https://sw.kovidgoyal.net/kitty/keyboard-protocol/> for more information.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct PopKeyboardEnhancementFlags;
|
||||||
|
|
||||||
|
impl Command for PopKeyboardEnhancementFlags {
|
||||||
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
|
f.write_str(csi!("<1u"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execute_winapi(&self) -> Result<()> {
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::Unsupported,
|
||||||
|
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn is_ansi_code_supported(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents an event.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(not(feature = "bracketed-paste"), derive(Copy))]
|
||||||
|
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
|
||||||
|
pub enum Event {
|
||||||
|
/// The terminal gained focus
|
||||||
|
FocusGained,
|
||||||
|
/// The terminal lost focus
|
||||||
|
FocusLost,
|
||||||
|
/// A single key event with additional pressed modifiers.
|
||||||
|
Key(KeyEvent),
|
||||||
|
/// A single mouse event with additional pressed modifiers.
|
||||||
|
Mouse(MouseEvent),
|
||||||
|
/// A string that was pasted into the terminal. Only emitted if bracketed paste has been
|
||||||
|
/// enabled.
|
||||||
|
#[cfg(feature = "bracketed-paste")]
|
||||||
|
Paste(String),
|
||||||
|
/// An resize event with new dimensions after resize (columns, rows).
|
||||||
|
/// **Note** that resize events can 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, etc.).
|
||||||
|
///
|
||||||
|
/// **Note:** `SUPER`, `HYPER`, and `META` can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct KeyModifiers: u8 {
|
||||||
|
const SHIFT = 0b0000_0001;
|
||||||
|
const CONTROL = 0b0000_0010;
|
||||||
|
const ALT = 0b0000_0100;
|
||||||
|
const SUPER = 0b0000_1000;
|
||||||
|
const HYPER = 0b0001_0000;
|
||||||
|
const META = 0b0010_0000;
|
||||||
|
const NONE = 0b0000_0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a keyboard event kind.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
|
pub enum KeyEventKind {
|
||||||
|
Press,
|
||||||
|
Repeat,
|
||||||
|
Release,
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
/// Represents extra state about the key event.
|
||||||
|
///
|
||||||
|
/// **Note:** This state can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub struct KeyEventState: u8 {
|
||||||
|
/// The key event origins from the keypad.
|
||||||
|
const KEYPAD = 0b0000_0001;
|
||||||
|
/// Caps Lock was enabled for this key event.
|
||||||
|
///
|
||||||
|
/// **Note:** this is set for the initial press of Caps Lock itself.
|
||||||
|
const CAPS_LOCK = 0b0000_1000;
|
||||||
|
/// Num Lock was enabled for this key event.
|
||||||
|
///
|
||||||
|
/// **Note:** this is set for the initial press of Num Lock itself.
|
||||||
|
const NUM_LOCK = 0b0000_1000;
|
||||||
|
const NONE = 0b0000_0000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a key event.
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[derive(Debug, PartialOrd, Clone, Copy)]
|
||||||
|
pub struct KeyEvent {
|
||||||
|
/// The key itself.
|
||||||
|
pub code: KeyCode,
|
||||||
|
/// Additional key modifiers.
|
||||||
|
pub modifiers: KeyModifiers,
|
||||||
|
/// Kind of event.
|
||||||
|
///
|
||||||
|
/// Only set if:
|
||||||
|
/// - Unix: [`KeyboardEnhancementFlags::REPORT_EVENT_TYPES`] has been enabled with [`PushKeyboardEnhancementFlags`].
|
||||||
|
/// - Windows: always
|
||||||
|
pub kind: KeyEventKind,
|
||||||
|
/// Keyboard state.
|
||||||
|
///
|
||||||
|
/// Only set if [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
pub state: KeyEventState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyEvent {
|
||||||
|
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
|
||||||
|
KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_with_kind(
|
||||||
|
code: KeyCode,
|
||||||
|
modifiers: KeyModifiers,
|
||||||
|
kind: KeyEventKind,
|
||||||
|
) -> KeyEvent {
|
||||||
|
KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers,
|
||||||
|
kind,
|
||||||
|
state: KeyEventState::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_with_kind_and_state(
|
||||||
|
code: KeyCode,
|
||||||
|
modifiers: KeyModifiers,
|
||||||
|
kind: KeyEventKind,
|
||||||
|
state: KeyEventState,
|
||||||
|
) -> KeyEvent {
|
||||||
|
KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers,
|
||||||
|
kind,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// modifies the KeyEvent,
|
||||||
|
// so that KeyModifiers::SHIFT is present iff
|
||||||
|
// an uppercase char is present.
|
||||||
|
fn normalize_case(mut self) -> KeyEvent {
|
||||||
|
let c = match self.code {
|
||||||
|
KeyCode::Char(c) => c,
|
||||||
|
_ => return self,
|
||||||
|
};
|
||||||
|
|
||||||
|
if c.is_ascii_uppercase() {
|
||||||
|
self.modifiers.insert(KeyModifiers::SHIFT);
|
||||||
|
} else if self.modifiers.contains(KeyModifiers::SHIFT) {
|
||||||
|
self.code = KeyCode::Char(c.to_ascii_uppercase())
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<KeyCode> for KeyEvent {
|
||||||
|
fn from(code: KeyCode) -> Self {
|
||||||
|
KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers: KeyModifiers::empty(),
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for KeyEvent {
|
||||||
|
fn eq(&self, other: &KeyEvent) -> bool {
|
||||||
|
let KeyEvent {
|
||||||
|
code: lhs_code,
|
||||||
|
modifiers: lhs_modifiers,
|
||||||
|
kind: lhs_kind,
|
||||||
|
state: lhs_state,
|
||||||
|
} = self.normalize_case();
|
||||||
|
let KeyEvent {
|
||||||
|
code: rhs_code,
|
||||||
|
modifiers: rhs_modifiers,
|
||||||
|
kind: rhs_kind,
|
||||||
|
state: rhs_state,
|
||||||
|
} = other.normalize_case();
|
||||||
|
(lhs_code == rhs_code)
|
||||||
|
&& (lhs_modifiers == rhs_modifiers)
|
||||||
|
&& (lhs_kind == rhs_kind)
|
||||||
|
&& (lhs_state == rhs_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for KeyEvent {}
|
||||||
|
|
||||||
|
impl Hash for KeyEvent {
|
||||||
|
fn hash<H: Hasher>(&self, hash_state: &mut H) {
|
||||||
|
let KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers,
|
||||||
|
kind,
|
||||||
|
state,
|
||||||
|
} = self.normalize_case();
|
||||||
|
code.hash(hash_state);
|
||||||
|
modifiers.hash(hash_state);
|
||||||
|
kind.hash(hash_state);
|
||||||
|
state.hash(hash_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a media key (as part of [`KeyCode::Media`]).
|
||||||
|
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum MediaKeyCode {
|
||||||
|
/// Play media key.
|
||||||
|
Play,
|
||||||
|
/// Pause media key.
|
||||||
|
Pause,
|
||||||
|
/// Play/Pause media key.
|
||||||
|
PlayPause,
|
||||||
|
/// Reverse media key.
|
||||||
|
Reverse,
|
||||||
|
/// Stop media key.
|
||||||
|
Stop,
|
||||||
|
/// Fast-forward media key.
|
||||||
|
FastForward,
|
||||||
|
/// Rewind media key.
|
||||||
|
Rewind,
|
||||||
|
/// Next-track media key.
|
||||||
|
TrackNext,
|
||||||
|
/// Previous-track media key.
|
||||||
|
TrackPrevious,
|
||||||
|
/// Record media key.
|
||||||
|
Record,
|
||||||
|
/// Lower-volume media key.
|
||||||
|
LowerVolume,
|
||||||
|
/// Raise-volume media key.
|
||||||
|
RaiseVolume,
|
||||||
|
/// Mute media key.
|
||||||
|
MuteVolume,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a modifier key (as part of [`KeyCode::Modifier`]).
|
||||||
|
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum ModifierKeyCode {
|
||||||
|
/// Left Shift key.
|
||||||
|
LeftShift,
|
||||||
|
/// Left Control key.
|
||||||
|
LeftControl,
|
||||||
|
/// Left Alt key.
|
||||||
|
LeftAlt,
|
||||||
|
/// Left Super key.
|
||||||
|
LeftSuper,
|
||||||
|
/// Left Hyper key.
|
||||||
|
LeftHyper,
|
||||||
|
/// Left Meta key.
|
||||||
|
LeftMeta,
|
||||||
|
/// Right Shift key.
|
||||||
|
RightShift,
|
||||||
|
/// Right Control key.
|
||||||
|
RightControl,
|
||||||
|
/// Right Alt key.
|
||||||
|
RightAlt,
|
||||||
|
/// Right Super key.
|
||||||
|
RightSuper,
|
||||||
|
/// Right Hyper key.
|
||||||
|
RightHyper,
|
||||||
|
/// Right Meta key.
|
||||||
|
RightMeta,
|
||||||
|
/// Iso Level3 Shift key.
|
||||||
|
IsoLevel3Shift,
|
||||||
|
/// Iso Level5 Shift key.
|
||||||
|
IsoLevel5Shift,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 down key.
|
||||||
|
PageDown,
|
||||||
|
/// Tab key.
|
||||||
|
Tab,
|
||||||
|
/// Shift + Tab key.
|
||||||
|
BackTab,
|
||||||
|
/// Delete key.
|
||||||
|
Delete,
|
||||||
|
/// Insert key.
|
||||||
|
Insert,
|
||||||
|
/// F key.
|
||||||
|
///
|
||||||
|
/// `KeyCode::F(1)` represents F1 key, etc.
|
||||||
|
F(u8),
|
||||||
|
/// A character.
|
||||||
|
///
|
||||||
|
/// `KeyCode::Char('c')` represents `c` character, etc.
|
||||||
|
Char(char),
|
||||||
|
/// Null.
|
||||||
|
Null,
|
||||||
|
/// Escape key.
|
||||||
|
Esc,
|
||||||
|
/// Caps Lock key.
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
CapsLock,
|
||||||
|
/// Scroll Lock key.
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
ScrollLock,
|
||||||
|
/// Num Lock key.
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
NumLock,
|
||||||
|
/// Print Screen key.
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
PrintScreen,
|
||||||
|
/// Pause key.
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
Pause,
|
||||||
|
/// Menu key.
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
Menu,
|
||||||
|
/// The "Begin" key (often mapped to the 5 key when Num Lock is turned on).
|
||||||
|
///
|
||||||
|
/// **Note:** this key can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
KeypadBegin,
|
||||||
|
/// A media key.
|
||||||
|
///
|
||||||
|
/// **Note:** these keys can only be read if
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
Media(MediaKeyCode),
|
||||||
|
/// A modifier key.
|
||||||
|
///
|
||||||
|
/// **Note:** these keys can only be read if **both**
|
||||||
|
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] and
|
||||||
|
/// [`KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES`] have been enabled with
|
||||||
|
/// [`PushKeyboardEnhancementFlags`].
|
||||||
|
Modifier(ModifierKeyCode),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
/// The progressive keyboard enhancement flags enabled by the terminal.
|
||||||
|
#[cfg(unix)]
|
||||||
|
KeyboardEnhancementFlags(KeyboardEnhancementFlags),
|
||||||
|
/// Attributes and architectural class of the terminal.
|
||||||
|
#[cfg(unix)]
|
||||||
|
PrimaryDeviceAttributes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
use super::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_equality() {
|
||||||
|
let lowercase_d_with_shift = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT);
|
||||||
|
let uppercase_d_with_shift = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT);
|
||||||
|
let uppercase_d = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE);
|
||||||
|
assert_eq!(lowercase_d_with_shift, uppercase_d_with_shift);
|
||||||
|
assert_eq!(uppercase_d, uppercase_d_with_shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash() {
|
||||||
|
let lowercase_d_with_shift_hash = {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT).hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
};
|
||||||
|
let uppercase_d_with_shift_hash = {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT).hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
};
|
||||||
|
let uppercase_d_hash = {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE).hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
};
|
||||||
|
assert_eq!(lowercase_d_with_shift_hash, uppercase_d_with_shift_hash);
|
||||||
|
assert_eq!(uppercase_d_hash, uppercase_d_with_shift_hash);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub(crate) mod waker;
|
pub(crate) mod waker;
|
||||||
|
|
||||||
pub(crate) mod file_descriptor;
|
pub(crate) mod file_descriptor;
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
|
@ -11,7 +11,9 @@ use crate::Result;
|
|||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
pub(crate) mod waker;
|
pub(crate) mod waker;
|
||||||
|
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub(crate) mod parse;
|
pub(crate) mod parse;
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub(crate) mod poll;
|
pub(crate) mod poll;
|
||||||
|
|
||||||
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
||||||
|
@ -98,6 +98,7 @@ use crate::{csi, impl_display, Result};
|
|||||||
|
|
||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
|
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub use sys::supports_keyboard_enhancement;
|
pub use sys::supports_keyboard_enhancement;
|
||||||
|
|
||||||
/// Tells whether the raw mode is enabled.
|
/// Tells whether the raw mode is enabled.
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
//! This module provides platform related functions.
|
//! This module provides platform related functions.
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
#[cfg(feature = "event")]
|
||||||
pub use self::unix::supports_keyboard_enhancement;
|
pub use self::unix::supports_keyboard_enhancement;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size};
|
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
#[cfg(feature = "event")]
|
||||||
pub use self::windows::supports_keyboard_enhancement;
|
pub use self::windows::supports_keyboard_enhancement;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub(crate) use self::windows::{
|
pub(crate) use self::windows::{
|
||||||
|
@ -1,21 +1,18 @@
|
|||||||
//! UNIX related logic for terminal manipulation.
|
//! UNIX related logic for terminal manipulation.
|
||||||
|
|
||||||
use std::fs::File;
|
use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc};
|
||||||
use std::io::Write;
|
|
||||||
use std::os::unix::io::{IntoRawFd, RawFd};
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::{io, mem, process};
|
|
||||||
|
|
||||||
use libc::{
|
use libc::{
|
||||||
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
|
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
|
||||||
TIOCGWINSZ,
|
TIOCGWINSZ,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use std::fs::File;
|
||||||
|
|
||||||
|
use std::os::unix::io::{IntoRawFd, RawFd};
|
||||||
|
|
||||||
|
use std::{io, mem, process};
|
||||||
|
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::event::filter::{KeyboardEnhancementFlagsFilter, PrimaryDeviceAttributesFilter};
|
|
||||||
use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc};
|
|
||||||
use crate::event::{poll_internal, read_internal, InternalEvent};
|
|
||||||
|
|
||||||
// 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
|
||||||
// None -> we're not in the raw mode
|
// None -> we're not in the raw mode
|
||||||
@ -96,6 +93,7 @@ pub(crate) fn disable_raw_mode() -> Result<()> {
|
|||||||
///
|
///
|
||||||
/// On unix systems, this function will block and possibly time out while
|
/// On unix systems, this function will block and possibly time out while
|
||||||
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
|
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
|
||||||
|
#[cfg(feature = "events")]
|
||||||
pub fn supports_keyboard_enhancement() -> Result<bool> {
|
pub fn supports_keyboard_enhancement() -> Result<bool> {
|
||||||
if is_raw_mode_enabled() {
|
if is_raw_mode_enabled() {
|
||||||
read_supports_keyboard_enhancement_raw()
|
read_supports_keyboard_enhancement_raw()
|
||||||
@ -104,6 +102,7 @@ pub fn supports_keyboard_enhancement() -> Result<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "events")]
|
||||||
fn read_supports_keyboard_enhancement_flags() -> Result<bool> {
|
fn read_supports_keyboard_enhancement_flags() -> Result<bool> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let flags = read_supports_keyboard_enhancement_raw();
|
let flags = read_supports_keyboard_enhancement_raw();
|
||||||
@ -111,7 +110,15 @@ fn read_supports_keyboard_enhancement_flags() -> Result<bool> {
|
|||||||
flags
|
flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "events")]
|
||||||
fn read_supports_keyboard_enhancement_raw() -> Result<bool> {
|
fn read_supports_keyboard_enhancement_raw() -> Result<bool> {
|
||||||
|
use crate::event::{
|
||||||
|
filter::{KeyboardEnhancementFlagsFilter, PrimaryDeviceAttributesFilter},
|
||||||
|
poll_internal, read_internal, InternalEvent,
|
||||||
|
};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
// This is the recommended method for testing support for the keyboard enhancement protocol.
|
// This is the recommended method for testing support for the keyboard enhancement protocol.
|
||||||
// We send a query for the flags supported by the terminal and then the primary device attributes
|
// We send a query for the flags supported by the terminal and then the primary device attributes
|
||||||
// query. If we receive the primary device attributes response but not the keyboard enhancement
|
// query. If we receive the primary device attributes response but not the keyboard enhancement
|
||||||
|
Loading…
x
Reference in New Issue
Block a user