Initial commit.
This commit is contained in:
commit
c04bde3ba4
8 changed files with 1182 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
\.*.swp
|
57
Cargo.lock
generated
Normal file
57
Cargo.lock
generated
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kt"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"termion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.91"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numtoa"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_termios"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||||
|
dependencies = [
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termion"
|
||||||
|
version = "1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"numtoa",
|
||||||
|
"redox_syscall",
|
||||||
|
"redox_termios",
|
||||||
|
]
|
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "kt"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["shy <shy@posteo.de>"]
|
||||||
|
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"
|
280
src/alarm.rs
Normal file
280
src/alarm.rs
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
use termion::{color, cursor, style};
|
||||||
|
use termion::raw::RawTerminal;
|
||||||
|
use crate::{Clock, Config, Layout, Position};
|
||||||
|
use crate::common::COLOR;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Countdown {
|
||||||
|
pub value: u32,
|
||||||
|
position: Option<Position>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Countdown {
|
||||||
|
pub fn new() -> Countdown {
|
||||||
|
Countdown {
|
||||||
|
value: 0,
|
||||||
|
position: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw countdown.
|
||||||
|
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>) {
|
||||||
|
if let Some(pos) = &self.position {
|
||||||
|
if self.value < 3600 {
|
||||||
|
// Show minutes and seconds.
|
||||||
|
write!(stdout,
|
||||||
|
"{}(-{:02}:{:02})",
|
||||||
|
cursor::Goto(pos.col, pos.line),
|
||||||
|
(self.value / 60) % 60,
|
||||||
|
self.value % 60)
|
||||||
|
.unwrap();
|
||||||
|
if self.value == 3599 {
|
||||||
|
// Write three additional spaces after switching from hour display to
|
||||||
|
// minute display.
|
||||||
|
write!(stdout, " ").unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show hours, minutes and seconds.
|
||||||
|
write!(stdout,
|
||||||
|
"{}(-{:02}:{:02}:{:02})",
|
||||||
|
cursor::Goto(pos.col, pos.line),
|
||||||
|
self.value / 3600,
|
||||||
|
(self.value / 60) % 60,
|
||||||
|
self.value % 60)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Alarm {
|
||||||
|
time: u32,
|
||||||
|
display: String,
|
||||||
|
color_index: usize,
|
||||||
|
exceeded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Alarm {
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.exceeded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AlarmRoster {
|
||||||
|
list: Vec<Alarm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlarmRoster {
|
||||||
|
pub fn new() -> AlarmRoster {
|
||||||
|
AlarmRoster {
|
||||||
|
list: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, buffer: &String)
|
||||||
|
-> Result<(), &'static str> {
|
||||||
|
|
||||||
|
let mut index = 0;
|
||||||
|
let mut time: u32 = 0;
|
||||||
|
|
||||||
|
// Parse input into seconds.
|
||||||
|
for sub in buffer.rsplit(':') {
|
||||||
|
if sub.len() > 0 {
|
||||||
|
let d = sub.parse::<u32>();
|
||||||
|
match d {
|
||||||
|
Ok(d) => time += d * 60u32.pow(index),
|
||||||
|
Err(_) => return Err("Could not parse number as <u32>."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
// More than 3 fields are an error.
|
||||||
|
if index > 3 { return Err("Too many colons to parse.") };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if time evaluated to zero.
|
||||||
|
if time == 0 { return Err("Evaluates to zero.") };
|
||||||
|
if time >= 24 * 60 * 60 { return Err("Values >24h not supported.") };
|
||||||
|
|
||||||
|
let alarm = Alarm {
|
||||||
|
display: buffer.clone(),
|
||||||
|
time,
|
||||||
|
color_index: (self.list.len() % COLOR.len()),
|
||||||
|
exceeded: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add to list, insert based on alarm time. Filter out double entries.
|
||||||
|
let mut i = self.list.len();
|
||||||
|
if i == 0 {
|
||||||
|
self.list.push(alarm);
|
||||||
|
} else {
|
||||||
|
while i > 0 {
|
||||||
|
// Filter out double entries.
|
||||||
|
if self.list[i - 1].time == time {
|
||||||
|
return Err("Already exists.");
|
||||||
|
} else if self.list[i - 1].time < time {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
self.list.insert(i, alarm);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(&mut self) -> Option<Alarm> {
|
||||||
|
self.list.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for exceeded alarms.
|
||||||
|
pub fn check(&mut self,
|
||||||
|
clock: &mut Clock,
|
||||||
|
layout: &Layout,
|
||||||
|
countdown: &mut Countdown) -> bool {
|
||||||
|
|
||||||
|
let mut hit = false;
|
||||||
|
let mut index = 0;
|
||||||
|
|
||||||
|
for alarm in &mut self.list {
|
||||||
|
// Ignore alarms already marked exceeded.
|
||||||
|
if !alarm.exceeded {
|
||||||
|
if alarm.time <= clock.elapsed {
|
||||||
|
// Found alarm to raise.
|
||||||
|
hit = true;
|
||||||
|
alarm.exceeded = true;
|
||||||
|
clock.color_index = Some(alarm.color_index);
|
||||||
|
countdown.value = 0;
|
||||||
|
countdown.position = None;
|
||||||
|
// Skip ahead to the next one.
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Reached the alarm to exceed next. Update countdown
|
||||||
|
// accordingly.
|
||||||
|
countdown.value = alarm.time - clock.elapsed;
|
||||||
|
if countdown.position.is_none() || layout.force_redraw {
|
||||||
|
// Compute position.
|
||||||
|
let mut col =
|
||||||
|
layout.roster.col
|
||||||
|
+ 3
|
||||||
|
+ alarm.display.len() as u16;
|
||||||
|
let mut line = layout.roster.line + index;
|
||||||
|
|
||||||
|
// Compensate for "hidden" items in the alarm roster.
|
||||||
|
// TODO: Make this more elegant and robust.
|
||||||
|
if let Some(offset) = (self.list.len() as u16)
|
||||||
|
.checked_sub(layout.roster_height + 1) {
|
||||||
|
|
||||||
|
if index <= offset{
|
||||||
|
// Draw next to placeholder ("[...]").
|
||||||
|
line = layout.roster.line;
|
||||||
|
col = layout.roster.col + 6;
|
||||||
|
} else {
|
||||||
|
line = line.checked_sub(offset).unwrap_or(layout.roster.line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
countdown.position = Some(Position { col, line, });
|
||||||
|
}
|
||||||
|
// Ignore other alarms.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
hit // Return value.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw alarm roster according to layout.
|
||||||
|
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>, layout: &mut Layout) {
|
||||||
|
let mut width: u16 = 0;
|
||||||
|
let mut index = 0;
|
||||||
|
|
||||||
|
// Find first item to print in case we lack space to print them all.
|
||||||
|
// Final '-1' to take account for the input buffer.
|
||||||
|
let mut first = 0;
|
||||||
|
|
||||||
|
if self.list.len() > layout.roster_height as usize {
|
||||||
|
// Actually -1 (zero indexing) +1 (first line containing "...").
|
||||||
|
first = self.list.len() - layout.roster_height as usize;
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}[...]{}",
|
||||||
|
cursor::Goto(layout.roster.col, layout.roster.line),
|
||||||
|
style::Faint,
|
||||||
|
style::Reset).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for alarm in &self.list[first..] {
|
||||||
|
if alarm.exceeded {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{} {}{} {}!{}",
|
||||||
|
cursor::Goto(layout.roster.col, layout.roster.line + index),
|
||||||
|
color::Bg(COLOR[alarm.color_index]),
|
||||||
|
color::Bg(color::Reset),
|
||||||
|
style::Bold,
|
||||||
|
alarm.display,
|
||||||
|
style::Reset)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{} {} {}",
|
||||||
|
cursor::Goto(layout.roster.col, layout.roster.line + index),
|
||||||
|
color::Bg(COLOR[alarm.color_index]),
|
||||||
|
color::Bg(color::Reset),
|
||||||
|
alarm.display)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
// Calculate roster width. Actual display width is 3 chars wider.
|
||||||
|
if 3 + alarm.display.len() as u16 > width {
|
||||||
|
width = 3 + alarm.display.len() as u16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update layout information.
|
||||||
|
if layout.roster_width != width {
|
||||||
|
layout.roster_width = width;
|
||||||
|
layout.force_recalc = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset every alarm.
|
||||||
|
pub fn reset_all(&mut self) {
|
||||||
|
for a in &mut self.list {
|
||||||
|
a.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the command given on the command line.
|
||||||
|
pub fn alarm_exec(config: &Config, elapsed: u32) {
|
||||||
|
let mut args: Vec<String> = Vec::new();
|
||||||
|
let time: String;
|
||||||
|
|
||||||
|
if elapsed < 3600 {
|
||||||
|
time = format!("{:02}:{:02}", elapsed / 60, elapsed % 60);
|
||||||
|
} else {
|
||||||
|
time = format!("{:02}:{:02}:{:02}", elapsed /3600, (elapsed / 60) % 60, elapsed % 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace every occurrence of "%s".
|
||||||
|
for s in config.alarm_exec.iter() {
|
||||||
|
args.push(s.replace("%s", &time));
|
||||||
|
}
|
||||||
|
|
||||||
|
if Command::new(
|
||||||
|
&config.alarm_exec[0])
|
||||||
|
.args(&args[1..])
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.spawn().is_err() {
|
||||||
|
|
||||||
|
eprintln!("Error: Could not execute command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
209
src/clock.rs
Normal file
209
src/clock.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
use std::time;
|
||||||
|
use std::io::Write;
|
||||||
|
use termion::{color, cursor};
|
||||||
|
use termion::raw::RawTerminal;
|
||||||
|
use crate::common::*;
|
||||||
|
use crate::Layout;
|
||||||
|
use crate::Position;
|
||||||
|
use crate::common::COLOR;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Clock {
|
||||||
|
pub start: time::Instant,
|
||||||
|
pub elapsed: u32,
|
||||||
|
pub days: u32,
|
||||||
|
pub paused: bool,
|
||||||
|
paused_at: Option<time::Instant>,
|
||||||
|
pub color_index: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clock {
|
||||||
|
pub fn new() -> Clock {
|
||||||
|
Clock {
|
||||||
|
start: time::Instant::now(),
|
||||||
|
elapsed: 0,
|
||||||
|
days: 0,
|
||||||
|
paused: false,
|
||||||
|
paused_at: None,
|
||||||
|
color_index: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.start = time::Instant::now();
|
||||||
|
self.elapsed = 0;
|
||||||
|
self.days = 0;
|
||||||
|
self.color_index = None;
|
||||||
|
|
||||||
|
// unpause will panic if we do not trigger a new pause here.
|
||||||
|
if self.paused {
|
||||||
|
self.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pause(&mut self) {
|
||||||
|
self.paused_at = Some(time::Instant::now());
|
||||||
|
self.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpause(&mut self) {
|
||||||
|
// Try to derive a new start instant.
|
||||||
|
if let Some(delay) = self.paused_at {
|
||||||
|
if let Some(new_start) = self.start.checked_add(delay.elapsed()) {
|
||||||
|
self.start = new_start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.paused_at = None;
|
||||||
|
self.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle(&mut self) {
|
||||||
|
if self.paused {
|
||||||
|
self.unpause();
|
||||||
|
} else {
|
||||||
|
self.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_day(&mut self) {
|
||||||
|
// Shift start 24h into the future.
|
||||||
|
let next = self.start.clone() + time::Duration::from_secs(60 * 60 * 24);
|
||||||
|
|
||||||
|
// Take care not to shift start into the future.
|
||||||
|
if next <= time::Instant::now() {
|
||||||
|
self.start = next;
|
||||||
|
self.elapsed = 0;
|
||||||
|
self.days = self.days.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw clock according to layout.
|
||||||
|
pub fn draw<W: Write>(&mut self, mut stdout: &mut RawTerminal<W>, layout: &Layout) {
|
||||||
|
// Draw hours if necessary.
|
||||||
|
if layout.force_redraw || self.elapsed % 3600 == 0 {
|
||||||
|
if self.elapsed >= 3600 {
|
||||||
|
self.draw_digit_pair(
|
||||||
|
&mut stdout,
|
||||||
|
self.elapsed / 3600,
|
||||||
|
&layout.clock_hr,
|
||||||
|
layout.plain);
|
||||||
|
|
||||||
|
// Draw colon.
|
||||||
|
self.draw_colon(
|
||||||
|
&mut stdout,
|
||||||
|
&layout.clock_colon1,
|
||||||
|
layout.plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw days.
|
||||||
|
if self.days > 0 {
|
||||||
|
let day_count = format!(
|
||||||
|
"+ {} {}",
|
||||||
|
self.days,
|
||||||
|
if self.days == 1 { "day" } else { "days" });
|
||||||
|
|
||||||
|
write!(stdout,
|
||||||
|
"{}{:>11}",
|
||||||
|
cursor::Goto(
|
||||||
|
layout.clock_days.col,
|
||||||
|
layout.clock_days.line,
|
||||||
|
),
|
||||||
|
day_count)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw minutes if necessary.
|
||||||
|
if layout.force_redraw || self.elapsed % 60 == 0 {
|
||||||
|
self.draw_digit_pair(
|
||||||
|
&mut stdout,
|
||||||
|
(self.elapsed % 3600) / 60,
|
||||||
|
&layout.clock_min,
|
||||||
|
layout.plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw colon if necessary.
|
||||||
|
if layout.force_redraw {
|
||||||
|
self.draw_colon(
|
||||||
|
&mut stdout,
|
||||||
|
&layout.clock_colon0,
|
||||||
|
layout.plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw seconds.
|
||||||
|
self.draw_digit_pair(
|
||||||
|
&mut stdout,
|
||||||
|
self.elapsed % 60,
|
||||||
|
&layout.clock_sec,
|
||||||
|
layout.plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_digit_pair<W: Write>(&self, stdout: &mut RawTerminal<W>, value: u32, pos: &Position, plain: bool) {
|
||||||
|
if let Some(c) = self.color_index {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}",
|
||||||
|
cursor::Goto(pos.col, pos.line),
|
||||||
|
color::Fg(COLOR[c]))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for l in 0..DIGIT_HEIGHT {
|
||||||
|
if plain {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{} {}",
|
||||||
|
cursor::Goto(pos.col, pos.line + l),
|
||||||
|
// First digit.
|
||||||
|
DIGITS_PLAIN[(value / 10) as usize][l as usize],
|
||||||
|
// Second digit.
|
||||||
|
DIGITS_PLAIN[(value % 10) as usize][l as usize])
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{} {}",
|
||||||
|
cursor::Goto(pos.col, pos.line + l),
|
||||||
|
// First digit.
|
||||||
|
DIGITS[(value / 10) as usize][l as usize],
|
||||||
|
// Second digit.
|
||||||
|
DIGITS[(value % 10) as usize][l as usize])
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.color_index != None {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}",
|
||||||
|
cursor::Goto(pos.col + DIGIT_WIDTH + 1, pos.line + DIGIT_HEIGHT),
|
||||||
|
color::Fg(color::Reset))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_colon<W: Write>(&self, stdout: &mut RawTerminal<W>, pos: &Position, plain: bool) {
|
||||||
|
let dot: char = if plain {'█'} else {'■'};
|
||||||
|
|
||||||
|
match self.color_index {
|
||||||
|
Some(c) => {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}{}{}{}",
|
||||||
|
cursor::Goto(pos.col, pos.line + 1),
|
||||||
|
color::Fg(COLOR[c]),
|
||||||
|
dot,
|
||||||
|
cursor::Goto(pos.col, pos.line + 3),
|
||||||
|
dot,
|
||||||
|
color::Fg(color::Reset))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}{}",
|
||||||
|
cursor::Goto(pos.col, pos.line + 1),
|
||||||
|
dot,
|
||||||
|
cursor::Goto(pos.col, pos.line + 3),
|
||||||
|
dot)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
160
src/common.rs
Normal file
160
src/common.rs
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
use termion::color;
|
||||||
|
|
||||||
|
pub const COLOR: [&dyn color::Color; 6] = [
|
||||||
|
&color::Cyan,
|
||||||
|
&color::Magenta,
|
||||||
|
&color::Green,
|
||||||
|
&color::Yellow,
|
||||||
|
&color::Blue,
|
||||||
|
&color::Red,
|
||||||
|
];
|
||||||
|
pub const DIGIT_HEIGHT: u16 = 5;
|
||||||
|
pub const DIGIT_WIDTH: u16 = 5;
|
||||||
|
pub const DIGITS: [[&str; DIGIT_HEIGHT as usize]; 10] = [
|
||||||
|
[
|
||||||
|
// 0
|
||||||
|
"█▀▀▀█",
|
||||||
|
"█ █",
|
||||||
|
"█ █",
|
||||||
|
"█ █",
|
||||||
|
"█▄▄▄█",
|
||||||
|
], [
|
||||||
|
// 1
|
||||||
|
" ▀█ ",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
" █ "
|
||||||
|
], [
|
||||||
|
// 2
|
||||||
|
"▀▀▀▀█",
|
||||||
|
" █",
|
||||||
|
"█▀▀▀▀",
|
||||||
|
"█ ",
|
||||||
|
"█▄▄▄▄"
|
||||||
|
], [
|
||||||
|
// 3
|
||||||
|
"▀▀▀▀█",
|
||||||
|
" █",
|
||||||
|
" ▀▀▀█",
|
||||||
|
" █",
|
||||||
|
"▄▄▄▄█"
|
||||||
|
], [
|
||||||
|
// 4
|
||||||
|
"█ ",
|
||||||
|
"█ █ ",
|
||||||
|
"▀▀▀█▀",
|
||||||
|
" █ ",
|
||||||
|
" █ "
|
||||||
|
], [
|
||||||
|
// 5
|
||||||
|
"█▀▀▀▀",
|
||||||
|
"█ ",
|
||||||
|
"▀▀▀▀█",
|
||||||
|
" █",
|
||||||
|
"▄▄▄▄█"
|
||||||
|
], [
|
||||||
|
// 6
|
||||||
|
"█ ",
|
||||||
|
"█ ",
|
||||||
|
"█▀▀▀█",
|
||||||
|
"█ █",
|
||||||
|
"█▄▄▄█"
|
||||||
|
], [
|
||||||
|
// 7
|
||||||
|
"▀▀▀▀█",
|
||||||
|
" █",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
], [
|
||||||
|
// 8
|
||||||
|
"█▀▀▀█",
|
||||||
|
"█ █",
|
||||||
|
"█▀▀▀█",
|
||||||
|
"█ █",
|
||||||
|
"█▄▄▄█"
|
||||||
|
], [
|
||||||
|
// 9
|
||||||
|
"█▀▀▀█",
|
||||||
|
"█ █",
|
||||||
|
"▀▀▀▀█",
|
||||||
|
" █",
|
||||||
|
" █"
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const DIGITS_PLAIN: [[&str; DIGIT_HEIGHT as usize]; 10] = [
|
||||||
|
[
|
||||||
|
// 0
|
||||||
|
"█████",
|
||||||
|
"█ █",
|
||||||
|
"█ █",
|
||||||
|
"█ █",
|
||||||
|
"█████"
|
||||||
|
], [
|
||||||
|
// 1
|
||||||
|
" ██ ",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
" █ "
|
||||||
|
], [
|
||||||
|
// 2
|
||||||
|
"█████",
|
||||||
|
" █",
|
||||||
|
"█████",
|
||||||
|
"█ ",
|
||||||
|
"█████"
|
||||||
|
], [
|
||||||
|
// 3
|
||||||
|
"█████",
|
||||||
|
" █",
|
||||||
|
" ████",
|
||||||
|
" █",
|
||||||
|
"█████"
|
||||||
|
], [
|
||||||
|
// 4
|
||||||
|
"█ ",
|
||||||
|
"█ █ ",
|
||||||
|
"█████",
|
||||||
|
" █ ",
|
||||||
|
" █ "
|
||||||
|
], [
|
||||||
|
// 5
|
||||||
|
"█████",
|
||||||
|
"█ ",
|
||||||
|
"█████",
|
||||||
|
" █",
|
||||||
|
"█████"
|
||||||
|
], [
|
||||||
|
// 6
|
||||||
|
"█ ",
|
||||||
|
"█ ",
|
||||||
|
"█████",
|
||||||
|
"█ █",
|
||||||
|
"█████"
|
||||||
|
], [
|
||||||
|
// 7
|
||||||
|
"█████",
|
||||||
|
" █",
|
||||||
|
" █ ",
|
||||||
|
" █ ",
|
||||||
|
" █ "
|
||||||
|
], [
|
||||||
|
// 8
|
||||||
|
"█████",
|
||||||
|
"█ █",
|
||||||
|
"█████",
|
||||||
|
"█ █",
|
||||||
|
"█████"
|
||||||
|
], [
|
||||||
|
// 9
|
||||||
|
"█████",
|
||||||
|
"█ █",
|
||||||
|
"█████",
|
||||||
|
" █",
|
||||||
|
" █"
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
130
src/layout.rs
Normal file
130
src/layout.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use crate::Config;
|
||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
// If screen size falls below these values we skip computation of new
|
||||||
|
// positions.
|
||||||
|
const MIN_WIDTH: u16 = DIGIT_WIDTH * 6 + 13;
|
||||||
|
const MIN_HEIGHT: u16 = DIGIT_HEIGHT + 2;
|
||||||
|
|
||||||
|
pub struct Position {
|
||||||
|
pub line: u16,
|
||||||
|
pub col: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Layout {
|
||||||
|
pub force_redraw: bool, // Redraw elements on screen.
|
||||||
|
pub force_recalc: bool, // Recalculate position of elements.
|
||||||
|
pub plain: bool, // Plain style clock.
|
||||||
|
pub width: u16,
|
||||||
|
pub height: u16,
|
||||||
|
pub clock_sec: Position,
|
||||||
|
pub clock_colon0: Position,
|
||||||
|
pub clock_min: Position,
|
||||||
|
pub clock_colon1: Position,
|
||||||
|
pub clock_hr: Position,
|
||||||
|
pub clock_days: Position,
|
||||||
|
pub roster: Position,
|
||||||
|
pub roster_width: u16,
|
||||||
|
pub roster_height: u16,
|
||||||
|
pub buffer: Position,
|
||||||
|
pub error: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout {
|
||||||
|
pub fn new(config: &Config) -> Layout {
|
||||||
|
Layout {
|
||||||
|
force_redraw: true,
|
||||||
|
force_recalc: false,
|
||||||
|
plain: config.plain,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
clock_sec: Position {col: 0, line: 0},
|
||||||
|
clock_colon0: Position {col: 0, line: 0},
|
||||||
|
clock_min: Position {col: 0, line: 0},
|
||||||
|
clock_colon1: Position {col: 0, line: 0},
|
||||||
|
clock_hr: Position {col: 0, line: 0},
|
||||||
|
clock_days: Position {col: 0, line: 0},
|
||||||
|
roster: Position {col: 1, line: 3},
|
||||||
|
roster_width: 0,
|
||||||
|
roster_height: 0,
|
||||||
|
buffer: Position {col: 0, line: 0},
|
||||||
|
error: Position {col: 0, line: 0},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, display_hours: bool) {
|
||||||
|
let (width, height) = termion::terminal_size()
|
||||||
|
.expect("Could not read terminal size!");
|
||||||
|
|
||||||
|
if self.force_recalc || self.width != width || self.height != height {
|
||||||
|
self.width = width;
|
||||||
|
self.height = height;
|
||||||
|
self.compute(display_hours);
|
||||||
|
self.force_redraw = true;
|
||||||
|
self.force_recalc = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the position of various elements based on the size of the
|
||||||
|
// terminal.
|
||||||
|
fn compute(&mut self, display_hours: bool) {
|
||||||
|
let middle: u16 = self.height / 2 - 1;
|
||||||
|
|
||||||
|
// Prevent integer overflow on very low screen sizes.
|
||||||
|
if self.width < MIN_WIDTH || self.height < MIN_HEIGHT { return; }
|
||||||
|
|
||||||
|
if display_hours {
|
||||||
|
// Seconds digits.
|
||||||
|
self.clock_sec.col = (self.width + self.roster_width) / 2 + DIGIT_WIDTH + 6;
|
||||||
|
// Colon separating minutes from seconds.
|
||||||
|
self.clock_colon0.col = (self.width + self.roster_width) / 2 + DIGIT_WIDTH + 3;
|
||||||
|
// Minute digits.
|
||||||
|
self.clock_min.col = (self.width + self.roster_width) / 2 - DIGIT_WIDTH;
|
||||||
|
|
||||||
|
// Colon separating hours from minutes.
|
||||||
|
self.clock_colon1 = Position {
|
||||||
|
col: (self.width + self.roster_width) / 2 - (DIGIT_WIDTH + 3),
|
||||||
|
line: middle,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hour digits.
|
||||||
|
self.clock_hr = Position {
|
||||||
|
col: (self.width + self.roster_width) / 2 - (DIGIT_WIDTH * 3 + 6),
|
||||||
|
line: middle,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Seconds digits.
|
||||||
|
self.clock_sec.col = (self.width + self.roster_width) / 2 + 3;
|
||||||
|
// Colon separating minutes from seconds.
|
||||||
|
self.clock_colon0.col = (self.width + self.roster_width) / 2;
|
||||||
|
// Minute digits.
|
||||||
|
self.clock_min.col = (self.width + self.roster_width) / 2 - (DIGIT_WIDTH * 2 + 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.clock_sec.line = middle;
|
||||||
|
self.clock_colon0.line = middle;
|
||||||
|
self.clock_min.line = middle;
|
||||||
|
|
||||||
|
// Days (based on position of seconds).
|
||||||
|
self.clock_days = Position {
|
||||||
|
line: self.clock_sec.line + DIGIT_HEIGHT + 1,
|
||||||
|
col: self.clock_sec.col,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Alarm roster height.
|
||||||
|
self.roster_height = self.height - self.roster.line - 1;
|
||||||
|
|
||||||
|
// Input buffer.
|
||||||
|
self.buffer = Position {
|
||||||
|
line: self.height,
|
||||||
|
col: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error messages.
|
||||||
|
self.error = Position {
|
||||||
|
line: self.height,
|
||||||
|
col: 12,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
333
src/main.rs
Normal file
333
src/main.rs
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
extern crate termion;
|
||||||
|
extern crate libc;
|
||||||
|
mod alarm;
|
||||||
|
mod clock;
|
||||||
|
mod common;
|
||||||
|
mod layout;
|
||||||
|
|
||||||
|
use std::{time, thread, env};
|
||||||
|
use std::io::Write;
|
||||||
|
use termion::{clear, color, cursor, style};
|
||||||
|
use termion::raw::{IntoRawMode, RawTerminal};
|
||||||
|
use termion::event::Key;
|
||||||
|
use termion::input::TermRead;
|
||||||
|
use clock::Clock;
|
||||||
|
use alarm::{Countdown, AlarmRoster, alarm_exec};
|
||||||
|
use layout::{Layout, Position};
|
||||||
|
|
||||||
|
|
||||||
|
const NAME: &str = "kt (kitchentime)";
|
||||||
|
const VERSION: &str = "0.0.1";
|
||||||
|
const USAGE: &str =
|
||||||
|
"USAGE: kt [-h|--help] [-v|--version] [-p|--plain] [-e|--exec COMMAND [...]]
|
||||||
|
-p, --plain Use simpler block chars.
|
||||||
|
-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.";
|
||||||
|
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";
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
plain: bool,
|
||||||
|
alarm_exec: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut config = Config {
|
||||||
|
plain: false,
|
||||||
|
alarm_exec: Vec::new(),
|
||||||
|
};
|
||||||
|
parse_args(&mut config);
|
||||||
|
|
||||||
|
let mut stdout = std::io::stdout().into_raw_mode()
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
eprintln!("Error opening stdout: {}", error);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
let mut input_keys = termion::async_stdin().keys();
|
||||||
|
let mut layout = Layout::new(&config);
|
||||||
|
let mut clock = Clock::new();
|
||||||
|
let mut alarm_roster = AlarmRoster::new();
|
||||||
|
let mut buffer = String::new();
|
||||||
|
let mut buffer_updated: bool = false;
|
||||||
|
let mut countdown = Countdown::new();
|
||||||
|
|
||||||
|
// Clear screen and hide cursor.
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}",
|
||||||
|
clear::All,
|
||||||
|
cursor::Hide)
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
eprintln!("Error writing to stdout: {}", error);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Process input.
|
||||||
|
if let Some(key) = input_keys.next() {
|
||||||
|
match key.expect("Error reading input") {
|
||||||
|
// Reset clock on 'r'.
|
||||||
|
Key::Char('r') => {
|
||||||
|
clock.reset();
|
||||||
|
alarm_roster.reset_all();
|
||||||
|
layout.force_redraw = true;
|
||||||
|
},
|
||||||
|
// (Un-)Pause on space.
|
||||||
|
Key::Char(' ') => {
|
||||||
|
clock.toggle();
|
||||||
|
},
|
||||||
|
// Clear clock color on 'c'.
|
||||||
|
Key::Char('c') => {
|
||||||
|
clock.color_index = None;
|
||||||
|
layout.force_redraw = true;
|
||||||
|
},
|
||||||
|
// Delete last alarm on 'd'.
|
||||||
|
Key::Char('d') => {
|
||||||
|
if alarm_roster.pop().is_some() {
|
||||||
|
// If we remove the last alarm we have to reset "countdown"
|
||||||
|
// manually. It is safe to do it anyway.
|
||||||
|
countdown.reset();
|
||||||
|
layout.force_redraw = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Exit on q and ^C.
|
||||||
|
Key::Char('q') | Key::Ctrl('c') => break,
|
||||||
|
// Force redraw on ^R.
|
||||||
|
Key::Ctrl('r') => layout.force_redraw = true,
|
||||||
|
// Suspend an ^Z.
|
||||||
|
Key::Ctrl('z') => {
|
||||||
|
suspend(&mut stdout);
|
||||||
|
layout.force_redraw = true;
|
||||||
|
},
|
||||||
|
// Enter.
|
||||||
|
Key::Char('\n') => {
|
||||||
|
if buffer.len() > 0 {
|
||||||
|
if let Err(e) = alarm_roster.add(&buffer) {
|
||||||
|
// Error while processing input buffer.
|
||||||
|
error_msg(&mut stdout, &layout, e);
|
||||||
|
} else {
|
||||||
|
// Input buffer processed without error.
|
||||||
|
layout.force_redraw = true;
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Escape ^W, and ^U clear input buffer.
|
||||||
|
Key::Esc | Key::Ctrl('w') | Key::Ctrl('u') => {
|
||||||
|
buffer.clear();
|
||||||
|
buffer_updated = true;
|
||||||
|
},
|
||||||
|
// Backspace.
|
||||||
|
Key::Backspace => {
|
||||||
|
// Delete last char in buffer. It makes no difference to us
|
||||||
|
// if this succeeds of fails.
|
||||||
|
let _ = buffer.pop();
|
||||||
|
buffer_updated = true;
|
||||||
|
},
|
||||||
|
Key::Char(c) => {
|
||||||
|
if c.is_ascii_digit() {
|
||||||
|
buffer.push(c);
|
||||||
|
buffer_updated = true;
|
||||||
|
} else if buffer.len() > 0 && c == ':' {
|
||||||
|
buffer.push(':');
|
||||||
|
buffer_updated = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update input buffer display.
|
||||||
|
if buffer_updated {
|
||||||
|
draw_buffer(&mut stdout, &layout, &buffer);
|
||||||
|
buffer_updated = false;
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsed = if clock.paused {
|
||||||
|
clock.elapsed
|
||||||
|
} else {
|
||||||
|
// Should never owerflow as we reestablish a new "start"
|
||||||
|
// instant every 24 hours.
|
||||||
|
clock.start.elapsed().as_secs() as u32
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update screen 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 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force recalculation of layout if we start displaying hours.
|
||||||
|
if clock.elapsed == 3600 { layout.force_recalc = true };
|
||||||
|
// Update screen size information and calculate the clock position.
|
||||||
|
layout.update(clock.elapsed >= 3600);
|
||||||
|
|
||||||
|
// Check for exceeded alarms.
|
||||||
|
if alarm_roster.check(&mut clock, &layout, &mut countdown) {
|
||||||
|
// Write ASCII bell code.
|
||||||
|
write!(stdout, "{}", 0x07 as char).unwrap();
|
||||||
|
layout.force_redraw = true;
|
||||||
|
|
||||||
|
if config.alarm_exec.len() > 0 {
|
||||||
|
alarm_exec(&config, clock.elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the screen and redraw menu bar, alarm roster and buffer if
|
||||||
|
// requested.
|
||||||
|
if layout.force_redraw {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}{}{}",
|
||||||
|
clear::All,
|
||||||
|
cursor::Goto(1, 1),
|
||||||
|
style::Faint,
|
||||||
|
// Use a compressed version of the menu bar if necessary.
|
||||||
|
if layout.width >= MENUBAR.len() as u16 {
|
||||||
|
MENUBAR
|
||||||
|
} else if layout.width >= MENUBAR_SHORT.len() as u16 {
|
||||||
|
MENUBAR_SHORT
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
style::Reset,)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Redraw list of alarms.
|
||||||
|
alarm_roster.draw(&mut stdout, &mut layout);
|
||||||
|
|
||||||
|
// Redraw buffer.
|
||||||
|
draw_buffer(&mut stdout, &layout, &buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
clock.draw(&mut stdout, &layout);
|
||||||
|
|
||||||
|
// Display countdown.
|
||||||
|
if countdown.value > 0 {
|
||||||
|
countdown.draw(&mut stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset redraw_all and flush stdout.
|
||||||
|
layout.force_redraw = false;
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main loop delay.
|
||||||
|
thread::sleep(time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main loop exited. Clear screen and restore cursor.
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}",
|
||||||
|
clear::BeforeCursor,
|
||||||
|
cursor::Goto(1, 1),
|
||||||
|
cursor::Show)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage() {
|
||||||
|
println!("{}\n{}", NAME, USAGE);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command line arguments into "config".
|
||||||
|
fn parse_args(config: &mut Config) {
|
||||||
|
for arg in env::args().skip(1) {
|
||||||
|
match arg.as_str() {
|
||||||
|
"-h" | "--help" => usage(),
|
||||||
|
"-v" | "--version" => {
|
||||||
|
println!("{} {}", NAME, VERSION);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
"-p" | "--plain" => { config.plain = true; },
|
||||||
|
"-e" | "--exec" => {
|
||||||
|
// Find position of this flag.
|
||||||
|
let i = env::args().position(|s| { s == "-e" || s == "--exec" }).unwrap();
|
||||||
|
// Copy everything thereafter.
|
||||||
|
config.alarm_exec = env::args().skip(i + 1).collect();
|
||||||
|
if config.alarm_exec.len() == 0 {
|
||||||
|
usage();
|
||||||
|
} else {
|
||||||
|
// Ignore everything after this flag.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => usage(), // Unrecognized flag.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend execution by raising SIGTSTP.
|
||||||
|
fn suspend<W: Write>(stdout: &mut RawTerminal<W>) {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}",
|
||||||
|
cursor::Goto(1,1),
|
||||||
|
clear::All,
|
||||||
|
cursor::Show)
|
||||||
|
.unwrap();
|
||||||
|
stdout.flush().unwrap();
|
||||||
|
stdout.suspend_raw_mode()
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.activate_raw_mode()
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
eprintln!("Failed to re-enter raw terminal mode after suspend: {}", error);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}",
|
||||||
|
clear::All,
|
||||||
|
cursor::Hide)
|
||||||
|
.unwrap_or_else(|error| {
|
||||||
|
eprintln!("Error writing to stdout: {}", error);
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw input buffer.
|
||||||
|
fn draw_buffer<W: Write>(stdout: &mut RawTerminal<W>, layout: &Layout, buffer: &String) {
|
||||||
|
if buffer.len() > 0 {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}Add alarm: {}",
|
||||||
|
cursor::Goto(layout.buffer.col, layout.buffer.line),
|
||||||
|
clear::CurrentLine,
|
||||||
|
buffer)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
// Clear buffer display.
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}",
|
||||||
|
cursor::Goto(layout.buffer.col, layout.buffer.line),
|
||||||
|
clear::CurrentLine)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print error message.
|
||||||
|
fn error_msg<W: Write>(stdout: &mut RawTerminal<W>, layout: &Layout, msg: &'static str) {
|
||||||
|
write!(stdout,
|
||||||
|
"{}{}{}{}",
|
||||||
|
cursor::Goto(layout.error.col, layout.error.line),
|
||||||
|
color::Fg(color::LightRed),
|
||||||
|
msg,
|
||||||
|
color::Fg(color::Reset))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue