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