diff --git a/Cargo.lock b/Cargo.lock index 6ef9447..fd82bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,15 +10,15 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" name = "kt" version = "0.1.0" dependencies = [ - "libc", + "signal-hook", "termion", ] [[package]] name = "libc" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7" +checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" [[package]] name = "numtoa" @@ -44,6 +44,25 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "signal-hook" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + [[package]] name = "termion" version = "1.5.6" diff --git a/Cargo.toml b/Cargo.toml index f902027..b2f4287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -termion = "1.5" -libc = "0.2" +termion = "1.5.6" +signal-hook = "0.3.8" diff --git a/src/main.rs b/src/main.rs index dbdc974..1ce5c57 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ extern crate termion; -extern crate libc; +extern crate signal_hook; mod alarm; mod clock; mod common; @@ -7,6 +7,9 @@ mod layout; use std::{time, thread, env}; use std::io::Write; +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use signal_hook::flag; use termion::{clear, color, cursor, style}; use termion::raw::{IntoRawMode, RawTerminal}; use termion::event::Key; @@ -24,11 +27,20 @@ const USAGE: &str = -e, --exec [COMMAND] Execute \"COMMAND\" on alarm. Must be the last flag on the command line. Everything after it is passed as argument to \"COMMAND\". Every \"%s\" will be replaced - with the elapsed time in [(HH:)MM:SS] format."; + with the elapsed time in [(HH:)MM:SS] format. + +SIGNALS: Reset clock. + Pause or un-pause clock."; const MENUBAR: &str = "[0-9] Add alarm [d] Delete alarm [SPACE] Pause [r] Reset [c] Clear color [q] Quit"; const MENUBAR_SHORT: &str = "[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit"; +// Needed for signal_hook. +const SIGTSTP: usize = signal_hook::consts::SIGTSTP as usize; +const SIGTERM: usize = signal_hook::consts::SIGTERM as usize; +const SIGINT: usize = signal_hook::consts::SIGINT as usize; +const SIGUSR1: usize = signal_hook::consts::SIGUSR1 as usize; +const SIGUSR2: usize = signal_hook::consts::SIGUSR2 as usize; pub struct Config { plain: bool, @@ -55,6 +67,10 @@ fn main() { let mut buffer = String::new(); let mut buffer_updated: bool = false; let mut countdown = Countdown::new(); + + // Register signal handlers. + let signal = Arc::new(AtomicUsize::new(0)); + register_signal_handlers(&signal); // Clear screen and hide cursor. write!(stdout, @@ -67,6 +83,29 @@ fn main() { }); loop { + // Process received signals. + match signal.swap(0, Ordering::Relaxed) { + // No signal received. + 0 => (), + // Suspend execution on SIGTSTP. + SIGTSTP => { + suspend(&mut stdout); + layout.force_redraw = true; + }, + // Exit main loop on SIGTERM and SIGINT. + SIGTERM | SIGINT => break, + // Reset clock on SIGUSR1. + SIGUSR1 => { + clock.reset(); + alarm_roster.reset_all(); + layout.force_redraw = true; + }, + // (Un-)Pause clock on SIGUSR2. + SIGUSR2 => clock.toggle(), + // We didn't register anything else. + _ => unreachable!(), + } + // Process input. if let Some(key) = input_keys.next() { match key.expect("Error reading input") { @@ -269,7 +308,15 @@ fn parse_args(config: &mut Config) { } } -// Suspend execution by raising SIGTSTP. +fn register_signal_handlers(signal: &Arc) { + flag::register_usize(SIGTSTP as i32, Arc::clone(&signal), SIGTSTP).unwrap(); + flag::register_usize(SIGTERM as i32, Arc::clone(&signal), SIGTERM).unwrap(); + flag::register_usize(SIGINT as i32, Arc::clone(&signal), SIGINT).unwrap(); + flag::register_usize(SIGUSR1 as i32, Arc::clone(&signal), SIGUSR1).unwrap(); + flag::register_usize(SIGUSR2 as i32, Arc::clone(&signal), SIGUSR2).unwrap(); +} + +// Suspend execution on SIGTSTP. fn suspend(stdout: &mut RawTerminal) { write!(stdout, "{}{}{}", @@ -283,9 +330,8 @@ fn suspend(stdout: &mut RawTerminal) { eprintln!("Failed to leave raw terminal mode prior to suspend: {}", error); }); - let result = unsafe { libc::raise(libc::SIGTSTP) }; - if result != 0 { - panic!("{}", std::io::Error::last_os_error()); + if let Err(error) = signal_hook::low_level::emulate_default_handler(SIGTSTP as i32) { + eprintln!("Error raising SIGTSTP: {}", error); } stdout.activate_raw_mode()