/* * Countdown timer * * Copyright (C) 2014 Tomasz Kramkowski * * This program is free software. It is licensed under version 3 of the * GNU General Public License. * * You should have received a copy of the GNU General Public License * along with this program. If not, see [http://www.gnu.org/licenses/]. */ #include #include #include #include #include #include #include #include #include #include #include #define SEC_SECOND 1 #define SEC_MINUTE 60 #define SEC_HOUR 3600 #define SEC_DAY 86400 #define SEC_MONTH 2629746 #define SEC_YEAR 31556940 static const unsigned long interval_nsec = 1000000000 / 4; unsigned long int get_seconds(char *code) { int length = strlen(code), multiplier = 0; char suffix = code[length - 1], value[length + 1]; unsigned long retval; if (length < 2) { return 0; } switch (suffix) { case 's': multiplier = SEC_SECOND; break; // 1 second case 'm': multiplier = SEC_MINUTE; break; // 1 minute case 'h': multiplier = SEC_HOUR; break; // 1 hour case 'D': multiplier = SEC_DAY; break; // 1 day case 'M': multiplier = SEC_MONTH; break; // 30.4368 days case 'Y': multiplier = SEC_YEAR; break; // 365.242 days default : return 0; } strncpy(value, code, length + 1); value[length - 1] = '\0'; errno = 0; retval = strtoul(value, NULL, 10) * multiplier; if (retval == ULONG_MAX && errno != 0) error(1, errno, "Error converting time specifier %s", code); return retval; } void clear_line(void) { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) error(1, errno, "IOCTL failed"); if (ws.ws_col <= 1) return; for (unsigned short i = 0; i < ws.ws_col - 1; i++) putchar(' '); putchar('\r'); if (fflush(stdout) != 0) error(1, errno, "Failed to flush stdout"); } void print_time(unsigned long total_sec, bool show_colon) { unsigned long years, months, days, hours, minutes, seconds; char colon = show_colon ? ':' : ' '; years = total_sec / SEC_YEAR; total_sec %= SEC_YEAR; months = total_sec / SEC_MONTH; total_sec %= SEC_MONTH; days = total_sec / SEC_DAY; total_sec %= SEC_DAY; hours = total_sec / SEC_HOUR; total_sec %= SEC_HOUR; minutes = total_sec / SEC_MINUTE; total_sec %= SEC_MINUTE; seconds = total_sec / SEC_SECOND; total_sec %= SEC_SECOND; if (total_sec != 0) error(1, 0, "An error occured during time formatting"); printf(" %luY %luM %luD %.2lu%c%.2lu%c%.2lu\r", years, months, days, hours, colon, minutes, colon, seconds); if (fflush(stdout) != 0) error(1, errno, "Failed to flush stdout"); } void usage(char *cmd) { printf("Usage:\n\t%s {s,m,h,D,M,Y} ...\n", cmd); } int main(int argc, char **argv) { bool blink = false; int sig; sigset_t sigset; struct itimerspec its; struct sigevent sev; timer_t timerid; unsigned long total_seconds = 0; if (argc < 2) { printf("Not enough arguments.\n"); usage(argv[0]); exit(1); } for (int i = 1; i < argc; i++) total_seconds += get_seconds(argv[i]); sev.sigev_notify = SIGEV_SIGNAL; sev.sigev_signo = SIGRTMIN; sev.sigev_value.sival_ptr = &timerid; if (timer_create(CLOCK_MONOTONIC, &sev, &timerid) != 0) error(1, errno, "Could not create timer"); its = (struct itimerspec){ {0, interval_nsec}, {0, interval_nsec} }; if (sigemptyset(&sigset) != 0) error(1, errno, "Could not empty signal set"); if (sigaddset(&sigset, SIGRTMIN) != 0) error(1, errno, "Could not add SIGRTMIN to signal set"); if (sigprocmask(SIG_BLOCK, &sigset, NULL) != 0) error(1, errno, "Could not block SIGRTMIN"); if (timer_settime(timerid, 0, &its, NULL) != 0) error(1, errno, "Could not set timer"); for (unsigned long i = 0; i < total_seconds; i++) for (int ii = 0; ii < 4; ii++) { if (sigwait(&sigset, &sig), sig != SIGRTMIN) error(1, 0, "sigwait returned unexpected signal %d", sig); if (sigaddset(&sigset, SIGRTMIN) != 0) error(1, errno, "Could not add signal to set"); clear_line(); print_time(total_seconds - i, ii < 2); } if (sigaddset(&sigset, SIGINT) != 0) error(1, errno, "Could not add SIGINT to signal set"); while (true) { blink = !blink; sigwait(&sigset, &sig); if (sig != SIGRTMIN) break; if (sigaddset(&sigset, SIGRTMIN) != 0) error(1, errno, "Could not add signal to set"); clear_line(); if (blink) printf(" -- BEEP -- \a\r"); if (fflush(stdout) != 0) error(1, errno, "Failed to flush stdout"); } timer_delete(timerid); clear_line(); return 0; }