Skip to content

Commit

Permalink
Send SIGINT on Ctrl-C
Browse files Browse the repository at this point in the history
 - Send Ctrl-C once for graceful exit. An atomic bool flag is set and
 then main process gets a signal and then quits just like if 'q' was
 issued.
 - Sending Ctrl-C again before the main process reads the signal
 (probably because it's frozen) forces a terminal restore and quits with
 130.
  • Loading branch information
epilys committed Oct 1, 2020
1 parent 0855fa9 commit f52e7b1
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 34 deletions.
43 changes: 37 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ use crossbeam::channel::{bounded, tick};
use crossbeam::select;
use libc::c_int;
use std::io::Error;
use std::sync::{
atomic::{AtomicBool, AtomicPtr},
Arc,
};
use std::time::Duration;

//#[allow(dead_code)]
Expand Down Expand Up @@ -131,8 +135,24 @@ pub mod username {
}
}

fn notify(signals: &[c_int]) -> Result<crossbeam::channel::Receiver<c_int>, Error> {
fn notify(
signals: &[c_int],
exit_flag: Arc<AtomicBool>,
state: Arc<AtomicPtr<StateStdout>>,
) -> Result<crossbeam::channel::Receiver<c_int>, Error> {
let (s, r) = bounded(100);
let _s = s.clone();
let sigint_handler = move |_info: &nix::libc::siginfo_t| {
if exit_flag.load(std::sync::atomic::Ordering::SeqCst) {
crate::state::restore_to_main_screen(state.clone());
std::process::exit(130);
}
exit_flag.store(true, std::sync::atomic::Ordering::SeqCst);
let _ = _s.send(signal_hook::SIGINT);
};
unsafe {
signal_hook_registry::register_sigaction(signal_hook::SIGINT, sigint_handler)?;
}
let signals = signal_hook::iterator::Signals::new(signals)?;
std::thread::spawn(move || {
for signal in signals.forever() {
Expand All @@ -143,21 +163,28 @@ fn notify(signals: &[c_int]) -> Result<crossbeam::channel::Receiver<c_int>, Erro
}

fn main() -> Result<(), Error> {
/* Create the application State */
let mut state = UIState::new();

let signals = &[
/*
signal_hook::SIGALRM,
signal_hook::SIGTERM,
signal_hook::SIGINT,
signal_hook::SIGQUIT,
*/
/* Catch SIGWINCH to handle terminal resizing */
signal_hook::SIGWINCH,
];

let ticker = tick(Duration::from_millis(1600));

let signal_recvr = notify(signals)?;

/* Create the application State */
let mut state = UIState::new();
let exit_flag: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
let signal_recvr = notify(
signals,
exit_flag.clone(),
state.stdout.as_ref().unwrap().clone(),
)?;

let receiver = state.receiver();
let window = Box::new(Window::new(
Expand All @@ -177,13 +204,17 @@ fn main() -> Result<(), Error> {
state.redraw(true);
},
recv(signal_recvr) -> sig => {
eprintln!("got signal {:?}", sig);
match sig.unwrap() {
signal_hook::SIGWINCH => {
state.update_size();
state.render();
state.redraw(true);
},
signal_hook::SIGINT => {

drop(state);
break 'main;
}
_ => {}
}
},
Expand Down
77 changes: 49 additions & 28 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ pub enum UIMode {
Input,
}

use std::sync::{
atomic::{AtomicPtr, Ordering},
Arc,
};
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;

struct InputHandler {
Expand Down Expand Up @@ -79,7 +83,7 @@ pub struct UIState {
rows: usize,

grid: CellBuffer,
stdout: Option<StateStdout>,
pub stdout: Option<Arc<AtomicPtr<StateStdout>>>,
components: Vec<Box<dyn Component>>,
pub dirty_areas: VecDeque<Area>,
sender: Sender<ThreadEvent>,
Expand Down Expand Up @@ -118,15 +122,11 @@ impl UIState {
let cols = termsize.map(|(w, _)| w).unwrap_or(0) as usize;
let rows = termsize.map(|(_, h)| h).unwrap_or(0) as usize;

let _stdout = std::io::stdout();
_stdout.lock();
let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());

let mut s = UIState {
cols,
rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
stdout: Some(stdout),
stdout: None,
components: Vec::with_capacity(1),
sender,
receiver,
Expand All @@ -138,16 +138,7 @@ impl UIState {
mode: UIMode::Normal,
};

write!(
s.stdout(),
"{}{}{}{}",
BracketModeStart,
cursor::Hide,
clear::All,
cursor::Goto(1, 1)
)
.unwrap();
s.flush();
s.switch_to_alternate_screen();
s.restore_input();
s
}
Expand All @@ -157,31 +148,44 @@ impl UIState {
pub fn switch_to_main_screen(&mut self) {
write!(
self.stdout(),
"{}{}{}",
"{}{}{}{}",
termion::screen::ToMainScreen,
cursor::Show,
RestoreWindowTitleIconFromStack,
BracketModeEnd,
)
.unwrap();
self.flush();
self.stdout = None;
if let Some(stdout) = self.stdout.take() {
let termios: Box<StateStdout> =
unsafe { std::boxed::Box::from_raw(stdout.load(Ordering::Relaxed)) };
drop(termios);
}

self.input.kill();
}

pub fn switch_to_alternate_screen(&mut self) {
let s = std::io::stdout();
s.lock();
self.stdout = Some(AlternateScreen::from(s.into_raw_mode().unwrap()));

let mut stdout = AlternateScreen::from(s.into_raw_mode().unwrap());

write!(
self.stdout(),
"{}{}{}{}{}",
&mut stdout,
"{save_title_to_stack}{}{}{}{window_title}{}{}",
termion::screen::ToAlternateScreen,
cursor::Hide,
clear::All,
cursor::Goto(1, 1),
BracketModeStart,
save_title_to_stack = SaveWindowTitleIconToStack,
window_title = "\x1b]2;bb\x07",
)
.unwrap();

let termios = Box::new(stdout);
let stdout = std::sync::atomic::AtomicPtr::new(std::boxed::Box::into_raw(termios));
self.stdout = Some(Arc::new(stdout));
self.flush();
}

Expand All @@ -197,9 +201,12 @@ impl UIState {
if termcols.unwrap_or(72) as usize != self.cols
|| termrows.unwrap_or(120) as usize != self.rows
{
eprintln!(
std::dbg!(
"Size updated, from ({}, {}) -> ({:?}, {:?})",
self.cols, self.rows, termcols, termrows
self.cols,
self.rows,
termcols,
termrows
);
}
self.cols = termcols.unwrap_or(72) as usize;
Expand Down Expand Up @@ -340,16 +347,30 @@ impl UIState {
}

fn flush(&mut self) {
if let Some(s) = self.stdout.as_mut() {
s.flush().unwrap();
}
self.stdout().flush().unwrap();
}

fn stdout(&mut self) -> &mut StateStdout {
self.stdout.as_mut().unwrap()
unsafe {
self.stdout
.as_ref()
.unwrap()
.load(Ordering::Relaxed)
.as_mut()
.unwrap()
}
}

pub fn restore_input(&self) {
self.input.restore(self.sender.clone());
}
}

pub fn restore_to_main_screen(stdout: Arc<AtomicPtr<StateStdout>>) {
let mut stdout: Box<StateStdout> =
unsafe { std::boxed::Box::from_raw(stdout.load(Ordering::SeqCst)) };
let _ = stdout.flush();
let _ = stdout.write_all(b"\x1B[?25h\x1B[?2004l");
let _ = stdout.flush();
drop(stdout);
}
14 changes: 14 additions & 0 deletions src/terminal/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ pub fn get_events(
}
};
match c {
Ok(TermionEvent::Key(TermionKey::Ctrl('c'))) if input_mode == InputMode::Normal => {
let self_pid = nix::unistd::Pid::this();
nix::sys::signal::kill(self_pid, nix::sys::signal::Signal::SIGINT).unwrap();
}
Ok(TermionEvent::Key(k)) if input_mode == InputMode::Normal => {
closure(Key::from(k));
}
Expand Down Expand Up @@ -235,3 +239,13 @@ derive_csi_sequence!(

pub const BRACKET_PASTE_START: &[u8] = b"\x1B[200~";
pub const BRACKET_PASTE_END: &[u8] = b"\x1B[201~";

derive_csi_sequence!(
#[doc = "`CSI Ps ; Ps ; Ps t`, where `Ps = 2 2 ; 0` -> Save xterm icon and window title on stack."]
(SaveWindowTitleIconToStack, "22;0t")
);

derive_csi_sequence!(
#[doc = "Restore window title and icon from terminal's title stack. `CSI Ps ; Ps ; Ps t`, where `Ps = 2 3 ; 0` -> Restore xterm icon and window title from stack."]
(RestoreWindowTitleIconFromStack, "23;0t")
);

0 comments on commit f52e7b1

Please sign in to comment.