9e536585268f1ac48d1ccdbb79c0214b689b968b
[xscreensaver] / driver / stderr.c
1 /* stderr.c --- capturing stdout/stderr output onto the screensaver window.
2  * xscreensaver, Copyright (c) 1991-1997 Jamie Zawinski <jwz@netscape.com>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* stderr hackery - Why Unix Sucks, reason number 32767.
14  */
15
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
19
20 #include <stdlib.h>
21
22 #include <stdio.h>
23 #include <time.h>
24
25 #ifdef HAVE_UNISTD_H
26 # include <unistd.h>
27 #endif
28
29 #ifdef HAVE_FCNTL
30 # include <fcntl.h>
31 #endif
32
33 #include <X11/Intrinsic.h>
34
35 #include "xscreensaver.h"
36 #include "resources.h"
37 #include "visual.h"
38
39 FILE *real_stderr = 0;
40 FILE *real_stdout = 0;
41
42
43 /* It's ok for these to be global, since they refer to the one and only
44    stderr stream, not to a particular screen or window or visual.
45  */
46 static char stderr_buffer [4096];
47 static char *stderr_tail = 0;
48 static time_t stderr_last_read = 0;
49
50 static void make_stderr_overlay_window (saver_screen_info *);
51
52
53 void
54 reset_stderr (saver_screen_info *ssi)
55 {
56   saver_info *si = ssi->global;
57
58   if (si->prefs.debug_p)
59     fprintf ((real_stderr ? real_stderr : stderr),
60              "%s: resetting stderr\n", progname);
61
62   ssi->stderr_text_x = 0;
63   ssi->stderr_text_y = 0;
64
65   if (ssi->stderr_gc)
66     XFreeGC (si->dpy, ssi->stderr_gc);
67   ssi->stderr_gc = 0;
68
69   if (ssi->stderr_overlay_window)
70     XDestroyWindow(si->dpy, ssi->stderr_overlay_window);
71   ssi->stderr_overlay_window = 0;
72
73   if (ssi->stderr_cmap)
74     XFreeColormap(si->dpy, ssi->stderr_cmap);
75   ssi->stderr_cmap = 0;
76 }
77
78 void
79 clear_stderr (saver_screen_info *ssi)
80 {
81   saver_info *si = ssi->global;
82   ssi->stderr_text_x = 0;
83   ssi->stderr_text_y = 0;
84   if (ssi->stderr_overlay_window)
85     XClearWindow (si->dpy, ssi->stderr_overlay_window);
86 }
87
88
89 static void
90 print_stderr_1 (saver_screen_info *ssi, char *string)
91 {
92   saver_info *si = ssi->global;
93   saver_preferences *p = &si->prefs;
94   Display *dpy = si->dpy;
95   Screen *screen = ssi->screen;
96   Window window = (ssi->stderr_overlay_window ?
97                    ssi->stderr_overlay_window :
98                    ssi->screensaver_window);
99   int h_border = 20;
100   int v_border = 20;
101   char *head = string;
102   char *tail;
103
104   /* In verbose mode, copy it to stderr as well. */
105   if (p->verbose_p)
106     fprintf (real_stderr, "%s", string);
107
108   if (! ssi->stderr_font)
109     {
110       char *font_name = get_string_resource ("font", "Font");
111       if (!font_name) font_name = "fixed";
112       ssi->stderr_font = XLoadQueryFont (dpy, font_name);
113       if (! ssi->stderr_font) ssi->stderr_font = XLoadQueryFont (dpy, "fixed");
114       ssi->stderr_line_height = (ssi->stderr_font->ascent +
115                                  ssi->stderr_font->descent);
116     }
117
118   if (! ssi->stderr_gc)
119     {
120       XGCValues gcv;
121       Pixel fg, bg;
122       Colormap cmap = ssi->cmap;
123
124       if (!ssi->stderr_overlay_window &&
125           get_boolean_resource("overlayStderr", "Boolean"))
126         {
127           make_stderr_overlay_window (ssi);
128           if (ssi->stderr_overlay_window)
129             window = ssi->stderr_overlay_window;
130           if (ssi->stderr_cmap)
131             cmap = ssi->stderr_cmap;
132         }
133
134       fg = get_pixel_resource ("textForeground", "Foreground", dpy, cmap);
135       bg = get_pixel_resource ("textBackground", "Background", dpy, cmap);
136       gcv.font = ssi->stderr_font->fid;
137       gcv.foreground = fg;
138       gcv.background = bg;
139       ssi->stderr_gc = XCreateGC (dpy, window,
140                                   (GCFont | GCForeground | GCBackground),
141                                   &gcv);
142     }
143
144
145   if (ssi->stderr_cmap)
146     XInstallColormap(si->dpy, ssi->stderr_cmap);
147
148   for (tail = string; *tail; tail++)
149     {
150       if (*tail == '\n' || *tail == '\r')
151         {
152           int maxy = HeightOfScreen (screen) - v_border - v_border;
153           if (tail != head)
154             XDrawImageString (dpy, window, ssi->stderr_gc,
155                               ssi->stderr_text_x + h_border,
156                               ssi->stderr_text_y + v_border +
157                               ssi->stderr_font->ascent,
158                               head, tail - head);
159           ssi->stderr_text_x = 0;
160           ssi->stderr_text_y += ssi->stderr_line_height;
161           head = tail + 1;
162           if (*tail == '\r' && *head == '\n')
163             head++, tail++;
164
165           if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
166             {
167 #if 0
168               ssi->stderr_text_y = 0;
169 #else
170               int offset = ssi->stderr_line_height * 5;
171               XCopyArea (dpy, window, window, ssi->stderr_gc,
172                          0, v_border + offset,
173                          WidthOfScreen (screen),
174                          (HeightOfScreen (screen) - v_border - v_border
175                           - offset),
176                          0, v_border);
177               XClearArea (dpy, window,
178                           0, HeightOfScreen (screen) - v_border - offset,
179                           WidthOfScreen (screen), offset, False);
180               ssi->stderr_text_y -= offset;
181 #endif
182             }
183         }
184     }
185   if (tail != head)
186     {
187       int direction, ascent, descent;
188       XCharStruct overall;
189       XDrawImageString (dpy, window, ssi->stderr_gc,
190                         ssi->stderr_text_x + h_border,
191                         ssi->stderr_text_y + v_border
192                           + ssi->stderr_font->ascent,
193                         head, tail - head);
194       XTextExtents (ssi->stderr_font, tail, tail - head,
195                     &direction, &ascent, &descent, &overall);
196       ssi->stderr_text_x += overall.width;
197     }
198 }
199
200 static void
201 make_stderr_overlay_window (saver_screen_info *ssi)
202 {
203   saver_info *si = ssi->global;
204   unsigned long transparent_pixel = 0;
205   Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
206   if (visual)
207     {
208       int depth = visual_depth (ssi->screen, visual);
209       XSetWindowAttributes attrs;
210       unsigned long attrmask;
211
212       if (si->prefs.debug_p)
213         fprintf(real_stderr,
214                 "%s: using overlay visual 0x%0x for stderr text layer.\n",
215                 progname, (int) XVisualIDFromVisual (visual));
216
217       ssi->stderr_cmap = XCreateColormap(si->dpy,
218                                          RootWindowOfScreen(ssi->screen),
219                                          visual, AllocNone);
220
221       attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
222                   CWBackingStore | CWSaveUnder);
223       attrs.colormap = ssi->stderr_cmap;
224       attrs.background_pixel = transparent_pixel;
225       attrs.backing_pixel = transparent_pixel;
226       attrs.border_pixel = transparent_pixel;
227       attrs.backing_store = NotUseful;
228       attrs.save_under = False;
229
230       ssi->stderr_overlay_window =
231         XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
232                       WidthOfScreen(ssi->screen), HeightOfScreen(ssi->screen),
233                       0, depth, InputOutput, visual, attrmask, &attrs);
234       XMapRaised(si->dpy, ssi->stderr_overlay_window);
235     }
236 }
237
238
239 static void
240 print_stderr (saver_info *si, char *string)
241 {
242   saver_preferences *p = &si->prefs;
243   int i;
244
245   /* In verbose mode, copy it to stderr as well. */
246   if (p->verbose_p)
247     fprintf (real_stderr, "%s", string);
248
249   for (i = 0; i < si->nscreens; i++)
250     print_stderr_1 (&si->screens[i], string);
251 }
252
253
254 static void
255 stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
256 {
257   saver_info *si = (saver_info *) closure;
258   char *s = stderr_buffer;
259   if (*s)
260     {
261       /* If too much data was printed, then something has gone haywire,
262          so truncate it. */
263       char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
264       int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
265       if (strlen (s) > max)
266         strcpy (s + max, trailer);
267       /* Now show the user. */
268       print_stderr (si, s);
269     }
270
271   stderr_tail = stderr_buffer;
272   si->stderr_popup_timer = 0;
273 }
274
275
276 static void
277 stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
278 {
279   saver_info *si = (saver_info *) closure;
280   char *s;
281   int left;
282   int size;
283   int read_this_time = 0;
284
285   if (stderr_tail == 0)
286     stderr_tail = stderr_buffer;
287
288   left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
289
290   s = stderr_tail;
291   *s = 0;
292
293   /* Read as much data from the fd as we can, up to our buffer size. */
294   if (left > 0)
295     {
296       while ((size = read (*fd, (void *) s, left)) > 0)
297         {
298           left -= size;
299           s += size;
300           read_this_time += size;
301         }
302       *s = 0;
303     }
304   else
305     {
306       char buf2 [1024];
307       /* The buffer is full; flush the rest of it. */
308       while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
309         ;
310     }
311
312   stderr_tail = s;
313   stderr_last_read = time ((time_t *) 0);
314
315   /* Now we have read some data that we would like to put up in a dialog
316      box.  But more data may still be coming in - so don't pop up the
317      dialog right now, but instead, start a timer that will pop it up
318      a second from now.  Should more data come in in the meantime, we
319      will be called again, and will reset that timer again.  So the
320      dialog will only pop up when a second has elapsed with no new data
321      being written to stderr.
322
323      However, if the buffer is full (meaning lots of data has been written)
324      then we don't reset the timer.
325    */
326   if (read_this_time > 0)
327     {
328       if (si->stderr_popup_timer)
329         XtRemoveTimeOut (si->stderr_popup_timer);
330
331       si->stderr_popup_timer =
332         XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
333                          (XtPointer) si);
334     }
335 }
336
337 void
338 initialize_stderr (saver_info *si)
339 {
340   static Boolean done = False;
341   int fds [2];
342   int in, out;
343   int new_stdout, new_stderr;
344   int stdout_fd = 1;
345   int stderr_fd = 2;
346   int flags = 0;
347   Boolean stderr_dialog_p, stdout_dialog_p;
348
349   if (done) return;
350   done = True;
351
352   real_stderr = stderr;
353   real_stdout = stdout;
354
355   stderr_dialog_p = get_boolean_resource ("captureStderr", "Boolean");
356   stdout_dialog_p = get_boolean_resource ("captureStdout", "Boolean");
357
358   if (!stderr_dialog_p && !stdout_dialog_p)
359     return;
360
361   if (pipe (fds))
362     {
363       perror ("error creating pipe:");
364       return;
365     }
366
367   in = fds [0];
368   out = fds [1];
369
370 # ifdef HAVE_FCNTL
371
372 #  if defined(O_NONBLOCK)
373    flags = O_NONBLOCK;
374 #  elif defined(O_NDELAY)
375    flags = O_NDELAY;
376 #  else
377    ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
378 #  endif
379
380     /* Set both sides of the pipe to nonblocking - this is so that
381        our reads (in stderr_callback) will terminate, and so that
382        out writes (in the client programs) will silently fail when
383        the pipe is full, instead of hosing the program. */
384   if (fcntl (in, F_SETFL, flags) != 0)
385     {
386       perror ("fcntl:");
387       return;
388     }
389   if (fcntl (out, F_SETFL, flags) != 0)
390     {
391       perror ("fcntl:");
392       return;
393     }
394
395 # endif /* !HAVE_FCNTL */
396
397   if (stderr_dialog_p)
398     {
399       FILE *new_stderr_file;
400       new_stderr = dup (stderr_fd);
401       if (new_stderr < 0)
402         {
403           perror ("could not dup() a stderr:");
404           return;
405         }
406       if (! (new_stderr_file = fdopen (new_stderr, "w")))
407         {
408           perror ("could not fdopen() the new stderr:");
409           return;
410         }
411       real_stderr = new_stderr_file;
412
413       close (stderr_fd);
414       if (dup2 (out, stderr_fd) < 0)
415         {
416           perror ("could not dup() a new stderr:");
417           return;
418         }
419     }
420
421   if (stdout_dialog_p)
422     {
423       FILE *new_stdout_file;
424       new_stdout = dup (stdout_fd);
425       if (new_stdout < 0)
426         {
427           perror ("could not dup() a stdout:");
428           return;
429         }
430       if (! (new_stdout_file = fdopen (new_stdout, "w")))
431         {
432           perror ("could not fdopen() the new stdout:");
433           return;
434         }
435       real_stdout = new_stdout_file;
436
437       close (stdout_fd);
438       if (dup2 (out, stdout_fd) < 0)
439         {
440           perror ("could not dup() a new stdout:");
441           return;
442         }
443     }
444
445   XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
446                  (XtPointer) si);
447 }