/* stderr.c --- capturing stdout/stderr output onto the screensaver window.
* xscreensaver, Copyright (c) 1991-2020 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;
Bool shutting_down_p = (id == 0); /* Called from shutdown_stderr() */
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 && !shutting_down_p)
{
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, Bool inhibit_p)
{
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;
if (inhibit_p)
return;
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().
Returns true if logging to a file.
*/
Bool
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 False;
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));
return True;
}
/* 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;
/* Copy any stragglers from the stderr pipe into stderr_buffer */
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;
}
}