1use std::sync::{
2 atomic::{AtomicBool, Ordering},
3 Arc,
4};
5use std::thread;
6use std::time::Duration;
7
8use crossterm::event::{self, Event};
9use tokio::sync::mpsc;
10
11const INPUT_POLL_INTERVAL: Duration = Duration::from_millis(10);
12const TERMINAL_EVENT_CHANNEL_CAPACITY: usize = 1024;
13
14pub struct TerminalEventSource {
17 stop: Arc<AtomicBool>,
18 thread: Option<thread::JoinHandle<()>>,
19}
20
21impl TerminalEventSource {
22 pub fn spawn() -> (Self, mpsc::Receiver<Event>) {
23 let (tx, rx) = mpsc::channel(TERMINAL_EVENT_CHANNEL_CAPACITY);
24 let stop = Arc::new(AtomicBool::new(false));
25 let thread_stop = Arc::clone(&stop);
26
27 let thread = thread::spawn(move || {
28 while !thread_stop.load(Ordering::Relaxed) {
29 match event::poll(INPUT_POLL_INTERVAL) {
30 Ok(true) => match event::read() {
31 Ok(event) => {
32 if tx.blocking_send(event).is_err() {
33 break;
34 }
35 }
36 Err(_) => break,
37 },
38 Ok(false) => {}
39 Err(_) => break,
40 }
41 }
42 });
43
44 (
45 Self {
46 stop,
47 thread: Some(thread),
48 },
49 rx,
50 )
51 }
52}
53
54impl Drop for TerminalEventSource {
55 fn drop(&mut self) {
56 self.stop.store(true, Ordering::Relaxed);
57 if let Some(thread) = self.thread.take() {
58 let _ = thread.join();
59 }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn terminal_event_source_can_be_dropped_without_events() {
69 let (source, _rx) = TerminalEventSource::spawn();
70 drop(source);
71 }
72}