summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Oram <dev@mitmaro.ca>2023-07-17 13:56:40 -0230
committerTim Oram <dev@mitmaro.ca>2023-07-17 15:44:00 -0230
commit731ec26d1e3cabf75bfd13d2f4c874f65b98d929 (patch)
tree389e25a0976e60da3a779ef209bc55f21f2a8c00
parentf298573ef4eaa4e4feac3e6f587a98493ba6dd15 (diff)
Normalize event handling across terminals
Depending on the operating system and terminal, how events were reports would change. On Windows, for example, would report events for key press and release, while some Linux terminals would only report key press. This change enables the Kitty keyboard protocol, to provide enhanced reporting on systems/terminals that support the protocol. As part of the extended event reporting, multiple event types have been filtered out at the event read level. This includes things like key release, mouse move, paste, and focus events. These events are not used in the application, and filtering them at the read level, they do not need to be ignored at use. There is also a small performance boost, since these events are never queued.
-rw-r--r--src/display/src/crossterm.rs17
-rw-r--r--src/input/src/event_provider.rs220
2 files changed, 232 insertions, 5 deletions
diff --git a/src/display/src/crossterm.rs b/src/display/src/crossterm.rs
index 31aac25..291a7e8 100644
--- a/src/display/src/crossterm.rs
+++ b/src/display/src/crossterm.rs
@@ -2,7 +2,13 @@ use std::io::{stdout, BufWriter, Stdout, Write};
use crossterm::{
cursor::{Hide, MoveTo, MoveToColumn, MoveToNextLine, Show},
- event::{DisableMouseCapture, EnableMouseCapture},
+ event::{
+ DisableMouseCapture,
+ EnableMouseCapture,
+ KeyboardEnhancementFlags,
+ PopKeyboardEnhancementFlags,
+ PushKeyboardEnhancementFlags,
+ },
style::{available_color_count, Attribute, Colors, Print, ResetColor, SetAttribute, SetColors},
terminal::{
disable_raw_mode,
@@ -118,12 +124,21 @@ impl Tui for CrossTerm {
self.queue_command(DisableLineWrap)?;
self.queue_command(Hide)?;
self.queue_command(EnableMouseCapture)?;
+ // this will fail on terminals without support, so ignore any errors
+ let _command_result = self.queue_command(PushKeyboardEnhancementFlags(
+ KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
+ | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
+ | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
+ | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES,
+ ));
enable_raw_mode().map_err(DisplayError::Unexpected)?;
self.flush()
}
#[inline]
fn end(&mut self) -> Result<(), DisplayError> {
+ // this will fail on terminals without support, so ignore any errors
+ let _command_result = self.queue_command(PopKeyboardEnhancementFlags);
self.queue_command(DisableMouseCapture)?;
self.queue_command(Show)?;
self.queue_command(EnableLineWrap)?;
diff --git a/src/input/src/event_provider.rs b/src/input/src/event_provider.rs
index f1a0440..7dd1f41 100644
--- a/src/input/src/event_provider.rs
+++ b/src/input/src/event_provider.rs
@@ -1,9 +1,9 @@
use std::time::Duration;
use anyhow::{anyhow, Result};
-use crossterm::event::Event;
#[cfg(not(test))]
use crossterm::event::{poll, read};
+use crossterm::event::{Event, KeyEvent, KeyEventKind, MouseEvent, MouseEventKind};
#[cfg(test)]
use read_event_mocks::{poll, read};
@@ -23,7 +23,20 @@ impl<FN: Fn() -> Result<Option<Event>> + Send + Sync + 'static> EventReaderFn fo
pub fn read_event() -> Result<Option<Event>> {
if poll(Duration::from_millis(20)).unwrap_or(false) {
read()
- .map(Some)
+ .map(|event| {
+ match event {
+ e @ (Event::Key(KeyEvent {
+ kind: KeyEventKind::Press | KeyEventKind::Repeat,
+ ..
+ })
+ | Event::Mouse(MouseEvent {
+ kind: MouseEventKind::Down(_) | MouseEventKind::ScrollDown | MouseEventKind::ScrollUp,
+ ..
+ })
+ | Event::Resize(..)) => Some(e),
+ Event::Key(_) | Event::Mouse(_) | Event::Paste(_) | Event::FocusGained | Event::FocusLost => None,
+ }
+ })
.map_err(|err| anyhow!("{:#}", err).context("Unexpected Error"))
}
else {
@@ -62,7 +75,7 @@ mod read_event_mocks {
mod tests {
use std::{io, io::ErrorKind};
- use crossterm::event::{KeyCode, KeyEvent};
+ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton};
use super::*;
@@ -102,7 +115,7 @@ mod tests {
#[test]
#[serial_test::serial]
- fn read_event_read_success() {
+ fn read_event_read_key_press() {
let mut lock = read_event_mocks::NEXT_EVENT.lock();
*lock = Ok(Event::Key(KeyEvent::from(KeyCode::Enter)));
drop(lock);
@@ -113,4 +126,203 @@ mod tests {
assert_eq!(read_event().unwrap(), Some(Event::Key(KeyEvent::from(KeyCode::Enter))));
}
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_key_repeat() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Key(KeyEvent::new_with_kind(
+ KeyCode::Enter,
+ KeyModifiers::NONE,
+ KeyEventKind::Repeat,
+ )));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(
+ read_event().unwrap(),
+ Some(Event::Key(KeyEvent::new_with_kind(
+ KeyCode::Enter,
+ KeyModifiers::NONE,
+ KeyEventKind::Repeat,
+ )))
+ );
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_mouse_down() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::Down(MouseButton::Right),
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE,
+ }));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(
+ read_event().unwrap(),
+ Some(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::Down(MouseButton::Right),
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE
+ }))
+ );
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_mouse_scroll_down() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::ScrollDown,
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE,
+ }));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(
+ read_event().unwrap(),
+ Some(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::ScrollDown,
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE
+ }))
+ );
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_mouse_scroll_up() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::ScrollDown,
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE,
+ }));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(
+ read_event().unwrap(),
+ Some(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::ScrollDown,
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE
+ }))
+ );
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_resize() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Resize(1, 1));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(read_event().unwrap(), Some(Event::Resize(1, 1)));
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_key_other() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Key(KeyEvent::new_with_kind(
+ KeyCode::Enter,
+ KeyModifiers::NONE,
+ KeyEventKind::Release,
+ )));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(read_event().unwrap(), None);
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_mouse_other() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Mouse(MouseEvent {
+ kind: MouseEventKind::Moved,
+ column: 0,
+ row: 0,
+ modifiers: KeyModifiers::NONE,
+ }));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(read_event().unwrap(), None);
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_paste() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::Paste(String::from("Foo")));
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(read_event().unwrap(), None);
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_focus_gained() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::FocusGained);
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(read_event().unwrap(), None);
+ }
+
+ #[test]
+ #[serial_test::serial]
+ fn read_event_read_focus_lost() {
+ let mut lock = read_event_mocks::NEXT_EVENT.lock();
+ *lock = Ok(Event::FocusLost);
+ drop(lock);
+
+ let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
+ *lock = Ok(true);
+ drop(lock);
+
+ assert_eq!(read_event().unwrap(), None);
+ }
}