xref: /freebsd/usr.bin/grdc/grdc.c (revision c57c26179033f64c2011a2d2a904ee3fa62e826a)
1 /*
2  * Grand digital clock for curses compatible terminals
3  * Usage: grdc [-st] [n]   -- run for n seconds (default infinity)
4  *        grdc -c n        -- countdown n seconds
5  * Flags: -c: Countdown timer mode
6  *        -s: scroll
7  *        -t: output time in 12-hour format
8  *
9  *
10  * modified 10-18-89 for curses (jrl)
11  * 10-18-89 added signal handling
12  * 02-18-02 added countdown timer mode
13  *
14  * modified 03-25-03 for 12 hour option
15  *     - Samy Al Bahra <samy@kerneled.com>
16  */
17 
18 #include <err.h>
19 #include <ncurses.h>
20 #include <signal.h>
21 #include <stdlib.h>
22 #include <time.h>
23 #include <unistd.h>
24 
25 #define YBASE	10
26 #define XBASE	10
27 #define XLENGTH 58
28 #define YDEPTH  7
29 
30 static struct timespec now;
31 static struct tm *tm;
32 static struct timespec end;
33 
34 static short disp[11] = {
35 	075557, 011111, 071747, 071717, 055711,
36 	074717, 074757, 071111, 075757, 075717, 002020
37 };
38 static long old[6], next[6], new[6], mask;
39 
40 static volatile sig_atomic_t sigtermed;
41 
42 static int hascolor = 0;
43 
44 static void set(int, int);
45 static void standt(int);
46 static void movto(int, int);
47 static void sighndl(int);
48 static void usage(void) __dead2;
49 
50 static void
51 sighndl(int signo)
52 {
53 
54 	sigtermed = signo;
55 }
56 
57 int
58 main(int argc, char *argv[])
59 {
60 	struct timespec delay;
61 	time_t prev_sec;
62 	long t, a;
63 	int i, j, s, k;
64 	int n;
65 	int ch;
66 	bool scrol = false, t12 = false, timer = false;
67 	int hour, minute, second;
68 
69 	while ((ch = getopt(argc, argv, "cst")) != -1)
70 	switch (ch) {
71 	case 'c':
72 		timer = true;
73 		break;
74 	case 's':
75 		scrol = true;
76 		break;
77 	case 't':
78 		t12 = true;
79 		break;
80 	case '?':
81 	default:
82 		usage();
83 		/* NOTREACHED */
84 	}
85 	argc -= optind;
86 	argv += optind;
87 
88 	if ((argc > 1) || (argc == 0 && timer)) {
89 		usage();
90 		/* NOTREACHED */
91 	}
92 
93 	if (argc > 0) {
94 		n = atoi(*argv) + 1;
95 		if (n < 1) {
96 			warnx("number of seconds is out of range");
97 			usage();
98 			/* NOTREACHED */
99 		}
100 	} else
101 		n = 0;
102 
103 	if (timer && n == 0)
104 		return(0);
105 
106 	initscr();
107 
108 	signal(SIGINT,sighndl);
109 	signal(SIGTERM,sighndl);
110 	signal(SIGHUP,sighndl);
111 
112 	cbreak();
113 	noecho();
114 	curs_set(0);
115 
116 	hascolor = has_colors();
117 
118 	if (hascolor) {
119 		start_color();
120 		init_pair(1, COLOR_BLACK, COLOR_RED);
121 		init_pair(2, COLOR_RED, COLOR_BLACK);
122 		init_pair(3, COLOR_WHITE, COLOR_BLACK);
123 		attrset(COLOR_PAIR(2));
124 	}
125 
126 	clear();
127 	refresh();
128 
129 	if (hascolor) {
130 		attrset(COLOR_PAIR(3));
131 
132 		mvaddch(YBASE - 2,  XBASE - 3, ACS_ULCORNER);
133 		hline(ACS_HLINE, XLENGTH);
134 		mvaddch(YBASE - 2,  XBASE - 2 + XLENGTH, ACS_URCORNER);
135 
136 		mvaddch(YBASE + YDEPTH - 1,  XBASE - 3, ACS_LLCORNER);
137 		hline(ACS_HLINE, XLENGTH);
138 		mvaddch(YBASE + YDEPTH - 1,  XBASE - 2 + XLENGTH, ACS_LRCORNER);
139 
140 		move(YBASE - 1,  XBASE - 3);
141 		vline(ACS_VLINE, YDEPTH);
142 
143 		move(YBASE - 1,  XBASE - 2 + XLENGTH);
144 		vline(ACS_VLINE, YDEPTH);
145 
146 		attrset(COLOR_PAIR(2));
147 	}
148 	clock_gettime(CLOCK_REALTIME_FAST, &now);
149 	prev_sec = now.tv_sec;
150 	if (timer) {
151 		end = now;
152 		end.tv_sec += n;
153 	}
154 	do {
155 		mask = 0;
156 		if (!timer) {
157 			tm = localtime(&now.tv_sec);
158 			if (t12) {
159 				if (tm->tm_hour < 12) {
160 					if (tm->tm_hour == 0)
161 						tm->tm_hour = 12;
162 					mvaddstr(YBASE + 5, XBASE + 52, "AM");
163 				} else {
164 					if (tm->tm_hour > 12)
165 						tm->tm_hour -= 12;
166 					mvaddstr(YBASE + 5, XBASE + 52, "PM");
167 				}
168 			}
169 			hour = tm->tm_hour;
170 			minute = tm->tm_min;
171 			second = tm->tm_sec;
172 		} else {
173 			n = end.tv_sec - now.tv_sec;
174 			if (n <= 0)
175 				break;
176 			hour = (n / 3600) % 100;
177 			minute = (n / 60) % 60;
178 			second = n % 60;
179 		}
180 		set(second % 10, 0);
181 		set(second / 10, 4);
182 		set(minute % 10, 10);
183 		set(minute / 10, 14);
184 		set(hour % 10, 20);
185 		set(hour / 10, 24);
186 		set(10, 7);
187 		set(10, 17);
188 		for(k=0; k<6; k++) {
189 			if(scrol) {
190 				for(i=0; i<5; i++)
191 					new[i] = (new[i]&~mask) | (new[i+1]&mask);
192 				new[5] = (new[5]&~mask) | (next[k]&mask);
193 			} else
194 				new[k] = (new[k]&~mask) | (next[k]&mask);
195 			next[k] = 0;
196 			for(s=1; s>=0; s--) {
197 				standt(s);
198 				for(i=0; i<6; i++) {
199 					if((a = (new[i]^old[i])&(s ? new : old)[i]) != 0) {
200 						for(j=0,t=1<<26; t; t>>=1,j++) {
201 							if(a&t) {
202 								if(!(a&(t<<1))) {
203 									movto(YBASE + i, XBASE + 2*j);
204 								}
205 								addstr("  ");
206 							}
207 						}
208 					}
209 					if(!s) {
210 						old[i] = new[i];
211 					}
212 				}
213 				if(!s) {
214 					refresh();
215 				}
216 			}
217 		}
218 		movto(6, 0);
219 		refresh();
220 		clock_gettime(CLOCK_REALTIME_FAST, &now);
221 		if (now.tv_sec == prev_sec) {
222 			if (delay.tv_nsec > 0) {
223 				delay.tv_sec = 0;
224 				delay.tv_nsec = 1000000000 - now.tv_nsec;
225 			} else {
226 				delay.tv_sec = 1;
227 				delay.tv_nsec = 0;
228 			}
229 			nanosleep(&delay, NULL);
230 			clock_gettime(CLOCK_REALTIME_FAST, &now);
231 		}
232 		n -= now.tv_sec - prev_sec;
233 		prev_sec = now.tv_sec;
234 		if (sigtermed) {
235 			standend();
236 			clear();
237 			refresh();
238 			endwin();
239 			errx(1, "terminated by signal %d", (int)sigtermed);
240 		}
241 	} while (n);
242 	standend();
243 	clear();
244 	refresh();
245 	endwin();
246 	return(0);
247 }
248 
249 static void
250 set(int t, int n)
251 {
252 	int i, m;
253 
254 	m = 7<<n;
255 	for(i=0; i<5; i++) {
256 		next[i] |= ((disp[t]>>(4-i)*3)&07)<<n;
257 		mask |= (next[i]^old[i])&m;
258 	}
259 	if(mask&m)
260 		mask |= m;
261 }
262 
263 static void
264 standt(int on)
265 {
266 	if (on) {
267 		if(hascolor) {
268 			attron(COLOR_PAIR(1));
269 		} else {
270 			attron(A_STANDOUT);
271 		}
272 	} else {
273 		if(hascolor) {
274 			attron(COLOR_PAIR(2));
275 		} else {
276 			attroff(A_STANDOUT);
277 		}
278 	}
279 }
280 
281 static void
282 movto(int line, int col)
283 {
284 	move(line, col);
285 }
286 
287 static void
288 usage(void)
289 {
290 
291 	(void)fprintf(stderr, "usage: grdc [-st] [n]\n"
292 	    "      grdc -c n\n");
293 	exit(1);
294 }
295