diff --git a/Cargo.lock b/Cargo.lock index 1e2782b..2f4233c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -698,6 +698,7 @@ checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ "bitflags 2.5.0", "crossterm_winapi", + "futures-core", "libc", "mio", "parking_lot 0.12.1", diff --git a/Cargo.toml b/Cargo.toml index ee87f7d..dd0d1e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ clap_derive = "4.5.4" cliclack = "0.2.5" color-eyre = "0.6.3" config = "0.14.0" -crossterm = "0.27.0" +crossterm = { version = "0.27.0", features = ["event-stream"] } futures = "0.3.30" keyring = "2.3.2" ratatui = "0.26.2" diff --git a/src/cli.rs b/src/cli.rs index df6497b..0766dea 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -113,8 +113,7 @@ mod ui { impl Args { pub async fn run(&self, tasks: SharedTasks) -> Result<()> { - let tui = crate::tui::Tui::new(tasks); - tui.run().await + crate::tui::Tui::run(tasks).await } } } diff --git a/src/tui.rs b/src/tui.rs index 69f89ff..0ba8cc6 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -6,6 +6,7 @@ use crossterm::{ ExecutableCommand, }; use futures::stream::StreamExt; +use futures::FutureExt; use ratatui::{ prelude::{CrosstermBackend, Stylize, Terminal}, widgets::Paragraph, @@ -14,39 +15,88 @@ use signal_hook::consts::*; use signal_hook_tokio::Signals; use std::io::stdout; -pub struct Tui { - tasks: SharedTasks, +pub struct Tui {} + +enum Event { + CrosstermEvent(crossterm::event::Event), + // CrosstermError(Arc), + Quit, + Tick, } impl Tui { - pub fn new(tasks: SharedTasks) -> Self { - Self { tasks } - } - - pub async fn run(&self) -> Result<()> { - self.tui().await - } - - async fn tui(&self) -> Result<()> { + pub async fn run(tasks: SharedTasks) -> Result<()> { stdout().execute(EnterAlternateScreen)?; enable_raw_mode()?; + let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; terminal.clear()?; - let mut signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?; - let signalled: Arc> = Arc::new(Mutex::new(false)); - let handle = signals.handle(); - let signaler = Arc::clone(&signalled); + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); + + let mut signals = Signals::new(&[SIGHUP, SIGTERM, SIGINT, SIGQUIT])?; + let signal_handle = signals.handle(); + let signal_sender = tx.clone(); tokio::spawn(async move { - // await a signal - once we receive one mark the boolean as true to - // quit the app + // await a signal - once we receive one send the quit event signals.next().await; - let mut signalled = signaler.lock().await; - *signalled = true; + signal_sender.send(Event::Quit).unwrap(); + }); + + let tick_rate = std::time::Duration::from_millis(1000); + + let event_publisher_loop = tokio::spawn(async move { + let mut crossterm_event_stream = crossterm::event::EventStream::new(); + let mut interval = tokio::time::interval(tick_rate); + + loop { + let delay = interval.tick(); + // TODO: read about FusedFutures + let crossterm_event = crossterm_event_stream.next().fuse(); + + // TODO: fix unwrap? + tokio::select! { + e = crossterm_event => { + match e { + Some(Ok(e)) => { + tx.send(Event::CrosstermEvent(e)).unwrap(); + }, + Some(Err(_e)) => { + // tx.send(Event::CrosstermError(Arc::new(e))).unwrap(); + }, + None => {}, + } + } + _ = delay => { + tx.send(Event::Tick).unwrap(); + } + } + } }); - // TODO: enable isig to re-allow ctrl-z? loop { + let event = rx.recv().await; + + match event { + Some(Event::CrosstermEvent(e)) => match e { + event::Event::Key(key) => { + if key.kind == KeyEventKind::Press + && key.code == KeyCode::Char('c') + && key.modifiers == KeyModifiers::CONTROL + { + break; + } + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + break; + } + } + _ => {} + }, + Some(Event::Tick) => {} + Some(Event::Quit) => break, + None => {} + } + terminal.draw(|frame| { let area = frame.size(); frame.render_widget( @@ -54,27 +104,13 @@ impl Tui { area, ); })?; - if event::poll(std::time::Duration::from_millis(10))? { - if let event::Event::Key(key) = event::read()? { - if key.kind == KeyEventKind::Press - && key.code == KeyCode::Char('c') - && key.modifiers == KeyModifiers::CONTROL - { - break; - } - if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { - break; - } - } - } - if *(*signalled).lock().await { - break; - } } - handle.close(); + signal_handle.close(); + stdout().execute(LeaveAlternateScreen)?; disable_raw_mode()?; + Ok(()) } }