mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
Merge maint into master: fipstop SSH/tmux garble fix
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
use ratatui::crossterm::event::{self, Event as CrosstermEvent, KeyEvent};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub enum Event {
|
||||
Key(KeyEvent),
|
||||
@@ -9,45 +11,87 @@ pub enum Event {
|
||||
Tick,
|
||||
}
|
||||
|
||||
/// Upper bound on a single `event::poll` wait. Kept short (vs the full
|
||||
/// tick interval) so [`EventHandler::stop`] can join the input thread
|
||||
/// promptly at quit instead of blocking for up to a refresh interval.
|
||||
const POLL_INTERVAL: Duration = Duration::from_millis(250);
|
||||
|
||||
pub struct EventHandler {
|
||||
rx: mpsc::Receiver<Event>,
|
||||
running: Arc<AtomicBool>,
|
||||
handle: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
pub fn new(tick_rate: Duration) -> Self {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let running = Arc::new(AtomicBool::new(true));
|
||||
let thread_running = Arc::clone(&running);
|
||||
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
if event::poll(tick_rate).unwrap_or(false) {
|
||||
if let Ok(evt) = event::read() {
|
||||
match evt {
|
||||
CrosstermEvent::Key(key) => {
|
||||
if tx.send(Event::Key(key)).is_err() {
|
||||
return;
|
||||
}
|
||||
let handle = thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
while thread_running.load(Ordering::Relaxed) {
|
||||
// Bound the poll by the time left until the next tick, but
|
||||
// never longer than POLL_INTERVAL so the running flag is
|
||||
// checked (and quit honored) promptly.
|
||||
let timeout = tick_rate
|
||||
.saturating_sub(last_tick.elapsed())
|
||||
.min(POLL_INTERVAL);
|
||||
|
||||
match event::poll(timeout) {
|
||||
Ok(true) => match event::read() {
|
||||
Ok(CrosstermEvent::Key(key)) => {
|
||||
if tx.send(Event::Key(key)).is_err() {
|
||||
return;
|
||||
}
|
||||
CrosstermEvent::Resize(..) => {
|
||||
if tx.send(Event::Resize).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Poll timed out — send a tick
|
||||
Ok(CrosstermEvent::Resize(..)) => {
|
||||
if tx.send(Event::Resize).is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(_) => return,
|
||||
},
|
||||
Ok(false) => {}
|
||||
Err(_) => return,
|
||||
}
|
||||
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
if tx.send(Event::Tick).is_err() {
|
||||
return;
|
||||
}
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self { rx }
|
||||
Self {
|
||||
rx,
|
||||
running,
|
||||
handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Result<Event, mpsc::RecvError> {
|
||||
self.rx.recv()
|
||||
}
|
||||
|
||||
/// Stop the input thread and wait for it to exit. Call this before
|
||||
/// restoring the terminal so the thread is not still reading stdin
|
||||
/// after raw mode is disabled — otherwise stray bytes (a keystroke or
|
||||
/// a terminal query response) echo onto the restored screen, which is
|
||||
/// especially visible over SSH/tmux.
|
||||
pub fn stop(&mut self) {
|
||||
self.running.store(false, Ordering::Relaxed);
|
||||
if let Some(handle) = self.handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventHandler {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,8 +283,14 @@ fn main() {
|
||||
eprintln!("fipstop: failed to initialize terminal: {e}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
// Force a full repaint of a known-blank screen before the first draw.
|
||||
// try_init enters the alternate screen but does not clear it, and the
|
||||
// first draw only emits cells that differ from an assumed-blank buffer;
|
||||
// on terminals that don't hand back a cleared alternate buffer (notably
|
||||
// tmux, and over SSH) that leaves stale content showing through.
|
||||
let _ = terminal.clear();
|
||||
let mut app = App::new(refresh);
|
||||
let events = EventHandler::new(refresh);
|
||||
let mut events = EventHandler::new(refresh);
|
||||
|
||||
// Initial fetch
|
||||
fetch_data(&rt, &client, &gateway_client, &mut app);
|
||||
@@ -535,5 +541,9 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the input thread before restoring the terminal so it is not
|
||||
// still reading stdin once raw mode is disabled (stray bytes would
|
||||
// otherwise echo onto the restored screen).
|
||||
events.stop();
|
||||
restore_terminal();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user