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