use std::env;
use std::error;
use std::io;
use std::mem;
use std::ptr;
use std::sync::atomic;
use structopt::StructOpt;
mod brush;
mod font;
mod term;
mod time;
mod view;
#[derive(Debug, StructOpt)]
#[structopt(name = "tock", about = "A digital clock for the terminal.")]
struct Opt {
#[structopt(short = "x", long = "x", default_value = "1")]
x: u16,
#[structopt(short = "y", long = "y", default_value = "1")]
y: u16,
#[structopt(short = "w", long = "width", default_value = "2")]
w: u16,
#[structopt(short = "h", long = "height", default_value = "1")]
h: u16,
#[structopt(short = "s", long = "seconds")]
second: bool,
#[structopt(short = "m", long = "military")]
military: bool,
#[structopt(short = "c", long = "center")]
center: bool,
#[structopt(short = "C", long = "color", default_value = "2")]
color: brush::Color,
#[structopt(short = "f", long = "format", default_value = "%F | %Z")]
format: String,
}
static FINISH: atomic::AtomicBool = atomic::AtomicBool::new(false);
static RESIZE: atomic::AtomicBool = atomic::AtomicBool::new(false);
extern "C" fn set_finish(_: libc::c_int) {
FINISH.store(true, atomic::Ordering::Relaxed)
}
extern "C" fn set_resize(_: libc::c_int) {
RESIZE.store(true, atomic::Ordering::Relaxed)
}
macro_rules! test {
($call:expr) => {
if $call != 0 {
return Err(Box::new(io::Error::last_os_error()))
}
}
}
fn main() -> Result<(), Box<dyn error::Error>> {
unsafe {
let mut action: libc::sigaction = mem::zeroed();
action.sa_flags |= libc::SA_RESTART;
test!(libc::sigemptyset(&mut action.sa_mask as _));
let finish = libc::sigaction { sa_sigaction: set_finish as _, .. action };
let resize = libc::sigaction { sa_sigaction: set_resize as _, .. action };
let null = ptr::null::<libc::sigaction>() as _;
test!(libc::sigaction(libc::SIGINT, &finish, null));
test!(libc::sigaction(libc::SIGTERM, &finish, null));
test!(libc::sigaction(libc::SIGWINCH, &resize, null));
}
let args = Opt::from_args();
let zone = env::var("TZ");
let mut stdin = io::stdin();
let mut stdout = io::stdout();
let mut term = term::Term::new(&mut stdin, &mut stdout)?;
let mut clock = view::Clock::new(
args.x,
args.y,
args.w,
args.h,
zone.as_ref()
.map(String::as_str)
.unwrap_or("Local"),
args.color,
args.center,
args.second,
args.military,
args.format,
);
let mut size = term.size()?;
clock.resize(size);
clock.reset(&mut term)?;
'main: while !FINISH.load(atomic::Ordering::Relaxed) {
let mut dirty = false;
if RESIZE.load(atomic::Ordering::Relaxed) {
RESIZE.store(false, atomic::Ordering::Relaxed);
dirty = true;
size = term.size()?;
clock.resize(size);
}
#[cfg(feature = "interactive")]
while let Some(c) = term.poll() {
match c {
| 'q' | 'Q' | '\x1B' => break 'main,
| 's' => {
dirty = true;
clock.toggle_second();
clock.resize(size);
}
| 'm' => {
dirty = true;
clock.toggle_military();
clock.resize(size);
}
| '0' ..= '7' => {
dirty = true;
clock.set_color(brush::Color::C8(brush::C8(c as u8 - 48)));
}
| _ => (),
}
}
if dirty { clock.reset(&mut term)?; }
clock.sync();
clock.update(&mut term)?;
}
Ok(())
}