Extensive refactoring.
This commit is contained in:
parent
f5490eb96b
commit
d95f2faaef
5 changed files with 289 additions and 303 deletions
|
@ -2,7 +2,9 @@ use std::io::Write;
|
||||||
use std::process::{Command, Stdio, Child};
|
use std::process::{Command, Stdio, Child};
|
||||||
use termion::{color, cursor, style};
|
use termion::{color, cursor, style};
|
||||||
use termion::raw::RawTerminal;
|
use termion::raw::RawTerminal;
|
||||||
use crate::{Clock, Config, Layout, Position};
|
use crate::Config;
|
||||||
|
use crate::clock::Clock;
|
||||||
|
use crate::layout::{Layout, Position};
|
||||||
use crate::utils::*;
|
use crate::utils::*;
|
||||||
use crate::consts::{COLOR, LABEL_SIZE_LIMIT};
|
use crate::consts::{COLOR, LABEL_SIZE_LIMIT};
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@ use std::io::Write;
|
||||||
use termion::{color, cursor};
|
use termion::{color, cursor};
|
||||||
use termion::raw::RawTerminal;
|
use termion::raw::RawTerminal;
|
||||||
use crate::consts::*;
|
use crate::consts::*;
|
||||||
use crate::Layout;
|
use crate::layout::{Layout, Position};
|
||||||
use crate::Position;
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Clock {
|
pub struct Clock {
|
||||||
|
|
|
@ -25,15 +25,6 @@ pub const MENUBAR_SHORT: &str =
|
||||||
pub const MENUBAR_INS: &str =
|
pub const MENUBAR_INS: &str =
|
||||||
"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit";
|
"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit";
|
||||||
|
|
||||||
// Needed for signal_hook.
|
|
||||||
pub const SIGTSTP: usize = signal_hook::consts::SIGTSTP as usize;
|
|
||||||
pub const SIGWINCH: usize = signal_hook::consts::SIGWINCH as usize;
|
|
||||||
pub const SIGCONT: usize = signal_hook::consts::SIGCONT as usize;
|
|
||||||
pub const SIGTERM: usize = signal_hook::consts::SIGTERM as usize;
|
|
||||||
pub const SIGINT: usize = signal_hook::consts::SIGINT as usize;
|
|
||||||
pub const SIGUSR1: usize = signal_hook::consts::SIGUSR1 as usize;
|
|
||||||
pub const SIGUSR2: usize = signal_hook::consts::SIGUSR2 as usize;
|
|
||||||
|
|
||||||
pub const COLOR: [&dyn termion::color::Color; 6] = [
|
pub const COLOR: [&dyn termion::color::Color; 6] = [
|
||||||
&termion::color::Cyan,
|
&termion::color::Cyan,
|
||||||
&termion::color::Magenta,
|
&termion::color::Magenta,
|
||||||
|
|
|
@ -1,23 +1,39 @@
|
||||||
extern crate termion;
|
extern crate termion;
|
||||||
|
pub mod alarm;
|
||||||
|
pub mod clock;
|
||||||
|
pub mod consts;
|
||||||
|
pub mod layout;
|
||||||
|
pub mod utils;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
use std::{process, thread, time};
|
use std::{env, process, thread, time};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use signal_hook::low_level;
|
use signal_hook::{flag, low_level};
|
||||||
use termion::{clear, color, cursor, style};
|
use termion::{clear, color, cursor, style};
|
||||||
use termion::raw::{IntoRawMode, RawTerminal};
|
use termion::raw::{IntoRawMode, RawTerminal};
|
||||||
use termion::event::Key;
|
use termion::event::Key;
|
||||||
use termion::input::TermRead;
|
use termion::input::TermRead;
|
||||||
//use termion::cursor::DetectCursorPos;
|
//use termion::cursor::DetectCursorPos;
|
||||||
use crate::clock::Clock;
|
use utils::*;
|
||||||
use crate::alarm::{Countdown, AlarmRoster, exec_command};
|
use clock::Clock;
|
||||||
use crate::layout::Layout;
|
use layout::Layout;
|
||||||
use crate::consts::*;
|
use alarm::{Countdown, exec_command};
|
||||||
use crate::utils::*;
|
pub use alarm::AlarmRoster;
|
||||||
use crate::Config;
|
pub use consts::*;
|
||||||
|
|
||||||
pub fn kitchentimer(
|
const SIGTSTP: usize = signal_hook::consts::SIGTSTP as usize;
|
||||||
|
const SIGWINCH: usize = signal_hook::consts::SIGWINCH as usize;
|
||||||
|
const SIGCONT: usize = signal_hook::consts::SIGCONT 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 fn run(
|
||||||
config: Config,
|
config: Config,
|
||||||
mut alarm_roster: AlarmRoster,
|
mut alarm_roster: AlarmRoster,
|
||||||
signal: Arc<AtomicUsize>,
|
signal: Arc<AtomicUsize>,
|
||||||
|
@ -34,30 +50,22 @@ pub fn kitchentimer(
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
// State variables.
|
// State variables.
|
||||||
|
// Request redraw of input buffer.
|
||||||
let mut update_buffer = false;
|
let mut update_buffer = false;
|
||||||
|
// Request redraw of menu.
|
||||||
let mut update_menu = false;
|
let mut update_menu = false;
|
||||||
|
// Are we in insert mode?
|
||||||
let mut insert_mode = false;
|
let mut insert_mode = false;
|
||||||
|
|
||||||
let async_stdin = termion::async_stdin();
|
let async_stdin = termion::async_stdin();
|
||||||
let mut input_keys = async_stdin.keys();
|
let mut input_keys = async_stdin.keys();
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
let mut stdout = stdout.lock().into_raw_mode()
|
let mut stdout = stdout.lock().into_raw_mode()?;
|
||||||
.unwrap_or_else(|error| {
|
|
||||||
eprintln!("Error opening stdout: {}", error);
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear window and hide cursor.
|
// Clear window and hide cursor.
|
||||||
write!(stdout,
|
write!(stdout, "{}{}", clear::All, cursor::Hide)?;
|
||||||
"{}{}",
|
|
||||||
clear::All,
|
|
||||||
cursor::Hide)
|
|
||||||
.unwrap_or_else(|error| {
|
|
||||||
eprintln!("Error writing to stdout: {}", error);
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enter main loop.
|
// Main loop entry.
|
||||||
loop {
|
loop {
|
||||||
// Process received signals.
|
// Process received signals.
|
||||||
match signal.swap(0, Ordering::Relaxed) {
|
match signal.swap(0, Ordering::Relaxed) {
|
||||||
|
@ -76,7 +84,7 @@ pub fn kitchentimer(
|
||||||
// Continuing after SIGSTOP.
|
// Continuing after SIGSTOP.
|
||||||
SIGCONT => {
|
SIGCONT => {
|
||||||
// This is reached when the process was suspended by SIGSTOP.
|
// This is reached when the process was suspended by SIGSTOP.
|
||||||
restore_after_suspend(&mut stdout);
|
restore_after_suspend(&mut stdout)?;
|
||||||
layout.force_redraw = true;
|
layout.force_redraw = true;
|
||||||
},
|
},
|
||||||
// Exit main loop on SIGTERM and SIGINT.
|
// Exit main loop on SIGTERM and SIGINT.
|
||||||
|
@ -94,6 +102,134 @@ pub fn kitchentimer(
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update input buffer display, if requested.
|
||||||
|
if update_buffer {
|
||||||
|
draw_buffer(&mut stdout, &mut layout, &buffer)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
update_buffer = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update elapsed time.
|
||||||
|
let elapsed = if clock.paused {
|
||||||
|
clock.elapsed
|
||||||
|
} else {
|
||||||
|
// Should never overflow as we reestablish a new "start"
|
||||||
|
// instant every 24 hours.
|
||||||
|
clock.start.elapsed().as_secs() as u32
|
||||||
|
};
|
||||||
|
|
||||||
|
// Conditional inner loop. Runs once every second or when explicitly
|
||||||
|
// requested.
|
||||||
|
if elapsed != clock.elapsed || layout.force_redraw {
|
||||||
|
// Update clock. Advance one day after 24 hours.
|
||||||
|
if elapsed < 24 * 60 * 60 {
|
||||||
|
clock.elapsed = elapsed;
|
||||||
|
} else {
|
||||||
|
clock.next_day();
|
||||||
|
// "clock.elapsed" set by "clock.next_day()".
|
||||||
|
alarm_roster.reset_all();
|
||||||
|
layout.force_recalc.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window size information and calculate the clock position.
|
||||||
|
// Also enforce recalculation of layout if we start displaying
|
||||||
|
// hours.
|
||||||
|
layout.update(clock.elapsed >= 3600, clock.elapsed == 3600);
|
||||||
|
|
||||||
|
// Check for exceeded alarms.
|
||||||
|
if let Some((time, label)) = alarm_roster.check(&mut clock, &layout, &mut countdown) {
|
||||||
|
// Write ASCII bell code.
|
||||||
|
write!(stdout, "{}", 0x07 as char)?;
|
||||||
|
layout.force_redraw = true;
|
||||||
|
|
||||||
|
// Run command if configured.
|
||||||
|
if config.command.is_some() {
|
||||||
|
if spawned.is_none() {
|
||||||
|
*spawned = exec_command(&config, time, &label);
|
||||||
|
} else {
|
||||||
|
// The last command is still running.
|
||||||
|
eprintln!("Not executing command, as its predecessor is still running");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Quit if configured.
|
||||||
|
if config.quit && !alarm_roster.active() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the window and redraw menu bar, alarm roster and buffer if
|
||||||
|
// requested.
|
||||||
|
if layout.force_redraw {
|
||||||
|
write!(stdout, "{}", clear::All)?;
|
||||||
|
|
||||||
|
// Redraw list of alarms.
|
||||||
|
alarm_roster.draw(&mut stdout, &mut layout);
|
||||||
|
|
||||||
|
// Redraw buffer.
|
||||||
|
draw_buffer(&mut stdout, &mut layout, &buffer)?;
|
||||||
|
|
||||||
|
// Schedule menu redraw.
|
||||||
|
update_menu = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if update_menu {
|
||||||
|
update_menu = false;
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}{}",
|
||||||
|
cursor::Goto(1, 1),
|
||||||
|
style::Faint,
|
||||||
|
// Switch menu bars. Use a compressed version or none at
|
||||||
|
// all if necessary.
|
||||||
|
match insert_mode {
|
||||||
|
true if layout.can_hold(MENUBAR_INS) => MENUBAR_INS,
|
||||||
|
false if layout.can_hold(MENUBAR) => MENUBAR,
|
||||||
|
false if layout.can_hold(MENUBAR_SHORT) => MENUBAR_SHORT,
|
||||||
|
_ => "",
|
||||||
|
},
|
||||||
|
style::Reset)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
clock.draw(&mut stdout, &layout);
|
||||||
|
|
||||||
|
// Display countdown.
|
||||||
|
if countdown.value > 0 {
|
||||||
|
countdown.draw(&mut stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move cursor to buffer position.
|
||||||
|
if insert_mode {
|
||||||
|
write!(
|
||||||
|
stdout,
|
||||||
|
"{}",
|
||||||
|
cursor::Goto(layout.cursor.col, layout.cursor.line))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check any spawned child process.
|
||||||
|
if let Some(ref mut child) = spawned {
|
||||||
|
match child.try_wait() {
|
||||||
|
// Process exited successfully.
|
||||||
|
Ok(Some(status)) if status.success() => *spawned = None,
|
||||||
|
// Abnormal exit.
|
||||||
|
Ok(Some(status)) => {
|
||||||
|
eprintln!("Spawned process terminated with non-zero exit status. ({})", status);
|
||||||
|
*spawned = None;
|
||||||
|
},
|
||||||
|
// Process is still running.
|
||||||
|
Ok(None) => (),
|
||||||
|
// Other error.
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Error executing command. ({})", error);
|
||||||
|
*spawned = None;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of conditional inner loop.
|
||||||
|
// Reset redraw_all and flush stdout.
|
||||||
|
layout.force_redraw = false;
|
||||||
|
stdout.flush()?;
|
||||||
|
}
|
||||||
|
|
||||||
// Process input.
|
// Process input.
|
||||||
if let Some(key) = input_keys.next() {
|
if let Some(key) = input_keys.next() {
|
||||||
match key.expect("Error reading input") {
|
match key.expect("Error reading input") {
|
||||||
|
@ -197,131 +333,6 @@ pub fn kitchentimer(
|
||||||
// Main loop delay.
|
// Main loop delay.
|
||||||
thread::sleep(time::Duration::from_millis(200));
|
thread::sleep(time::Duration::from_millis(200));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update input buffer display.
|
|
||||||
if update_buffer {
|
|
||||||
draw_buffer(&mut stdout, &mut layout, &buffer)?;
|
|
||||||
update_buffer = false;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elapsed = if clock.paused {
|
|
||||||
clock.elapsed
|
|
||||||
} else {
|
|
||||||
// Should never overflow as we reestablish a new "start"
|
|
||||||
// instant every 24 hours.
|
|
||||||
clock.start.elapsed().as_secs() as u32
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update window content if necessary.
|
|
||||||
if elapsed != clock.elapsed || layout.force_redraw {
|
|
||||||
// Update clock. Advance one day after 24 hours.
|
|
||||||
if elapsed < 24 * 60 * 60 {
|
|
||||||
clock.elapsed = elapsed;
|
|
||||||
} else {
|
|
||||||
clock.next_day();
|
|
||||||
// "clock.elapsed" set by "clock.next_day()".
|
|
||||||
alarm_roster.reset_all();
|
|
||||||
layout.force_recalc.store(true, Ordering::Relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update window size information and calculate the clock position.
|
|
||||||
// Also enforce recalculation of layout if we start displaying
|
|
||||||
// hours.
|
|
||||||
layout.update(clock.elapsed >= 3600, clock.elapsed == 3600);
|
|
||||||
|
|
||||||
// Check for exceeded alarms.
|
|
||||||
if let Some((time, label)) = alarm_roster.check(&mut clock, &layout, &mut countdown) {
|
|
||||||
// Write ASCII bell code.
|
|
||||||
write!(stdout, "{}", 0x07 as char)?;
|
|
||||||
layout.force_redraw = true;
|
|
||||||
|
|
||||||
// Run command if configured.
|
|
||||||
if config.command.is_some() {
|
|
||||||
if spawned.is_none() {
|
|
||||||
*spawned = exec_command(&config, time, &label);
|
|
||||||
} else {
|
|
||||||
// The last command is still running.
|
|
||||||
eprintln!("Not executing command, as its predecessor is still running");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Quit if configured.
|
|
||||||
if config.quit && !alarm_roster.active() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the window and redraw menu bar, alarm roster and buffer if
|
|
||||||
// requested.
|
|
||||||
if layout.force_redraw {
|
|
||||||
write!(stdout, "{}", clear::All)?;
|
|
||||||
|
|
||||||
// Redraw list of alarms.
|
|
||||||
alarm_roster.draw(&mut stdout, &mut layout);
|
|
||||||
|
|
||||||
// Redraw buffer.
|
|
||||||
draw_buffer(&mut stdout, &mut layout, &buffer)?;
|
|
||||||
|
|
||||||
// Schedule menu redraw.
|
|
||||||
update_menu = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if update_menu {
|
|
||||||
update_menu = false;
|
|
||||||
write!(stdout,
|
|
||||||
"{}{}{}{}",
|
|
||||||
cursor::Goto(1, 1),
|
|
||||||
style::Faint,
|
|
||||||
// Switch menu bars. Use a compressed version or none at
|
|
||||||
// all if necessary.
|
|
||||||
match insert_mode {
|
|
||||||
true if layout.can_hold(MENUBAR_INS) => MENUBAR_INS,
|
|
||||||
false if layout.can_hold(MENUBAR) => MENUBAR,
|
|
||||||
false if layout.can_hold(MENUBAR_SHORT) => MENUBAR_SHORT,
|
|
||||||
_ => "",
|
|
||||||
},
|
|
||||||
style::Reset)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
clock.draw(&mut stdout, &layout);
|
|
||||||
|
|
||||||
// Display countdown.
|
|
||||||
if countdown.value > 0 {
|
|
||||||
countdown.draw(&mut stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move cursor to buffer position.
|
|
||||||
if insert_mode {
|
|
||||||
write!(
|
|
||||||
stdout,
|
|
||||||
"{}",
|
|
||||||
cursor::Goto(layout.cursor.col, layout.cursor.line))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check any spawned child process.
|
|
||||||
if let Some(ref mut child) = spawned {
|
|
||||||
match child.try_wait() {
|
|
||||||
// Process exited successfully.
|
|
||||||
Ok(Some(status)) if status.success() => *spawned = None,
|
|
||||||
// Abnormal exit.
|
|
||||||
Ok(Some(status)) => {
|
|
||||||
eprintln!("Spawned process terminated with non-zero exit status. ({})", status);
|
|
||||||
*spawned = None;
|
|
||||||
},
|
|
||||||
// Process is still running.
|
|
||||||
Ok(None) => (),
|
|
||||||
// Other error.
|
|
||||||
Err(error) => {
|
|
||||||
eprintln!("Error executing command. ({})", error);
|
|
||||||
*spawned = None;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset redraw_all and flush stdout.
|
|
||||||
layout.force_redraw = false;
|
|
||||||
stdout.flush()?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main loop exited. Clear window and restore cursor.
|
// Main loop exited. Clear window and restore cursor.
|
||||||
|
@ -334,6 +345,111 @@ pub fn kitchentimer(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
plain: bool,
|
||||||
|
quit: bool,
|
||||||
|
command: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
// Parse command line arguments into "config".
|
||||||
|
pub fn new(args: env::Args, alarm_roster: &mut AlarmRoster)
|
||||||
|
-> Result<Config, String>
|
||||||
|
{
|
||||||
|
let mut config = Config {
|
||||||
|
plain: false,
|
||||||
|
quit: false,
|
||||||
|
command: None,
|
||||||
|
};
|
||||||
|
let mut iter = args.skip(1);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(arg) = iter.next() {
|
||||||
|
match arg.as_str() {
|
||||||
|
"-h" | "--help" => {
|
||||||
|
// Print usage information and exit
|
||||||
|
println!("{}", USAGE);
|
||||||
|
process::exit(0);
|
||||||
|
},
|
||||||
|
"-v" | "--version" => {
|
||||||
|
println!("{} {}", NAME, VERSION);
|
||||||
|
process::exit(0);
|
||||||
|
},
|
||||||
|
"-p" | "--plain" => config.plain = true,
|
||||||
|
"-q" | "--quit" => config.quit = true,
|
||||||
|
"-e" | "--exec" => {
|
||||||
|
if let Some(e) = iter.next() {
|
||||||
|
config.command = Some(Config::parse_to_command(&e));
|
||||||
|
} else {
|
||||||
|
return Err(format!("Missing parameter to \"{}\".", arg));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
any if any.starts_with('-') => {
|
||||||
|
// Unrecognized flag.
|
||||||
|
return Err(format!("Unrecognized option: \"{}\"\nUse \"-h\" or \"--help\" for a list of valid command line options.", any));
|
||||||
|
},
|
||||||
|
any => {
|
||||||
|
// Alarm to add.
|
||||||
|
if let Err(error) = alarm_roster.add(&String::from(any)) {
|
||||||
|
return Err(format!("Error adding \"{}\" as alarm. ({})", any, error));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else { break; } // All command line parameters processed.
|
||||||
|
}
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line argument to --command into a vector of strings suitable
|
||||||
|
// for process::Command::new().
|
||||||
|
fn parse_to_command(input: &str) -> Vec<String> {
|
||||||
|
let mut command: Vec<String> = Vec::new();
|
||||||
|
let mut buffer: String = String::new();
|
||||||
|
let mut quoted = false;
|
||||||
|
let mut escaped = false;
|
||||||
|
|
||||||
|
for byte in input.chars() {
|
||||||
|
match byte {
|
||||||
|
'\\' if !escaped => {
|
||||||
|
// Next char is escaped.
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
' ' if escaped || quoted => { &buffer.push(' '); },
|
||||||
|
' ' => {
|
||||||
|
if !&buffer.is_empty() {
|
||||||
|
command.push(buffer.clone());
|
||||||
|
&buffer.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'"' | '\'' if !escaped => quoted = !quoted,
|
||||||
|
_ => {
|
||||||
|
if escaped { &buffer.push('\\'); }
|
||||||
|
&buffer.push(byte);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
command.push(buffer);
|
||||||
|
command.shrink_to_fit();
|
||||||
|
command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_signals(
|
||||||
|
signal: &Arc<AtomicUsize>,
|
||||||
|
recalc_flag: &Arc<AtomicBool>,
|
||||||
|
) {
|
||||||
|
flag::register_usize(SIGTSTP as i32, Arc::clone(&signal), SIGTSTP).unwrap();
|
||||||
|
flag::register_usize(SIGCONT as i32, Arc::clone(&signal), SIGCONT).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();
|
||||||
|
// SIGWINCH sets "force_recalc" directly.
|
||||||
|
flag::register(SIGWINCH as i32, Arc::clone(&recalc_flag)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw input buffer.
|
// Draw input buffer.
|
||||||
fn draw_buffer<W: Write>(
|
fn draw_buffer<W: Write>(
|
||||||
stdout: &mut RawTerminal<W>,
|
stdout: &mut RawTerminal<W>,
|
||||||
|
@ -399,12 +515,13 @@ fn suspend<W: Write>(mut stdout: &mut RawTerminal<W>)
|
||||||
eprintln!("Error raising SIGTSTP: {}", error);
|
eprintln!("Error raising SIGTSTP: {}", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
restore_after_suspend(&mut stdout);
|
restore_after_suspend(&mut stdout)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up terminal after SIGTSTP or SIGSTOP.
|
// Set up terminal after SIGTSTP or SIGSTOP.
|
||||||
fn restore_after_suspend<W: Write>(stdout: &mut RawTerminal<W>) {
|
fn restore_after_suspend<W: Write>(stdout: &mut RawTerminal<W>)
|
||||||
|
-> Result<(), std::io::Error>
|
||||||
|
{
|
||||||
stdout.activate_raw_mode()
|
stdout.activate_raw_mode()
|
||||||
.unwrap_or_else(|error| {
|
.unwrap_or_else(|error| {
|
||||||
eprintln!("Failed to re-enter raw terminal mode after suspend: {}", error);
|
eprintln!("Failed to re-enter raw terminal mode after suspend: {}", error);
|
||||||
|
@ -413,10 +530,7 @@ fn restore_after_suspend<W: Write>(stdout: &mut RawTerminal<W>) {
|
||||||
write!(stdout,
|
write!(stdout,
|
||||||
"{}{}",
|
"{}{}",
|
||||||
clear::All,
|
clear::All,
|
||||||
cursor::Hide)
|
cursor::Hide)?;
|
||||||
.unwrap_or_else(|error| {
|
Ok(())
|
||||||
eprintln!("Error writing to stdout: {}", error);
|
|
||||||
process::exit(1);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
148
src/main.rs
148
src/main.rs
|
@ -1,64 +1,38 @@
|
||||||
extern crate signal_hook;
|
extern crate signal_hook;
|
||||||
mod alarm;
|
|
||||||
mod clock;
|
|
||||||
mod consts;
|
|
||||||
mod kitchentimer;
|
|
||||||
mod layout;
|
|
||||||
mod utils;
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
use std::{env, process};
|
use std::{env, process};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
use std::sync::atomic::{AtomicBool, AtomicUsize};
|
||||||
use signal_hook::flag;
|
use kitchentimer::{Config, AlarmRoster, register_signals, run};
|
||||||
use clock::Clock;
|
|
||||||
use alarm::AlarmRoster;
|
|
||||||
use layout::{Layout, Position};
|
|
||||||
use consts::*;
|
|
||||||
use kitchentimer::kitchentimer;
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
plain: bool,
|
|
||||||
quit: bool,
|
|
||||||
command: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut config = Config {
|
let args = env::args();
|
||||||
plain: false,
|
|
||||||
quit: false,
|
|
||||||
command: None,
|
|
||||||
};
|
|
||||||
let mut alarm_roster = AlarmRoster::new();
|
let mut alarm_roster = AlarmRoster::new();
|
||||||
// Parse command line arguments into config and alarm roster.
|
// Parse command line arguments into config and alarm roster.
|
||||||
parse_args(&mut config, &mut alarm_roster);
|
let config = Config::new(args, &mut alarm_roster)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
println!("{}", e);
|
||||||
|
process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
// Register signal handlers.
|
// Register signal handlers.
|
||||||
let signal = Arc::new(AtomicUsize::new(0));
|
let signal = Arc::new(AtomicUsize::new(0));
|
||||||
let sigwinch = Arc::new(AtomicBool::new(true));
|
let sigwinch = Arc::new(AtomicBool::new(true));
|
||||||
register_signal_handlers(&signal, &sigwinch);
|
register_signals(&signal, &sigwinch);
|
||||||
// Spawned child process if any.
|
// Holds spawned child process if any.
|
||||||
let mut spawned: Option<process::Child> = None;
|
let mut spawned: Option<process::Child> = None;
|
||||||
|
|
||||||
// Runs main loop.
|
// Run main loop.
|
||||||
match kitchentimer(
|
if let Err(e) = run(config, alarm_roster, signal, sigwinch, &mut spawned) {
|
||||||
config,
|
println!("Main loop exited with error: {}", e);
|
||||||
alarm_roster,
|
process::exit(1);
|
||||||
signal,
|
|
||||||
sigwinch,
|
|
||||||
&mut spawned,
|
|
||||||
) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) => eprintln!("Main loop exited with error: {}", e),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for remaining spawned processes to exit.
|
// Wait for remaining spawned processes to exit.
|
||||||
if let Some(ref mut child) = spawned {
|
if let Some(ref mut child) = spawned {
|
||||||
print!("Waiting for spawned processes (PID {}) to exit ...", child.id());
|
print!("Waiting for spawned process (PID {}) to exit ...", child.id());
|
||||||
std::io::stdout().flush().unwrap();
|
std::io::stdout().flush().unwrap();
|
||||||
|
|
||||||
match child.wait() {
|
match child.wait() {
|
||||||
|
@ -68,98 +42,4 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print usage information and exit.
|
|
||||||
fn usage() {
|
|
||||||
println!("{}", USAGE);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse command line arguments into "config".
|
|
||||||
fn parse_args(config: &mut Config, alarm_roster: &mut AlarmRoster) {
|
|
||||||
let mut iter = env::args().skip(1);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if let Some(arg) = iter.next() {
|
|
||||||
match arg.as_str() {
|
|
||||||
"-h" | "--help" => usage(),
|
|
||||||
"-v" | "--version" => {
|
|
||||||
println!("{} {}", NAME, VERSION);
|
|
||||||
process::exit(0);
|
|
||||||
},
|
|
||||||
"-p" | "--plain" => config.plain = true,
|
|
||||||
"-q" | "--quit" => config.quit = true,
|
|
||||||
"-e" | "--exec" => {
|
|
||||||
if let Some(e) = iter.next() {
|
|
||||||
config.command = Some(parse_to_command(&e));
|
|
||||||
} else {
|
|
||||||
println!("Missing parameter to \"{}\".", arg);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
any if any.starts_with('-') => {
|
|
||||||
// Unrecognized flag.
|
|
||||||
println!("Unrecognized option: \"{}\"", any);
|
|
||||||
println!("Use \"-h\" or \"--help\" for a list of valid command line options");
|
|
||||||
process::exit(1);
|
|
||||||
},
|
|
||||||
any => {
|
|
||||||
// Alarm to add.
|
|
||||||
if let Err(error) = alarm_roster.add(&String::from(any)) {
|
|
||||||
println!("Error adding \"{}\" as alarm. ({})", any, error);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else { break; } // All command line parameters processed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse command line argument to --command into a vector of strings suitable
|
|
||||||
// for process::Command::new().
|
|
||||||
fn parse_to_command(input: &str) -> Vec<String> {
|
|
||||||
let mut command: Vec<String> = Vec::new();
|
|
||||||
let mut buffer: String = String::new();
|
|
||||||
let mut quoted = false;
|
|
||||||
let mut escaped = false;
|
|
||||||
|
|
||||||
for byte in input.chars() {
|
|
||||||
match byte {
|
|
||||||
'\\' if !escaped => {
|
|
||||||
// Next char is escaped.
|
|
||||||
escaped = true;
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
' ' if escaped || quoted => { &buffer.push(' '); },
|
|
||||||
' ' => {
|
|
||||||
if !&buffer.is_empty() {
|
|
||||||
command.push(buffer.clone());
|
|
||||||
&buffer.clear();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'"' | '\'' if !escaped => quoted = !quoted,
|
|
||||||
_ => {
|
|
||||||
if escaped { &buffer.push('\\'); }
|
|
||||||
&buffer.push(byte);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
escaped = false;
|
|
||||||
}
|
|
||||||
command.push(buffer);
|
|
||||||
command.shrink_to_fit();
|
|
||||||
command
|
|
||||||
}
|
|
||||||
|
|
||||||
fn register_signal_handlers(
|
|
||||||
signal: &Arc<AtomicUsize>,
|
|
||||||
recalc_flag: &Arc<AtomicBool>,
|
|
||||||
) {
|
|
||||||
flag::register_usize(SIGTSTP as i32, Arc::clone(&signal), SIGTSTP).unwrap();
|
|
||||||
flag::register_usize(SIGCONT as i32, Arc::clone(&signal), SIGCONT).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();
|
|
||||||
// SIGWINCH sets "force_recalc" directly.
|
|
||||||
flag::register(SIGWINCH as i32, Arc::clone(&recalc_flag)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue