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