1 /****************************************************************************
2 * Copyright 2019,2020 Thomas E. Dickey *
3 * Copyright 1998-2016,2017 Free Software Foundation, Inc. *
4 * *
5 * Permission is hereby granted, free of charge, to any person obtaining a *
6 * copy of this software and associated documentation files (the *
7 * "Software"), to deal in the Software without restriction, including *
8 * without limitation the rights to use, copy, modify, merge, publish, *
9 * distribute, distribute with modifications, sublicense, and/or sell *
10 * copies of the Software, and to permit persons to whom the Software is *
11 * furnished to do so, subject to the following conditions: *
12 * *
13 * The above copyright notice and this permission notice shall be included *
14 * in all copies or substantial portions of the Software. *
15 * *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
23 * *
24 * Except as contained in this notice, the name(s) of the above copyright *
25 * holders shall not be used in advertising or otherwise to promote the *
26 * sale, use or other dealings in this Software without prior written *
27 * authorization. *
28 ****************************************************************************/
29 /*
30 * Grand digital clock for curses compatible terminals
31 * Usage: gdc [-s] [-t hh:mm:ss] [n] -- run for n seconds (default infinity)
32 * Flags: -s: scroll
33 *
34 * modified 10-18-89 for curses (jrl)
35 * 10-18-89 added signal handling
36 *
37 * $Id: gdc.c,v 1.54 2020/02/02 23:34:34 tom Exp $
38 */
39
40 #include <test.priv.h>
41
42 #include <time.h>
43
44 #define YBASE 10
45 #define XBASE 10
46 #define XLENGTH 54
47 #define YDEPTH 5
48
49 #define PAIR_DIGITS 1
50 #define PAIR_OTHERS 2
51 #define PAIR_FRAMES 3
52
53 static short disp[11] =
54 {
55 075557, 011111, 071747, 071717, 055711,
56 074717, 074757, 071111, 075757, 075717, 002020
57 };
58 static long older[6], next[6], newer[6], mask;
59
60 static int sigtermed = 0;
61 static bool redirected = FALSE;
62 static bool hascolor = FALSE;
63
64 static void
sighndl(int signo)65 sighndl(int signo)
66 {
67 signal(signo, sighndl);
68 sigtermed = signo;
69 if (redirected) {
70 stop_curses();
71 ExitProgram(EXIT_FAILURE);
72 }
73 }
74
75 static void
check_term(void)76 check_term(void)
77 {
78 if (sigtermed) {
79 (void) standend();
80 stop_curses();
81 fprintf(stderr, "gdc terminated by signal %d\n", sigtermed);
82 ExitProgram(EXIT_FAILURE);
83 }
84 }
85
86 static void
drawbox(bool scrolling)87 drawbox(bool scrolling)
88 {
89 chtype bottom[XLENGTH + 1];
90
91 if (hascolor)
92 (void) attrset(AttrArg(COLOR_PAIR(PAIR_FRAMES), 0));
93
94 MvAddCh(YBASE - 1, XBASE - 1, ACS_ULCORNER);
95 hline(ACS_HLINE, XLENGTH);
96 MvAddCh(YBASE - 1, XBASE + XLENGTH, ACS_URCORNER);
97
98 MvAddCh(YBASE + YDEPTH, XBASE - 1, ACS_LLCORNER);
99 if ((mvinchnstr(YBASE + YDEPTH, XBASE, bottom, XLENGTH)) != ERR) {
100 int n;
101 for (n = 0; n < XLENGTH; n++) {
102 if (!scrolling)
103 bottom[n] &= ~A_COLOR;
104 bottom[n] = ACS_HLINE | (bottom[n] & (A_ATTRIBUTES | A_COLOR));
105 }
106 (void) mvaddchnstr(YBASE + YDEPTH, XBASE, bottom, XLENGTH);
107 }
108 MvAddCh(YBASE + YDEPTH, XBASE + XLENGTH, ACS_LRCORNER);
109
110 move(YBASE, XBASE - 1);
111 vline(ACS_VLINE, YDEPTH);
112
113 move(YBASE, XBASE + XLENGTH);
114 vline(ACS_VLINE, YDEPTH);
115
116 if (hascolor)
117 (void) attrset(AttrArg(COLOR_PAIR(PAIR_OTHERS), 0));
118 }
119
120 static void
standt(int on)121 standt(int on)
122 {
123 if (on) {
124 if (hascolor) {
125 attron(COLOR_PAIR(PAIR_DIGITS));
126 } else {
127 attron(A_STANDOUT);
128 }
129 } else {
130 if (hascolor) {
131 attron(COLOR_PAIR(PAIR_OTHERS));
132 } else {
133 attroff(A_STANDOUT);
134 }
135 }
136 }
137
138 static void
set(int t,int n)139 set(int t, int n)
140 {
141 int i, m;
142
143 m = 7 << n;
144 for (i = 0; i < 5; i++) {
145 next[i] |= ((disp[t] >> ((4 - i) * 3)) & 07) << n;
146 mask |= (next[i] ^ older[i]) & m;
147 }
148 if (mask & m)
149 mask |= m;
150 }
151
152 static void
usage(void)153 usage(void)
154 {
155 static const char *msg[] =
156 {
157 "Usage: gdc [options] [count]"
158 ,""
159 ,"Options:"
160 #if HAVE_USE_DEFAULT_COLORS
161 ," -d invoke use_default_colors"
162 #endif
163 ," -n redirect input to /dev/null"
164 ," -s scroll each number into place, rather than flipping"
165 ," -t hh:mm:ss specify starting time (default is ``now'')"
166 ,""
167 ,"If you specify a count, gdc runs for that number of seconds"
168 };
169 unsigned j;
170 for (j = 0; j < SIZEOF(msg); j++)
171 fprintf(stderr, "%s\n", msg[j]);
172 ExitProgram(EXIT_FAILURE);
173 }
174
175 static time_t
parse_time(const char * value)176 parse_time(const char *value)
177 {
178 int hh, mm, ss;
179 int check;
180 time_t result;
181 char c;
182 struct tm *tm;
183
184 if (sscanf(value, "%d:%d:%d%c", &hh, &mm, &ss, &c) != 3) {
185 if (sscanf(value, "%02d%02d%02d%c", &hh, &mm, &ss, &c) != 3) {
186 usage();
187 }
188 }
189
190 if ((hh < 0) || (hh >= 24) ||
191 (mm < 0) || (mm >= 60) ||
192 (ss < 0) || (ss >= 60)) {
193 usage();
194 }
195
196 /* adjust so that the localtime in the main loop will give usable time */
197 result = (hh * 3600) + ((mm * 60) + ss);
198 for (check = 0; check < 24; ++check) {
199 tm = localtime(&result);
200 if (tm->tm_hour == hh)
201 break;
202 result += 3600;
203 }
204
205 if (tm->tm_hour != hh) {
206 fprintf(stderr, "Cannot find local time for %s!\n", value);
207 usage();
208 }
209 return result;
210 }
211
212 int
main(int argc,char * argv[])213 main(int argc, char *argv[])
214 {
215 time_t now;
216 struct tm *tm;
217 long t, a;
218 int i, j, s, k;
219 int count = 0;
220 FILE *ofp = stdout;
221 FILE *ifp = stdin;
222 bool smooth = FALSE;
223 bool stages = FALSE;
224 time_t starts = 0;
225 #if HAVE_USE_DEFAULT_COLORS
226 bool d_option = FALSE;
227 #endif
228
229 setlocale(LC_ALL, "");
230
231 while ((k = getopt(argc, argv, "dnst:")) != -1) {
232 switch (k) {
233 #if HAVE_USE_DEFAULT_COLORS
234 case 'd':
235 d_option = TRUE;
236 break;
237 #endif
238 case 'n':
239 ifp = fopen("/dev/null", "r");
240 redirected = TRUE;
241 break;
242 case 's':
243 smooth = TRUE;
244 break;
245 case 't':
246 starts = parse_time(optarg);
247 break;
248 default:
249 usage();
250 }
251 }
252 if (optind < argc) {
253 count = atoi(argv[optind++]);
254 assert(count >= 0);
255 if (optind < argc)
256 usage();
257 }
258
259 InitAndCatch({
260 if (redirected) {
261 char *name = getenv("TERM");
262 if (name == 0
263 || newterm(name, ofp, ifp) == 0) {
264 fprintf(stderr, "cannot open terminal\n");
265 ExitProgram(EXIT_FAILURE);
266 }
267 } else {
268 initscr();
269 }
270 }
271 ,sighndl);
272
273 cbreak();
274 noecho();
275 nodelay(stdscr, 1);
276 curs_set(0);
277
278 hascolor = has_colors();
279
280 if (hascolor) {
281 short bg = COLOR_BLACK;
282 start_color();
283 #if HAVE_USE_DEFAULT_COLORS
284 if (d_option && (use_default_colors() == OK))
285 bg = -1;
286 #endif
287 init_pair(PAIR_DIGITS, COLOR_BLACK, COLOR_RED);
288 init_pair(PAIR_OTHERS, COLOR_RED, bg);
289 init_pair(PAIR_FRAMES, COLOR_WHITE, bg);
290 (void) attrset(AttrArg(COLOR_PAIR(PAIR_OTHERS), 0));
291 }
292
293 restart:
294 for (j = 0; j < 5; j++)
295 older[j] = newer[j] = next[j] = 0;
296
297 clear();
298 drawbox(FALSE);
299
300 do {
301 char buf[40];
302
303 if (starts != 0) {
304 now = ++starts;
305 } else {
306 time(&now);
307 }
308 tm = localtime(&now);
309
310 mask = 0;
311 set(tm->tm_sec % 10, 0);
312 set(tm->tm_sec / 10, 4);
313 set(tm->tm_min % 10, 10);
314 set(tm->tm_min / 10, 14);
315 set(tm->tm_hour % 10, 20);
316 set(tm->tm_hour / 10, 24);
317 set(10, 7);
318 set(10, 17);
319
320 for (k = 0; k < 6; k++) {
321 if (smooth) {
322 for (i = 0; i < 5; i++)
323 newer[i] = (newer[i] & ~mask) | (newer[i + 1] & mask);
324 newer[5] = (newer[5] & ~mask) | (next[k] & mask);
325 } else {
326 newer[k] = (newer[k] & ~mask) | (next[k] & mask);
327 }
328 next[k] = 0;
329 for (s = 1; s >= 0; s--) {
330 standt(s);
331 for (i = 0; i < 6; i++) {
332 if ((a = (newer[i] ^ older[i]) & (s ? newer : older)[i])
333 != 0) {
334 for (j = 0, t = 1 << 26; t; t >>= 1, j++) {
335 if (a & t) {
336 if (!(a & (t << 1))) {
337 move(YBASE + i, XBASE + 2 * j);
338 }
339 addstr(" ");
340 }
341 }
342 }
343 if (!s) {
344 older[i] = newer[i];
345 }
346 }
347 if (!s) {
348 if (smooth)
349 drawbox(TRUE);
350 refresh();
351 /*
352 * If we're scrolling, space out the refreshes to fake
353 * movement. That's 7 frames, or 6 intervals, which would
354 * be 166 msec if we spread it out over a second. It looks
355 * better (but will work on a slow terminal, e.g., less
356 * than 9600bd) to squeeze that into a half-second, and use
357 * half of 170 msec to ensure that the program doesn't eat
358 * a lot of time when asking what time it is, at the top of
359 * this loop -T.Dickey
360 */
361 if (smooth)
362 napms(85);
363 if (stages) {
364 stages = FALSE;
365 switch (wgetch(stdscr)) {
366 case 'q':
367 count = 1;
368 break;
369 case 'S':
370 stages = TRUE;
371 /* FALLTHRU */
372 case 's':
373 nodelay(stdscr, FALSE);
374 break;
375 case ' ':
376 nodelay(stdscr, TRUE);
377 break;
378 #ifdef KEY_RESIZE
379 case KEY_RESIZE:
380 #endif
381 case '?':
382 goto restart;
383 case ERR:
384 check_term();
385 /* FALLTHRU */
386 default:
387 continue;
388 }
389 }
390 }
391 }
392 }
393
394 /* this depends on the detailed format of ctime(3) */
395 _nc_STRNCPY(buf, ctime(&now), (size_t) 30);
396 {
397 char *d2 = buf + 10;
398 char *s2 = buf + 19;
399 while ((*d2++ = *s2++) != '\0') ;
400 }
401 MvAddStr(16, 30, buf);
402
403 move(6, 0);
404 drawbox(FALSE);
405 refresh();
406
407 /*
408 * If we're not smooth-scrolling, wait 1000 msec (1 sec). Use napms()
409 * rather than sleep() because the latter does odd things on some
410 * systems, e.g., suspending output as well.
411 */
412 if (smooth)
413 napms(500);
414 else
415 napms(1000);
416
417 /*
418 * This is a safe way to check if we're interrupted - making the signal
419 * handler set a flag that we can check. Since we're running
420 * nodelay(), the wgetch() call returns immediately, and in particular
421 * will return an error if interrupted. This works only if we can
422 * read from the input, of course.
423 */
424 stages = FALSE;
425 switch (wgetch(stdscr)) {
426 case 'q':
427 count = 1;
428 break;
429 case 'S':
430 stages = TRUE;
431 /* FALLTHRU */
432 case 's':
433 nodelay(stdscr, FALSE);
434 break;
435 case ' ':
436 nodelay(stdscr, TRUE);
437 break;
438 #ifdef KEY_RESIZE
439 case KEY_RESIZE:
440 #endif
441 case '?':
442 goto restart;
443 case ERR:
444 check_term();
445 /* FALLTHRU */
446 default:
447 continue;
448 }
449 } while (--count);
450 (void) standend();
451 stop_curses();
452 ExitProgram(EXIT_SUCCESS);
453 }
454