1 /* stderr.c --- capturing stdout/stderr output onto the screensaver window.
2 * xscreensaver, Copyright (c) 1991-2012 Jamie Zawinski <jwz@jwz.org>
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
13 /* stderr hackery - Why Unix Sucks, reason number 32767.
33 #include <X11/Intrinsic.h>
35 #include "xscreensaver.h"
36 #include "resources.h"
39 FILE *real_stderr = 0;
40 FILE *real_stdout = 0;
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.
46 static char stderr_buffer [4096];
47 static char *stderr_tail = 0;
48 static time_t stderr_last_read = 0;
50 static int stderr_stdout_read_fd = -1;
52 static void make_stderr_overlay_window (saver_screen_info *);
55 /* Recreates the stderr window or GCs: do this when the xscreensaver window
56 on a screen has been re-created.
59 reset_stderr (saver_screen_info *ssi)
61 saver_info *si = ssi->global;
63 if (si->prefs.debug_p)
64 fprintf ((real_stderr ? real_stderr : stderr),
65 "%s: resetting stderr\n", blurb());
67 ssi->stderr_text_x = 0;
68 ssi->stderr_text_y = 0;
71 XFreeGC (si->dpy, ssi->stderr_gc);
74 if (ssi->stderr_overlay_window)
75 XDestroyWindow(si->dpy, ssi->stderr_overlay_window);
76 ssi->stderr_overlay_window = 0;
79 XFreeColormap(si->dpy, ssi->stderr_cmap);
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
88 clear_stderr (saver_screen_info *ssi)
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);
98 /* Draws the string on the screen's window.
101 print_stderr_1 (saver_screen_info *ssi, char *string)
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);
114 if (! ssi->stderr_font)
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);
125 if (! ssi->stderr_gc)
129 Colormap cmap = ssi->cmap;
131 if (!ssi->stderr_overlay_window &&
132 get_boolean_resource(dpy, "overlayStderr", "Boolean"))
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;
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;
146 ssi->stderr_gc = XCreateGC (dpy, window,
147 (GCFont | GCForeground | GCBackground),
152 if (ssi->stderr_cmap)
153 XInstallColormap(si->dpy, ssi->stderr_cmap);
155 for (tail = string; *tail; tail++)
157 if (*tail == '\n' || *tail == '\r')
159 int maxy = HeightOfScreen (screen) - v_border - v_border;
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,
166 ssi->stderr_text_x = 0;
167 ssi->stderr_text_y += ssi->stderr_line_height;
169 if (*tail == '\r' && *head == '\n')
172 if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
175 ssi->stderr_text_y = 0;
177 int offset = ssi->stderr_line_height * 5;
178 XWindowAttributes xgwa;
179 XGetWindowAttributes (dpy, window, &xgwa);
181 XCopyArea (dpy, window, window, ssi->stderr_gc,
182 0, v_border + offset,
184 (xgwa.height - v_border - v_border - offset),
186 XClearArea (dpy, window,
187 0, xgwa.height - v_border - offset,
188 xgwa.width, offset, False);
189 ssi->stderr_text_y -= offset;
196 int direction, ascent, descent;
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,
203 XTextExtents (ssi->stderr_font, tail, tail - head,
204 &direction, &ascent, &descent, &overall);
205 ssi->stderr_text_x += overall.width;
210 make_stderr_overlay_window (saver_screen_info *ssi)
212 saver_info *si = ssi->global;
213 unsigned long transparent_pixel = 0;
214 Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
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);
223 if (si->prefs.debug_p)
225 "%s: using overlay visual 0x%0x for stderr text layer.\n",
226 blurb(), (int) XVisualIDFromVisual (visual));
228 ssi->stderr_cmap = XCreateColormap(si->dpy,
229 RootWindowOfScreen(ssi->screen),
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;
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);
250 /* Draws the string on each screen's window as error text.
253 print_stderr (saver_info *si, char *string)
255 saver_preferences *p = &si->prefs;
258 /* In verbose mode, copy it to stderr as well. */
260 fprintf (real_stderr, "%s", string);
262 for (i = 0; i < si->nscreens; i++)
263 print_stderr_1 (&si->screens[i], string);
267 /* Polls the stderr buffer every few seconds and if it finds any text,
268 writes it on all screens.
271 stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
273 saver_info *si = (saver_info *) closure;
274 char *s = stderr_buffer;
277 /* If too much data was printed, then something has gone haywire,
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);
287 stderr_tail = stderr_buffer;
288 si->stderr_popup_timer = 0;
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.
296 stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
298 saver_info *si = (saver_info *) closure;
302 int read_this_time = 0;
304 if (!fd || *fd < 0 || *fd != stderr_stdout_read_fd)
307 if (stderr_tail == 0)
308 stderr_tail = stderr_buffer;
310 left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
315 /* Read as much data from the fd as we can, up to our buffer size. */
318 while ((size = read (*fd, (void *) s, left)) > 0)
322 read_this_time += size;
329 /* The buffer is full; flush the rest of it. */
330 while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
335 stderr_last_read = time ((time_t *) 0);
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.
345 However, if the buffer is full (meaning lots of data has been written)
346 then we don't reset the timer.
348 if (read_this_time > 0)
350 if (si->stderr_popup_timer)
351 XtRemoveTimeOut (si->stderr_popup_timer);
353 si->stderr_popup_timer =
354 XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
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.
364 initialize_stderr (saver_info *si)
366 static Boolean done = False;
369 int new_stdout, new_stderr;
373 Boolean stderr_dialog_p;
378 real_stderr = stderr;
379 real_stdout = stdout;
381 stderr_dialog_p = get_boolean_resource (si->dpy, "captureStderr", "Boolean");
383 if (!stderr_dialog_p)
388 perror ("error creating pipe:");
397 # if defined(O_NONBLOCK)
399 # elif defined(O_NDELAY)
402 ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
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)
414 if (fcntl (out, F_SETFL, flags) != 0)
420 # endif /* !HAVE_FCNTL */
424 FILE *new_stderr_file;
425 FILE *new_stdout_file;
427 new_stderr = dup (stderr_fd);
430 perror ("could not dup() a stderr:");
433 if (! (new_stderr_file = fdopen (new_stderr, "w")))
435 perror ("could not fdopen() the new stderr:");
438 real_stderr = new_stderr_file;
441 if (dup2 (out, stderr_fd) < 0)
443 perror ("could not dup() a new stderr:");
448 new_stdout = dup (stdout_fd);
451 perror ("could not dup() a stdout:");
454 if (! (new_stdout_file = fdopen (new_stdout, "w")))
456 perror ("could not fdopen() the new stdout:");
459 real_stdout = new_stdout_file;
462 if (dup2 (out, stdout_fd) < 0)
464 perror ("could not dup() a new stdout:");
470 stderr_stdout_read_fd = in;
471 XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
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().
481 stderr_log_file (saver_info *si)
485 const char *filename = get_string_resource (si->dpy, "logFile", "LogFile");
488 if (!filename || !*filename) return;
490 fd = open (filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
496 sprintf (buf, "%.100s: %.100s", blurb(), filename);
503 fprintf (stderr, "%s: logging to file %s\n", blurb(), filename);
505 if (dup2 (fd, stdout_fd) < 0) goto FAIL;
506 if (dup2 (fd, stderr_fd) < 0) goto FAIL;
508 fprintf (stderr, "\n\n"
509 "##########################################################################\n"
510 "%s: logging to \"%s\" at %s\n"
511 "##########################################################################\n"
513 blurb(), filename, timestring());
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.
522 shutdown_stderr (saver_info *si)
527 if (!real_stderr || stderr_stdout_read_fd < 0)
530 stderr_callback ((XtPointer) si, &stderr_stdout_read_fd, 0);
533 stderr_buffer < stderr_tail)
536 fprintf (real_stderr, "%s", stderr_buffer);
537 stderr_tail = stderr_buffer;
540 if (real_stdout) fflush (real_stdout);
541 if (real_stderr) fflush (real_stderr);
543 if (stdout != real_stdout)
545 dup2 (fileno(real_stdout), fileno(stdout));
546 fclose (real_stdout);
547 real_stdout = stdout;
549 if (stderr != real_stderr)
551 dup2 (fileno(real_stderr), fileno(stderr));
552 fclose (real_stderr);
553 real_stderr = stderr;
555 if (stderr_stdout_read_fd != -1)
557 close (stderr_stdout_read_fd);
558 stderr_stdout_read_fd = -1;