Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### Fixed

* Fix Ctrl+C handling to use SIGINT signal instead of keypress #491 - @chiranjeevi-max
* Update CONTRIBUTING information #438 - @YJDoc2 @cyqsimon
* Fix new clippy lint #457 - @cyqsimon
* Apply new clippy lints #468 - @cyqsimon
Expand Down
101 changes: 91 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ chrono = "0.4"
clap-verbosity-flag = "3.0.3"
clap = { version = "4.5.41", features = ["derive"] }
crossterm = "0.29.0"
ctrlc = "3.4"
derive_more = { version = "2.0.1", features = ["debug"] }
eyre = "0.6.12"
itertools = "0.14.0"
Expand Down
28 changes: 21 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ fn main() -> eyre::Result<()> {
let _ = crossterm::execute!(&mut stdout, terminal::EnterAlternateScreen);
let terminal_backend = CrosstermBackend::new(stdout);
start(terminal_backend, os_input, opts);

// Ensure terminal is restored after exit (handles SIGINT case).
// These operations are idempotent, so safe to call even if 'q' already cleaned up.
let _ = terminal::disable_raw_mode();
let _ = crossterm::execute!(std::io::stdout(), terminal::LeaveAlternateScreen);
}
Ok(())
}
Expand All @@ -96,6 +101,17 @@ where
let cumulative_time = Arc::new(RwLock::new(Duration::new(0, 0)));
let table_cycle_offset = Arc::new(AtomicUsize::new(0));

// handle SIGINT properly instead of as a keypress
// see https://github.com/imsnif/bandwhich/issues/487
#[cfg(not(test))]
{
let running = running.clone();
ctrlc::set_handler(move || {
running.store(false, Ordering::Release);
})
.expect("failed to set SIGINT handler");
}

Comment thread
chiranjeevi-max marked this conversation as resolved.
let mut active_threads = vec![];

let terminal_events = os_input.terminal_events;
Expand Down Expand Up @@ -175,7 +191,11 @@ where
let display_handler = display_handler.thread().clone();

move || {
for evt in terminal_events {
let mut terminal_events = terminal_events;
while running.load(Ordering::Acquire) {
let Some(evt) = terminal_events.next() else {
continue;
};
let mut ui = ui.lock().unwrap();

match evt {
Expand All @@ -192,12 +212,6 @@ where
);
}
Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('c'),
kind: KeyEventKind::Press,
..
})
| Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Char('q'),
kind: KeyEventKind::Press,
Expand Down
19 changes: 16 additions & 3 deletions src/os/shared.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use std::{
io::{self, ErrorKind, Write},
net::Ipv4Addr,
time,
time::{self, Duration},
};

use crossterm::event::{read, Event};
use crossterm::event::{poll, read, Event};
use eyre::{bail, eyre};
use itertools::Itertools;
use log::{debug, warn};
Expand Down Expand Up @@ -35,12 +35,25 @@ impl ProcessInfo {
}
}

/// Poll timeout for terminal events.
/// This allows the event loop to periodically check the `running` flag
/// for graceful shutdown on SIGINT.
const POLL_TIMEOUT: Duration = Duration::from_millis(100);

pub struct TerminalEvents;

impl Iterator for TerminalEvents {
type Item = Event;
/// Returns the next terminal event, or `None` if no event is available
/// within the poll timeout.
///
/// Note: `None` here means "no event right now", not "iteration complete".
/// The consumer should use `while running` instead of `for evt in ...`.
fn next(&mut self) -> Option<Event> {
read().ok()
match poll(POLL_TIMEOUT) {
Ok(true) => read().ok(),
Ok(false) | Err(_) => None,
}
}
Comment thread
cyqsimon marked this conversation as resolved.
}

Expand Down
8 changes: 4 additions & 4 deletions src/tests/cases/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use crate::{
pub fn sleep_and_quit_events(sleep_num: usize) -> Box<TerminalEvents> {
let events = iter::repeat_n(None, sleep_num)
.chain([Some(Event::Key(KeyEvent::new(
KeyCode::Char('c'),
KeyModifiers::CONTROL,
KeyCode::Char('q'),
KeyModifiers::NONE,
)))])
.collect();
Box::new(TerminalEvents::new(events))
Expand All @@ -37,8 +37,8 @@ pub fn sleep_resize_and_quit_events(sleep_num: usize) -> Box<TerminalEvents> {
.chain([
Some(Event::Resize(100, 100)),
Some(Event::Key(KeyEvent::new(
KeyCode::Char('c'),
KeyModifiers::CONTROL,
KeyCode::Char('q'),
KeyModifiers::NONE,
))),
])
.collect();
Expand Down
Loading