xref: /haiku/src/bin/watch.c (revision 21258e2674226d6aa732321b6f8494841895af5f)
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 
28 static 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 
37 static char usage[] =
38     "Usage: %s [-dhntv] [--differences[=cumulative]] [--help] [--interval=<n>] [--no-title] [--version] <command>\n";
39 
40 static char *progname;
41 
42 static int curses_started = 0;
43 static int height = 24, width = 80;
44 static int screen_size_changed = 0;
45 static int first_screen = 1;
46 static 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;
51 static void do_usage(void)
52 {
53 	fprintf(stderr, usage, progname);
54 	exit(1);
55 }
56 
57 //static void do_exit(int status) NORETURN;
58 static 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;
67 static void die(int notused)
68 {
69 	(void) notused;
70 	do_exit(0);
71 }
72 
73 static void
74 winch_handler(int notused)
75 {
76 	(void) notused;
77 	screen_size_changed = 1;
78 }
79 
80 static void
81 get_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 
92 int
93 main(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