refactor: simplify signal handling and update TUI controls

This commit is contained in:
monosans
2025-07-23 15:57:25 +03:00
parent 1f6d921da3
commit e2bb4841fa
2 changed files with 58 additions and 104 deletions
+20 -69
View File
@@ -153,73 +153,6 @@ async fn download_output_dependencies(
Ok(())
}
#[cfg(unix)]
async fn watch_signals(token: tokio_util::sync::CancellationToken) {
let token_clone = token.clone();
tokio::select! {
biased;
() = token_clone.cancelled() => {},
() = async move {
match (
tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::interrupt(),
),
tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::terminate(),
)
) {
(Ok(mut a), Ok(mut b)) => {
tokio::select! {
_ = a.recv() => {
tracing::info!("Received SIGINT, exiting...");
token.cancel();
},
_ = b.recv() => {
tracing::info!("Received SIGTERM, exiting...");
token.cancel();
}
};
}
(Err(e), Ok(mut s)) => {
tracing::warn!("Failed to create SIGINT handler: {}", e);
s.recv().await;
tracing::info!("Received SIGTERM, exiting...");
token.cancel();
}
(Ok(mut s), Err(e)) => {
tracing::warn!("Failed to create SIGTERM handler: {}", e);
s.recv().await;
tracing::info!("Received SIGINT, exiting...");
token.cancel();
}
(Err(e), Err(e2)) => {
tracing::warn!("Failed to create signal handlers: {}, {}", e, e2);
}
}
} => {}
};
}
#[cfg(not(unix))]
async fn watch_signals(token: tokio_util::sync::CancellationToken) {
let token_clone = token.clone();
tokio::select! {
biased;
() = token_clone.cancelled() => {},
ctrl_c = tokio::signal::ctrl_c() => {
match ctrl_c {
Ok(()) => {
tracing::info!("Received Ctrl+C, exiting...");
token.cancel();
}
Err(e) => {
tracing::warn!("Failed to create Ctrl+C handler: {}", e);
}
}
}
};
}
async fn main_task(
config: Arc<config::Config>,
token: tokio_util::sync::CancellationToken,
@@ -285,8 +218,6 @@ async fn run_with_tui(
let terminal_guard = tui::RatatuiRestoreGuard;
let token = tokio_util::sync::CancellationToken::new();
tokio::spawn(watch_signals(token.clone()));
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
tokio::try_join!(
@@ -301,6 +232,26 @@ async fn run_with_tui(
Ok(())
}
#[cfg(not(feature = "tui"))]
async fn watch_signals(token: tokio_util::sync::CancellationToken) {
let token_clone = token.clone();
tokio::select! {
biased;
() = token_clone.cancelled() => {},
ctrl_c = tokio::signal::ctrl_c() => {
match ctrl_c {
Ok(()) => {
tracing::info!("Received Ctrl+C, exiting...");
token.cancel();
}
Err(e) => {
tracing::warn!("Failed to create Ctrl+C handler: {}", e);
}
}
}
};
}
#[cfg(not(feature = "tui"))]
async fn run_without_tui(
config: Arc<config::Config>,
+38 -35
View File
@@ -24,7 +24,6 @@ use crate::{
event::{AppEvent, Event},
ipdb,
proxy::ProxyType,
utils::is_docker,
};
const FPS: f64 = 30.0;
@@ -49,8 +48,7 @@ pub async fn run(
let logger_state = TuiWidgetState::default();
while !matches!(app_state.mode, AppMode::Quit) {
if let Some(event) = rx.recv().await {
if handle_event(event, &mut app_state, &token, &logger_state).await
{
if handle_event(event, &mut app_state, &token, &logger_state) {
terminal
.draw(|frame| draw(frame, &app_state, &logger_state))
.wrap_err("failed to draw tui")?;
@@ -70,6 +68,15 @@ pub enum AppMode {
Quit,
}
impl AppMode {
pub const fn next(&self) -> Self {
match self {
Self::Running => Self::Done,
Self::Done | Self::Quit => Self::Quit,
}
}
}
#[derive(Default)]
pub struct AppState {
pub mode: AppMode,
@@ -148,7 +155,7 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
// Scraping and checking
Constraint::Length(1 + (3 * 3) + 1),
// Hotkeys
Constraint::Length(3),
Constraint::Length(4),
])
.split(outer_block.inner(f.area()));
@@ -270,25 +277,32 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
working_proxies_block.inner(layout[2]),
);
}
let lines = vec![
Line::from("\u{2b06}\u{fe0f} Up/PageUp/k - scroll logs up"),
Line::from("\u{2b07}\u{fe0f} Down/PageDown/j - scroll logs down"),
if matches!(state.mode, AppMode::Running) {
Line::from("\u{1f6d1} ESC/q/Ctrl-C - stop")
.style(Style::default().fg(Color::Yellow))
let running = matches!(state.mode, AppMode::Running);
let mut lines = Vec::with_capacity(if running { 4 } else { 3 });
lines.push(Line::from("\u{2b06}\u{fe0f} Up / PageUp / k - scroll logs up"));
lines.push(Line::from(
"\u{2b07}\u{fe0f} Down / PageDown / j - scroll logs down",
));
if running {
lines.push(
Line::from("\u{1f6d1} ESC / q - stop")
.style(Style::default().fg(Color::Yellow)),
);
}
lines.push(
Line::from(if running {
"\u{1f6aa} Ctrl-C - quit"
} else {
Line::from("\u{1f6aa} ESC/q/Ctrl-C - quit")
.style(Style::default().fg(Color::Red))
},
];
"\u{1f6aa} ESC / q / Ctrl-C - quit"
})
.style(Style::default().fg(Color::Red)),
);
f.render_widget(Text::from(lines).centered(), outer_layout[3]);
}
async fn is_interactive() -> bool {
!is_docker().await
}
async fn handle_event(
fn handle_event(
event: Event,
state: &mut AppState,
token: &tokio_util::sync::CancellationToken,
@@ -300,21 +314,13 @@ async fn handle_event(
match crossterm_event {
CrosstermEvent::Key(key_event) => match key_event.code {
KeyCode::Esc | KeyCode::Char('q' | 'Q') => {
state.mode = if matches!(state.mode, AppMode::Running) {
AppMode::Done
} else {
AppMode::Quit
};
state.mode = state.mode.next();
token.cancel();
}
KeyCode::Char('c' | 'C')
if key_event.modifiers == KeyModifiers::CONTROL =>
{
state.mode = if matches!(state.mode, AppMode::Running) {
AppMode::Done
} else {
AppMode::Quit
};
state.mode = AppMode::Quit;
token.cancel();
}
KeyCode::Up | KeyCode::PageUp | KeyCode::Char('k') => {
@@ -382,12 +388,9 @@ async fn handle_event(
.or_insert(1);
}
AppEvent::Done => {
state.mode =
if !token.is_cancelled() && is_interactive().await {
AppMode::Done
} else {
AppMode::Quit
};
if matches!(state.mode, AppMode::Running) {
state.mode = AppMode::Done;
}
}
}
false