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 }