Fix relative order of `automake' and `autoconf'.
[xtoys] / xcatch.c
1 /* -*-c-*-
2 *
3 * $Id: xcatch.c,v 1.8 1999/06/19 23:42:55 mdw Exp $
4 *
5 * Catch input and trap it in an X window
6 *
7 * (c) 1998 Straylight/Edgeware
8 */
9
10 /*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of the Edgeware X tools collection.
13 *
14 * X tools is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * X tools is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with X tools; if not, write to the Free Software Foundation,
26 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 */
28
29 /*----- Revision history --------------------------------------------------*
30 *
31 * $Log: xcatch.c,v $
32 * Revision 1.8 1999/06/19 23:42:55 mdw
33 * Improve signal handling. Fix options parsing to POSIX order only.
34 *
35 * Revision 1.7 1999/05/21 22:09:19 mdw
36 * Take advantage of new dynamic string macros.
37 *
38 * Revision 1.6 1999/05/19 20:41:15 mdw
39 * Track gratuitous change in mdwopt interface.
40 *
41 * Revision 1.5 1999/05/05 18:55:18 mdw
42 * Block SIGCHLD around the `fork' call to prevent a race.
43 *
44 * Revision 1.4 1999/03/24 22:23:57 mdw
45 * Improve display for large files. Keep newly added material in view if
46 * scrolled to bottom of window.
47 *
48 * Revision 1.3 1998/12/20 17:19:16 mdw
49 * Return exit status of child process, rather than always returning
50 * success.
51 *
52 * Revision 1.2 1998/12/16 00:10:58 mdw
53 * Fix tabbing in help text.
54 *
55 * Revision 1.1 1998/12/15 23:46:50 mdw
56 * New program: captures input and puts it in a window.
57 *
58 */
59
60 /*----- Header files ------------------------------------------------------*/
61
62 #include <errno.h>
63 #include <signal.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67
68 #include <sys/types.h>
69 #include <sys/wait.h>
70
71 #include <fcntl.h>
72 #include <unistd.h>
73
74 #include <gtk/gtk.h>
75
76 #include <mLib/dstr.h>
77 #include <mLib/mdwopt.h>
78 #include <mLib/report.h>
79 #include <mLib/quis.h>
80
81 #include <mgLib/cancel.h>
82 #include <mgLib/msg.h>
83
84 /*----- Inportant state ---------------------------------------------------*/
85
86 static unsigned int flags;
87
88 enum {
89 f_closed = 1,
90 f_bogus = 2
91 };
92
93 static GtkWidget *textbox = 0;
94 static GdkFont *font;
95
96 static pid_t kid = -1;
97 static int status;
98
99 /*----- Main code ---------------------------------------------------------*/
100
101 /* --- The window's closed --- */
102
103 static void killwin(GtkWidget *w, gpointer p)
104 {
105 if (flags & f_closed)
106 gtk_main_quit();
107 else
108 textbox = 0;
109 }
110
111 /* --- Some input has arrived --- */
112
113 static void ready(gpointer data, gint fd, GdkInputCondition c)
114 {
115 char buf[1024];
116 int doscroll = 1;
117 GtkText *t;
118 GtkAdjustment *va;
119
120 /* --- If not ready to read then go away --- */
121
122 if (!(c & GDK_INPUT_READ))
123 return;
124
125 /* --- Decide whether to scroll the window --- */
126
127 if (textbox) {
128 t = GTK_TEXT(textbox);
129 va = t->vadj;
130 if (va->value + va->page_size < va->upper)
131 doscroll = 0;
132 gtk_text_freeze(t);
133 }
134
135 /* --- Read data into the buffer --- *
136 *
137 * This is a bit of a mess.
138 */
139
140 for (;;) {
141 int r = read(fd, buf, sizeof(buf));
142
143 /* --- The read failed --- *
144 *
145 * Maybe there's no more data to read. In this case, we get
146 * @EWOULDBLOCK@, indicating it's time to stop and do something else.
147 * Otherwise something serious has happened.
148 */
149
150 if (r < 0) {
151 if (errno == EWOULDBLOCK)
152 break;
153 msg(":~OK", "error reading data: %s", strerror(errno));
154 exit(EXIT_FAILURE);
155 }
156
157 /* --- End of file --- *
158 *
159 * If the box is closed, then exit quiety; otherwise wait for it to
160 * go away.
161 */
162
163 if (r == 0) {
164 close(fd);
165 if (textbox)
166 flags |= f_closed;
167 else
168 gtk_main_quit();
169 break;
170 }
171
172 /* --- If there's no output window, create one --- */
173
174 if (!textbox) {
175 GtkWidget *win;
176 GtkWidget *tbl;
177 GtkWidget *w;
178
179 win = gtk_dialog_new();
180 gtk_signal_connect(GTK_OBJECT(win), "destroy",
181 GTK_SIGNAL_FUNC(killwin), 0);
182
183 tbl = gtk_table_new(2, 2, 0);
184 gtk_container_border_width(GTK_CONTAINER(tbl), 8);
185 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(win)->vbox), tbl, 1, 1, 0);
186 gtk_widget_show(tbl);
187
188 textbox = gtk_text_new(0, 0);
189 t = GTK_TEXT(textbox);
190 va = t->vadj;
191 gtk_table_attach(GTK_TABLE(tbl), textbox, 0, 1, 0, 1,
192 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
193 GTK_EXPAND | GTK_SHRINK | GTK_FILL,
194 0, 0);
195 gtk_text_set_editable(t, 0);
196 gtk_widget_set_usize(textbox, 500, 300);
197 gtk_text_freeze(t);
198 gtk_widget_show(textbox);
199
200 w = gtk_vscrollbar_new(va);
201 gtk_table_attach(GTK_TABLE(tbl), w, 1, 2, 0, 1,
202 0, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
203 gtk_widget_show(w);
204
205 w = gtk_hscrollbar_new(t->hadj);
206 gtk_table_attach(GTK_TABLE(tbl), w, 0, 1, 1, 2,
207 GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0, 0);
208 gtk_widget_show(w);
209
210 gtk_box_set_homogeneous(GTK_BOX(GTK_DIALOG(win)->action_area), 0);
211 w = gtk_button_new_with_label("Dismiss");
212 gtk_signal_connect_object(GTK_OBJECT(w), "clicked",
213 GTK_SIGNAL_FUNC(gtk_object_destroy),
214 GTK_OBJECT(win));
215 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(win)->action_area), w, 0, 0, 0);
216 GTK_WIDGET_SET_FLAGS(w, GTK_CAN_DEFAULT);
217 gtk_widget_grab_default(w);
218 cancel(GTK_WINDOW(win), w);
219 gtk_widget_show(w);
220
221 gtk_widget_show(win);
222 }
223
224 gtk_text_insert(t, font, 0, 0, buf, r);
225 }
226
227 if (textbox) {
228 gtk_text_thaw(t);
229 if (doscroll)
230 gtk_adjustment_set_value(va, va->upper - va->page_size);
231 }
232 }
233
234 /* --- Signal handler --- */
235
236 static void reap(int sig)
237 {
238 pid_t k;
239 int s;
240 int e = errno;
241
242 for (;;) {
243 k = waitpid(-1, &s, WNOHANG);
244 if (k <= 0)
245 break;
246 if (k == kid) {
247 if (WIFEXITED(s))
248 status = WEXITSTATUS(s);
249 else
250 status = 127;
251 }
252 }
253 errno = e;
254 }
255
256 /* --- Main program --- */
257
258 static void version(FILE *fp)
259 {
260 fprintf(fp, "%s (xtoys version " VERSION ")\n", QUIS);
261 }
262
263 static void usage(FILE *fp)
264 {
265 fprintf(fp, "Usage: %s [-f file] [-F font] [command [args...]]\n", QUIS);
266 }
267
268 int main(int argc, char *argv[])
269 {
270 int fd = -1;
271
272 ego(argv[0]);
273
274 gtk_init(&argc, &argv);
275
276 for (;;) {
277 static struct option opt[] = {
278 { "help", 0, 0, 'h' },
279 { "usage", 0, 0, 'u' },
280 { "version", 0, 0, 'v' },
281 { "file", OPTF_ARGREQ, 0, 'f' },
282 { "font", OPTF_ARGREQ, 0, 'F' },
283 { 0, 0, 0, 0 }
284 };
285 int i = mdwopt(argc, argv, "+huvf:F:", opt, 0, 0, 0);
286
287 if (i < 0)
288 break;
289
290 switch (i) {
291 case 'h':
292 version(stdout);
293 fputc('\n', stdout);
294 usage(stdout);
295 fputs(
296 "\n"
297 "Catches input from a pipe or other source, and captures it in a window.\n"
298 "Nothing is displayed if there's no input.\n"
299 "\n"
300 "Options provided:\n"
301 "\n"
302 "-h, --help Display this help text\n"
303 "-u, --usage Display a quick usage summary\n"
304 "-v, --version Display the version number\n"
305 "-f, --file=FILE\t Read input from the named file\n"
306 "-F, --font=FONT\t Display output in the named font\n",
307 stdout);
308 exit(0);
309 break;
310 case 'u':
311 usage(stdout);
312 exit(0);
313 break;
314 case 'v':
315 version(stdout);
316 exit(0);
317 break;
318 case 'f':
319 if ((fd = open(optarg, O_RDONLY)) < 0) {
320 die(1, "couldn't open file: %s", strerror(errno));
321 exit(1);
322 }
323 break;
324 case 'F':
325 font = gdk_font_load(optarg);
326 break;
327 default:
328 flags |= f_bogus;
329 break;
330 }
331 }
332
333 if (flags & f_bogus) {
334 usage(stderr);
335 exit(1);
336 }
337
338 if (fd == -1) {
339 if (optind == argc)
340 fd = STDIN_FILENO;
341 else {
342 int pfd[2];
343 struct sigaction sa;
344 sigset_t newmask, oldmask;
345
346 /* --- Set up a signal handler --- */
347
348 sa.sa_handler = reap;
349 sigemptyset(&sa.sa_mask);
350 sa.sa_flags = SA_NOCLDSTOP;
351 #ifdef SA_RESTART
352 sa.sa_flags |= SA_RESTART;
353 #endif
354 sigaction(SIGCHLD, &sa, 0);
355
356 /* --- Start a child program --- */
357
358 if (pipe(pfd))
359 die(1, "couldn't open pipe: %s", strerror(errno));
360
361 sigemptyset(&newmask);
362 sigaddset(&newmask, SIGCHLD);
363 sigprocmask(SIG_BLOCK, &newmask, &oldmask);
364
365 kid = fork();
366 if (kid < 0)
367 die(1, "couldn't fork: %s", strerror(errno));
368 if (kid == 0) {
369 dstr d = DSTR_INIT;
370
371 close(pfd[0]);
372 if (pfd[1] != STDOUT_FILENO)
373 dup2(pfd[1], STDOUT_FILENO);
374 if (pfd[1] != STDERR_FILENO)
375 dup2(pfd[1], STDERR_FILENO);
376 if (pfd[1] != STDOUT_FILENO && pfd[1] != STDERR_FILENO)
377 close(pfd[1]);
378 execvp(argv[optind], argv + optind);
379
380 dstr_putf(&d, "%s: couldn't run `%s': %s\n",
381 QUIS, argv[optind], strerror(errno));
382 write(STDERR_FILENO, d.buf, d.len);
383 dstr_destroy(&d);
384 _exit(127);
385 }
386
387 sigprocmask(SIG_SETMASK, &oldmask, 0);
388 fd = pfd[0];
389 close(pfd[1]);
390 }
391 }
392
393 {
394 int f = fcntl(fd, F_GETFL);
395 fcntl(fd, F_SETFL, f | O_NONBLOCK);
396 }
397
398 gdk_input_add(fd, GDK_INPUT_READ, ready, 0);
399 gtk_main();
400 return (status);
401 }
402
403 /*----- That's all, folks -------------------------------------------------*/