aboutsummaryrefslogblamecommitdiffstats
path: root/mongotimer.c
blob: a59e9c08300377f620c5d295db8bbefdc3e95a9a (plain) (tree)
1
2
3
4
5
6
7
8
9
10


                                                         
                  





                   
                    





























                                                    
                                              
























                                                                  






                         

























                                                                          



                                              
 
                                                 
























                                                                                            
























                                                                                                       





























                                                                                    








                                                      








                                                                   
    














                                                          



                                              
 
                                                 









                                                                                            
























                                                                                                       









                                                                                     
                              


















                                                                                    
                              












                                                                           





                                                      
                                      
















                                                                   
    











                                           
                                                    
                                          

                                     
                                               

                          
                                               
                                        

































                                                           

















                                                                      





                                                           
 
























                                                                 


                                                     
                                                        






                                                  



                                                             
                    
                                                       
                  
                                                                






                                                  



                                                             
                    
                                                       
                  
                                                                

                               

                
/* See LICENSE file for copyright and license details. */
#include <sys/ioctl.h>
#include <sys/timerfd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>

#include "arg.h"

#define DX 16
#define DY 12
#define DC 16
#define DM 16

#include "mongo_0.h"
#include "mongo_1.h"
#include "mongo_2.h"
#include "mongo_3.h"
#include "mongo_4.h"
#include "mongo_5.h"
#include "mongo_6.h"
#include "mongo_7.h"
#include "mongo_8.h"
#include "mongo_9.h"
#include "mongo_c.h"
#include "mongo_m.h"

static const char **mongo_ds[] = {
	mongo_0, mongo_1, mongo_2, mongo_3, mongo_4,
	mongo_5, mongo_6, mongo_7, mongo_8, mongo_9
};

static volatile sig_atomic_t caught_sigterm = 0;
static volatile sig_atomic_t caught_sigwinch = 1;
static volatile sig_atomic_t caught_sigio = 0;

char *argv0;

static void
usage(void)
{
	fprintf(stderr, "usage: %s [[-e] [[hh]:mm:]ss]\n", argv0);
	exit(1);
}

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, size_t y, size_t x)
{
	size_t r, c;

	fprintf(stdout, "\033[%zu;1H\033[1J", y + 1);

	for (r = 0; r < DY; r++) {
		fprintf(stdout, "\033[%zu;%zuH\033[1K", y + r + 1, x + 1);
		for (c = 0; str[c]; c++)
			fprintf(stdout, "%s", str[c][r]);
		fprintf(stdout, "\033[0K");
	}

	fprintf(stdout, "\033[0J");
	fflush(stdout);
}

static int
display_stopwatch(int timerfd)
{
	uint64_t overrun, total_overrun = 0;
	struct winsize winsize;
	size_t x = 0, y = 0, width, i;
	const char **digits[23];
	uint64_t h, m, s, h_div = 1, th;
	size_t h_digits = 0;
	int small = 0, paused = 0;
	struct itimerspec old_time, zero_time;
	ssize_t r;
	char c;

	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;
		recenter:
			y = winsize.ws_row;
			x = winsize.ws_col;
			width = 4 * DX + DC;
			if (h_digits) {
				width += DC;
				width += h_digits * DX;
			}
			if      (y < DY)    small = 1;
			else if (x < width) small = 1;
			else                small = 0;
			y -= DY;
			x -= width;
			y /= 2;
			x /= 2;
		}
		if (caught_sigio) {
			caught_sigio = 0;
			for (;;) {
				r = read(STDIN_FILENO, &c, 1);
				if (r <= 0) {
					if (!r)
						goto out;
					if (errno == EAGAIN)
						break;
					if (errno == EINTR)
						continue;
					goto fail;
				}
				if (c == ' ' || c == '\n') {
					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;
					}
				}
			}
		}

		if (small) {
			fprintf(stdout, "\033[H\033[2J%s\n", "Screen is too small");
			fflush(stdout);
			pause();
			continue;
		}

		s = total_overrun % 60;
		m = total_overrun / 60 % 60;
		h = total_overrun / 60 / 60;
		if (h / h_div) {
			h_div *= 10;
			h_digits++;
			goto recenter;
		}

		i = 0;
		if (h_digits) {
			for (th = h; th; th /= 10)
				digits[h_digits - ++i] = mongo_ds[th % 10];
			digits[i++] = mongo_c;
		}
		digits[i++] = mongo_ds[m / 10];
		digits[i++] = mongo_ds[m % 10];
		digits[i++] = mongo_c;
		digits[i++] = mongo_ds[s / 10];
		digits[i++] = mongo_ds[s % 10];
		digits[i++] = NULL;

		if (paused) {
			fprintf(stdout, "\033[33m\n");
			print_time(digits, y, x);
			fprintf(stdout, "\033[39m\n");
			pause();
			continue;
		} else {
			print_time(digits, y, x);
		}

		if (read(timerfd, &overrun, sizeof(overrun)) < 0) {
			if (errno != EINTR)
				goto fail;
		} else {
			total_overrun += overrun;
		}
	}

out:
	return 0;

fail:
	return -1;
}

static int
display_timer(int timerfd, int64_t time, int exit_on_zero)
{
	uint64_t overrun, abstime;
	struct winsize winsize;
	size_t x = 0, y = 0, width;
	const char **digits[24];
	uint64_t h, m, s, th, h_div;
	size_t h_digits, i;
	int small = 0, paused = 0;
	struct itimerspec old_time, zero_time;
	ssize_t r;
	char c;

	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;
		}
		if (caught_sigio) {
			caught_sigio = 0;
			for (;;) {
				r = read(STDIN_FILENO, &c, 1);
				if (r <= 0) {
					if (!r)
						goto out;
					if (errno == EAGAIN)
						break;
					if (errno == EINTR)
						continue;
					goto fail;
				}
				if (c == ' ' || c == '\n') {
					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;
					}
				}
			}
		}

		abstime = time < 0 ? (uint64_t)-time : (uint64_t)time;
		s = abstime % 60;
		m = abstime / 60 % 60;
		h = abstime / 60 / 60;

		y = winsize.ws_row;
		x = winsize.ws_col;
		for (h_div = 1, h_digits = 0; h / h_div; h_div *= 10, h_digits += 1);
		width = 4 * DX + DC;
		if (time <= 0)
			width += DM;
		if (h_digits)
			width += h_digits * DX + DC;
		if      (y < DY)    small = 1;
		else if (x < width) small = 1;
		else                small = 0;
		y -= DY;
		x -= width;
		y /= 2;
		x /= 2;

		if (small) {
			fprintf(stdout, "\033[H\033[2J%s\n", "Screen is too small");
			fflush(stdout);
			pause();
			continue;
		}

		i = 0;
		if (time <= 0)
			digits[i++] = mongo_m;
		if (h_digits) {
			for (th = h; th; th /= 10)
				digits[h_digits - ++i] = mongo_ds[th % 10];
			digits[i++] = mongo_c;
		}
		digits[i++] = mongo_ds[m / 10];
		digits[i++] = mongo_ds[m % 10];
		digits[i++] = mongo_c;
		digits[i++] = mongo_ds[s / 10];
		digits[i++] = mongo_ds[s % 10];
		digits[i++] = NULL;

		if (paused) {
			fprintf(stdout, "\033[33m\n");
			print_time(digits, y, x);
			fprintf(stdout, "\033[39m\n");
			pause();
			continue;
		} else if (time > 0) {
			print_time(digits, y, x);
		} else {
			fprintf(stdout, "\033[31m\n");
			print_time(digits, y, x);
			fprintf(stdout, "\033[39m\n");
		}

		if (read(timerfd, &overrun, sizeof(overrun)) < 0) {
			if (errno != EINTR)
				goto fail;
		} else {
			time -= (int64_t)overrun;
			if (time <= 0 && exit_on_zero)
				break;
		}
	}

out:
	if (caught_sigterm && exit_on_zero)
		return -1;

	return 0;

fail:
	return -1;
}

int
main(int argc, char *argv[])
{
	int timerfd = -1, old_flags = -1, tcset = 0;
	int exit_on_zero = 0, old_sig = 0;
	struct itimerspec itimerspec;
	struct sigaction sigact;
	int64_t time = 0, t = 0, owner_set = 0;
	size_t colons = 0;
	char *s;
	struct f_owner_ex old_owner, new_owner;
	struct termios stty, saved_stty;

	ARGBEGIN {
	case 'e':
		exit_on_zero = 1;
		break;
	default:
		usage();
	} ARGEND;

	if (argc > 1 || (exit_on_zero && !argc))
		usage();

	fprintf(stdout, "\033[?1049h\033[?25l");

	itimerspec.it_interval.tv_sec = 1;
	itimerspec.it_interval.tv_nsec = 0;
	itimerspec.it_value.tv_sec = 1;
	itimerspec.it_value.tv_nsec = 0;
	timerfd = timerfd_create(CLOCK_BOOTTIME, 0);
	if (timerfd < 0)
		goto fail;
	if (timerfd_settime(timerfd, 0, &itimerspec, 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 (argc) {
		for (s = argv[0]; *s; s++) {
			if ('0' <= *s && *s <= '9') {
				t = t * 10 + (*s & 15);
			} else if (*s == ':' && colons++ < 2) {
				if (s == argv[0] || s[-1] == ':')
					goto fail_usage;
				time = (time + t) * 60;
				t = 0;
			} else {
				goto fail_usage;
			}
		}
		if (s == argv[0] || s[-1] == ':')
			goto fail_usage;
		time += t;
		if (display_timer(timerfd, time, exit_on_zero))
			goto fail;
	} else {
		if (display_stopwatch(timerfd))
			goto fail;
	}

	fprintf(stdout, "\033[?25h\n\033[?1049l");
	fflush(stdout);
	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);
	close(timerfd);
	return 0;

fail:
	perror(argv0 ? argv0 : "mongotimer");
	fprintf(stdout, "\033[?25h\n\033[?1049l");
	fflush(stdout);
	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;

fail_usage:
	fprintf(stdout, "\033[?25h\n\033[?1049l");
	fflush(stdout);
	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);
	usage();
}