├── Makefile ├── README ├── config.mk ├── pomo.c ├── pomod.c ├── util.c └── util.h /Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | SRCS = pomod.c pomo.c util.c 4 | OBJS = ${SRCS:.c=.o} 5 | 6 | all: pomod pomo 7 | 8 | pomod: pomod.o util.o 9 | ${CC} -o $@ pomod.o util.o ${LDFLAGS} 10 | 11 | pomo: pomo.o util.o 12 | ${CC} -o $@ pomo.o util.o ${LDFLAGS} 13 | 14 | pomo.o: util.h 15 | pomod.o: util.h 16 | 17 | .c.o: 18 | ${CC} ${CFLAGS} -c $< 19 | 20 | install: all 21 | install -D -m 755 pomod ${DESTDIR}${PREFIX}/bin/${PROG} 22 | install -D -m 755 pomo ${DESTDIR}${PREFIX}/bin/${PROG} 23 | 24 | uninstall: 25 | rm -f ${DESTDIR}${PREFIX}/bin/pomod 26 | rm -f ${DESTDIR}${PREFIX}/bin/pomo 27 | 28 | clean: 29 | -rm ${OBJS} pomod pomo 30 | 31 | .PHONY: all clean install uninstall 32 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | POMOD(1) X Notes POMOD(1) 2 | 3 | NAME 4 | pomod - pomodoro daemon 5 | 6 | SYNOPSIS 7 | pomod [-S socket] [-l time] [-p time] [-s time] 8 | pomo [-S socket] [start|stop|info] 9 | 10 | DESCRIPTION 11 | pomod is a timer daemon that implements the pomodoro technique. 12 | The pomod command is the server; it times pomodoro and break 13 | cycles while waiting for commands from the client. The pomo 14 | command is the client; it send commands to the server for 15 | controling the timer. 16 | 17 | When called, the pomod command begins with the timer stopped and 18 | waits for the client to start it. Once started, it begins a 19 | pomodoro. After a pomodoro, a break begins. After each four 20 | pomodoros, the break is a long break, otherwise it is a short 21 | break. By default, a pomodoro lasts 25 minutes; a short break 22 | lasts 5 minutes and a long break lasts 30 minutes. When the 23 | cycle changes (from a pomodoro to a short break, for example), 24 | the name of the new step is printed to stdout. 25 | 26 | The pomo command has three subcommands, which control the server 27 | or get information from it. The following subcommands are known: 28 | 29 | start Begins a new pomodoro. 30 | 31 | stop Stop the timer. 32 | 33 | info Get the current cycle from the server and print the timer 34 | in minutes and seconds. 35 | 36 | The options for both commands are as follows: 37 | 38 | -S socket 39 | The path to the socket. By default the socket is 40 | /tmp/pomodoro.UID 41 | 42 | The options for pomod are as follows: 43 | 44 | -l time 45 | Time in minutes for the long break. 46 | 47 | -p time 48 | Time in minutes for the pomodoro. 49 | 50 | -s time 51 | Time in minutes for the short break. 52 | 53 | FILES 54 | ./pomod.c 55 | Code for the server. 56 | 57 | ./pomo.c 58 | Code for the client. 59 | 60 | ./util.[ch] 61 | Code shared by server and client. 62 | 63 | /tmp/pomodoro.UID 64 | Socket for the user. 65 | 66 | LICENSE 67 | This software is in public domain and is provided AS IS, 68 | with NO WARRANTY. 69 | 70 | SEE ALSO 71 | https://en.wikipedia.org/wiki/Pomodoro_Technique 72 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # paths 2 | PREFIX = /usr/local 3 | MANPREFIX = ${PREFIX}/share/man 4 | 5 | CFLAGS = -g -O0 -Wall -Wextra ${INCS} ${CPPFLAGS} 6 | LDFLAGS = ${LIBS} 7 | CC = cc 8 | -------------------------------------------------------------------------------- /pomo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "util.h" 10 | 11 | static char *sockpath = NULL; 12 | 13 | static void 14 | usage(void) 15 | { 16 | (void)fprintf(stderr, "usage: %s [-S socket] [start|stop|info]\n", getprogname()); 17 | exit(1); 18 | } 19 | 20 | static int 21 | parseargs(int argc, char *argv[]) 22 | { 23 | int ch; 24 | 25 | while ((ch = getopt(argc, argv, "S:")) != -1) { 26 | switch (ch) { 27 | case 'S': 28 | sockpath = optarg; 29 | break; 30 | default: 31 | usage(); 32 | break; 33 | } 34 | } 35 | argc -= optind; 36 | argv += optind; 37 | if (argc != 1) 38 | usage(); 39 | if (sockpath == NULL) 40 | sockpath = getsockpath(); 41 | if (strcasecmp(*argv, "stop") == 0) 42 | return STOP; 43 | else if (strcasecmp(*argv, "start") == 0) 44 | return START; 45 | else if (strcasecmp(*argv, "info") == 0) 46 | return INFO; 47 | usage(); 48 | return 0; /* NOTREACHED */ 49 | } 50 | 51 | static int 52 | connectsocket(const char *path) 53 | { 54 | struct sockaddr_un saddr; 55 | int fd; 56 | 57 | if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 58 | err(1, "socket"); 59 | memset(&saddr, 0, sizeof saddr); 60 | saddr.sun_family = AF_UNIX; 61 | strncpy(saddr.sun_path, path, (sizeof saddr.sun_path) - 1); 62 | if (connect(fd, (struct sockaddr *)&saddr, sizeof saddr) == -1) 63 | err(1, "connect"); 64 | return fd; 65 | } 66 | 67 | static void 68 | sendcommand(char cmd, int fd) 69 | { 70 | if (write(fd, &cmd, 1) == -1) { 71 | err(1, "write"); 72 | } 73 | } 74 | 75 | static void 76 | printinfo(int fd) 77 | { 78 | int n; 79 | char buf[INFOSIZ]; 80 | 81 | if ((n = read(fd, buf, INFOSIZ)) != INFOSIZ) 82 | errx(1, "could not get info"); 83 | printf("%s: %02u:%02u\n", getcyclename(buf[CYCLE]), buf[MIN], buf[SEC]); 84 | } 85 | 86 | int 87 | main(int argc, char *argv[]) 88 | { 89 | int fd; /* socket file descriptor */ 90 | char cmd; 91 | 92 | setprogname(argv[0]); 93 | cmd = parseargs(argc, argv); 94 | fd = connectsocket(sockpath); 95 | sendcommand(cmd, fd); 96 | if (cmd == INFO) 97 | printinfo(fd); 98 | close(fd); 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /pomod.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "util.h" 14 | 15 | #define BACKLOG 5 16 | #define SECONDS 60 /* seconds in a minute */ 17 | #define MINUTES 60 /* minutes in a hour */ 18 | #define MILISECONDS 1000 /* miliseconds in a second */ 19 | #define NANOPERMILI 1000000 20 | #define MAXCLIENTS 10 21 | 22 | enum Duration { 23 | POMODORO_SECS = SECONDS * 25, 24 | SHORTBREAK_SECS = SECONDS * 5, 25 | LONGBREAK_SECS = SECONDS * 30 26 | }; 27 | 28 | static char *sockpath; 29 | static struct timespec pomodoro = {.tv_sec = POMODORO_SECS}; 30 | static struct timespec shortbreak = {.tv_sec = SHORTBREAK_SECS}; 31 | static struct timespec longbreak = {.tv_sec = LONGBREAK_SECS}; 32 | 33 | static void 34 | usage(void) 35 | { 36 | (void)fprintf(stderr, "usage: %s [-S socket] [-l time] [-p time] [-s time]\n", getprogname()); 37 | exit(1); 38 | } 39 | 40 | static int 41 | gettime(char *s) 42 | { 43 | char *ep; 44 | long l; 45 | 46 | l = strtol(s, &ep, 10); 47 | if (*s == '\0' || *ep != '\0' || l <= 0 || l >= INT_MAX / SECONDS) 48 | goto error; 49 | return SECONDS * (int)l; 50 | error: 51 | errx(1, "%s: invalid time", s); 52 | } 53 | 54 | /* parse arguments and set global variables */ 55 | static void 56 | parseargs(int argc, char *argv[]) 57 | { 58 | int ch; 59 | 60 | while ((ch = getopt(argc, argv, "l:p:S:s:")) != -1) { 61 | switch (ch) { 62 | case 'S': 63 | sockpath = optarg; 64 | break; 65 | case 'l': 66 | longbreak.tv_sec = gettime(optarg); 67 | break; 68 | case 'p': 69 | pomodoro.tv_sec = gettime(optarg); 70 | break; 71 | case 's': 72 | shortbreak.tv_sec = gettime(optarg); 73 | break; 74 | default: 75 | usage(); 76 | break; 77 | } 78 | } 79 | argc -= optind; 80 | argv += optind; 81 | if (argc > 0) { 82 | usage(); 83 | } 84 | if (sockpath == NULL) { 85 | sockpath = getsockpath(); 86 | } 87 | } 88 | 89 | static int 90 | createsocket(const char *path, int backlog) 91 | { 92 | struct sockaddr_un saddr; 93 | int sd; 94 | 95 | memset(&saddr, 0, sizeof saddr); 96 | saddr.sun_family = AF_UNIX; 97 | strncpy(saddr.sun_path, path, (sizeof saddr.sun_path) - 1); 98 | unlink(path); 99 | if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) 100 | goto error; 101 | if (bind(sd, (struct sockaddr *)&saddr, sizeof saddr) == -1) 102 | goto error; 103 | if (listen(sd, backlog) == -1) 104 | goto error; 105 | return sd; 106 | error: 107 | err(1, "%s", path); 108 | } 109 | 110 | /* return 1 if we handle client */ 111 | static int 112 | acceptclient(struct pollfd *pfds, size_t n) 113 | { 114 | struct sockaddr_un caddr; 115 | socklen_t len; 116 | size_t i; 117 | int cd; /* client file descriptor */ 118 | 119 | len = sizeof caddr; 120 | if ((cd = accept(pfds[0].fd, (struct sockaddr *)&caddr, &len)) == -1) 121 | err(1, "accept"); 122 | for (i = 1; i <= n; i++) { 123 | if (pfds[i].fd <= 0) { 124 | pfds[i].fd = cd; 125 | pfds[i].events = POLLIN; 126 | return 1; 127 | } 128 | } 129 | close(cd); /* ignore if we have MAXCLIENTS or more */ 130 | return 0; 131 | } 132 | 133 | /* return 1 if we do not handle client anymore */ 134 | static int 135 | handleclient(int fd) 136 | { 137 | char cmd; 138 | int n; 139 | 140 | if ((n = read(fd, &cmd, 1)) != 1) 141 | return BYE; 142 | return cmd; 143 | } 144 | 145 | static void 146 | gettimespec(struct timespec *ts) 147 | { 148 | if (clock_gettime(CLOCK_REALTIME, ts) == -1) { 149 | err(1, "time"); 150 | } 151 | } 152 | 153 | static void 154 | notify(char cycle) 155 | { 156 | printf("%s\n", getcyclename(cycle)); 157 | fflush(stdout); 158 | } 159 | 160 | static void 161 | timesub(struct timespec *a, struct timespec *b, struct timespec *c) 162 | { 163 | timespecsub(a, b, c); 164 | if (c->tv_sec < 0) { 165 | c->tv_sec = 0; 166 | } 167 | if (c->tv_nsec < 0) { 168 | c->tv_nsec = 0; 169 | } 170 | } 171 | 172 | static void 173 | info(int fd, struct timespec *stoptime, char cycle) 174 | { 175 | struct timespec now, diff; 176 | uint8_t buf[INFOSIZ]; 177 | 178 | gettimespec(&now); 179 | timesub(stoptime, &now, &diff); 180 | buf[CYCLE] = cycle; 181 | buf[MIN] = buf[SEC] = 0; 182 | switch (cycle) { 183 | case POMODORO: 184 | buf[MIN] = (uint8_t)(pomodoro.tv_sec - diff.tv_sec) / SECONDS; 185 | buf[SEC] = (uint8_t)(pomodoro.tv_sec - diff.tv_sec) % SECONDS; 186 | break; 187 | case SHORTBREAK: 188 | buf[MIN] = (uint8_t)(shortbreak.tv_sec - diff.tv_sec) / SECONDS; 189 | buf[SEC] = (uint8_t)(shortbreak.tv_sec - diff.tv_sec) % SECONDS; 190 | break; 191 | case LONGBREAK: 192 | buf[MIN] = (uint8_t)(longbreak.tv_sec - diff.tv_sec) / SECONDS; 193 | buf[SEC] = (uint8_t)(longbreak.tv_sec - diff.tv_sec) % SECONDS; 194 | break; 195 | } 196 | write(fd, buf, INFOSIZ); 197 | } 198 | 199 | static int 200 | gettimeout(struct timespec *stoptime) 201 | { 202 | struct timespec now, diff; 203 | 204 | gettimespec(&now); 205 | timesub(stoptime, &now, &diff); 206 | return MILISECONDS * diff.tv_sec + diff.tv_nsec / NANOPERMILI; 207 | } 208 | 209 | static void 210 | run(int sd) 211 | { 212 | struct pollfd pfds[MAXCLIENTS + 1]; 213 | struct timespec now, stoptime; 214 | size_t i; 215 | int timeout; 216 | int pomocount; 217 | int n; 218 | char cycle; 219 | 220 | timeout = -1; 221 | pfds[0].fd = sd; 222 | pfds[0].events = POLLIN; 223 | for (i = 1; i <= MAXCLIENTS; i++) 224 | pfds[i].fd = -1; 225 | cycle = STOPPED; 226 | pomocount = 0; 227 | for (;;) { 228 | if ((n = poll(pfds, MAXCLIENTS + 1, timeout)) == -1) 229 | err(1, "poll"); 230 | if (n > 0) { 231 | if (pfds[0].revents & POLLHUP) /* socket has been disconnected */ 232 | return; 233 | if (pfds[0].revents & POLLIN) /* handle new client */ 234 | acceptclient(pfds, MAXCLIENTS); 235 | for (i = 1; i <= MAXCLIENTS; i++) { /* handle existing client */ 236 | if (pfds[i].fd <= 0 || !(pfds[i].events & POLLIN)) 237 | continue; 238 | switch (handleclient(pfds[i].fd)) { 239 | case BYE: 240 | pfds[i].fd = -1; 241 | break; 242 | case START: 243 | pomocount = 0; 244 | gettimespec(&stoptime); 245 | stoptime.tv_sec += pomodoro.tv_sec; 246 | notify(cycle = POMODORO); 247 | break; 248 | case STOP: 249 | cycle = STOPPED; 250 | break; 251 | case INFO: 252 | info(pfds[i].fd, &stoptime, cycle); 253 | break; 254 | } 255 | } 256 | } 257 | gettimespec(&now); 258 | switch (cycle) { 259 | case STOPPED: 260 | timeout = -1; 261 | break; 262 | case POMODORO: 263 | if (timespeccmp(&now, &stoptime, >=)) { 264 | pomocount++; 265 | if (pomocount < 4) { 266 | notify(cycle = SHORTBREAK); 267 | stoptime.tv_sec += shortbreak.tv_sec; 268 | timeout = gettimeout(&stoptime); 269 | } else { 270 | pomocount = 0; 271 | notify(cycle = LONGBREAK); 272 | stoptime.tv_sec += longbreak.tv_sec; 273 | timeout = gettimeout(&stoptime); 274 | } 275 | } else { 276 | timeout = gettimeout(&stoptime); 277 | } 278 | break; 279 | case SHORTBREAK: 280 | if (timespeccmp(&now, &stoptime, >)) { 281 | notify(cycle = POMODORO); 282 | stoptime.tv_sec += pomodoro.tv_sec; 283 | timeout = gettimeout(&stoptime); 284 | } else { 285 | timeout = gettimeout(&stoptime); 286 | } 287 | break; 288 | case LONGBREAK: 289 | pomocount = 0; 290 | if (timespeccmp(&now, &stoptime, >)) { 291 | notify(cycle = POMODORO); 292 | stoptime.tv_sec += pomodoro.tv_sec; 293 | timeout = gettimeout(&stoptime); 294 | } else { 295 | timeout = gettimeout(&stoptime); 296 | } 297 | break; 298 | } 299 | } 300 | } 301 | 302 | int 303 | main(int argc, char *argv[]) 304 | { 305 | int sd; /* socket file descriptor */ 306 | 307 | setprogname(argv[0]); 308 | parseargs(argc, argv); 309 | sd = createsocket(sockpath, BACKLOG); 310 | run(sd); 311 | close(sd); 312 | unlink(sockpath); 313 | return 0; 314 | } 315 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "util.h" 4 | 5 | #define PATHSIZ 104 6 | #define PATHPREFIX "/tmp/pomodoro." 7 | 8 | char * 9 | getsockpath(void) 10 | { 11 | static char buf[PATHSIZ]; 12 | 13 | snprintf(buf, PATHSIZ, PATHPREFIX "%ld", (long)geteuid()); 14 | return buf; 15 | } 16 | 17 | char * 18 | getcyclename(char cycle) 19 | { 20 | switch (cycle) { 21 | case STOPPED: 22 | return "stopped"; 23 | case POMODORO: 24 | return "pomodoro"; 25 | case SHORTBREAK: 26 | return "shortbreak"; 27 | case LONGBREAK: 28 | return "longbreak"; 29 | } 30 | return ""; 31 | } 32 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | enum Command { 2 | BYE = 0, 3 | STOP = 'x', 4 | START = 's', 5 | INFO = 'i' 6 | }; 7 | 8 | enum Cycle { 9 | STOPPED = 'x', 10 | POMODORO = 'p', 11 | SHORTBREAK = 's', 12 | LONGBREAK = 'l' 13 | }; 14 | 15 | enum Info { 16 | CYCLE = 0, 17 | MIN = 1, 18 | SEC = 2, 19 | INFOSIZ = 3, 20 | }; 21 | 22 | char *getsockpath(void); 23 | char *getcyclename(char); 24 | --------------------------------------------------------------------------------