use std::io::Read;
use tokio::signal;
use tokio::sync::mpsc;
use zond_engine::ScanHandle;
const QUIT: [u8; 2] = *b"qQ";
pub(crate) struct StopRequests {
requests: mpsc::Receiver<()>,
_terminal: Option<terminal::Cbreak>,
}
impl StopRequests {
pub(crate) async fn recv(&mut self) -> Option<()> {
self.requests.recv().await
}
}
pub(crate) fn watch(handle: &ScanHandle) -> StopRequests {
let (requests, receiver) = mpsc::channel(2);
watch_signals(requests.clone(), handle.clone());
let terminal = terminal::cbreak();
if terminal.is_some() {
watch_keys(requests, handle.clone());
}
StopRequests {
requests: receiver,
_terminal: terminal,
}
}
fn watch_signals(requests: mpsc::Sender<()>, handle: ScanHandle) {
tokio::spawn(async move {
while signal::ctrl_c().await.is_ok() {
handle.abort();
if requests.send(()).await.is_err() {
break;
}
}
});
}
fn watch_keys(requests: mpsc::Sender<()>, handle: ScanHandle) {
std::thread::spawn(move || {
let mut stdin = std::io::stdin().lock();
let mut typed = [0u8; 1];
while !requests.is_closed() {
match stdin.read(&mut typed) {
Ok(read) if read > 0 && QUIT.contains(&typed[0]) => {
handle.abort();
if requests.blocking_send(()).is_err() {
break;
}
}
Ok(_) => {}
Err(_) => break,
}
}
});
}
#[cfg(unix)]
mod terminal {
use rustix::stdio::stdin;
use rustix::termios::{
LocalModes, OptionalActions, SpecialCodeIndex, Termios, isatty, tcgetattr, tcsetattr,
};
const READ_TIMEOUT_DECISECONDS: u8 = 1;
pub(super) struct Cbreak(Termios);
pub(super) fn cbreak() -> Option<Cbreak> {
if !isatty(stdin()) {
return None;
}
let original = tcgetattr(stdin()).ok()?;
let mut wanted = original.clone();
wanted
.local_modes
.remove(LocalModes::ICANON | LocalModes::ECHO);
wanted.special_codes[SpecialCodeIndex::VMIN] = 0;
wanted.special_codes[SpecialCodeIndex::VTIME] = READ_TIMEOUT_DECISECONDS;
tcsetattr(stdin(), OptionalActions::Now, &wanted).ok()?;
Some(Cbreak(original))
}
impl Drop for Cbreak {
fn drop(&mut self) {
let _ = tcsetattr(stdin(), OptionalActions::Now, &self.0);
}
}
}
#[cfg(not(unix))]
mod terminal {
pub(super) struct Cbreak;
pub(super) fn cbreak() -> Option<Cbreak> {
None
}
}