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