diff options
author | Tim Oram <dev@mitmaro.ca> | 2023-07-17 13:56:40 -0230 |
---|---|---|
committer | Tim Oram <dev@mitmaro.ca> | 2023-07-17 15:44:00 -0230 |
commit | 731ec26d1e3cabf75bfd13d2f4c874f65b98d929 (patch) | |
tree | 389e25a0976e60da3a779ef209bc55f21f2a8c00 | |
parent | f298573ef4eaa4e4feac3e6f587a98493ba6dd15 (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.rs | 17 | ||||
-rw-r--r-- | src/input/src/event_provider.rs | 220 |
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); + } } |