Normally, I would use a crate called ctrlc to gracefully handle when a user wants to stop my app with CTRL-C.
In a recent app I wanted to implement a nice text UI for selecting items from a list. I turned to the crate crossterm to help with cursor positioning and keyboard events.
In order to accomplish these goals, you must enable raw mode:
terminal::enable_raw_mode()
However, this intercepts CTRL-C, and your ctrlc handler will never be called.
My solution?
The only workaround I could come up with is to implement a background thread to monitor keystrokes, and if we see a CTRL-C, then signal the main thread.
std::thread::spawn(move || -> Result<()> {
loop {
if event::poll(std::time::Duration::from_millis(100))? {
if let Event::Key(key_event) = event::read()? {
if key_event.code == KeyCode::Char('c') && key_event.modifiers.contains(crossterm::event::KeyModifiers::CONTROL) {
// signal!
r.store(false, Ordering::SeqCst);
}
}
}
}
});
Here we use an Atomic boolean, but we can easily switch to messaging or some other means of communication.
You can find the complete example project here:
Top comments (3)
Your workaround is the valid pattern for GUI/TUI.
Main thread should never listen to key strokes.
Idiomatic solution would be:
Encapsulate events that your application is interested in:
Create method that will listen to key strokes and convert them to events:
Create method that will redraw your GUI/TUI:
Connect key listener with main thread:
This way your app will be super responsive, without fixed 100ms delay and you can easily add heavy computation jobs on threads as long as they produce some kind of
Event
to MPSC channel.Cool! Thanks for the comments. Agreed, main thread should never listen for keystrokes.
BTW: youtube.com/watch?v=awX7DUp-r14 is great introduction for everyone who wants to start working with crossterm and ratatui widgets for terminal applications.