/* See LICENSE file for copyright and license details. */ #include "common.h" /* TODO add support for in application entering of time */ /* TODO add support for rat input */ /* TODO add support for audible alert */ /* TODO add support for vibration alert */ /* TODO add support for flash/led/screen alert */ /* TODO add support for messages */ /* TODO add support for multiple timers */ /* TODO add support for CLOCK_BOOTTIME_ALARM */ /* TODO add support for ptuting timer in the background */ USAGE("[-2s] [[hours:]minutes:]second[.deciseconds]"); static volatile sig_atomic_t caught_sigterm = 0; static volatile sig_atomic_t caught_sigwinch = 1; static volatile sig_atomic_t caught_sigio = 1; static struct itimerspec orig_time; static int64_t initial = 0; static int with_small = 0; static int with_quad = 0; static const char **large_digits[] = { large_0, large_1, large_2, large_3, large_4, large_5, large_6, large_7, large_8, large_9 }; static const char **small_digits[] = { small_0, small_1, small_2, small_3, small_4, small_5, small_6, small_7, small_8, small_9 }; static void sigterm(int signo) { caught_sigterm = 1; (void) signo; } static void sigwinch(int signo) { caught_sigwinch = 1; (void) signo; } static void sigio(int signo) { caught_sigio = 1; (void) signo; } static void print_time(const char ***str, int left, size_t height) { size_t r, c; for (r = 0;;) { printf("%*.s", left, ""); for (c = 0; str[c]; c++) fprintf(stdout, "%s", str[c][r]); if (++r == height) { fprintf(stdout, "\033[K"); break; } else { fprintf(stdout, "\033[K\n"); } } } static int display_timer(int timerfd) { const char **digits[sizeof(int64_t) * 3 + sizeof("-:00:00.0")]; char buffer[sizeof(int64_t) * 3 + sizeof("-:00:00.0")]; int r, x = 0, y = 0, was_quad = 0, paused = 0, large_width, small_width, len, neg; int64_t overrun, now = initial, h, m, s, ds; struct itimerspec zero_time, old_time; struct winsize winsize; char c; ssize_t rd; size_t i; memset(&zero_time, 0, sizeof(zero_time)); while (!caught_sigterm) { if (caught_sigwinch) { if (ioctl(STDOUT_FILENO, (unsigned long)TIOCGWINSZ, &winsize) < 0) { if (errno == EINTR) continue; goto fail; } caught_sigwinch = 0; y = winsize.ws_row; x = winsize.ws_col; if (was_quad) { was_quad = 0; for (r = 0; r < y; r++) printf("%s\033#5", r ? "\n" : "\033[H"); } } if (caught_sigio) { caught_sigio = 0; for (;;) { rd = read(STDIN_FILENO, &c, 1); if (rd <= 0) { if (!rd) goto out; if (errno == EAGAIN) break; if (errno == EINTR) continue; goto fail; } if (c == 'q') { goto out; } else if (c == 'r') { now = initial; if (paused) memcpy(&old_time, &orig_time, sizeof(orig_time)); else if (timerfd_settime(timerfd, 0, &orig_time, NULL)) goto fail; } else if (c == ' ' || c == 'p') { paused ^= 1; if (paused) { if (timerfd_settime(timerfd, 0, &zero_time, &old_time)) goto fail; } else { if (timerfd_settime(timerfd, 0, &old_time, NULL)) goto fail; } } } } neg = now < 0; ds = neg ? -now : now; s = ds / 10; ds %= 10; m = s / 60; s %= 60; h = m / 60; m %= 60; if (h) len = sprintf(buffer, "%s%02ji:%02ji:%02ji.%ji", neg ? "-" : "", h, m, s, ds); else len = sprintf(buffer, "%s%02ji:%02ji.%ji", neg ? "-" : "", m, s, ds); large_width = neg * LARGE_XM + (1 + (h > 0)) * LARGE_XC + LARGE_XD + (len - neg - 2 - (h > 0)) * LARGE_X; small_width = neg * SMALL_XM + (1 + (h > 0)) * SMALL_XC + SMALL_XD + (len - neg - 2 - (h > 0)) * SMALL_X; printf("\033[H"); if (paused) printf("\033[33m"); else if (neg) printf("\033[31m"); if (x >= large_width && y >= LARGE_Y + paused) { for (i = 0; buffer[i]; i++) { if (buffer[i] == '-') digits[i] = large_minus; else if (buffer[i] == ':') digits[i] = large_colon; else if (buffer[i] == '.') digits[i] = large_dot; else digits[i] = large_digits[buffer[i] & 15]; } digits[i] = NULL; print_time(digits, (x - large_width) / 2, LARGE_Y); } else if (with_small && x >= small_width && y >= SMALL_Y + paused) { for (i = 0; buffer[i]; i++) { if (buffer[i] == '-') digits[i] = small_minus; else if (buffer[i] == ':') digits[i] = small_colon; else if (buffer[i] == '.') digits[i] = small_dot; else digits[i] = small_digits[buffer[i] & 15]; } digits[i] = NULL; print_time(digits, (x - small_width) / 2, SMALL_Y); } else if (with_quad && x > 8 * 2 && y > 2) { printf("\033#3%*.s%s\033[K\n", (x / 2 - len) / 2, "", buffer); printf("\033#4%*.s%s\033[K\n", (x / 2 - len) / 2, "", buffer); printf("\033#5"); if (paused) printf("%*.s(paused)\033[K", (x - 8) / 2, ""); was_quad = 1; goto paused_printed; } else { printf("%*.s%s\033[K", (x - len) / 2, "", buffer); } if (paused) printf("\n%*.s(paused)\033[K", (x - 8) / 2, ""); paused_printed: if (paused || neg) printf("\033[m"); printf("\033[J"); fflush(stdout); if (read(timerfd, &overrun, sizeof(overrun)) < 0) { if (errno != EINTR) goto fail; } else { now -= overrun; } } out: return 0; fail: return -1; } int main(int argc, char *argv[]) { int timerfd = -1, tcset = 0, old_flags = -1, old_sig = 0, owner_set = 0; int saved_errno, colons = 0, dots = 0; int64_t part = 0; struct sigaction sigact; struct termios stty, saved_stty; struct f_owner_ex old_owner, new_owner; char *p; ARGBEGIN { case '2': with_quad = 1; break; case 's': with_small = 1; break; default: usage(); } ARGEND; if (argc != 1) usage(); if (!isdigit(argv[0][0])) usage(); for (p = argv[0]; *p; p++) { if (isdigit(*p)) { part *= 10; part += (int64_t)(*p & 15); } else if (*p == ':') { if (dots || ++colons > 2 || !isdigit(p[1])) usage(); initial += part; initial *= 60; part = 0; } else if (*p == '.') { if (dots++ || !isdigit(p[1]) || p[2]) usage(); initial += part; initial *= 10; part = 0; } else { usage(); } } initial += part; if (!dots) initial *= 10; fprintf(stdout, "\033[?1049h\033[?25l"); orig_time.it_interval.tv_sec = 0; orig_time.it_interval.tv_nsec = 100000000L; orig_time.it_value.tv_sec = 0; orig_time.it_value.tv_nsec = 100000000L; timerfd = timerfd_create(CLOCK_BOOTTIME, 0); if (timerfd < 0) goto fail; if (timerfd_settime(timerfd, 0, &orig_time, NULL)) goto fail; memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = sigterm; sigaction(SIGTERM, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); sigact.sa_handler = sigwinch; sigaction(SIGWINCH, &sigact, NULL); sigact.sa_handler = sigio; sigaction(SIGIO, &sigact, NULL); sigaction(SIGURG, &sigact, NULL); if (fcntl(STDIN_FILENO, F_GETOWN_EX, &old_owner)) goto fail; memset(&new_owner, 0, sizeof(new_owner)); new_owner.type = F_OWNER_PID; new_owner.pid = getpid(); if (fcntl(STDIN_FILENO, F_SETOWN_EX, &new_owner)) goto fail; owner_set = 1; old_flags = fcntl(STDIN_FILENO, F_GETFL); fcntl(STDIN_FILENO, F_SETFL, old_flags | FASYNC | O_NONBLOCK); fcntl(STDIN_FILENO, F_GETSIG, &old_sig); if (old_sig) fcntl(STDIN_FILENO, F_SETSIG, 0); if (!tcgetattr(STDIN_FILENO, &stty)) { saved_stty = stty; stty.c_lflag &= (tcflag_t)~(ECHO | ICANON); tcsetattr(STDIN_FILENO, TCSAFLUSH, &stty); tcset = 1; } if (display_timer(timerfd)) goto fail; fprintf(stdout, "\033[?25h\n\033[?1049l"); fflush(stdout); close(timerfd); fcntl(STDIN_FILENO, F_SETOWN_EX, &old_owner); fcntl(STDIN_FILENO, F_SETFL, old_flags); fcntl(STDIN_FILENO, F_SETSIG, old_sig); tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty); return 0; fail: saved_errno = errno; fprintf(stdout, "\033[?25h\n\033[?1049l"); fflush(stdout); errno = saved_errno; perror(argv0 ? argv0 : "timer"); if (owner_set) fcntl(STDIN_FILENO, F_SETOWN_EX, &old_owner); if (old_flags != -1) fcntl(STDIN_FILENO, F_SETFL, old_flags); if (old_sig) fcntl(STDIN_FILENO, F_SETSIG, old_sig); if (tcset) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_stty); if (timerfd >= 0) close(timerfd); return 1; }