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