d4361df8d5e3a4f72732e3f0282ad5b141faa3f4
[xscreensaver] / driver / stderr.c
1 /* stderr.c --- capturing stdout/stderr output onto the screensaver window.
2  * xscreensaver, Copyright (c) 1991-1998 Jamie Zawinski <jwz@jwz.org>
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 int stderr_stdout_read_fd = -1;
51
52 static void make_stderr_overlay_window (saver_screen_info *);
53
54
55 /* Recreates the stderr window or GCs: do this when the xscreensaver window
56    on a screen has been re-created.
57  */
58 void
59 reset_stderr (saver_screen_info *ssi)
60 {
61   saver_info *si = ssi->global;
62
63   if (si->prefs.debug_p)
64     fprintf ((real_stderr ? real_stderr : stderr),
65              "%s: resetting stderr\n", blurb());
66
67   ssi->stderr_text_x = 0;
68   ssi->stderr_text_y = 0;
69
70   if (ssi->stderr_gc)
71     XFreeGC (si->dpy, ssi->stderr_gc);
72   ssi->stderr_gc = 0;
73
74   if (ssi->stderr_overlay_window)
75     XDestroyWindow(si->dpy, ssi->stderr_overlay_window);
76   ssi->stderr_overlay_window = 0;
77
78   if (ssi->stderr_cmap)
79     XFreeColormap(si->dpy, ssi->stderr_cmap);
80   ssi->stderr_cmap = 0;
81 }
82
83 /* Erases any stderr text overlaying the screen (if possible) and resets
84    the stderr output cursor to the upper left.  Do this when the xscreensaver
85    window is cleared.
86  */
87 void
88 clear_stderr (saver_screen_info *ssi)
89 {
90   saver_info *si = ssi->global;
91   ssi->stderr_text_x = 0;
92   ssi->stderr_text_y = 0;
93   if (ssi->stderr_overlay_window)
94     XClearWindow (si->dpy, ssi->stderr_overlay_window);
95 }
96
97
98 /* Draws the string on the screen's window.
99  */
100 static void
101 print_stderr_1 (saver_screen_info *ssi, char *string)
102 {
103   saver_info *si = ssi->global;
104   Display *dpy = si->dpy;
105   Screen *screen = ssi->screen;
106   Window window = (ssi->stderr_overlay_window ?
107                    ssi->stderr_overlay_window :
108                    ssi->screensaver_window);
109   int h_border = 20;
110   int v_border = 20;
111   char *head = string;
112   char *tail;
113
114   if (! ssi->stderr_font)
115     {
116       char *font_name = get_string_resource ("font", "Font");
117       if (!font_name) font_name = "fixed";
118       ssi->stderr_font = XLoadQueryFont (dpy, font_name);
119       if (! ssi->stderr_font) ssi->stderr_font = XLoadQueryFont (dpy, "fixed");
120       ssi->stderr_line_height = (ssi->stderr_font->ascent +
121                                  ssi->stderr_font->descent);
122     }
123
124   if (! ssi->stderr_gc)
125     {
126       XGCValues gcv;
127       Pixel fg, bg;
128       Colormap cmap = ssi->cmap;
129
130       if (!ssi->stderr_overlay_window &&
131           get_boolean_resource("overlayStderr", "Boolean"))
132         {
133           make_stderr_overlay_window (ssi);
134           if (ssi->stderr_overlay_window)
135             window = ssi->stderr_overlay_window;
136           if (ssi->stderr_cmap)
137             cmap = ssi->stderr_cmap;
138         }
139
140       fg = get_pixel_resource ("overlayTextForeground","Foreground",dpy,cmap);
141       bg = get_pixel_resource ("overlayTextBackground","Background",dpy,cmap);
142       gcv.font = ssi->stderr_font->fid;
143       gcv.foreground = fg;
144       gcv.background = bg;
145       ssi->stderr_gc = XCreateGC (dpy, window,
146                                   (GCFont | GCForeground | GCBackground),
147                                   &gcv);
148     }
149
150
151   if (ssi->stderr_cmap)
152     XInstallColormap(si->dpy, ssi->stderr_cmap);
153
154   for (tail = string; *tail; tail++)
155     {
156       if (*tail == '\n' || *tail == '\r')
157         {
158           int maxy = HeightOfScreen (screen) - v_border - v_border;
159           if (tail != head)
160             XDrawImageString (dpy, window, ssi->stderr_gc,
161                               ssi->stderr_text_x + h_border,
162                               ssi->stderr_text_y + v_border +
163                               ssi->stderr_font->ascent,
164                               head, tail - head);
165           ssi->stderr_text_x = 0;
166           ssi->stderr_text_y += ssi->stderr_line_height;
167           head = tail + 1;
168           if (*tail == '\r' && *head == '\n')
169             head++, tail++;
170
171           if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
172             {
173 #if 0
174               ssi->stderr_text_y = 0;
175 #else
176               int offset = ssi->stderr_line_height * 5;
177               XWindowAttributes xgwa;
178               XGetWindowAttributes (dpy, window, &xgwa);
179
180               XCopyArea (dpy, window, window, ssi->stderr_gc,
181                          0, v_border + offset,
182                          xgwa.width,
183                          (xgwa.height - v_border - v_border - offset),
184                          0, v_border);
185               XClearArea (dpy, window,
186                           0, xgwa.height - v_border - offset,
187                           xgwa.width, offset, False);
188               ssi->stderr_text_y -= offset;
189 #endif
190             }
191         }
192     }
193   if (tail != head)
194     {
195       int direction, ascent, descent;
196       XCharStruct overall;
197       XDrawImageString (dpy, window, ssi->stderr_gc,
198                         ssi->stderr_text_x + h_border,
199                         ssi->stderr_text_y + v_border
200                           + ssi->stderr_font->ascent,
201                         head, tail - head);
202       XTextExtents (ssi->stderr_font, tail, tail - head,
203                     &direction, &ascent, &descent, &overall);
204       ssi->stderr_text_x += overall.width;
205     }
206 }
207
208 static void
209 make_stderr_overlay_window (saver_screen_info *ssi)
210 {
211   saver_info *si = ssi->global;
212   unsigned long transparent_pixel = 0;
213   Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
214   if (visual)
215     {
216       int depth = visual_depth (ssi->screen, visual);
217       XSetWindowAttributes attrs;
218       XWindowAttributes xgwa;
219       unsigned long attrmask;
220       XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
221
222       if (si->prefs.debug_p)
223         fprintf(real_stderr,
224                 "%s: using overlay visual 0x%0x for stderr text layer.\n",
225                 blurb(), (int) XVisualIDFromVisual (visual));
226
227       ssi->stderr_cmap = XCreateColormap(si->dpy,
228                                          RootWindowOfScreen(ssi->screen),
229                                          visual, AllocNone);
230
231       attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
232                   CWBackingStore | CWSaveUnder);
233       attrs.colormap = ssi->stderr_cmap;
234       attrs.background_pixel = transparent_pixel;
235       attrs.backing_pixel = transparent_pixel;
236       attrs.border_pixel = transparent_pixel;
237       attrs.backing_store = NotUseful;
238       attrs.save_under = False;
239
240       ssi->stderr_overlay_window =
241         XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
242                       xgwa.width, xgwa.height,
243                       0, depth, InputOutput, visual, attrmask, &attrs);
244       XMapRaised(si->dpy, ssi->stderr_overlay_window);
245     }
246 }
247
248
249 /* Draws the string on each screen's window as error text.
250  */
251 static void
252 print_stderr (saver_info *si, char *string)
253 {
254   saver_preferences *p = &si->prefs;
255   int i;
256
257   /* In verbose mode, copy it to stderr as well. */
258   if (p->verbose_p)
259     fprintf (real_stderr, "%s", string);
260
261   for (i = 0; i < si->nscreens; i++)
262     print_stderr_1 (&si->screens[i], string);
263 }
264
265
266 /* Polls the stderr buffer every few seconds and if it finds any text,
267    writes it on all screens.
268  */
269 static void
270 stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
271 {
272   saver_info *si = (saver_info *) closure;
273   char *s = stderr_buffer;
274   if (*s)
275     {
276       /* If too much data was printed, then something has gone haywire,
277          so truncate it. */
278       char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
279       int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
280       if (strlen (s) > max)
281         strcpy (s + max, trailer);
282       /* Now show the user. */
283       print_stderr (si, s);
284     }
285
286   stderr_tail = stderr_buffer;
287   si->stderr_popup_timer = 0;
288 }
289
290
291 /* Called when data becomes available on the stderr pipe.  Copies it into
292    stderr_buffer where stderr_popup_timer_fn() can find it later.
293  */
294 static void
295 stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
296 {
297   saver_info *si = (saver_info *) closure;
298   char *s;
299   int left;
300   int size;
301   int read_this_time = 0;
302
303   if (!fd || *fd < 0 || *fd != stderr_stdout_read_fd)
304     abort();
305
306   if (stderr_tail == 0)
307     stderr_tail = stderr_buffer;
308
309   left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
310
311   s = stderr_tail;
312   *s = 0;
313
314   /* Read as much data from the fd as we can, up to our buffer size. */
315   if (left > 0)
316     {
317       while ((size = read (*fd, (void *) s, left)) > 0)
318         {
319           left -= size;
320           s += size;
321           read_this_time += size;
322         }
323       *s = 0;
324     }
325   else
326     {
327       char buf2 [1024];
328       /* The buffer is full; flush the rest of it. */
329       while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
330         ;
331     }
332
333   stderr_tail = s;
334   stderr_last_read = time ((time_t *) 0);
335
336   /* Now we have read some data that we would like to put up in a dialog
337      box.  But more data may still be coming in - so don't pop up the
338      dialog right now, but instead, start a timer that will pop it up
339      a second from now.  Should more data come in in the meantime, we
340      will be called again, and will reset that timer again.  So the
341      dialog will only pop up when a second has elapsed with no new data
342      being written to stderr.
343
344      However, if the buffer is full (meaning lots of data has been written)
345      then we don't reset the timer.
346    */
347   if (read_this_time > 0)
348     {
349       if (si->stderr_popup_timer)
350         XtRemoveTimeOut (si->stderr_popup_timer);
351
352       si->stderr_popup_timer =
353         XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
354                          (XtPointer) si);
355     }
356 }
357
358 /* If stderr capturing is desired, this replaces `stdout' and `stderr'
359    with a pipe, so that any output written to them will show up on the
360    screen as well as on the original value of those streams.
361  */
362 void
363 initialize_stderr (saver_info *si)
364 {
365   static Boolean done = False;
366   int fds [2];
367   int in, out;
368   int new_stdout, new_stderr;
369   int stdout_fd = 1;
370   int stderr_fd = 2;
371   int flags = 0;
372   Boolean stderr_dialog_p;
373
374   if (done) return;
375   done = True;
376
377   real_stderr = stderr;
378   real_stdout = stdout;
379
380   stderr_dialog_p = get_boolean_resource ("captureStderr", "Boolean");
381
382   if (!stderr_dialog_p)
383     return;
384
385   if (pipe (fds))
386     {
387       perror ("error creating pipe:");
388       return;
389     }
390
391   in = fds [0];
392   out = fds [1];
393
394 # ifdef HAVE_FCNTL
395
396 #  if defined(O_NONBLOCK)
397    flags = O_NONBLOCK;
398 #  elif defined(O_NDELAY)
399    flags = O_NDELAY;
400 #  else
401    ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
402 #  endif
403
404     /* Set both sides of the pipe to nonblocking - this is so that
405        our reads (in stderr_callback) will terminate, and so that
406        out writes (in the client programs) will silently fail when
407        the pipe is full, instead of hosing the program. */
408   if (fcntl (in, F_SETFL, flags) != 0)
409     {
410       perror ("fcntl:");
411       return;
412     }
413   if (fcntl (out, F_SETFL, flags) != 0)
414     {
415       perror ("fcntl:");
416       return;
417     }
418
419 # endif /* !HAVE_FCNTL */
420
421   if (stderr_dialog_p)
422     {
423       FILE *new_stderr_file;
424       FILE *new_stdout_file;
425
426       new_stderr = dup (stderr_fd);
427       if (new_stderr < 0)
428         {
429           perror ("could not dup() a stderr:");
430           return;
431         }
432       if (! (new_stderr_file = fdopen (new_stderr, "w")))
433         {
434           perror ("could not fdopen() the new stderr:");
435           return;
436         }
437       real_stderr = new_stderr_file;
438
439       close (stderr_fd);
440       if (dup2 (out, stderr_fd) < 0)
441         {
442           perror ("could not dup() a new stderr:");
443           return;
444         }
445
446
447       new_stdout = dup (stdout_fd);
448       if (new_stdout < 0)
449         {
450           perror ("could not dup() a stdout:");
451           return;
452         }
453       if (! (new_stdout_file = fdopen (new_stdout, "w")))
454         {
455           perror ("could not fdopen() the new stdout:");
456           return;
457         }
458       real_stdout = new_stdout_file;
459
460       close (stdout_fd);
461       if (dup2 (out, stdout_fd) < 0)
462         {
463           perror ("could not dup() a new stdout:");
464           return;
465         }
466     }
467
468   stderr_stdout_read_fd = in;
469   XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
470                  (XtPointer) si);
471 }
472
473
474 /* If there is anything in the stderr buffer, flush it to the real stderr.
475    This does no X operations.  Call this when exiting to make sure any
476    last words actually show up.
477  */
478 void
479 shutdown_stderr (saver_info *si)
480 {
481   if (!real_stderr || stderr_stdout_read_fd < 0)
482     return;
483
484   stderr_callback ((XtPointer) si, &stderr_stdout_read_fd, 0);
485
486   if (stderr_buffer &&
487       stderr_tail &&
488       stderr_buffer < stderr_tail)
489     {
490       *stderr_tail = 0;
491       fprintf (real_stderr, "%s", stderr_buffer);
492       fflush (real_stderr);
493       stderr_tail = stderr_buffer;
494     }
495 }