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