Skip to main content

katui_rt/
events.rs

1//! The event pump: terminal input on its own thread and messages from
2//! the application's own threads, merged into one queue.
3
4use std::collections::VecDeque;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::mpsc::{self, Receiver, Sender};
8use std::thread::JoinHandle;
9use std::time::{Duration, Instant};
10
11use katui_proto::input::{InputEvent, InputParser};
12
13use crate::app::is_terminal_answer;
14use crate::pane::{self, PaneView};
15use crate::tty::RawInput;
16
17/// How long a reader waits for input before checking for shutdown, and
18/// the silence after which a lone escape counts as the Esc key.
19const INPUT_POLL: Duration = Duration::from_millis(50);
20
21/// How long a finishing pump keeps raw mode for input still travelling,
22/// such as the release of the key that quit, and the silence that ends
23/// that wait sooner.
24const LATE_INPUT_LIMIT: Duration = Duration::from_millis(600);
25const LATE_INPUT_SILENCE: Duration = Duration::from_millis(150);
26
27/// One item off the event queue.
28#[derive(Debug, PartialEq, Eq)]
29pub enum Signal<M> {
30    /// Something the terminal reported.
31    Input(InputEvent),
32    /// A message from one of the application's threads.
33    Message(M),
34    /// What our multiplexer pane can show changed.
35    Pane(PaneView),
36}
37
38/// Lets any thread put a message on the event queue.
39#[derive(Debug)]
40pub struct Waker<M>(pub(crate) Sender<Signal<M>>);
41
42impl<M> Clone for Waker<M> {
43    fn clone(&self) -> Self {
44        Self(self.0.clone())
45    }
46}
47
48impl<M> Waker<M> {
49    /// Queue `message`; `false` once the event loop has ended.
50    pub fn send(&self, message: M) -> bool {
51        self.0.send(Signal::Message(message)).is_ok()
52    }
53}
54
55impl<M: Send + 'static> Waker<M> {
56    /// Send the message `message` makes every `interval` from a thread of
57    /// its own, until the event loop ends. Use it to drive clocks, spinners
58    /// and expiring toasts.
59    pub fn every(&self, interval: Duration, message: impl Fn() -> M + Send + 'static) {
60        let waker = self.clone();
61        std::thread::spawn(move || {
62            while waker.send(message()) {
63                std::thread::sleep(interval);
64            }
65        });
66    }
67}
68
69/// The merged event queue and the thread that reads the terminal.
70#[derive(Debug)]
71pub struct EventPump<M> {
72    sender: Sender<Signal<M>>,
73    receiver: Receiver<Signal<M>>,
74    queued: VecDeque<Signal<M>>,
75    stop: Arc<AtomicBool>,
76    reader: Option<JoinHandle<()>>,
77}
78
79impl<M: Send + 'static> EventPump<M> {
80    /// A pump reading the controlling terminal, in raw mode until
81    /// [`EventPump::finish`]. With no terminal it carries messages only.
82    #[must_use]
83    pub fn start() -> Self {
84        let (sender, receiver) = mpsc::channel();
85        let stop = Arc::new(AtomicBool::new(false));
86        let reader = RawInput::open().ok().map(|input| {
87            let (sender, stop) = (sender.clone(), stop.clone());
88            std::thread::spawn(move || read_terminal(input, &stop, &sender))
89        });
90        Self {
91            sender,
92            receiver,
93            queued: VecDeque::new(),
94            stop,
95            reader,
96        }
97    }
98
99    /// Report what the multiplexer `program` lets our pane show.
100    pub fn watch_pane_visibility(&self, program: &str) {
101        let sender = self.sender.clone();
102        pane::watch(program.to_owned(), self.stop.clone(), move |view| {
103            sender.send(Signal::Pane(view)).is_ok()
104        });
105    }
106
107    /// Whether a terminal is being read.
108    #[must_use]
109    pub fn reads_terminal(&self) -> bool {
110        self.reader.is_some()
111    }
112
113    /// A handle other threads use to queue messages.
114    #[must_use]
115    pub fn waker(&self) -> Waker<M> {
116        Waker(self.sender.clone())
117    }
118
119    /// The next item, waiting at most `wait`.
120    pub fn next(&mut self, wait: Duration) -> Option<Signal<M>> {
121        if let Some(signal) = self.queued.pop_front() {
122            return Some(signal);
123        }
124        self.receiver.recv_timeout(wait).ok()
125    }
126
127    /// Wait for the graphics answer naming `image`, holding everything
128    /// else for later; `false` when no answer arrives within `wait`.
129    pub fn await_answer(&mut self, image: u32, wait: Duration) -> bool {
130        self.await_event(
131            |event| matches!(event, InputEvent::GraphicsAnswer(answered) if *answered == image),
132            wait,
133        )
134    }
135
136    /// Wait for the device attributes answer, holding everything else for
137    /// later; `false` when no answer arrives within `wait`.
138    pub fn await_device_attributes(&mut self, wait: Duration) -> bool {
139        self.await_event(|event| *event == InputEvent::DeviceAttributes, wait)
140    }
141
142    fn await_event(&mut self, wanted: impl Fn(&InputEvent) -> bool, wait: Duration) -> bool {
143        let deadline = Instant::now() + wait;
144        while let Ok(signal) = self
145            .receiver
146            .recv_timeout(deadline.saturating_duration_since(Instant::now()))
147        {
148            match signal {
149                Signal::Input(event) if wanted(&event) => return true,
150                Signal::Input(event) if is_terminal_answer(&event) => {}
151                other => self.queued.push_back(other),
152            }
153        }
154        false
155    }
156
157    /// Stop reading and leave raw mode once input goes quiet, so that
158    /// answers and key releases still on their way never reach the shell.
159    pub fn finish(self) {
160        let deadline = Instant::now() + LATE_INPUT_LIMIT;
161        while Instant::now() < deadline && self.receiver.recv_timeout(LATE_INPUT_SILENCE).is_ok() {}
162    }
163}
164
165impl<M> Drop for EventPump<M> {
166    fn drop(&mut self) {
167        self.stop.store(true, Ordering::Relaxed);
168        if let Some(handle) = self.reader.take() {
169            let _ = handle.join();
170        }
171    }
172}
173
174fn read_terminal<M>(mut input: RawInput, stop: &AtomicBool, sender: &Sender<Signal<M>>) {
175    let mut parser = InputParser::default();
176    while !stop.load(Ordering::Relaxed) {
177        let events = match input.read_chunk(INPUT_POLL) {
178            Some(bytes) => parser.feed(&bytes),
179            None => parser.idle().into_iter().collect(),
180        };
181        for event in events {
182            let _ = sender.send(Signal::Input(event));
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use std::time::Duration;
190
191    use katui_proto::input::{InputEvent, Key, Mods};
192
193    use super::{EventPump, Signal};
194
195    const NO_WAIT: Duration = Duration::from_millis(0);
196
197    fn pump() -> EventPump<u32> {
198        let (sender, receiver) = std::sync::mpsc::channel();
199        EventPump {
200            sender,
201            receiver,
202            queued: std::collections::VecDeque::new(),
203            stop: std::sync::Arc::default(),
204            reader: None,
205        }
206    }
207
208    fn answer(image: u32) -> Signal<u32> {
209        Signal::Input(InputEvent::GraphicsAnswer(image))
210    }
211
212    #[test]
213    fn a_waker_delivers_from_another_thread() {
214        let mut events = pump();
215        let waker = events.waker();
216        std::thread::spawn(move || waker.send(7))
217            .join()
218            .expect("the sender thread cannot panic");
219        assert_eq!(events.next(NO_WAIT), Some(Signal::Message(7)));
220        assert_eq!(events.next(NO_WAIT), None);
221    }
222
223    #[test]
224    fn waiting_for_an_answer_keeps_other_events_in_order() {
225        let mut events = pump();
226        let key = InputEvent::Key(Key::Char('x'), Mods::NONE);
227        events
228            .sender
229            .send(Signal::Input(key.clone()))
230            .expect("the receiver is alive");
231        events
232            .sender
233            .send(answer(5))
234            .expect("the receiver is alive");
235        events
236            .sender
237            .send(Signal::Message(9))
238            .expect("the receiver is alive");
239        assert!(events.await_answer(5, NO_WAIT));
240        assert_eq!(events.next(NO_WAIT), Some(Signal::Input(key)));
241        assert_eq!(events.next(NO_WAIT), Some(Signal::Message(9)));
242    }
243
244    #[test]
245    fn unrelated_events_cannot_extend_the_wait_forever() {
246        let mut events = pump();
247        for _ in 0..3 {
248            events
249                .sender
250                .send(Signal::Message(1))
251                .expect("the receiver is alive");
252        }
253        let started = std::time::Instant::now();
254        assert!(!events.await_answer(5, Duration::from_millis(20)));
255        assert!(started.elapsed() < Duration::from_secs(2));
256        assert_eq!(events.queued.len(), 3, "the events wait their turn");
257    }
258
259    #[test]
260    fn a_stale_answer_cannot_stand_in_for_the_awaited_one() {
261        let mut events = pump();
262        events
263            .sender
264            .send(answer(4))
265            .expect("the receiver is alive");
266        assert!(!events.await_answer(5, NO_WAIT));
267    }
268}