Skip to content

Commit 731ec26

Browse files
committed
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.
1 parent f298573 commit 731ec26

File tree

2 files changed

+232
-5
lines changed

2 files changed

+232
-5
lines changed

src/display/src/crossterm.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ use std::io::{stdout, BufWriter, Stdout, Write};
22

33
use crossterm::{
44
cursor::{Hide, MoveTo, MoveToColumn, MoveToNextLine, Show},
5-
event::{DisableMouseCapture, EnableMouseCapture},
5+
event::{
6+
DisableMouseCapture,
7+
EnableMouseCapture,
8+
KeyboardEnhancementFlags,
9+
PopKeyboardEnhancementFlags,
10+
PushKeyboardEnhancementFlags,
11+
},
612
style::{available_color_count, Attribute, Colors, Print, ResetColor, SetAttribute, SetColors},
713
terminal::{
814
disable_raw_mode,
@@ -118,12 +124,21 @@ impl Tui for CrossTerm {
118124
self.queue_command(DisableLineWrap)?;
119125
self.queue_command(Hide)?;
120126
self.queue_command(EnableMouseCapture)?;
127+
// this will fail on terminals without support, so ignore any errors
128+
let _command_result = self.queue_command(PushKeyboardEnhancementFlags(
129+
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
130+
| KeyboardEnhancementFlags::REPORT_EVENT_TYPES
131+
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
132+
| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES,
133+
));
121134
enable_raw_mode().map_err(DisplayError::Unexpected)?;
122135
self.flush()
123136
}
124137

125138
#[inline]
126139
fn end(&mut self) -> Result<(), DisplayError> {
140+
// this will fail on terminals without support, so ignore any errors
141+
let _command_result = self.queue_command(PopKeyboardEnhancementFlags);
127142
self.queue_command(DisableMouseCapture)?;
128143
self.queue_command(Show)?;
129144
self.queue_command(EnableLineWrap)?;

src/input/src/event_provider.rs

Lines changed: 216 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::time::Duration;
22

33
use anyhow::{anyhow, Result};
4-
use crossterm::event::Event;
54
#[cfg(not(test))]
65
use crossterm::event::{poll, read};
6+
use crossterm::event::{Event, KeyEvent, KeyEventKind, MouseEvent, MouseEventKind};
77
#[cfg(test)]
88
use read_event_mocks::{poll, read};
99

@@ -23,7 +23,20 @@ impl<FN: Fn() -> Result<Option<Event>> + Send + Sync + 'static> EventReaderFn fo
2323
pub fn read_event() -> Result<Option<Event>> {
2424
if poll(Duration::from_millis(20)).unwrap_or(false) {
2525
read()
26-
.map(Some)
26+
.map(|event| {
27+
match event {
28+
e @ (Event::Key(KeyEvent {
29+
kind: KeyEventKind::Press | KeyEventKind::Repeat,
30+
..
31+
})
32+
| Event::Mouse(MouseEvent {
33+
kind: MouseEventKind::Down(_) | MouseEventKind::ScrollDown | MouseEventKind::ScrollUp,
34+
..
35+
})
36+
| Event::Resize(..)) => Some(e),
37+
Event::Key(_) | Event::Mouse(_) | Event::Paste(_) | Event::FocusGained | Event::FocusLost => None,
38+
}
39+
})
2740
.map_err(|err| anyhow!("{:#}", err).context("Unexpected Error"))
2841
}
2942
else {
@@ -62,7 +75,7 @@ mod read_event_mocks {
6275
mod tests {
6376
use std::{io, io::ErrorKind};
6477

65-
use crossterm::event::{KeyCode, KeyEvent};
78+
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton};
6679

6780
use super::*;
6881

@@ -102,7 +115,7 @@ mod tests {
102115

103116
#[test]
104117
#[serial_test::serial]
105-
fn read_event_read_success() {
118+
fn read_event_read_key_press() {
106119
let mut lock = read_event_mocks::NEXT_EVENT.lock();
107120
*lock = Ok(Event::Key(KeyEvent::from(KeyCode::Enter)));
108121
drop(lock);
@@ -113,4 +126,203 @@ mod tests {
113126

114127
assert_eq!(read_event().unwrap(), Some(Event::Key(KeyEvent::from(KeyCode::Enter))));
115128
}
129+
130+
#[test]
131+
#[serial_test::serial]
132+
fn read_event_read_key_repeat() {
133+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
134+
*lock = Ok(Event::Key(KeyEvent::new_with_kind(
135+
KeyCode::Enter,
136+
KeyModifiers::NONE,
137+
KeyEventKind::Repeat,
138+
)));
139+
drop(lock);
140+
141+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
142+
*lock = Ok(true);
143+
drop(lock);
144+
145+
assert_eq!(
146+
read_event().unwrap(),
147+
Some(Event::Key(KeyEvent::new_with_kind(
148+
KeyCode::Enter,
149+
KeyModifiers::NONE,
150+
KeyEventKind::Repeat,
151+
)))
152+
);
153+
}
154+
155+
#[test]
156+
#[serial_test::serial]
157+
fn read_event_read_mouse_down() {
158+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
159+
*lock = Ok(Event::Mouse(MouseEvent {
160+
kind: MouseEventKind::Down(MouseButton::Right),
161+
column: 0,
162+
row: 0,
163+
modifiers: KeyModifiers::NONE,
164+
}));
165+
drop(lock);
166+
167+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
168+
*lock = Ok(true);
169+
drop(lock);
170+
171+
assert_eq!(
172+
read_event().unwrap(),
173+
Some(Event::Mouse(MouseEvent {
174+
kind: MouseEventKind::Down(MouseButton::Right),
175+
column: 0,
176+
row: 0,
177+
modifiers: KeyModifiers::NONE
178+
}))
179+
);
180+
}
181+
182+
#[test]
183+
#[serial_test::serial]
184+
fn read_event_read_mouse_scroll_down() {
185+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
186+
*lock = Ok(Event::Mouse(MouseEvent {
187+
kind: MouseEventKind::ScrollDown,
188+
column: 0,
189+
row: 0,
190+
modifiers: KeyModifiers::NONE,
191+
}));
192+
drop(lock);
193+
194+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
195+
*lock = Ok(true);
196+
drop(lock);
197+
198+
assert_eq!(
199+
read_event().unwrap(),
200+
Some(Event::Mouse(MouseEvent {
201+
kind: MouseEventKind::ScrollDown,
202+
column: 0,
203+
row: 0,
204+
modifiers: KeyModifiers::NONE
205+
}))
206+
);
207+
}
208+
209+
#[test]
210+
#[serial_test::serial]
211+
fn read_event_read_mouse_scroll_up() {
212+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
213+
*lock = Ok(Event::Mouse(MouseEvent {
214+
kind: MouseEventKind::ScrollDown,
215+
column: 0,
216+
row: 0,
217+
modifiers: KeyModifiers::NONE,
218+
}));
219+
drop(lock);
220+
221+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
222+
*lock = Ok(true);
223+
drop(lock);
224+
225+
assert_eq!(
226+
read_event().unwrap(),
227+
Some(Event::Mouse(MouseEvent {
228+
kind: MouseEventKind::ScrollDown,
229+
column: 0,
230+
row: 0,
231+
modifiers: KeyModifiers::NONE
232+
}))
233+
);
234+
}
235+
236+
#[test]
237+
#[serial_test::serial]
238+
fn read_event_read_resize() {
239+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
240+
*lock = Ok(Event::Resize(1, 1));
241+
drop(lock);
242+
243+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
244+
*lock = Ok(true);
245+
drop(lock);
246+
247+
assert_eq!(read_event().unwrap(), Some(Event::Resize(1, 1)));
248+
}
249+
250+
#[test]
251+
#[serial_test::serial]
252+
fn read_event_read_key_other() {
253+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
254+
*lock = Ok(Event::Key(KeyEvent::new_with_kind(
255+
KeyCode::Enter,
256+
KeyModifiers::NONE,
257+
KeyEventKind::Release,
258+
)));
259+
drop(lock);
260+
261+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
262+
*lock = Ok(true);
263+
drop(lock);
264+
265+
assert_eq!(read_event().unwrap(), None);
266+
}
267+
268+
#[test]
269+
#[serial_test::serial]
270+
fn read_event_read_mouse_other() {
271+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
272+
*lock = Ok(Event::Mouse(MouseEvent {
273+
kind: MouseEventKind::Moved,
274+
column: 0,
275+
row: 0,
276+
modifiers: KeyModifiers::NONE,
277+
}));
278+
drop(lock);
279+
280+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
281+
*lock = Ok(true);
282+
drop(lock);
283+
284+
assert_eq!(read_event().unwrap(), None);
285+
}
286+
287+
#[test]
288+
#[serial_test::serial]
289+
fn read_event_read_paste() {
290+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
291+
*lock = Ok(Event::Paste(String::from("Foo")));
292+
drop(lock);
293+
294+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
295+
*lock = Ok(true);
296+
drop(lock);
297+
298+
assert_eq!(read_event().unwrap(), None);
299+
}
300+
301+
#[test]
302+
#[serial_test::serial]
303+
fn read_event_read_focus_gained() {
304+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
305+
*lock = Ok(Event::FocusGained);
306+
drop(lock);
307+
308+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
309+
*lock = Ok(true);
310+
drop(lock);
311+
312+
assert_eq!(read_event().unwrap(), None);
313+
}
314+
315+
#[test]
316+
#[serial_test::serial]
317+
fn read_event_read_focus_lost() {
318+
let mut lock = read_event_mocks::NEXT_EVENT.lock();
319+
*lock = Ok(Event::FocusLost);
320+
drop(lock);
321+
322+
let mut lock = read_event_mocks::HAS_POLLED_EVENT.lock();
323+
*lock = Ok(true);
324+
drop(lock);
325+
326+
assert_eq!(read_event().unwrap(), None);
327+
}
116328
}

0 commit comments

Comments
 (0)