summaryrefslogtreecommitdiffstats
path: root/driver/stderr.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/stderr.c')
-rw-r--r--driver/stderr.c560
1 files changed, 560 insertions, 0 deletions
diff --git a/driver/stderr.c b/driver/stderr.c
new file mode 100644
index 0000000..84fa697
--- /dev/null
+++ b/driver/stderr.c
@@ -0,0 +1,560 @@
+/* stderr.c --- capturing stdout/stderr output onto the screensaver window.
+ * xscreensaver, Copyright (c) 1991-2016 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* stderr hackery - Why Unix Sucks, reason number 32767.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+
+#include <stdio.h>
+#include <time.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_FCNTL
+# include <fcntl.h>
+#endif
+
+#include <X11/Intrinsic.h>
+
+#include "xscreensaver.h"
+#include "resources.h"
+#include "visual.h"
+
+FILE *real_stderr = 0;
+FILE *real_stdout = 0;
+
+
+/* It's ok for these to be global, since they refer to the one and only
+ stderr stream, not to a particular screen or window or visual.
+ */
+static char stderr_buffer [4096];
+static char *stderr_tail = 0;
+static time_t stderr_last_read = 0;
+
+static int stderr_stdout_read_fd = -1;
+
+static void make_stderr_overlay_window (saver_screen_info *);
+
+
+/* Recreates the stderr window or GCs: do this when the xscreensaver window
+ on a screen has been re-created.
+ */
+void
+reset_stderr (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+
+ if (si->prefs.debug_p)
+ fprintf ((real_stderr ? real_stderr : stderr),
+ "%s: resetting stderr\n", blurb());
+
+ ssi->stderr_text_x = 0;
+ ssi->stderr_text_y = 0;
+
+ if (ssi->stderr_gc)
+ XFreeGC (si->dpy, ssi->stderr_gc);
+ ssi->stderr_gc = 0;
+
+ if (ssi->stderr_overlay_window)
+ XDestroyWindow(si->dpy, ssi->stderr_overlay_window);
+ ssi->stderr_overlay_window = 0;
+
+ if (ssi->stderr_cmap)
+ XFreeColormap(si->dpy, ssi->stderr_cmap);
+ ssi->stderr_cmap = 0;
+}
+
+/* Erases any stderr text overlaying the screen (if possible) and resets
+ the stderr output cursor to the upper left. Do this when the xscreensaver
+ window is cleared.
+ */
+void
+clear_stderr (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ ssi->stderr_text_x = 0;
+ ssi->stderr_text_y = 0;
+ if (ssi->stderr_overlay_window)
+ XClearWindow (si->dpy, ssi->stderr_overlay_window);
+}
+
+
+/* Draws the string on the screen's window.
+ */
+static void
+print_stderr_1 (saver_screen_info *ssi, char *string)
+{
+ saver_info *si = ssi->global;
+ Display *dpy = si->dpy;
+ Screen *screen = ssi->screen;
+ Window window = (ssi->stderr_overlay_window ?
+ ssi->stderr_overlay_window :
+ ssi->screensaver_window);
+ int h_border = 20;
+ int v_border = 20;
+ char *head = string;
+ char *tail;
+
+ if (! ssi->stderr_font)
+ {
+ char *font_name = get_string_resource (dpy, "font", "Font");
+ if (!font_name) font_name = strdup ("fixed");
+ ssi->stderr_font = XLoadQueryFont (dpy, font_name);
+ if (! ssi->stderr_font) ssi->stderr_font = XLoadQueryFont (dpy, "fixed");
+ ssi->stderr_line_height = (ssi->stderr_font->ascent +
+ ssi->stderr_font->descent);
+ free (font_name);
+ }
+
+ if (! ssi->stderr_gc)
+ {
+ XGCValues gcv;
+ Pixel fg, bg;
+ Colormap cmap = ssi->cmap;
+
+ if (!ssi->stderr_overlay_window &&
+ get_boolean_resource(dpy, "overlayStderr", "Boolean"))
+ {
+ make_stderr_overlay_window (ssi);
+ if (ssi->stderr_overlay_window)
+ window = ssi->stderr_overlay_window;
+ if (ssi->stderr_cmap)
+ cmap = ssi->stderr_cmap;
+ }
+
+ fg = get_pixel_resource (dpy,cmap,"overlayTextForeground","Foreground");
+ bg = get_pixel_resource (dpy,cmap,"overlayTextBackground","Background");
+ gcv.font = ssi->stderr_font->fid;
+ gcv.foreground = fg;
+ gcv.background = bg;
+ ssi->stderr_gc = XCreateGC (dpy, window,
+ (GCFont | GCForeground | GCBackground),
+ &gcv);
+ }
+
+
+ if (ssi->stderr_cmap)
+ XInstallColormap(si->dpy, ssi->stderr_cmap);
+
+ for (tail = string; *tail; tail++)
+ {
+ if (*tail == '\n' || *tail == '\r')
+ {
+ int maxy = HeightOfScreen (screen) - v_border - v_border;
+ if (tail != head)
+ XDrawImageString (dpy, window, ssi->stderr_gc,
+ ssi->stderr_text_x + h_border,
+ ssi->stderr_text_y + v_border +
+ ssi->stderr_font->ascent,
+ head, tail - head);
+ ssi->stderr_text_x = 0;
+ ssi->stderr_text_y += ssi->stderr_line_height;
+ head = tail + 1;
+ if (*tail == '\r' && *head == '\n')
+ head++, tail++;
+
+ if (ssi->stderr_text_y > maxy - ssi->stderr_line_height)
+ {
+#if 0
+ ssi->stderr_text_y = 0;
+#else
+ int offset = ssi->stderr_line_height * 5;
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, window, &xgwa);
+
+ XCopyArea (dpy, window, window, ssi->stderr_gc,
+ 0, v_border + offset,
+ xgwa.width,
+ (xgwa.height - v_border - v_border - offset),
+ 0, v_border);
+ XClearArea (dpy, window,
+ 0, xgwa.height - v_border - offset,
+ xgwa.width, offset, False);
+ ssi->stderr_text_y -= offset;
+#endif
+ }
+ }
+ }
+ if (tail != head)
+ {
+ int direction, ascent, descent;
+ XCharStruct overall;
+ XDrawImageString (dpy, window, ssi->stderr_gc,
+ ssi->stderr_text_x + h_border,
+ ssi->stderr_text_y + v_border
+ + ssi->stderr_font->ascent,
+ head, tail - head);
+ XTextExtents (ssi->stderr_font, tail, tail - head,
+ &direction, &ascent, &descent, &overall);
+ ssi->stderr_text_x += overall.width;
+ }
+}
+
+static void
+make_stderr_overlay_window (saver_screen_info *ssi)
+{
+ saver_info *si = ssi->global;
+ unsigned long transparent_pixel = 0;
+ Visual *visual = get_overlay_visual (ssi->screen, &transparent_pixel);
+ if (visual)
+ {
+ int depth = visual_depth (ssi->screen, visual);
+ XSetWindowAttributes attrs;
+ XWindowAttributes xgwa;
+ unsigned long attrmask;
+ XGetWindowAttributes (si->dpy, ssi->screensaver_window, &xgwa);
+
+ if (si->prefs.debug_p)
+ fprintf(real_stderr,
+ "%s: using overlay visual 0x%0x for stderr text layer.\n",
+ blurb(), (int) XVisualIDFromVisual (visual));
+
+ ssi->stderr_cmap = XCreateColormap(si->dpy,
+ RootWindowOfScreen(ssi->screen),
+ visual, AllocNone);
+
+ attrmask = (CWColormap | CWBackPixel | CWBackingPixel | CWBorderPixel |
+ CWBackingStore | CWSaveUnder);
+ attrs.colormap = ssi->stderr_cmap;
+ attrs.background_pixel = transparent_pixel;
+ attrs.backing_pixel = transparent_pixel;
+ attrs.border_pixel = transparent_pixel;
+ attrs.backing_store = NotUseful;
+ attrs.save_under = False;
+
+ ssi->stderr_overlay_window =
+ XCreateWindow(si->dpy, ssi->screensaver_window, 0, 0,
+ xgwa.width, xgwa.height,
+ 0, depth, InputOutput, visual, attrmask, &attrs);
+ XMapRaised(si->dpy, ssi->stderr_overlay_window);
+ }
+}
+
+
+/* Draws the string on each screen's window as error text.
+ */
+static void
+print_stderr (saver_info *si, char *string)
+{
+ saver_preferences *p = &si->prefs;
+ int i;
+
+ /* In verbose mode, copy it to stderr as well. */
+ if (p->verbose_p)
+ fprintf (real_stderr, "%s", string);
+
+ for (i = 0; i < si->nscreens; i++)
+ print_stderr_1 (&si->screens[i], string);
+}
+
+
+/* Polls the stderr buffer every few seconds and if it finds any text,
+ writes it on all screens.
+ */
+static void
+stderr_popup_timer_fn (XtPointer closure, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ char *s = stderr_buffer;
+ if (*s)
+ {
+ /* If too much data was printed, then something has gone haywire,
+ so truncate it. */
+ char *trailer = "\n\n<< stderr diagnostics have been truncated >>\n\n";
+ int max = sizeof (stderr_buffer) - strlen (trailer) - 5;
+ if (strlen (s) > max)
+ strcpy (s + max, trailer);
+ /* Now show the user. */
+ print_stderr (si, s);
+ }
+
+ stderr_tail = stderr_buffer;
+ si->stderr_popup_timer = 0;
+}
+
+
+/* Called when data becomes available on the stderr pipe. Copies it into
+ stderr_buffer where stderr_popup_timer_fn() can find it later.
+ */
+static void
+stderr_callback (XtPointer closure, int *fd, XtIntervalId *id)
+{
+ saver_info *si = (saver_info *) closure;
+ char *s;
+ int left;
+ int size;
+ int read_this_time = 0;
+
+ if (!fd || *fd < 0 || *fd != stderr_stdout_read_fd)
+ abort();
+
+ if (stderr_tail == 0)
+ stderr_tail = stderr_buffer;
+
+ left = ((sizeof (stderr_buffer) - 2) - (stderr_tail - stderr_buffer));
+
+ s = stderr_tail;
+ *s = 0;
+
+ /* Read as much data from the fd as we can, up to our buffer size. */
+ if (left > 0)
+ {
+ while ((size = read (*fd, (void *) s, left)) > 0)
+ {
+ left -= size;
+ s += size;
+ read_this_time += size;
+ }
+ *s = 0;
+ }
+ else
+ {
+ char buf2 [1024];
+ /* The buffer is full; flush the rest of it. */
+ while (read (*fd, (void *) buf2, sizeof (buf2)) > 0)
+ ;
+ }
+
+ stderr_tail = s;
+ stderr_last_read = time ((time_t *) 0);
+
+ /* Now we have read some data that we would like to put up in a dialog
+ box. But more data may still be coming in - so don't pop up the
+ dialog right now, but instead, start a timer that will pop it up
+ a second from now. Should more data come in in the meantime, we
+ will be called again, and will reset that timer again. So the
+ dialog will only pop up when a second has elapsed with no new data
+ being written to stderr.
+
+ However, if the buffer is full (meaning lots of data has been written)
+ then we don't reset the timer.
+ */
+ if (read_this_time > 0)
+ {
+ if (si->stderr_popup_timer)
+ XtRemoveTimeOut (si->stderr_popup_timer);
+
+ si->stderr_popup_timer =
+ XtAppAddTimeOut (si->app, 1 * 1000, stderr_popup_timer_fn,
+ (XtPointer) si);
+ }
+}
+
+/* If stderr capturing is desired, this replaces `stdout' and `stderr'
+ with a pipe, so that any output written to them will show up on the
+ screen as well as on the original value of those streams.
+ */
+void
+initialize_stderr (saver_info *si)
+{
+ static Boolean done = False;
+ int fds [2];
+ int in, out;
+ int new_stdout, new_stderr;
+ int stdout_fd = 1;
+ int stderr_fd = 2;
+ int flags = 0;
+ Boolean stderr_dialog_p;
+
+ if (done) return;
+ done = True;
+
+ real_stderr = stderr;
+ real_stdout = stdout;
+
+ stderr_dialog_p = get_boolean_resource (si->dpy, "captureStderr", "Boolean");
+
+ if (!stderr_dialog_p)
+ return;
+
+ if (pipe (fds))
+ {
+ perror ("error creating pipe:");
+ return;
+ }
+
+ in = fds [0];
+ out = fds [1];
+
+# ifdef HAVE_FCNTL
+
+# if defined(O_NONBLOCK)
+ flags = O_NONBLOCK;
+# elif defined(O_NDELAY)
+ flags = O_NDELAY;
+# else
+ ERROR!! neither O_NONBLOCK nor O_NDELAY are defined.
+# endif
+
+ /* Set both sides of the pipe to nonblocking - this is so that
+ our reads (in stderr_callback) will terminate, and so that
+ out writes (in the client programs) will silently fail when
+ the pipe is full, instead of hosing the program. */
+ if (fcntl (in, F_SETFL, flags) != 0)
+ {
+ perror ("fcntl:");
+ return;
+ }
+ if (fcntl (out, F_SETFL, flags) != 0)
+ {
+ perror ("fcntl:");
+ return;
+ }
+
+# endif /* !HAVE_FCNTL */
+
+ if (stderr_dialog_p)
+ {
+ FILE *new_stderr_file;
+ FILE *new_stdout_file;
+
+ new_stderr = dup (stderr_fd);
+ if (new_stderr < 0)
+ {
+ perror ("could not dup() a stderr:");
+ return;
+ }
+ if (! (new_stderr_file = fdopen (new_stderr, "w")))
+ {
+ perror ("could not fdopen() the new stderr:");
+ return;
+ }
+ real_stderr = new_stderr_file;
+
+ close (stderr_fd);
+ if (dup2 (out, stderr_fd) < 0)
+ {
+ perror ("could not dup() a new stderr:");
+ return;
+ }
+
+
+ new_stdout = dup (stdout_fd);
+ if (new_stdout < 0)
+ {
+ perror ("could not dup() a stdout:");
+ return;
+ }
+ if (! (new_stdout_file = fdopen (new_stdout, "w")))
+ {
+ perror ("could not fdopen() the new stdout:");
+ return;
+ }
+ real_stdout = new_stdout_file;
+
+ close (stdout_fd);
+ if (dup2 (out, stdout_fd) < 0)
+ {
+ perror ("could not dup() a new stdout:");
+ return;
+ }
+ close (out);
+ }
+
+ stderr_stdout_read_fd = in;
+ XtAppAddInput (si->app, in, (XtPointer) XtInputReadMask, stderr_callback,
+ (XtPointer) si);
+}
+
+
+/* If the "-log file" command-line option has been specified,
+ open the file for append, and redirect stdout/stderr there.
+ This is called very early, before initialize_stderr().
+ */
+void
+stderr_log_file (saver_info *si)
+{
+ int stdout_fd = 1;
+ int stderr_fd = 2;
+ const char *filename = get_string_resource (si->dpy, "logFile", "LogFile");
+ int fd;
+
+ if (!filename || !*filename) return;
+
+ fd = open (filename, O_WRONLY | O_APPEND | O_CREAT, 0666);
+
+ if (fd < 0)
+ {
+ char buf[255];
+ FAIL:
+ sprintf (buf, "%.100s: %.100s", blurb(), filename);
+ perror (buf);
+ fflush (stderr);
+ fflush (stdout);
+ exit (1);
+ }
+
+ fprintf (stderr, "%s: logging to file %s\n", blurb(), filename);
+
+ if (dup2 (fd, stdout_fd) < 0) goto FAIL;
+ if (dup2 (fd, stderr_fd) < 0) goto FAIL;
+
+ fprintf (stderr, "\n\n"
+ "##########################################################################\n"
+ "%s: logging to \"%s\" at %s\n"
+ "##########################################################################\n"
+ "\n",
+ blurb(), filename, timestring(0));
+}
+
+
+/* If there is anything in the stderr buffer, flush it to the real stderr.
+ This does no X operations. Call this when exiting to make sure any
+ last words actually show up.
+ */
+void
+shutdown_stderr (saver_info *si)
+{
+ fflush (stdout);
+ fflush (stderr);
+
+ if (!real_stderr || stderr_stdout_read_fd < 0)
+ return;
+
+ stderr_callback ((XtPointer) si, &stderr_stdout_read_fd, 0);
+
+ if (stderr_tail &&
+ stderr_buffer < stderr_tail)
+ {
+ *stderr_tail = 0;
+ fprintf (real_stderr, "%s", stderr_buffer);
+ stderr_tail = stderr_buffer;
+ }
+
+ if (real_stdout) fflush (real_stdout);
+ if (real_stderr) fflush (real_stderr);
+
+ if (stdout != real_stdout)
+ {
+ dup2 (fileno(real_stdout), fileno(stdout));
+ fclose (real_stdout);
+ real_stdout = stdout;
+ }
+ if (stderr != real_stderr)
+ {
+ dup2 (fileno(real_stderr), fileno(stderr));
+ fclose (real_stderr);
+ real_stderr = stderr;
+ }
+ if (stderr_stdout_read_fd != -1)
+ {
+ close (stderr_stdout_read_fd);
+ stderr_stdout_read_fd = -1;
+ }
+}