pomodoro

A simple pomodoro timer
git clone git://nocebo.space/pomodoro
Log | Files | Refs | LICENSE

pomodoro.c (5352B)


      1 #include <err.h>
      2 #include <errno.h>
      3 #include <limits.h>
      4 #include <poll.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 #include <time.h>
      9 #include <unistd.h>
     10 #include <X11/Xlib.h>
     11 #include <X11/Xatom.h>
     12 #include <X11/Xutil.h>
     13 
     14 /*
     15  * Pomodoro timer:
     16  * Options include a one-off mode in addition to the interactive mode
     17  * One-off mode requires two arguments:
     18  * * pomodoro_period
     19  * * break_period
     20  * Usage: pomodoro b b_period p p_period
     21  *
     22  * Interactive mode provides a defalt pomodoro period of 25 mins &
     23  * break period of 5 mins.
     24  * Every 3 pomodoros get a 30-min break.
     25  *
     26  * Completed pomodoros are logged in ~/.pomodoro
     27  */
     28 
     29 enum {FALSE, TRUE};
     30 
     31 extern char *optarg;
     32 
     33 void usage(void);
     34 void take_break(unsigned int);
     35 Bool evpredicate(Display *, XEvent *, XPointer);
     36 void record(FILE *);
     37 
     38 int
     39 main(int argc, char **argv)
     40 {
     41 	unsigned int break_period;
     42 	int ch;
     43 	unsigned int interactive_mode;
     44 	unsigned int pomodoro_period;
     45 	FILE *fp;
     46 	const char *str, *home;
     47 	char logfile[PATH_MAX] = "";
     48 
     49 	break_period = 15;
     50 	interactive_mode = TRUE;
     51 	pomodoro_period = 25;
     52 
     53 	str = "b:l:p:v";
     54 
     55 	while ((ch = getopt(argc, argv, str)) != -1) {
     56 		switch (ch) {
     57 		case 'b':
     58 			interactive_mode = FALSE;
     59 			break_period = atoi(optarg);
     60 			break;
     61 		case 'l':
     62 			snprintf(logfile, sizeof(logfile), "%s", optarg);
     63 			if (logfile[0] == '\0') {
     64 				usage();
     65 				exit(1);
     66 			}
     67 			break;
     68 		case 'p':
     69 			interactive_mode = FALSE;
     70 			pomodoro_period = atoi(optarg);
     71 			break;
     72 		case 'v':
     73 			printf("pomodoro-%s\n", VERSION);
     74 			exit(0);
     75 		default:
     76 			usage();
     77 			exit(1);
     78 		}
     79 	}
     80 
     81 	if (logfile[0] == '\0') {
     82 		if (!(home = getenv("HOME")))
     83 			errx(1, "$HOME not set, cannot determine logfile location");
     84 		snprintf(logfile, sizeof(logfile), "%s/.pomodoro", home);
     85 	}
     86 
     87 	if (!(fp = fopen(logfile, "a")))
     88 		err(1, "fopen");
     89 
     90 	if (interactive_mode) {
     91 		int c;
     92 		int p_counter; /* counts the number of pomodoros */
     93 
     94 		p_counter = 0;
     95 		while (1) {
     96 			fpurge(stdin);
     97 			printf("Start Pomodoro? [y/n] ");
     98 			c = getchar();
     99 
    100 			if ((c == 'y') || (c == 'Y')) {
    101 				/* defacto pomodoro period: 25 mins */
    102 				/* defacto rest period: 5 mins */
    103 				sleep(25 * 60);
    104 				p_counter++;
    105 
    106 				if (p_counter > 3) {
    107 					p_counter = 0;
    108 					take_break(30);
    109 					record(fp);
    110 				} else {
    111 					take_break(5);
    112 					record(fp);
    113 				}
    114 
    115 			} else {
    116 				printf("Thank you\n");
    117 				exit(0);
    118 			}
    119 		}
    120 	} else {
    121 		sleep(pomodoro_period * 60);
    122 		take_break(break_period);
    123 		record(fp);
    124 	}
    125 
    126 	exit(0);
    127 }
    128 
    129 void
    130 usage(void)
    131 {
    132 	fprintf(stderr,
    133 	        "usage: pomodoro [-b break_period] [-l logfile] [-p pomodoro_period]\n"
    134 	        "-b\tspecify break period\n"
    135 	        "-l\tlogfile (default: ~/.pomodoro)\n"
    136 	        "-p\tspecify pomodoro period\n"
    137 	        "-v\tshow version\n"
    138 	       );
    139 }
    140 
    141 void
    142 take_break(unsigned int bp)
    143 {
    144 	Display *d;
    145 	int dfd;
    146 	struct pollfd pfd[1];
    147 	time_t curtime, prevtime;
    148 	if (!(d = XOpenDisplay(NULL))) {
    149 		err(1, "XOpenDisplay failed\n");
    150 		return;
    151 	}
    152 	const char *n = "pomodoro";
    153 	char m[64];
    154 	snprintf(m, sizeof(m), "Take a %d min break!", bp);
    155 	int s = DefaultScreen(d), ww = 128, wh = 64;
    156 	Window w = XCreateSimpleWindow(d, RootWindow(d, s),
    157 	                               DisplayWidth(d, s) / 2,
    158 				       DisplayHeight(d, s) / 2, ww, wh, 1,
    159 	                               BlackPixel(d, s), 0x202020L);
    160 	Atom ad = XInternAtom(d, "WM_DELETE_WINDOW", False),
    161 	an = XInternAtom(d, "_NET_WM_NAME", False),
    162 	ai = XInternAtom(d, "_NET_WM_ICON_NAME", False),
    163 	au = XInternAtom(d, "UTF8_STRING", False);
    164 	XGCValues gcv;
    165 	GC gc = XCreateGC(d, w, 0, &gcv);
    166 	XFontStruct *f = XLoadQueryFont(d, "-*-terminus-medium-*");
    167 	XEvent e;
    168 	XChangeProperty(d, w, an, au, 8, 0, (unsigned char *)n, strlen(n));
    169 	XChangeProperty(d, w, ai, au, 8, 0, (unsigned char *)n, strlen(n));
    170 	XSetTransientForHint(d, w, RootWindow(d, s));
    171 	XSetWMProtocols(d , w, &ad, 1);
    172 	XSelectInput(d, w, ExposureMask);
    173 	if (f)
    174 		XSetFont(d, gc, f->fid);
    175 	XSetForeground(d, gc, 0xc0c0c0L);
    176 	XMapWindow(d, w);
    177 	prevtime = time(NULL);
    178 	dfd = ConnectionNumber(d);
    179 
    180 	while (1) {
    181 		pfd[0].fd = dfd;
    182 		pfd[0].events = POLLOUT;
    183 		switch (poll(pfd, 1, 1)) {
    184 		case -1:
    185 			if (errno != EINTR)
    186 				err(1, "poll");
    187 			break;
    188 		case 0:
    189 			fprintf(stderr, "case 0\n");
    190 			break;
    191 		default:
    192 			curtime = time(NULL);
    193 			if (difftime(curtime,prevtime) >= bp * 60) {
    194 				XFreeGC(d, gc);
    195 				XDestroyWindow(d, w);
    196 				XCloseDisplay(d);
    197 				return;
    198 			}
    199 			if ((pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)))
    200 				errx(1, "bad fd: %d", pfd[0].fd);
    201 			while (XCheckIfEvent(d, &e, evpredicate, NULL)) {
    202 				switch (e.type) {
    203 				case Expose:
    204 					XDrawString(d, w, gc, (ww - XTextWidth(f, m,
    205 					            strlen(m))) / 2, (wh + f->ascent +
    206 					            f->descent) / 2, m, strlen(m));
    207 					break;
    208 				case ClientMessage:
    209 					XFreeGC(d, gc);
    210 					XDestroyWindow(d, w);
    211 					XCloseDisplay(d);
    212 					return;
    213 				}
    214 				break;
    215 			}
    216 		}
    217 	}
    218 }
    219 
    220 Bool
    221 evpredicate(Display *dpy, XEvent *ev, XPointer p)
    222 {
    223 	(void)dpy;
    224 	(void)ev;
    225 	(void)p;
    226 
    227 	return True;
    228 }
    229 
    230 void
    231 record(FILE *f)
    232 {
    233 	char timestamp[64];
    234 	struct tm *tm;
    235 	time_t now;
    236 
    237 	if ((now = time(NULL)) == (time_t)-1)
    238 		err(1, "time");
    239 	if (!(tm = localtime(&now)))
    240 		err(1, "localtime");
    241 	if (!strftime(timestamp, sizeof(timestamp), "%a %b %e %H:%M:%S %Z %Y",
    242 	              tm))
    243 		err(1, "strftime");
    244 
    245 	fprintf(stderr, "%s\n", timestamp);
    246 	fprintf(f, "%s\n", timestamp);
    247 }