1/* watch -- execute a program repeatedly, displaying output fullscreen
2 *
3 * Based on the original 1991 'watch' by Tony Rems <rembo@unisoft.com>
4 * (with mods and corrections by Francois Pinard).
5 *
6 * Substantially reworked, new features (differences option, SIGWINCH
7 * handling, unlimited command length, long line handling) added Apr 1999 by
8 * Mike Coleman <mkc@acm.org>.
9 *
10 * Changes by Albert Cahalan, 2002-2003.
11 */
12
13#define VERSION "0.2.0"
14
15#include <ctype.h>
16#include <getopt.h>
17#include <signal.h>
18#include <ncurses.h>
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <sys/ioctl.h>
23#include <time.h>
24#include <unistd.h>
25#include <locale.h>
26/* #include "proc/procps.h" */
27
28static struct option longopts[] = {
29	{"differences", optional_argument, 0, 'd'},
30	{"help", no_argument, 0, 'h'},
31	{"interval", required_argument, 0, 'n'},
32	{"no-title", no_argument, 0, 't'},
33	{"version", no_argument, 0, 'v'},
34	{0, 0, 0, 0}
35};
36
37static char usage[] =
38    "Usage: %s [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>] [--no-title] [--version] <command>\n";
39
40static char *progname;
41
42static int curses_started = 0;
43static int height = 24, width = 80;
44static int screen_size_changed = 0;
45static int first_screen = 1;
46static int show_title = 2;  // number of lines used, 2 or 0
47
48#define min(x,y) ((x) > (y) ? (y) : (x))
49
50//static void do_usage(void) NORETURN;
51static void do_usage(void)
52{
53	fprintf(stderr, usage, progname);
54	exit(1);
55}
56
57//static void do_exit(int status) NORETURN;
58static void do_exit(int status)
59{
60	if (curses_started)
61		endwin();
62	exit(status);
63}
64
65/* signal handler */
66//static void die(int notused) NORETURN;
67static void die(int notused)
68{
69	(void) notused;
70	do_exit(0);
71}
72
73static void
74winch_handler(int notused)
75{
76	(void) notused;
77	screen_size_changed = 1;
78}
79
80static void
81get_terminal_size(void)
82{
83	struct winsize w;
84	if (ioctl(2, TIOCGWINSZ, &w) == 0) {
85		if (w.ws_row > 0)
86			height = w.ws_row;
87		if (w.ws_col > 0)
88			width = w.ws_col;
89	}
90}
91
92int
93main(int argc, char *argv[])
94{
95	int optc;
96	int option_differences = 0,
97	    option_differences_cumulative = 0,
98	    option_help = 0, option_version = 0;
99	int interval = 2;
100	char *command;
101	int command_length = 0;	/* not including final \0 */
102
103	setlocale(LC_ALL, "");
104	progname = argv[0];
105
106	while ((optc = getopt_long(argc, argv, "+d::hn:vt", longopts, (int *) 0))
107	       != EOF) {
108		switch (optc) {
109		case 'd':
110			option_differences = 1;
111			if (optarg)
112				option_differences_cumulative = 1;
113			break;
114		case 'h':
115			option_help = 1;
116			break;
117		case 't':
118			show_title = 0;
119			break;
120		case 'n':
121			{
122				char *str;
123				interval = strtol(optarg, &str, 10);
124				if (!*optarg || *str)
125					do_usage();
126			}
127			break;
128		case 'v':
129			option_version = 1;
130			break;
131		default:
132			do_usage();
133			break;
134		}
135	}
136
137	if (option_version) {
138		fprintf(stderr, "%s\n", VERSION);
139		if (!option_help)
140			exit(0);
141	}
142
143	if (option_help) {
144		fprintf(stderr, usage, progname);
145		fputs("  -d, --differences[=cumulative]\thighlight changes between updates\n", stderr);
146		fputs("\t\t(cumulative means highlighting is cumulative)\n", stderr);
147		fputs("  -h, --help\t\t\t\tprint a summary of the options\n", stderr);
148		fputs("  -n, --interval=<seconds>\t\tseconds to wait between updates\n", stderr);
149		fputs("  -v, --version\t\t\t\tprint the version number\n", stderr);
150		fputs("  -t, --no-title\t\t\tturns off showing the header\n", stderr);
151		exit(0);
152	}
153
154	if (optind >= argc)
155		do_usage();
156
157	command = strdup(argv[optind++]);
158	command_length = strlen(command);
159	for (; optind < argc; optind++) {
160		char *endp;
161		int s = strlen(argv[optind]);
162		command = realloc(command, command_length + s + 2);	/* space and \0 */
163		endp = command + command_length;
164		*endp = ' ';
165		memcpy(endp + 1, argv[optind], s);
166		command_length += 1 + s;	/* space then string length */
167		command[command_length] = '\0';
168	}
169
170	get_terminal_size();
171
172	/* Catch keyboard interrupts so we can put tty back in a sane state.  */
173	signal(SIGINT, die);
174	signal(SIGTERM, die);
175	signal(SIGHUP, die);
176	signal(SIGWINCH, winch_handler);
177
178	/* Set up tty for curses use.  */
179	curses_started = 1;
180	initscr();
181	nonl();
182	noecho();
183	cbreak();
184
185	for (;;) {
186		time_t t = time(NULL);
187		char *ts = ctime(&t);
188		int tsl = strlen(ts);
189		char *header;
190		FILE *p;
191		int x, y;
192		int oldeolseen = 1;
193
194		if (screen_size_changed) {
195			get_terminal_size();
196			resizeterm(height, width);
197			clear();
198			/* redrawwin(stdscr); */
199			screen_size_changed = 0;
200			first_screen = 1;
201		}
202
203		if (show_title) {
204			// left justify interval and command,
205			// right justify time, clipping all to fit window width
206			asprintf(&header, "Every %ds: %.*s",
207				 interval, min(width - 1, command_length), command);
208			mvaddstr(0, 0, header);
209			if (strlen(header) > (size_t) (width - tsl - 1))
210				mvaddstr(0, width - tsl - 4, "...  ");
211			mvaddstr(0, width - tsl + 1, ts);
212			free(header);
213		}
214
215		if (!(p = popen(command, "r"))) {
216			perror("popen");
217			do_exit(2);
218		}
219
220		for (y = show_title; y < height; y++) {
221			int eolseen = 0, tabpending = 0;
222			for (x = 0; x < width; x++) {
223				int c = ' ';
224				int attr = 0;
225
226				if (!eolseen) {
227					/* if there is a tab pending, just spit spaces until the
228					   next stop instead of reading characters */
229					if (!tabpending)
230						do
231							c = getc(p);
232						while (c != EOF && !isprint(c)
233						       && c != '\n'
234						       && c != '\t');
235					if (c == '\n')
236						if (!oldeolseen && x == 0) {
237							x = -1;
238							continue;
239						} else
240							eolseen = 1;
241					else if (c == '\t')
242						tabpending = 1;
243					if (c == EOF || c == '\n' || c == '\t')
244						c = ' ';
245					if (tabpending && (((x + 1) % 8) == 0))
246						tabpending = 0;
247				}
248				move(y, x);
249				if (option_differences) {
250					int oldch = inch();
251					char oldc = oldch & A_CHARTEXT;
252					attr = !first_screen
253					    && (c != oldc
254						||
255						(option_differences_cumulative
256						 && (oldch & A_ATTRIBUTES)));
257				}
258				if (attr)
259					standout();
260				addch(c);
261				if (attr)
262					standend();
263			}
264			oldeolseen = eolseen;
265		}
266
267		pclose(p);
268
269		first_screen = 0;
270		refresh();
271		sleep(interval);
272	}
273
274	endwin();
275
276	return 0;
277}
278