xref: /openbsd-src/games/grdc/grdc.c (revision e99802964e3755c60b0b400980860f7318d8bfaa)
1 /*	$OpenBSD: grdc.c,v 1.37 2022/09/27 03:01:42 deraadt Exp $	*/
2 /*
3  *
4  * Copyright 2002 Amos Shapir.  Public domain.
5  *
6  * Grand digital clock for curses compatible terminals
7  * Usage: grdc [-s] [n]   -- run for n seconds (default infinity)
8  * Flags: -s: scroll
9  *
10  * modified 10-18-89 for curses (jrl)
11  * 10-18-89 added signal handling
12  */
13 
14 #include <sys/ioctl.h>
15 
16 #include <curses.h>
17 #include <err.h>
18 #include <limits.h>
19 #include <poll.h>
20 #include <signal.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 #include <unistd.h>
26 
27 #define XLENGTH 58
28 #define YDEPTH  7
29 
30 struct timespec now;
31 struct tm *tm;
32 
33 short disp[11] = {
34 	075557, 011111, 071747, 071717, 055711,
35 	074717, 074757, 071111, 075757, 075717, 002020
36 };
37 long old[6], next[6], new[6], mask;
38 
39 volatile sig_atomic_t sigalrmed = 0;
40 volatile sig_atomic_t sigtermed = 0;
41 volatile sig_atomic_t sigwinched = 0;
42 
43 int hascolor = 0;
44 
45 void getwinsize(int *, int *);
46 void set(int, int);
47 void standt(int);
48 void __dead usage(void);
49 
50 void
sigalrm(int signo)51 sigalrm(int signo)
52 {
53 	sigalrmed = signo;
54 }
55 
56 void
sighndl(int signo)57 sighndl(int signo)
58 {
59 	sigtermed = signo;
60 }
61 
62 void
sigresize(int signo)63 sigresize(int signo)
64 {
65 	sigwinched = signo;
66 }
67 
68 int
main(int argc,char * argv[])69 main(int argc, char *argv[])
70 {
71 	long t, a;
72 	int i, j, s, k, rv;
73 	int scrol;
74 	unsigned int n = 0;
75 	struct timespec delay;
76 	struct pollfd pfd;
77 	const char *errstr;
78 	long scroldelay = 50000000;
79 	int xbase;
80 	int ybase;
81 	int wintoosmall;
82 	int tz_len = 0;
83 	int h, m;
84 	int prev_tm_gmtoff;
85 	char *tz;
86 
87 	tz = getenv("TZ");
88 	if (tz != NULL)
89 		tz_len = strlen(tz);
90 
91 	scrol = wintoosmall = 0;
92 	while ((i = getopt(argc, argv, "sh")) != -1) {
93 		switch (i) {
94 		case 's':
95 			scrol = 1;
96 			break;
97 		case 'h':
98 		default:
99 			usage();
100 		}
101 	}
102 	argv += optind;
103 	argc -= optind;
104 
105 	if (argc > 1)
106 		usage();
107 	if (argc == 1) {
108 		n = strtonum(*argv, 1, UINT_MAX, &errstr);
109 		if (errstr) {
110 			warnx("number of seconds is %s", errstr);
111 			usage();
112 		}
113 	}
114 
115 	initscr();
116 
117 	if (pledge("stdio tty", NULL) == -1)
118 		err(1, "pledge");
119 
120 	signal(SIGINT, sighndl);
121 	signal(SIGTERM, sighndl);
122 	signal(SIGHUP, sighndl);
123 	signal(SIGWINCH, sigresize);
124 	signal(SIGCONT, sigresize);	/* for resizes during suspend */
125 
126 	pfd.fd = STDIN_FILENO;
127 	pfd.events = POLLIN;
128 
129 	cbreak();
130 	noecho();
131 
132 	hascolor = has_colors();
133 
134 	if (hascolor) {
135 		start_color();
136 		init_pair(1, COLOR_BLACK, COLOR_RED);
137 		init_pair(2, COLOR_RED, COLOR_BLACK);
138 		init_pair(3, COLOR_WHITE, COLOR_BLACK);
139 		attrset(COLOR_PAIR(2));
140 	}
141 
142 	curs_set(0);
143 	sigwinched = 1;	/* force initial sizing */
144 	prev_tm_gmtoff = 24 * 3600; /* force initial header printing */
145 
146 	clock_gettime(CLOCK_REALTIME, &now);
147 	if (n) {
148 		signal(SIGALRM, sigalrm);
149 		alarm(n);
150 	}
151 	do {
152 		mask = 0;
153 		tm = localtime(&now.tv_sec);
154 		set(tm->tm_sec % 10, 0);
155 		set(tm->tm_sec / 10, 4);
156 		set(tm->tm_min % 10, 10);
157 		set(tm->tm_min / 10, 14);
158 		set(tm->tm_hour % 10, 20);
159 		set(tm->tm_hour / 10, 24);
160 		set(10, 7);
161 		set(10, 17);
162 		/* force repaint if window size changed or DST changed */
163 		if (sigwinched || prev_tm_gmtoff != tm->tm_gmtoff) {
164 			sigwinched = 0;
165 			wintoosmall = 0;
166 			prev_tm_gmtoff = tm->tm_gmtoff;
167 			getwinsize(&i, &j);
168 			if (i >= XLENGTH + 2)
169 				xbase = (i - XLENGTH) / 2;
170 			else
171 				wintoosmall = 1;
172 			if (j >= YDEPTH + 2)
173 				ybase = (j - YDEPTH) / 2;
174 			else
175 				wintoosmall = 1;
176 			resizeterm(j, i);
177 			clear();
178 			refresh();
179 			if (hascolor && !wintoosmall) {
180 				attrset(COLOR_PAIR(3));
181 
182 				mvaddch(ybase - 1, xbase - 1, ACS_ULCORNER);
183 				hline(ACS_HLINE, XLENGTH);
184 				mvaddch(ybase - 1, xbase + XLENGTH, ACS_URCORNER);
185 
186 				mvaddch(ybase + YDEPTH, xbase - 1, ACS_LLCORNER);
187 				hline(ACS_HLINE, XLENGTH);
188 				mvaddch(ybase + YDEPTH, xbase + XLENGTH, ACS_LRCORNER);
189 
190 				move(ybase, xbase - 1);
191 				vline(ACS_VLINE, YDEPTH);
192 
193 				move(ybase, xbase + XLENGTH);
194 				vline(ACS_VLINE, YDEPTH);
195 
196 				move(ybase - 1, xbase);
197 
198 				h = tm->tm_gmtoff / 3600;
199 				m = abs((int)tm->tm_gmtoff % 3600 / 60);
200 
201 				if (tz_len > 0 && tz_len <= XLENGTH -
202 				    strlen("[  () +0000 ]") -
203 				    strlen(tm->tm_zone))
204 					printw("[ %s (%s) %+2.2d%02d ]", tz,
205 					    tm->tm_zone, h, m);
206 				else
207 					printw("[ %s %+2.2d%02d ]",
208 					    tm->tm_zone, h, m);
209 
210 				attrset(COLOR_PAIR(2));
211 			}
212 			for (k = 0; k < 6; k++)
213 				old[k] = 0;
214 		}
215 		if (wintoosmall) {
216 			move(0, 0);
217 			printw("%02d:%02d:%02d", tm->tm_hour, tm->tm_min,
218 			    tm->tm_sec);
219 		} else for (k = 0; k < 6; k++) {
220 			if (scrol) {
221 				for(i = 0; i < 5; i++)
222 					new[i] = (new[i] & ~mask) | (new[i + 1] & mask);
223 				new[5] = (new[5] & ~mask) | (next[k] & mask);
224 			} else
225 				new[k] = (new[k] & ~mask) | (next[k] & mask);
226 			next[k] = 0;
227 			for (s = 1; s >= 0; s--) {
228 				standt(s);
229 				for (i = 0; i < 6; i++) {
230 					if ((a = (new[i] ^ old[i]) & (s ? new : old)[i]) != 0) {
231 						for (j = 0, t = 1 << 26; t; t >>= 1, j++) {
232 							if (a & t) {
233 								if (!(a & (t << 1))) {
234 									move(ybase + i + 1, xbase + 2 * (j + 1));
235 								}
236 								addstr("  ");
237 							}
238 						}
239 					}
240 					if (!s) {
241 						old[i] = new[i];
242 					}
243 				}
244 				if (!s) {
245 					refresh();
246 				}
247 			}
248 			if (scrol && k <= 4) {
249 				clock_gettime(CLOCK_REALTIME, &now);
250 				delay.tv_sec = 0;
251 				delay.tv_nsec = 1000000000 - now.tv_nsec
252 				    - (4 - k) * scroldelay;
253 				if (delay.tv_nsec <= scroldelay &&
254 				    delay.tv_nsec > 0)
255 					nanosleep(&delay, NULL);
256 			}
257 		}
258 		move(6, 0);
259 		refresh();
260 		clock_gettime(CLOCK_REALTIME, &now);
261 		delay.tv_sec = 0;
262 		delay.tv_nsec = (1000000000 - now.tv_nsec);
263 		/* want scrolling to END on the second */
264 		if (scrol && !wintoosmall)
265 			delay.tv_nsec -= 5 * scroldelay;
266 		rv = ppoll(&pfd, 1, &delay, NULL);
267 		if (rv == 1) {
268 			char q = 0;
269 			read(STDIN_FILENO, &q, 1);
270 			if (q == 'q')
271 				sigalrmed = 1;
272 		}
273 		now.tv_sec++;
274 
275 		if (sigtermed) {
276 			standend();
277 			clear();
278 			refresh();
279 			endwin();
280 			exit(0);
281 		}
282 	} while (!sigalrmed);
283 	standend();
284 	clear();
285 	refresh();
286 	endwin();
287 	return 0;
288 }
289 
290 void
set(int t,int n)291 set(int t, int n)
292 {
293 	int i, m;
294 
295 	m = 7 << n;
296 	for (i = 0; i < 5; i++) {
297 		next[i] |= ((disp[t] >> (4 - i) * 3) & 07) << n;
298 		mask |= (next[i] ^ old[i]) & m;
299 	}
300 	if (mask & m)
301 		mask |= m;
302 }
303 
304 void
standt(int on)305 standt(int on)
306 {
307 	if (on) {
308 		if (hascolor) {
309 			attron(COLOR_PAIR(1));
310 		} else {
311 			attron(A_STANDOUT);
312 		}
313 	} else {
314 		if (hascolor) {
315 			attron(COLOR_PAIR(2));
316 		} else {
317 			attroff(A_STANDOUT);
318 		}
319 	}
320 }
321 
322 void
getwinsize(int * wid,int * ht)323 getwinsize(int *wid, int *ht)
324 {
325 	struct winsize size;
326 
327 	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) == -1) {
328 		*wid = 80;     /* Default */
329 		*ht = 24;
330 	} else {
331 		*wid = size.ws_col;
332 		*ht = size.ws_row;
333 	}
334 }
335 
336 void __dead
usage(void)337 usage(void)
338 {
339 	fprintf(stderr, "usage: %s [-s] [number]\n", getprogname());
340 	exit(1);
341 }
342