1/*
2 * Grand digital clock for curses compatible terminals
3 * Usage: gdc [-s] [n]   -- run for n seconds (default infinity)
4 * Flags: -s: scroll
5 *
6 * modified 10-18-89 for curses (jrl)
7 * 10-18-89 added signal handling
8 *
9 * $Id: gdc.c,v 1.26 2005/05/28 21:39:39 tom Exp $
10 */
11
12#include <test.priv.h>
13
14#include <time.h>
15
16#define YBASE	10
17#define XBASE	10
18#define XLENGTH	54
19#define YDEPTH	5
20
21#define PAIR_DIGITS 1
22#define PAIR_OTHERS 2
23#define PAIR_FRAMES 3
24
25static short disp[11] =
26{
27    075557, 011111, 071747, 071717, 055711,
28    074717, 074757, 071111, 075757, 075717, 002020
29};
30static long older[6], next[6], newer[6], mask;
31
32static int sigtermed = 0;
33static bool redirected = FALSE;
34static bool hascolor = FALSE;
35
36static RETSIGTYPE
37sighndl(int signo)
38{
39    signal(signo, sighndl);
40    sigtermed = signo;
41    if (redirected) {
42	endwin();
43	ExitProgram(EXIT_FAILURE);
44    }
45}
46
47static void
48drawbox(bool scrolling)
49{
50    chtype bottom[XLENGTH + 1];
51    int n;
52
53    if (hascolor)
54	attrset(COLOR_PAIR(PAIR_FRAMES));
55
56    mvaddch(YBASE - 1, XBASE - 1, ACS_ULCORNER);
57    hline(ACS_HLINE, XLENGTH);
58    mvaddch(YBASE - 1, XBASE + XLENGTH, ACS_URCORNER);
59
60    mvaddch(YBASE + YDEPTH, XBASE - 1, ACS_LLCORNER);
61    mvinchnstr(YBASE + YDEPTH, XBASE, bottom, XLENGTH);
62    for (n = 0; n < XLENGTH; n++) {
63	if (!scrolling)
64	    bottom[n] &= ~A_COLOR;
65	bottom[n] = ACS_HLINE | (bottom[n] & (A_ATTRIBUTES | A_COLOR));
66    }
67    mvaddchnstr(YBASE + YDEPTH, XBASE, bottom, XLENGTH);
68    mvaddch(YBASE + YDEPTH, XBASE + XLENGTH, ACS_LRCORNER);
69
70    move(YBASE, XBASE - 1);
71    vline(ACS_VLINE, YDEPTH);
72
73    move(YBASE, XBASE + XLENGTH);
74    vline(ACS_VLINE, YDEPTH);
75
76    if (hascolor)
77	attrset(COLOR_PAIR(PAIR_OTHERS));
78}
79
80static void
81standt(int on)
82{
83    if (on) {
84	if (hascolor) {
85	    attron(COLOR_PAIR(PAIR_DIGITS));
86	} else {
87	    attron(A_STANDOUT);
88	}
89    } else {
90	if (hascolor) {
91	    attron(COLOR_PAIR(PAIR_OTHERS));
92	} else {
93	    attroff(A_STANDOUT);
94	}
95    }
96}
97
98static void
99set(int t, int n)
100{
101    int i, m;
102
103    m = 7 << n;
104    for (i = 0; i < 5; i++) {
105	next[i] |= ((disp[t] >> ((4 - i) * 3)) & 07) << n;
106	mask |= (next[i] ^ older[i]) & m;
107    }
108    if (mask & m)
109	mask |= m;
110}
111
112static void
113usage(void)
114{
115    static const char *msg[] =
116    {
117	"Usage: gdc [options] [count]"
118	,""
119	,"Options:"
120	,"  -n  redirect input to /dev/null"
121	,"  -s  scroll each number into place, rather than flipping"
122	,""
123	,"If you specify a count, gdc runs for that number of seconds"
124    };
125    unsigned j;
126    for (j = 0; j < SIZEOF(msg); j++)
127	fprintf(stderr, "%s\n", msg[j]);
128    ExitProgram(EXIT_FAILURE);
129}
130
131int
132main(int argc, char *argv[])
133{
134    time_t now;
135    struct tm *tm;
136    long t, a;
137    int i, j, s, k;
138    int count = 0;
139    FILE *ofp = stdout;
140    FILE *ifp = stdin;
141    bool scrol = FALSE;
142
143    setlocale(LC_ALL, "");
144
145    signal(SIGINT, sighndl);
146    signal(SIGTERM, sighndl);
147
148    while ((k = getopt(argc, argv, "sn")) != EOF) {
149	switch (k) {
150	case 's':
151	    scrol = TRUE;
152	    break;
153	case 'n':
154	    ifp = fopen("/dev/null", "r");
155	    redirected = TRUE;
156	    break;
157	default:
158	    usage();
159	}
160    }
161    if (optind < argc) {
162	count = atoi(argv[optind++]);
163    }
164    if (optind < argc)
165	usage();
166
167    if (redirected) {
168	char *name = getenv("TERM");
169	if (name == 0
170	    || newterm(name, ofp, ifp) == 0) {
171	    fprintf(stderr, "cannot open terminal\n");
172	    ExitProgram(EXIT_FAILURE);
173	}
174
175    } else {
176	initscr();
177    }
178    cbreak();
179    noecho();
180    nodelay(stdscr, 1);
181    curs_set(0);
182
183    hascolor = has_colors();
184
185    if (hascolor) {
186	int bg = COLOR_BLACK;
187	start_color();
188#if HAVE_USE_DEFAULT_COLORS
189	if (use_default_colors() == OK)
190	    bg = -1;
191#endif
192	init_pair(PAIR_DIGITS, COLOR_BLACK, COLOR_RED);
193	init_pair(PAIR_OTHERS, COLOR_RED, bg);
194	init_pair(PAIR_FRAMES, COLOR_WHITE, bg);
195	attrset(COLOR_PAIR(PAIR_OTHERS));
196    }
197
198  restart:
199    for (j = 0; j < 5; j++)
200	older[j] = newer[j] = next[j] = 0;
201
202    clear();
203    drawbox(FALSE);
204
205    do {
206	char buf[30];
207
208	time(&now);
209	tm = localtime(&now);
210
211	mask = 0;
212	set(tm->tm_sec % 10, 0);
213	set(tm->tm_sec / 10, 4);
214	set(tm->tm_min % 10, 10);
215	set(tm->tm_min / 10, 14);
216	set(tm->tm_hour % 10, 20);
217	set(tm->tm_hour / 10, 24);
218	set(10, 7);
219	set(10, 17);
220
221	for (k = 0; k < 6; k++) {
222	    if (scrol) {
223		for (i = 0; i < 5; i++)
224		    newer[i] = (newer[i] & ~mask) | (newer[i + 1] & mask);
225		newer[5] = (newer[5] & ~mask) | (next[k] & mask);
226	    } else
227		newer[k] = (newer[k] & ~mask) | (next[k] & mask);
228	    next[k] = 0;
229	    for (s = 1; s >= 0; s--) {
230		standt(s);
231		for (i = 0; i < 6; i++) {
232		    if ((a = (newer[i] ^ older[i]) & (s ? newer : older)[i])
233			!= 0) {
234			for (j = 0, t = 1 << 26; t; t >>= 1, j++) {
235			    if (a & t) {
236				if (!(a & (t << 1))) {
237				    move(YBASE + i, XBASE + 2 * j);
238				}
239				addstr("  ");
240			    }
241			}
242		    }
243		    if (!s) {
244			older[i] = newer[i];
245		    }
246		}
247		if (!s) {
248		    if (scrol)
249			drawbox(TRUE);
250		    refresh();
251		    /*
252		     * If we're scrolling, space out the refreshes to fake
253		     * movement.  That's 7 frames, or 6 intervals, which would
254		     * be 166 msec if we spread it out over a second.  It looks
255		     * better (but will work on a slow terminal, e.g., less
256		     * than 9600bd) to squeeze that into a half-second, and use
257		     * half of 170 msec to ensure that the program doesn't eat
258		     * a lot of time when asking what time it is, at the top of
259		     * this loop -T.Dickey
260		     */
261		    if (scrol)
262			napms(85);
263		}
264	    }
265	}
266
267	/* this depends on the detailed format of ctime(3) */
268	(void) strcpy(buf, ctime(&now));
269	(void) strcpy(buf + 10, buf + 19);
270	mvaddstr(16, 30, buf);
271
272	move(6, 0);
273	drawbox(FALSE);
274	refresh();
275
276	/*
277	 * If we're not scrolling, wait 1000 msec (1 sec).  Use napms() rather
278	 * than sleep() because the latter does odd things on some systems,
279	 * e.g., suspending output as well.
280	 */
281	if (scrol)
282	    napms(500);
283	else
284	    napms(1000);
285
286	/*
287	 * This is a safe way to check if we're interrupted - making the signal
288	 * handler set a flag that we can check.  Since we're running
289	 * nodelay(), the wgetch() call returns immediately, and in particular
290	 * will return an error if interrupted.  This works only if we can
291	 * read from the input, of course.
292	 */
293	switch (wgetch(stdscr)) {
294	case 'q':
295	    count = 1;
296	    break;
297	case 's':
298	    nodelay(stdscr, FALSE);
299	    break;
300	case ' ':
301	    nodelay(stdscr, TRUE);
302	    break;
303#ifdef KEY_RESIZE
304	case KEY_RESIZE:
305#endif
306	case '?':
307	    goto restart;
308	case ERR:
309	    if (sigtermed) {
310		standend();
311		endwin();
312		fprintf(stderr, "gdc terminated by signal %d\n", sigtermed);
313		ExitProgram(EXIT_FAILURE);
314	    }
315	    /* FALLTHRU */
316	default:
317	    continue;
318	}
319    } while (--count);
320    standend();
321    endwin();
322    ExitProgram(EXIT_SUCCESS);
323}
324