0c999e2fcfd55cdef991c2712069e15b3243b902
[xscreensaver] / driver / stderr.c
1 /* stderr.c --- capturing stdout/stderr output onto the screensaver window.
2  * xscreensaver, Copyright (c) 1991-1998 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   Display *dpy = si->dpy;
94   Screen *screen = ssi->screen;
95   Window window = (ssi->stderr_overlay_window ?
96                    ssi->stderr_overlay_window :
97                    ssi->screensaver_window);
98   int h_border = 20;
99   int v_border = 20;
100   char *head = string;
101   char *tail;
102
103   if (! ssi->stderr_font)
104     {
105       char *font_name = get_string_resource ("font", "Font");
106       if (!font_name) font_name = "fixed";
107       ssi->stderr_font = XLoadQueryFont (dpy, font_name);
108       if (! ssi->stderr_font) ssi->stderr_font = XLoadQueryFont (dpy, "fixed");
109       ssi->stderr_line_height = (ssi->stderr_font->ascent +
110                                  ssi->stderr_font->descent);
111     }
112
113   if (! ssi->stderr_gc)
114     {
115       XGCValues gcv;
116       Pixel fg, bg;
117       Colormap cmap = ssi->cmap;
118
119       if (!ssi->stderr_overlay_window &&
120           get_boolean_resource("overlayStderr", "Boolean"))
121         {
122           make_stderr_overlay_window (ssi);
123           if (ssi->stderr_overlay_window)
124             window = ssi->stderr_overlay_window;
125           if (ssi->stderr_cmap)
126             cmap = ssi->stderr_cmap;
127         }
128
129       fg = get_pixel_resource ("overlayTextForeground","Foreground",dpy,cmap);
130       bg = get_pixel_resource ("overlayTextBackground","Background",dpy,cmap);
131       gcv.font = ssi->stderr_font->fid;
132       gcv.foreground = fg;
133       gcv.background = bg;
134       ssi->stderr_gc = XCreateGC (dpy, window,
135                                   (GCFont | GCForeground | GCBackground),
136                                   &gcv);
137     }
138
139
140   if (ssi->stderr_cmap)
141     XInstallColormap(si->dpy, ssi->stderr_cmap);
142
143   for (tail = string; *tail; tail++)
144     {
145       if (*tail == '\n' || *tail == '\r')
146         {
147           int maxy = HeightOfScreen (screen) - v_border - v_border;
148           if (tail != head)
149             XDrawImageString (dpy, window, ssi->stderr_gc,
150                               ssi->stderr_text_x + h_border,
151                               ssi->stderr_text_y + v_border +
152                               ssi->stderr_font->ascent,
153                               head, tail - head);
154           ssi->stderr_text_x = 0;
155           ssi->stderr_text_y += ssi->stderr_line_height;
156           head = tail + 1;
157           if (*tail == '\r' && *head == '\n')
158             head++, tail++;
159
160           if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
161             {
162 #if 0
163               ssi->stderr_text_y = 0;
164 #else
165               int offset = ssi->stderr_line_height * 5;
166               XCopyArea (dpy, window, window, ssi->stderr_gc,
167                          0, v_border + offset,
168                          WidthOfScreen (screen),
169                          (HeightOfScreen (screen) - v_border - v_border
170                           - offset),
171                          0, v_border);
172               XClearArea (dpy, window,
173                           0, HeightOfScreen (screen) - v_border - offset,
174                           WidthOfScreen (screen), offset, False);
175               ssi->stderr_text_y -= offset;
176 #endif
177             }
178         }
179     }
180   if (tail != head)
181     {
182       int direction, ascent, descent;
183       XCharStruct overall;
184       XDrawImageString (dpy, window, ssi->stderr_gc,
185                         ssi->stderr_text_x + h_border,
186                         ssi->stderr_text_y + v_border
187                           + ssi->stderr_font->ascent,
188                         head, tail - head);
189       XTextExtents (ssi->stderr_font, tail, tail - head,
190                     &direction, &ascent, &descent, &overall);
191       ssi->stderr_text_x += overall.width;
192     }
193 }
194
195 static void
196 make_stderr_overlay_window (saver_screen_info *ssi)
197 {
198   saver_info *si = ssi->global;
199   unsigned long transparent_pixel = 0;
200   Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
201   if (visual)
202     {
203       int depth = visual_depth (ssi->screen, visual);
204       XSetWindowAttributes attrs;
205       unsigned long attrmask;
206
207       if (si->prefs.debug_p)
208         fprintf(real_stderr,
209                 "%s: using overlay visual 0x%0x for stderr text layer.\n",
210                 progname, (int) XVisualIDFromVisual (visual));
211
212       ssi->stderr_cmap = XCreateColormap(si->dpy,
213                                          RootWindowOfScreen(ssi->screen),
214                                          visual, AllocNone);
215
216       attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
217                   CWBackingStore | CWSaveUnder);
218       attrs.colormap = ssi->stderr_cmap;
219       attrs.background_pixel = transparent_pixel;
220       attrs.backing_pixel = transparent_pixel;
221       attrs.border_pixel = transparent_pixel;
222       attrs.backing_store = NotUseful;
223       attrs.save_under = False;
224
225       ssi->stderr_overlay_window =
226         XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
227                       WidthOfScreen(ssi->screen), HeightOfScreen(ssi->screen),
228                       0, depth, InputOutput, visual, attrmask, &attrs);
229       XMapRaised(si->dpy, ssi->stderr_overlay_window);
230     }
231 }
232
233
234 static void
235 print_stderr (saver_info *si, char *string)
236 {
237   saver_preferences *p = &si->prefs;
238   int i;
239
240   /* In verbose mode, copy it to stderr as well. */
241   if (p->verbose_p)
242     fprintf (real_stderr, "%s", string);
243
244   for (i = 0; i < si->nscreens; i++)
245     print_stderr_1 (&si->screens[i], string);
246 }
247
248
249 static void
250 stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
251 {
252   saver_info *si = (saver_info *) closure;
253   char *s = stderr_buffer;
254   if (*s)
255     {
256       /* If too much data was printed, then something has gone haywire,
257          so truncate it. */
258       char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
259       int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
260       if (strlen (s) > max)
261         strcpy (s + max, trailer);
262       /* Now show the user. */
263       print_stderr (si, s);
264     }
265
266   stderr_tail = stderr_buffer;
267   si->stderr_popup_timer = 0;
268 }
269
270
271 static void
272 stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
273 {
274   saver_info *si = (saver_info *) closure;
275   char *s;
276   int left;
277   int size;
278   int read_this_time = 0;
279
280   if (stderr_tail == 0)
281     stderr_tail = stderr_buffer;
282
283   left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
284
285   s = stderr_tail;
286   *s = 0;
287
288   /* Read as much data from the fd as we can, up to our buffer size. */
289   if (left > 0)
290     {
291       while ((size = read (*fd, (void *) s, left)) > 0)
292         {
293           left -= size;
294           s += size;
295           read_this_time += size;
296         }
297       *s = 0;
298     }
299   else
300     {
301       char buf2 [1024];
302       /* The buffer is full; flush the rest of it. */
303       while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
304         ;
305     }
306
307   stderr_tail = s;
308   stderr_last_read = time ((time_t *) 0);
309
310   /* Now we have read some data that we would like to put up in a dialog
311      box.  But more data may still be coming in - so don't pop up the
312      dialog right now, but instead, start a timer that will pop it up
313      a second from now.  Should more data come in in the meantime, we
314      will be called again, and will reset that timer again.  So the
315      dialog will only pop up when a second has elapsed with no new data
316      being written to stderr.
317
318      However, if the buffer is full (meaning lots of data has been written)
319      then we don't reset the timer.
320    */
321   if (read_this_time > 0)
322     {
323       if (si->stderr_popup_timer)
324         XtRemoveTimeOut (si->stderr_popup_timer);
325
326       si->stderr_popup_timer =
327         XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
328                          (XtPointer) si);
329     }
330 }
331
332 void
333 initialize_stderr (saver_info *si)
334 {
335   static Boolean done = False;
336   int fds [2];
337   int in, out;
338   int new_stdout, new_stderr;
339   int stdout_fd = 1;
340   int stderr_fd = 2;
341   int flags = 0;
342   Boolean stderr_dialog_p, stdout_dialog_p;
343
344   if (done) return;
345   done = True;
346
347   real_stderr = stderr;
348   real_stdout = stdout;
349
350   stderr_dialog_p = get_boolean_resource ("captureStderr", "Boolean");
351   stdout_dialog_p = get_boolean_resource ("captureStdout", "Boolean");
352
353   if (!stderr_dialog_p && !stdout_dialog_p)
354     return;
355
356   if (pipe (fds))
357     {
358       perror ("error creating pipe:");
359       return;
360     }
361
362   in = fds [0];
363   out = fds [1];
364
365 # ifdef HAVE_FCNTL
366
367 #  if defined(O_NONBLOCK)
368    flags = O_NONBLOCK;
369 #  elif defined(O_NDELAY)
370    flags = O_NDELAY;
371 #  else
372    ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
373 #  endif
374
375     /* Set both sides of the pipe to nonblocking - this is so that
376        our reads (in stderr_callback) will terminate, and so that
377        out writes (in the client programs) will silently fail when
378        the pipe is full, instead of hosing the program. */
379   if (fcntl (in, F_SETFL, flags) != 0)
380     {
381       perror ("fcntl:");
382       return;
383     }
384   if (fcntl (out, F_SETFL, flags) != 0)
385     {
386       perror ("fcntl:");
387       return;
388     }
389
390 # endif /* !HAVE_FCNTL */
391
392   if (stderr_dialog_p)
393     {
394       FILE *new_stderr_file;
395       new_stderr = dup (stderr_fd);
396       if (new_stderr < 0)
397         {
398           perror ("could not dup() a stderr:");
399           return;
400         }
401       if (! (new_stderr_file = fdopen (new_stderr, "w")))
402         {
403           perror ("could not fdopen() the new stderr:");
404           return;
405         }
406       real_stderr = new_stderr_file;
407
408       close (stderr_fd);
409       if (dup2 (out, stderr_fd) < 0)
410         {
411           perror ("could not dup() a new stderr:");
412           return;
413         }
414     }
415
416   if (stdout_dialog_p)
417     {
418       FILE *new_stdout_file;
419       new_stdout = dup (stdout_fd);
420       if (new_stdout < 0)
421         {
422           perror ("could not dup() a stdout:");
423           return;
424         }
425       if (! (new_stdout_file = fdopen (new_stdout, "w")))
426         {
427           perror ("could not fdopen() the new stdout:");
428           return;
429         }
430       real_stdout = new_stdout_file;
431
432       close (stdout_fd);
433       if (dup2 (out, stdout_fd) < 0)
434         {
435           perror ("could not dup() a new stdout:");
436           return;
437         }
438     }
439
440   XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
441                  (XtPointer) si);
442 }