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