event_read/
event-read.rs

1// CREDIT: This module is mostly based on crossterm's `event-read` example with minor
2// modifications to adapt to the termina API.
3// <https://github.com/crossterm-rs/crossterm/blob/36d95b26a26e64b0f8c12edfe11f410a6d56a812/examples/event-read.rs>
4use std::{
5    io::{self, Write as _},
6    time::Duration,
7};
8
9use termina::{
10    escape::csi::{self, KittyKeyboardFlags},
11    event::{KeyCode, KeyEvent},
12    Event, PlatformTerminal, Terminal, WindowSize,
13};
14
15const HELP: &str = r#"Blocking read()
16 - Keyboard, mouse, focus and terminal resize events enabled
17 - Hit "c" to print current cursor position
18 - Use Esc to quit
19"#;
20
21macro_rules! decset {
22    ($mode:ident) => {
23        csi::Csi::Mode(csi::Mode::SetDecPrivateMode(csi::DecPrivateMode::Code(
24            csi::DecPrivateModeCode::$mode,
25        )))
26    };
27}
28macro_rules! decreset {
29    ($mode:ident) => {
30        csi::Csi::Mode(csi::Mode::ResetDecPrivateMode(csi::DecPrivateMode::Code(
31            csi::DecPrivateModeCode::$mode,
32        )))
33    };
34}
35
36fn main() -> io::Result<()> {
37    println!("{HELP}");
38
39    let mut terminal = PlatformTerminal::new()?;
40    terminal.enter_raw_mode()?;
41
42    write!(
43        terminal,
44        "{}{}{}{}{}{}{}{}",
45        csi::Csi::Keyboard(csi::Keyboard::PushFlags(
46            KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES
47                | KittyKeyboardFlags::REPORT_ALTERNATE_KEYS
48        )),
49        decset!(FocusTracking),
50        decset!(BracketedPaste),
51        decset!(MouseTracking),
52        decset!(ButtonEventMouse),
53        decset!(AnyEventMouse),
54        decset!(RXVTMouse),
55        decset!(SGRMouse),
56    )?;
57    terminal.flush()?;
58
59    let mut size = terminal.get_dimensions()?;
60    loop {
61        let event = terminal.read(|event| !event.is_escape())?;
62
63        println!("Event: {event:?}\r");
64
65        match event {
66            Event::Key(KeyEvent {
67                code: KeyCode::Escape,
68                ..
69            }) => break,
70            Event::Key(KeyEvent {
71                code: KeyCode::Char('c'),
72                ..
73            }) => {
74                write!(
75                    terminal,
76                    "{}",
77                    csi::Csi::Cursor(csi::Cursor::RequestActivePositionReport),
78                )?;
79                terminal.flush()?;
80                let filter = |event: &Event| {
81                    matches!(
82                        event,
83                        Event::Csi(csi::Csi::Cursor(csi::Cursor::ActivePositionReport { .. }))
84                    )
85                };
86                if terminal.poll(filter, Some(Duration::from_millis(50)))? {
87                    let Event::Csi(csi::Csi::Cursor(csi::Cursor::ActivePositionReport {
88                        line,
89                        col,
90                    })) = terminal.read(filter)?
91                    else {
92                        unreachable!()
93                    };
94                    println!(
95                        "Cursor position: {:?}\r",
96                        (line.get_zero_based(), col.get_zero_based())
97                    );
98                } else {
99                    eprintln!("Failed to read the cursor position within 50msec\r");
100                }
101            }
102            Event::WindowResized(dimensions) => {
103                let new_size = flush_resize_events(&terminal, dimensions);
104                println!("Resize from {size:?} to {new_size:?}\r");
105                size = new_size;
106            }
107            _ => (),
108        }
109    }
110
111    write!(
112        terminal,
113        "{}{}{}{}{}{}{}{}",
114        csi::Csi::Keyboard(csi::Keyboard::PopFlags(1)),
115        decreset!(FocusTracking),
116        decreset!(BracketedPaste),
117        decreset!(MouseTracking),
118        decreset!(ButtonEventMouse),
119        decreset!(AnyEventMouse),
120        decreset!(RXVTMouse),
121        decreset!(SGRMouse),
122    )?;
123
124    Ok(())
125}
126
127fn flush_resize_events(terminal: &PlatformTerminal, original_size: WindowSize) -> WindowSize {
128    let mut size = original_size;
129    let filter = |event: &Event| matches!(event, Event::WindowResized { .. });
130    while let Ok(true) = terminal.poll(filter, Some(Duration::from_millis(50))) {
131        if let Ok(Event::WindowResized(dimensions)) = terminal.read(filter) {
132            size = dimensions;
133        }
134    }
135    size
136}