84fa697290d6268c34846fb36fb548ea56b8503a
[xscreensaver] / driver / stderr.c
1 /* stderr.c --- capturing stdout/stderr output onto the screensaver window.
2  * xscreensaver, Copyright (c) 1991-2016 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 (dpy, "font", "Font");
117       if (!font_name) font_name = strdup ("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       free (font_name);
123     }
124
125   if (! ssi->stderr_gc)
126     {
127       XGCValues gcv;
128       Pixel fg, bg;
129       Colormap cmap = ssi->cmap;
130
131       if (!ssi->stderr_overlay_window &&
132           get_boolean_resource(dpy, "overlayStderr", "Boolean"))
133         {
134           make_stderr_overlay_window (ssi);
135           if (ssi->stderr_overlay_window)
136             window = ssi->stderr_overlay_window;
137           if (ssi->stderr_cmap)
138             cmap = ssi->stderr_cmap;
139         }
140
141       fg = get_pixel_resource (dpy,cmap,"overlayTextForeground","Foreground");
142       bg = get_pixel_resource (dpy,cmap,"overlayTextBackground","Background");
143       gcv.font = ssi->stderr_font->fid;
144       gcv.foreground = fg;
145       gcv.background = bg;
146       ssi->stderr_gc = XCreateGC (dpy, window,
147                                   (GCFont | GCForeground | GCBackground),
148                                   &gcv);
149     }
150
151
152   if (ssi->stderr_cmap)
153     XInstallColormap(si->dpy, ssi->stderr_cmap);
154
155   for (tail = string; *tail; tail++)
156     {
157       if (*tail == '\n' || *tail == '\r')
158         {
159           int maxy = HeightOfScreen (screen) - v_border - v_border;
160           if (tail != head)
161             XDrawImageString (dpy, window, ssi->stderr_gc,
162                               ssi->stderr_text_x + h_border,
163                               ssi->stderr_text_y + v_border +
164                               ssi->stderr_font->ascent,
165                               head, tail - head);
166           ssi->stderr_text_x = 0;
167           ssi->stderr_text_y += ssi->stderr_line_height;
168           head = tail + 1;
169           if (*tail == '\r' && *head == '\n')
170             head++, tail++;
171
172           if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
173             {
174 #if 0
175               ssi->stderr_text_y = 0;
176 #else
177               int offset = ssi->stderr_line_height * 5;
178               XWindowAttributes xgwa;
179               XGetWindowAttributes (dpy, window, &xgwa);
180
181               XCopyArea (dpy, window, window, ssi->stderr_gc,
182                          0, v_border + offset,
183                          xgwa.width,
184                          (xgwa.height - v_border - v_border - offset),
185                          0, v_border);
186               XClearArea (dpy, window,
187                           0, xgwa.height - v_border - offset,
188                           xgwa.width, offset, False);
189               ssi->stderr_text_y -= offset;
190 #endif
191             }
192         }
193     }
194   if (tail != head)
195     {
196       int direction, ascent, descent;
197       XCharStruct overall;
198       XDrawImageString (dpy, window, ssi->stderr_gc,
199                         ssi->stderr_text_x + h_border,
200                         ssi->stderr_text_y + v_border
201                           + ssi->stderr_font->ascent,
202                         head, tail - head);
203       XTextExtents (ssi->stderr_font, tail, tail - head,
204                     &direction, &ascent, &descent, &overall);
205       ssi->stderr_text_x += overall.width;
206     }
207 }
208
209 static void
210 make_stderr_overlay_window (saver_screen_info *ssi)
211 {
212   saver_info *si = ssi->global;
213   unsigned long transparent_pixel = 0;
214   Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
215   if (visual)
216     {
217       int depth = visual_depth (ssi->screen, visual);
218       XSetWindowAttributes attrs;
219       XWindowAttributes xgwa;
220       unsigned long attrmask;
221       XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
222
223       if (si->prefs.debug_p)
224         fprintf(real_stderr,
225                 "%s: using overlay visual 0x%0x for stderr text layer.\n",
226                 blurb(), (int) XVisualIDFromVisual (visual));
227
228       ssi->stderr_cmap = XCreateColormap(si->dpy,
229                                          RootWindowOfScreen(ssi->screen),
230                                          visual, AllocNone);
231
232       attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
233                   CWBackingStore | CWSaveUnder);
234       attrs.colormap = ssi->stderr_cmap;
235       attrs.background_pixel = transparent_pixel;
236       attrs.backing_pixel = transparent_pixel;
237       attrs.border_pixel = transparent_pixel;
238       attrs.backing_store = NotUseful;
239       attrs.save_under = False;
240
241       ssi->stderr_overlay_window =
242         XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
243                       xgwa.width, xgwa.height,
244                       0, depth, InputOutput, visual, attrmask, &attrs);
245       XMapRaised(si->dpy, ssi->stderr_overlay_window);
246     }
247 }
248
249
250 /* Draws the string on each screen's window as error text.
251  */
252 static void
253 print_stderr (saver_info *si, char *string)
254 {
255   saver_preferences *p = &si->prefs;
256   int i;
257
258   /* In verbose mode, copy it to stderr as well. */
259   if (p->verbose_p)
260     fprintf (real_stderr, "%s", string);
261
262   for (i = 0; i < si->nscreens; i++)
263     print_stderr_1 (&si->screens[i], string);
264 }
265
266
267 /* Polls the stderr buffer every few seconds and if it finds any text,
268    writes it on all screens.
269  */
270 static void
271 stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
272 {
273   saver_info *si = (saver_info *) closure;
274   char *s = stderr_buffer;
275   if (*s)
276     {
277       /* If too much data was printed, then something has gone haywire,
278          so truncate it. */
279       char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
280       int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
281       if (strlen (s) > max)
282         strcpy (s + max, trailer);
283       /* Now show the user. */
284       print_stderr (si, s);
285     }
286
287   stderr_tail = stderr_buffer;
288   si->stderr_popup_timer = 0;
289 }
290
291
292 /* Called when data becomes available on the stderr pipe.  Copies it into
293    stderr_buffer where stderr_popup_timer_fn() can find it later.
294  */
295 static void
296 stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
297 {
298   saver_info *si = (saver_info *) closure;
299   char *s;
300   int left;
301   int size;
302   int read_this_time = 0;
303
304   if (!fd || *fd < 0 || *fd != stderr_stdout_read_fd)
305     abort();
306
307   if (stderr_tail == 0)
308     stderr_tail = stderr_buffer;
309
310   left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
311
312   s = stderr_tail;
313   *s = 0;
314
315   /* Read as much data from the fd as we can, up to our buffer size. */
316   if (left > 0)
317     {
318       while ((size = read (*fd, (void *) s, left)) > 0)
319         {
320           left -= size;
321           s += size;
322           read_this_time += size;
323         }
324       *s = 0;
325     }
326   else
327     {
328       char buf2 [1024];
329       /* The buffer is full; flush the rest of it. */
330       while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
331         ;
332     }
333
334   stderr_tail = s;
335   stderr_last_read = time ((time_t *) 0);
336
337   /* Now we have read some data that we would like to put up in a dialog
338      box.  But more data may still be coming in - so don't pop up the
339      dialog right now, but instead, start a timer that will pop it up
340      a second from now.  Should more data come in in the meantime, we
341      will be called again, and will reset that timer again.  So the
342      dialog will only pop up when a second has elapsed with no new data
343      being written to stderr.
344
345      However, if the buffer is full (meaning lots of data has been written)
346      then we don't reset the timer.
347    */
348   if (read_this_time > 0)
349     {
350       if (si->stderr_popup_timer)
351         XtRemoveTimeOut (si->stderr_popup_timer);
352
353       si->stderr_popup_timer =
354         XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
355                          (XtPointer) si);
356     }
357 }
358
359 /* If stderr capturing is desired, this replaces `stdout' and `stderr'
360    with a pipe, so that any output written to them will show up on the
361    screen as well as on the original value of those streams.
362  */
363 void
364 initialize_stderr (saver_info *si)
365 {
366   static Boolean done = False;
367   int fds [2];
368   int in, out;
369   int new_stdout, new_stderr;
370   int stdout_fd = 1;
371   int stderr_fd = 2;
372   int flags = 0;
373   Boolean stderr_dialog_p;
374
375   if (done) return;
376   done = True;
377
378   real_stderr = stderr;
379   real_stdout = stdout;
380
381   stderr_dialog_p = get_boolean_resource (si->dpy, "captureStderr", "Boolean");
382
383   if (!stderr_dialog_p)
384     return;
385
386   if (pipe (fds))
387     {
388       perror ("error creating pipe:");
389       return;
390     }
391
392   in = fds [0];
393   out = fds [1];
394
395 # ifdef HAVE_FCNTL
396
397 #  if defined(O_NONBLOCK)
398    flags = O_NONBLOCK;
399 #  elif defined(O_NDELAY)
400    flags = O_NDELAY;
401 #  else
402    ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
403 #  endif
404
405     /* Set both sides of the pipe to nonblocking - this is so that
406        our reads (in stderr_callback) will terminate, and so that
407        out writes (in the client programs) will silently fail when
408        the pipe is full, instead of hosing the program. */
409   if (fcntl (in, F_SETFL, flags) != 0)
410     {
411       perror ("fcntl:");
412       return;
413     }
414   if (fcntl (out, F_SETFL, flags) != 0)
415     {
416       perror ("fcntl:");
417       return;
418     }
419
420 # endif /* !HAVE_FCNTL */
421
422   if (stderr_dialog_p)
423     {
424       FILE *new_stderr_file;
425       FILE *new_stdout_file;
426
427       new_stderr = dup (stderr_fd);
428       if (new_stderr < 0)
429         {
430           perror ("could not dup() a stderr:");
431           return;
432         }
433       if (! (new_stderr_file = fdopen (new_stderr, "w")))
434         {
435           perror ("could not fdopen() the new stderr:");
436           return;
437         }
438       real_stderr = new_stderr_file;
439
440       close (stderr_fd);
441       if (dup2 (out, stderr_fd) < 0)
442         {
443           perror ("could not dup() a new stderr:");
444           return;
445         }
446
447
448       new_stdout = dup (stdout_fd);
449       if (new_stdout < 0)
450         {
451           perror ("could not dup() a stdout:");
452           return;
453         }
454       if (! (new_stdout_file = fdopen (new_stdout, "w")))
455         {
456           perror ("could not fdopen() the new stdout:");
457           return;
458         }
459       real_stdout = new_stdout_file;
460
461       close (stdout_fd);
462       if (dup2 (out, stdout_fd) < 0)
463         {
464           perror ("could not dup() a new stdout:");
465           return;
466         }
467       close (out);
468     }
469
470   stderr_stdout_read_fd = in;
471   XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
472                  (XtPointer) si);
473 }
474
475
476 /* If the "-log file" command-line option has been specified,
477    open the file for append, and redirect stdout/stderr there.
478    This is called very early, before initialize_stderr().
479  */
480 void
481 stderr_log_file (saver_info *si)
482 {
483   int stdout_fd = 1;
484   int stderr_fd = 2;
485   const char *filename = get_string_resource (si->dpy, "logFile", "LogFile");
486   int fd;
487
488   if (!filename || !*filename) return;
489
490   fd = open (filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
491
492   if (fd < 0)
493     {
494       char buf[255];
495     FAIL:
496       sprintf (buf, "%.100s: %.100s", blurb(), filename);
497       perror (buf);
498       fflush (stderr);
499       fflush (stdout);
500       exit (1);
501     }
502
503   fprintf (stderr, "%s: logging to file %s\n", blurb(), filename);
504
505   if (dup2 (fd, stdout_fd) < 0) goto FAIL;
506   if (dup2 (fd, stderr_fd) < 0) goto FAIL;
507
508   fprintf (stderr, "\n\n"
509  "##########################################################################\n"
510            "%s: logging to \"%s\" at %s\n"
511  "##########################################################################\n"
512            "\n",
513            blurb(), filename, timestring(0));
514 }
515
516
517 /* If there is anything in the stderr buffer, flush it to the real stderr.
518    This does no X operations.  Call this when exiting to make sure any
519    last words actually show up.
520  */
521 void
522 shutdown_stderr (saver_info *si)
523 {
524   fflush (stdout);
525   fflush (stderr);
526
527   if (!real_stderr || stderr_stdout_read_fd < 0)
528     return;
529
530   stderr_callback ((XtPointer) si, &stderr_stdout_read_fd, 0);
531
532   if (stderr_tail &&
533       stderr_buffer < stderr_tail)
534     {
535       *stderr_tail = 0;
536       fprintf (real_stderr, "%s", stderr_buffer);
537       stderr_tail = stderr_buffer;
538     }
539
540   if (real_stdout) fflush (real_stdout);
541   if (real_stderr) fflush (real_stderr);
542
543   if (stdout != real_stdout)
544     {
545       dup2 (fileno(real_stdout), fileno(stdout));
546       fclose (real_stdout);
547       real_stdout = stdout;
548     }
549   if (stderr != real_stderr)
550     {
551       dup2 (fileno(real_stderr), fileno(stderr));
552       fclose (real_stderr);
553       real_stderr = stderr;
554     }
555   if (stderr_stdout_read_fd != -1)
556     {
557       close (stderr_stdout_read_fd);
558       stderr_stdout_read_fd = -1;
559     }
560 }