mirror of
https://github.com/crossterm-rs/crossterm.git
synced 2025-10-02 15:26:05 +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]
|
||||
default = ["bracketed-paste", "windows"]
|
||||
default = ["bracketed-paste", "windows", "events"]
|
||||
windows = ["winapi", "crossterm_winapi"]
|
||||
bracketed-paste = []
|
||||
event-stream = ["futures-core"]
|
||||
use-dev-tty = ["filedescriptor"]
|
||||
|
||||
events = ["mio", "signal-hook", "signal-hook-mio"]
|
||||
#
|
||||
# Shared dependencies
|
||||
#
|
||||
@ -59,10 +59,10 @@ crossterm_winapi = { version = "0.9", optional = true }
|
||||
#
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
signal-hook = { version = "0.3.13" }
|
||||
signal-hook = { version = "0.3.13", optional = true }
|
||||
filedescriptor = { version = "0.8", optional = true }
|
||||
mio = { version = "0.8", features = ["os-poll"] }
|
||||
signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"] }
|
||||
mio = { version = "0.8", features = ["os-poll"], optional = true }
|
||||
signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"], optional = true }
|
||||
|
||||
#
|
||||
# Dev dependencies (examples, ...)
|
||||
|
12
README.md
12
README.md
@ -144,6 +144,12 @@ features = ["event-stream"]
|
||||
|:---------------|:---------------------------------------------|
|
||||
| `event-stream` | `futures::Stream` producing `Result<Event>`. |
|
||||
| `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
|
||||
|
||||
@ -151,9 +157,9 @@ features = ["event-stream"]
|
||||
|:---------------|:---------------------------------------------------------------------------------|:--------------------------------------|
|
||||
| `bitflags` | `KeyModifiers`, those are differ based on input. | 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 |
|
||||
| `Mio` | event readiness polling, waking up poller | UNIX only |
|
||||
| `signal-hook` | signal-hook is used to handle terminal resize SIGNAL with Mio. | 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 | optional (`events` feature), 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 |
|
||||
| `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 |
|
||||
|
@ -48,8 +48,6 @@ use std::fmt;
|
||||
use crate::Result;
|
||||
use crate::{csi, impl_display, Command};
|
||||
|
||||
pub use sys::position;
|
||||
|
||||
pub(crate) mod sys;
|
||||
|
||||
/// A command that moves the terminal cursor to the given position (column, row).
|
||||
@ -432,7 +430,7 @@ mod tests {
|
||||
use crate::execute;
|
||||
|
||||
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
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! This module provides platform related functions.
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "events")]
|
||||
pub use self::unix::position;
|
||||
#[cfg(windows)]
|
||||
pub use self::windows::position;
|
||||
@ -14,4 +15,5 @@ pub(crate) use self::windows::{
|
||||
pub(crate) mod windows;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "events")]
|
||||
pub(crate) mod unix;
|
||||
|
764
src/event.rs
764
src/event.rs
@ -81,166 +81,31 @@
|
||||
//! them (`event-*`).
|
||||
|
||||
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")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{csi, Command, Result};
|
||||
use filter::{EventFilter, Filter};
|
||||
use read::InternalEventReader;
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub use stream::EventStream;
|
||||
use timeout::PollTimeout;
|
||||
use crate::{csi, Command};
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
pub(crate) mod filter;
|
||||
#[cfg(feature = "events")]
|
||||
mod read;
|
||||
#[cfg(feature = "events")]
|
||||
mod source;
|
||||
#[cfg(feature = "event-stream")]
|
||||
#[cfg(feature = "events")]
|
||||
mod stream;
|
||||
pub(crate) mod sys;
|
||||
#[cfg(feature = "events")]
|
||||
mod timeout;
|
||||
|
||||
/// 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);
|
||||
#[cfg(feature = "events")]
|
||||
mod events_api;
|
||||
|
||||
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),
|
||||
))
|
||||
}
|
||||
#[cfg(feature = "events")]
|
||||
pub use events_api::*;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
pub(crate) mod sys;
|
||||
|
||||
/// A command that enables mouse event capturing.
|
||||
///
|
||||
@ -265,7 +130,7 @@ impl Command for EnableMouseCapture {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> Result<()> {
|
||||
fn execute_winapi(&self) -> crate::Result<()> {
|
||||
sys::windows::enable_mouse_capture()
|
||||
}
|
||||
|
||||
@ -294,7 +159,7 @@ impl Command for DisableMouseCapture {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> Result<()> {
|
||||
fn execute_winapi(&self) -> crate::Result<()> {
|
||||
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.
|
||||
///
|
||||
/// It should be paired with [`DisableFocusChange`] at the end of execution.
|
||||
@ -431,7 +183,7 @@ impl Command for EnableFocusChange {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> Result<()> {
|
||||
fn execute_winapi(&self) -> crate::Result<()> {
|
||||
// Focus events are always enabled on Windows
|
||||
Ok(())
|
||||
}
|
||||
@ -447,7 +199,7 @@ impl Command for DisableFocusChange {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> Result<()> {
|
||||
fn execute_winapi(&self) -> crate::Result<()> {
|
||||
// Focus events can't be disabled on Windows
|
||||
Ok(())
|
||||
}
|
||||
@ -494,489 +246,3 @@ impl Command for DisableBracketedPaste {
|
||||
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 = "events")]
|
||||
pub(crate) mod waker;
|
||||
|
||||
pub(crate) mod file_descriptor;
|
||||
#[cfg(feature = "events")]
|
||||
pub(crate) mod parse;
|
||||
|
@ -11,7 +11,9 @@ use crate::Result;
|
||||
#[cfg(feature = "event-stream")]
|
||||
pub(crate) mod waker;
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
pub(crate) mod parse;
|
||||
#[cfg(feature = "events")]
|
||||
pub(crate) mod poll;
|
||||
|
||||
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
||||
|
@ -98,6 +98,7 @@ use crate::{csi, impl_display, Result};
|
||||
|
||||
pub(crate) mod sys;
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
pub use sys::supports_keyboard_enhancement;
|
||||
|
||||
/// Tells whether the raw mode is enabled.
|
||||
|
@ -1,10 +1,12 @@
|
||||
//! This module provides platform related functions.
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "event")]
|
||||
pub use self::unix::supports_keyboard_enhancement;
|
||||
#[cfg(unix)]
|
||||
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size};
|
||||
#[cfg(windows)]
|
||||
#[cfg(feature = "event")]
|
||||
pub use self::windows::supports_keyboard_enhancement;
|
||||
#[cfg(windows)]
|
||||
pub(crate) use self::windows::{
|
||||
|
@ -1,21 +1,18 @@
|
||||
//! UNIX related logic for terminal manipulation.
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::{IntoRawFd, RawFd};
|
||||
use std::time::Duration;
|
||||
use std::{io, mem, process};
|
||||
|
||||
use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc};
|
||||
use libc::{
|
||||
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
|
||||
TIOCGWINSZ,
|
||||
};
|
||||
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::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
|
||||
// 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
|
||||
/// [`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> {
|
||||
if is_raw_mode_enabled() {
|
||||
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> {
|
||||
enable_raw_mode()?;
|
||||
let flags = read_supports_keyboard_enhancement_raw();
|
||||
@ -111,7 +110,15 @@ fn read_supports_keyboard_enhancement_flags() -> Result<bool> {
|
||||
flags
|
||||
}
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
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.
|
||||
// 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user