ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.tar.gz
[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 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", blurb());
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               XWindowAttributes xgwa;
167               XGetWindowAttributes (dpy, window, &xgwa);
168
169               XCopyArea (dpy, window, window, ssi->stderr_gc,
170                          0, v_border + offset,
171                          xgwa.width,
172                          (xgwa.height - v_border - v_border - offset),
173                          0, v_border);
174               XClearArea (dpy, window,
175                           0, xgwa.height - v_border - offset,
176                           xgwa.width, offset, False);
177               ssi->stderr_text_y -= offset;
178 #endif
179             }
180         }
181     }
182   if (tail != head)
183     {
184       int direction, ascent, descent;
185       XCharStruct overall;
186       XDrawImageString (dpy, window, ssi->stderr_gc,
187                         ssi->stderr_text_x + h_border,
188                         ssi->stderr_text_y + v_border
189                           + ssi->stderr_font->ascent,
190                         head, tail - head);
191       XTextExtents (ssi->stderr_font, tail, tail - head,
192                     &direction, &ascent, &descent, &overall);
193       ssi->stderr_text_x += overall.width;
194     }
195 }
196
197 static void
198 make_stderr_overlay_window (saver_screen_info *ssi)
199 {
200   saver_info *si = ssi->global;
201   unsigned long transparent_pixel = 0;
202   Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
203   if (visual)
204     {
205       int depth = visual_depth (ssi->screen, visual);
206       XSetWindowAttributes attrs;
207       XWindowAttributes xgwa;
208       unsigned long attrmask;
209       XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
210
211       if (si->prefs.debug_p)
212         fprintf(real_stderr,
213                 "%s: using overlay visual 0x%0x for stderr text layer.\n",
214                 blurb(), (int) XVisualIDFromVisual (visual));
215
216       ssi->stderr_cmap = XCreateColormap(si->dpy,
217                                          RootWindowOfScreen(ssi->screen),
218                                          visual, AllocNone);
219
220       attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
221                   CWBackingStore | CWSaveUnder);
222       attrs.colormap = ssi->stderr_cmap;
223       attrs.background_pixel = transparent_pixel;
224       attrs.backing_pixel = transparent_pixel;
225       attrs.border_pixel = transparent_pixel;
226       attrs.backing_store = NotUseful;
227       attrs.save_under = False;
228
229       ssi->stderr_overlay_window =
230         XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
231                       xgwa.width, xgwa.height,
232                       0, depth, InputOutput, visual, attrmask, &attrs);
233       XMapRaised(si->dpy, ssi->stderr_overlay_window);
234     }
235 }
236
237
238 static void
239 print_stderr (saver_info *si, char *string)
240 {
241   saver_preferences *p = &si->prefs;
242   int i;
243
244   /* In verbose mode, copy it to stderr as well. */
245   if (p->verbose_p)
246     fprintf (real_stderr, "%s", string);
247
248   for (i = 0; i < si->nscreens; i++)
249     print_stderr_1 (&si->screens[i], string);
250 }
251
252
253 static void
254 stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
255 {
256   saver_info *si = (saver_info *) closure;
257   char *s = stderr_buffer;
258   if (*s)
259     {
260       /* If too much data was printed, then something has gone haywire,
261          so truncate it. */
262       char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
263       int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
264       if (strlen (s) > max)
265         strcpy (s + max, trailer);
266       /* Now show the user. */
267       print_stderr (si, s);
268     }
269
270   stderr_tail = stderr_buffer;
271   si->stderr_popup_timer = 0;
272 }
273
274
275 static void
276 stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
277 {
278   saver_info *si = (saver_info *) closure;
279   char *s;
280   int left;
281   int size;
282   int read_this_time = 0;
283
284   if (stderr_tail == 0)
285     stderr_tail = stderr_buffer;
286
287   left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
288
289   s = stderr_tail;
290   *s = 0;
291
292   /* Read as much data from the fd as we can, up to our buffer size. */
293   if (left > 0)
294     {
295       while ((size = read (*fd, (void *) s, left)) > 0)
296         {
297           left -= size;
298           s += size;
299           read_this_time += size;
300         }
301       *s = 0;
302     }
303   else
304     {
305       char buf2 [1024];
306       /* The buffer is full; flush the rest of it. */
307       while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
308         ;
309     }
310
311   stderr_tail = s;
312   stderr_last_read = time ((time_t *) 0);
313
314   /* Now we have read some data that we would like to put up in a dialog
315      box.  But more data may still be coming in - so don't pop up the
316      dialog right now, but instead, start a timer that will pop it up
317      a second from now.  Should more data come in in the meantime, we
318      will be called again, and will reset that timer again.  So the
319      dialog will only pop up when a second has elapsed with no new data
320      being written to stderr.
321
322      However, if the buffer is full (meaning lots of data has been written)
323      then we don't reset the timer.
324    */
325   if (read_this_time > 0)
326     {
327       if (si->stderr_popup_timer)
328         XtRemoveTimeOut (si->stderr_popup_timer);
329
330       si->stderr_popup_timer =
331         XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
332                          (XtPointer) si);
333     }
334 }
335
336 void
337 initialize_stderr (saver_info *si)
338 {
339   static Boolean done = False;
340   int fds [2];
341   int in, out;
342   int new_stdout, new_stderr;
343   int stdout_fd = 1;
344   int stderr_fd = 2;
345   int flags = 0;
346   Boolean stderr_dialog_p;
347
348   if (done) return;
349   done = True;
350
351   real_stderr = stderr;
352   real_stdout = stdout;
353
354   stderr_dialog_p = get_boolean_resource ("captureStderr", "Boolean");
355
356   if (!stderr_dialog_p)
357     return;
358
359   if (pipe (fds))
360     {
361       perror ("error creating pipe:");
362       return;
363     }
364
365   in = fds [0];
366   out = fds [1];
367
368 # ifdef HAVE_FCNTL
369
370 #  if defined(O_NONBLOCK)
371    flags = O_NONBLOCK;
372 #  elif defined(O_NDELAY)
373    flags = O_NDELAY;
374 #  else
375    ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
376 #  endif
377
378     /* Set both sides of the pipe to nonblocking - this is so that
379        our reads (in stderr_callback) will terminate, and so that
380        out writes (in the client programs) will silently fail when
381        the pipe is full, instead of hosing the program. */
382   if (fcntl (in, F_SETFL, flags) != 0)
383     {
384       perror ("fcntl:");
385       return;
386     }
387   if (fcntl (out, F_SETFL, flags) != 0)
388     {
389       perror ("fcntl:");
390       return;
391     }
392
393 # endif /* !HAVE_FCNTL */
394
395   if (stderr_dialog_p)
396     {
397       FILE *new_stderr_file;
398       FILE *new_stdout_file;
399
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       new_stdout = dup (stdout_fd);
422       if (new_stdout < 0)
423         {
424           perror ("could not dup() a stdout:");
425           return;
426         }
427       if (! (new_stdout_file = fdopen (new_stdout, "w")))
428         {
429           perror ("could not fdopen() the new stdout:");
430           return;
431         }
432       real_stdout = new_stdout_file;
433
434       close (stdout_fd);
435       if (dup2 (out, stdout_fd) < 0)
436         {
437           perror ("could not dup() a new stdout:");
438           return;
439         }
440     }
441
442   XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
443                  (XtPointer) si);
444 }