summaryrefslogtreecommitdiffstats
path: root/driver/fade.c
diff options
context:
space:
mode:
Diffstat (limited to 'driver/fade.c')
-rw-r--r--driver/fade.c1749
1 files changed, 1749 insertions, 0 deletions
diff --git a/driver/fade.c b/driver/fade.c
new file mode 100644
index 0000000..9451582
--- /dev/null
+++ b/driver/fade.c
@@ -0,0 +1,1749 @@
+/* xscreensaver, Copyright © 1992-2021 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.
+ */
+
+/* There are several different mechanisms here for fading the desktop to
+ black, and then fading it back in again.
+
+ - Colormaps: This only works on 8-bit displays, which basically haven't
+ existed since the 90s. It takes the current colormap, makes a writable
+ copy of it, and then animates the color cells to fade and unfade.
+
+ - XF86 Gamma or RANDR Gamma: These do the fade by altering the brightness
+ settings of the screen. This works on any system that has the "XF86
+ Video-Mode" extension (which is every modern system) AND ALSO has gamma
+ support in the video driver. But it turns out that as of 2021, the
+ Raspberry Pi HDMI video driver still does not support gamma. And there's
+ no way to determine that the video driver lacks gamma support even though
+ the extension exists. Since the Pi is probably the single most popular
+ platform for running X11 on the desktop these days, that makes this
+ method pretty much useless now.
+
+ - SGI VC: Same as the above, but only works on SGI.
+
+ - XSHM: This works by taking a screenshot and hacking the bits by hand.
+ It's slow. Also, in order to fade in from black to the desktop (possibly
+ hours after it faded out) it has to retain that first screenshot of the
+ desktop to fade back to. But if the desktop had changed in the meantime,
+ there will be a glitch at the end as it snaps from the screenshot to the
+ new current reality.
+
+ In summary, everything is terrible because X11 doesn't support alpha.
+
+
+ The fade process goes like this:
+
+ Screen saver activates:
+ - Fade out:
+ - Desktop is visible
+ - Save screenshot for later
+ - Map invisible temp windows
+ - Fade from desktop to black
+ - Erase saver windows to black and raise them
+ - Destroy temp windows
+
+ Screen saver deactivates:
+ - Fade out:
+ - Saver graphics are visible
+ - Map invisible temp windows
+ - Do not save a screenshot
+ - Fade from graphics to black
+ - Erase saver windows to black and raise them
+ - Destroy temp windows
+
+ - Fade in:
+ - Screen is black
+ - Map invisible temp windows
+ - Do not save a screenshot
+ - Unmap saver windows
+ - Fade from black to saved screenshot
+ - Destroy temp windows
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+#ifdef HAVE_JWXYZ
+# include "jwxyz.h"
+#else /* real X11 */
+# include <X11/Xlib.h>
+# include <X11/Xatom.h>
+# include <X11/Xproto.h>
+# include <X11/Intrinsic.h>
+#endif /* !HAVE_JWXYZ */
+
+#include "blurb.h"
+#include "visual.h"
+#include "usleep.h"
+#include "fade.h"
+#include "xshm.h"
+#include "atoms.h"
+#include "clientmsg.h"
+#include "xmu.h"
+
+/* Since gamma fading doesn't work on the Raspberry Pi, probably the single
+ most popular desktop Linux system these days, let's not use this fade
+ method even if the extension exists (which it does).
+ */
+#undef HAVE_XF86VMODE_GAMMA
+
+/* I'm not sure that the RANDR fade method brings anything to the party
+ that the XF86 method does See below.
+ */
+#undef HAVE_RANDR_12
+
+#define HAVE_XINPUT2 1 /* Mandatory */
+
+
+#ifdef HAVE_XINPUT2
+# include <X11/extensions/XInput2.h>
+# include "xinput.h"
+#endif
+
+
+typedef struct {
+ int nscreens;
+ Pixmap *screenshots;
+} fade_state;
+
+
+/* #### There's a bunch of duplicated code in the back half of the
+ four _fade and _whack functions that could probably be combined.
+ */
+#ifdef HAVE_SGI_VC_EXTENSION
+static int sgi_gamma_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p);
+#endif
+#ifdef HAVE_XF86VMODE_GAMMA
+static int xf86_gamma_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p);
+#endif
+#ifdef HAVE_RANDR_12
+static int randr_gamma_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p);
+#endif
+static int colormap_fade (XtAppContext, Display *, Window *wins, int count,
+ double secs, Bool out_p, Bool from_desktop_p);
+static int xshm_fade (XtAppContext, Display *,
+ Window *wins, int count, double secs,
+ Bool out_p, Bool from_desktop_p, fade_state *);
+
+
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+
+#ifdef HAVE_XINPUT2
+static int xi_opcode = -1;
+#endif
+
+/* Closure arg points to a Bool saying whether motion events count.
+ Motion aborts fade-out, but only clicks and keys abort fade-in.
+ */
+static Bool
+user_event_p (Display *dpy, XEvent *event, XPointer arg)
+{
+ Bool motion_p = *((Bool *) arg);
+
+ switch (event->xany.type) {
+ case KeyPress: case ButtonPress:
+ return True;
+ break;
+ case MotionNotify:
+ if (motion_p) return True;
+ break;
+# ifdef HAVE_XINPUT2
+ case GenericEvent:
+ {
+ XIRawEvent *re;
+ if (event->xcookie.extension != xi_opcode)
+ return False;
+ if (! event->xcookie.data)
+ XGetEventData (dpy, &event->xcookie);
+ if (! event->xcookie.data)
+ return False;
+ re = event->xcookie.data;
+
+ if (re->evtype == XI_RawKeyPress ||
+ re->evtype == XI_RawButtonPress)
+ return True;
+ else if (motion_p && re->evtype == XI_RawMotion)
+ return True;
+
+ /* Calling XFreeEventData here is bad news */
+ }
+ break;
+# endif /* HAVE_XINPUT2 */
+ default: break;
+ }
+
+ return False;
+}
+
+
+static Bool
+user_active_p (XtAppContext app, Display *dpy, Bool fade_out_p)
+{
+ XEvent event;
+ XtInputMask m;
+ Bool motion_p = fade_out_p; /* Motion aborts fade-out, not fade-in. */
+ motion_p = False; /* Naah, never abort on motion only */
+
+# ifdef HAVE_XINPUT2
+ if (xi_opcode == -1)
+ {
+ Bool ov = verbose_p;
+ xi_opcode = 0; /* only init once */
+ verbose_p = False; /* xscreensaver already printed this */
+ init_xinput (dpy, &xi_opcode);
+ verbose_p = ov;
+ }
+# endif
+
+ m = XtAppPending (app);
+ if (m & ~XtIMXEvent)
+ {
+ /* Process timers and signals only, don't block. */
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: Xt pending %ld\n", blurb(), m);
+ XtAppProcessEvent (app, m);
+ }
+
+ /* If there is user activity, bug out. (Bug out on keypresses or
+ mouse presses, but not motion, and not release events. Bugging
+ out on motion made the unfade hack be totally useless, I think.)
+ */
+ if (XCheckIfEvent (dpy, &event, &user_event_p, (XPointer) &motion_p))
+ {
+ if (verbose_p > 1)
+ {
+ XIRawEvent *re = 0;
+ if (event.xany.type == GenericEvent && !event.xcookie.data)
+ {
+ XGetEventData (dpy, &event.xcookie);
+ re = event.xcookie.data;
+ }
+ fprintf (stderr, "%s: user input %d %d\n", blurb(),
+ event.xany.type,
+ (re ? re->evtype : -1));
+ }
+ XPutBackEvent (dpy, &event);
+ return True;
+ }
+
+ return False;
+}
+
+
+static void
+flush_user_input (Display *dpy)
+{
+ XEvent event;
+ Bool motion_p = True;
+ while (XCheckIfEvent (dpy, &event, &user_event_p, (XPointer) &motion_p))
+ if (verbose_p > 1)
+ {
+ XIRawEvent *re = 0;
+ if (event.xany.type == GenericEvent && !event.xcookie.data)
+ {
+ XGetEventData (dpy, &event.xcookie);
+ re = event.xcookie.data;
+ }
+ fprintf (stderr, "%s: flushed user event %d %d\n", blurb(),
+ event.xany.type,
+ (re ? re->evtype : -1));
+ }
+}
+
+
+/* This bullshit is needed because the VidMode and SHM extensions don't work
+ on remote displays. */
+static Bool error_handler_hit_p = False;
+
+static int
+ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
+{
+ if (verbose_p > 1)
+ XmuPrintDefaultErrorMessage (dpy, error, stderr);
+ error_handler_hit_p = True;
+ return 0;
+}
+
+
+/* Returns true if canceled by user activity. */
+Bool
+fade_screens (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p, Bool from_desktop_p,
+ void **closureP)
+{
+ int status = False;
+ fade_state *state = 0;
+
+ if (nwindows <= 0) abort();
+ if (!saver_windows) abort();
+
+ if (!closureP) abort();
+ state = (fade_state *) *closureP;
+ if (!state)
+ {
+ state = (fade_state *) calloc (1, sizeof (*state));
+ *closureP = state;
+ }
+
+ if (from_desktop_p && !out_p)
+ abort(); /* Fading in from desktop makes no sense */
+
+ if (out_p)
+ flush_user_input (dpy); /* Flush at start of cycle */
+
+# ifdef HAVE_SGI_VC_EXTENSION
+ /* First try to do it by fading the gamma in an SGI-specific way... */
+ status = sgi_gamma_fade (app, dpy, saver_windows, nwindows, seconds, out_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+# endif
+
+# ifdef HAVE_RANDR_12
+ /* Then try to do it by fading the gamma in an RANDR-specific way... */
+ status = randr_gamma_fade (app, dpy, saver_windows, nwindows, seconds, out_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+# endif
+
+# ifdef HAVE_XF86VMODE_GAMMA
+ /* Then try to do it by fading the gamma in an XFree86-specific way... */
+ status = xf86_gamma_fade(app, dpy, saver_windows, nwindows, seconds, out_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+# endif
+
+ if (has_writable_cells (DefaultScreenOfDisplay (dpy),
+ DefaultVisual (dpy, 0)))
+ {
+ /* Do it the old-fashioned way, which only really worked on
+ 8-bit displays. */
+ status = colormap_fade (app, dpy, saver_windows, nwindows, seconds,
+ out_p, from_desktop_p);
+ if (status == 0 || status == 1)
+ return status; /* faded, possibly canceled */
+ }
+
+ /* Else do it the hard way, by hacking a screenshot. */
+ status = xshm_fade (app, dpy, saver_windows, nwindows, seconds, out_p,
+ from_desktop_p, state);
+ status = (status ? True : False);
+
+ return status;
+}
+
+/****************************************************************************
+
+ Colormap fading
+
+ ****************************************************************************/
+
+
+/* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
+ hardware, which is capable of installing multiple (4) colormaps
+ simultaneously. We have to install multiple copies of the same set of
+ colors in order to fill up all the available slots in the hardware color
+ lookup table, so we install an extra N colormaps per screen to make sure
+ that all screens really go black.
+
+ I'm told that this trick also works with XInside's AcceleratedX when using
+ the Matrox Millennium card (which also allows multiple PseudoColor and
+ TrueColor visuals to co-exist and display properly at the same time.)
+
+ This trick works ok on the 24-bit Indy video hardware, but doesn't work at
+ all on the O2 24-bit hardware. I guess the higher-end hardware is too
+ "good" for this to work (dammit.) So... I figured out the "right" way to
+ do this on SGIs, which is to ramp the monitor's gamma down to 0. That's
+ what is implemented in sgi_gamma_fade(), so we use that if we can.
+
+ Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+colormap_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p, Bool from_desktop_p)
+{
+ int status = -1;
+ Colormap *window_cmaps = 0;
+ int i, j, k;
+ int cmaps_per_screen = 5;
+ int nscreens = ScreenCount(dpy);
+ int ncmaps = nscreens * cmaps_per_screen;
+ Colormap *fade_cmaps = 0;
+ Bool installed = False;
+ int total_ncolors;
+ XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
+ int screen;
+
+ window_cmaps = (Colormap *) calloc(sizeof(Colormap), nwindows);
+ if (!window_cmaps) abort();
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, saver_windows[screen], &xgwa);
+ window_cmaps[screen] = xgwa.colormap;
+ }
+
+ error_handler_hit_p = False;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: colormap fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ total_ncolors = 0;
+ for (i = 0; i < nscreens; i++)
+ total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
+
+ orig_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
+ current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
+
+ /* Get the contents of the colormap we are fading from or to. */
+ screen_colors = orig_colors;
+ for (i = 0; i < nscreens; i++)
+ {
+ Screen *sc = ScreenOfDisplay (dpy, i);
+ int ncolors = CellsOfScreen (sc);
+ Colormap cmap = (from_desktop_p || !out_p
+ ? DefaultColormapOfScreen(sc)
+ : window_cmaps[i]);
+ for (j = 0; j < ncolors; j++)
+ screen_colors[j].pixel = j;
+ XQueryColors (dpy, cmap, screen_colors, ncolors);
+
+ screen_colors += ncolors;
+ }
+
+ memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
+
+
+ /* Make the writable colormaps (we keep these around and reuse them.) */
+ if (!fade_cmaps)
+ {
+ fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
+ for (i = 0; i < nscreens; i++)
+ {
+ Visual *v = DefaultVisual(dpy, i);
+ Screen *s = ScreenOfDisplay(dpy, i);
+ if (has_writable_cells (s, v))
+ for (j = 0; j < cmaps_per_screen; j++)
+ fade_cmaps[(i * cmaps_per_screen) + j] =
+ XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ /* For each screen, compute the current value of each color...
+ */
+ orig_screen_colors = orig_colors;
+ screen_colors = current_colors;
+ for (j = 0; j < nscreens; j++)
+ {
+ int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
+ for (k = 0; k < ncolors; k++)
+ {
+ /* This doesn't take into account the relative luminance of the
+ RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
+ the difference is imperceptible for this application... */
+ screen_colors[k].red = orig_screen_colors[k].red * ratio;
+ screen_colors[k].green = orig_screen_colors[k].green * ratio;
+ screen_colors[k].blue = orig_screen_colors[k].blue * ratio;
+ }
+ screen_colors += ncolors;
+ orig_screen_colors += ncolors;
+ }
+
+ /* Put the colors into the maps...
+ */
+ screen_colors = current_colors;
+ for (j = 0; j < nscreens; j++)
+ {
+ int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
+ for (k = 0; k < cmaps_per_screen; k++)
+ {
+ Colormap c = fade_cmaps[j * cmaps_per_screen + k];
+ if (c)
+ XStoreColors (dpy, c, screen_colors, ncolors);
+ }
+ screen_colors += ncolors;
+ }
+
+ /* Put the maps on the screens, and then take the windows off the
+ screen. (only need to do this the first time through the loop.)
+ */
+ if (!installed)
+ {
+ for (j = 0; j < ncmaps; j++)
+ if (fade_cmaps[j])
+ XInstallColormap (dpy, fade_cmaps[j]);
+ installed = True;
+
+ if (!out_p)
+ for (j = 0; j < nwindows; j++)
+ {
+ XUnmapWindow (dpy, saver_windows[j]);
+ XClearWindow (dpy, saver_windows[j]);
+ }
+ }
+
+ XSync (dpy, False);
+
+ if (error_handler_hit_p)
+ goto DONE;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (orig_colors) free (orig_colors);
+ if (current_colors) free (current_colors);
+
+ /* If we've been given windows to raise after blackout, raise them before
+ releasing the colormaps.
+ */
+ if (out_p)
+ {
+ for (i = 0; i < nwindows; i++)
+ {
+ XClearWindow (dpy, saver_windows[i]);
+ XMapRaised (dpy, saver_windows[i]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* Now put the target maps back.
+ If we're fading out, use the given cmap (or the default cmap, if none.)
+ If we're fading in, always use the default cmap.
+ */
+ for (i = 0; i < nscreens; i++)
+ {
+ Colormap cmap = window_cmaps[i];
+ if (!cmap || !out_p)
+ cmap = DefaultColormap(dpy, i);
+ XInstallColormap (dpy, cmap);
+ }
+
+ /* The fade (in or out) is complete, so we don't need the black maps on
+ stage any more.
+ */
+ for (i = 0; i < ncmaps; i++)
+ if (fade_cmaps[i])
+ {
+ XUninstallColormap(dpy, fade_cmaps[i]);
+ XFreeColormap(dpy, fade_cmaps[i]);
+ fade_cmaps[i] = 0;
+ }
+ free (window_cmaps);
+ free(fade_cmaps);
+ fade_cmaps = 0;
+
+ if (error_handler_hit_p) status = -1;
+ return status;
+}
+
+
+/****************************************************************************
+
+ SGI gamma fading
+
+ ****************************************************************************/
+
+#ifdef HAVE_SGI_VC_EXTENSION
+
+# include <X11/extensions/XSGIvc.h>
+
+struct screen_sgi_gamma_info {
+ int gamma_map; /* ??? always using 0 */
+ int nred, ngreen, nblue;
+ unsigned short *red1, *green1, *blue1;
+ unsigned short *red2, *green2, *blue2;
+ int gamma_size;
+ int gamma_precision;
+ Bool alpha_p;
+};
+
+
+static void sgi_whack_gamma(Display *dpy, int screen,
+ struct screen_sgi_gamma_info *info, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+sgi_gamma_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p)
+{
+ int nscreens = ScreenCount(dpy);
+ struct timeval then, now;
+ int i, screen;
+ int status = -1;
+ struct screen_sgi_gamma_info *info = (struct screen_sgi_gamma_info *)
+ calloc(nscreens, sizeof(*info));
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: sgi fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ /* Get the current gamma maps for all screens.
+ Bug out and return -1 if we can't get them for some screen.
+ */
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
+ &info[screen].gamma_size,
+ &info[screen].gamma_precision,
+ &info[screen].alpha_p))
+ goto FAIL;
+
+ if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+ XSGIVC_COMPONENT_RED,
+ &info[screen].nred, &info[screen].red1))
+ goto FAIL;
+ if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+ XSGIVC_COMPONENT_GREEN,
+ &info[screen].ngreen, &info[screen].green1))
+ goto FAIL;
+ if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
+ XSGIVC_COMPONENT_BLUE,
+ &info[screen].nblue, &info[screen].blue1))
+ goto FAIL;
+
+ if (info[screen].gamma_precision == 8) /* Scale it up to 16 bits. */
+ {
+ int j;
+ for(j = 0; j < info[screen].nred; j++)
+ info[screen].red1[j] =
+ ((info[screen].red1[j] << 8) | info[screen].red1[j]);
+ for(j = 0; j < info[screen].ngreen; j++)
+ info[screen].green1[j] =
+ ((info[screen].green1[j] << 8) | info[screen].green1[j]);
+ for(j = 0; j < info[screen].nblue; j++)
+ info[screen].blue1[j] =
+ ((info[screen].blue1[j] << 8) | info[screen].blue1[j]);
+ }
+
+ info[screen].red2 = (unsigned short *)
+ malloc(sizeof(*info[screen].red2) * (info[screen].nred+1));
+ info[screen].green2 = (unsigned short *)
+ malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
+ info[screen].blue2 = (unsigned short *)
+ malloc(sizeof(*info[screen].blue2) * (info[screen].nblue+1));
+ }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+ gettimeofday(&then, &tzp);
+#else
+ gettimeofday(&then);
+#endif
+
+ /* If we're fading in (from black), then first crank the gamma all the
+ way down to 0, then take the windows off the screen.
+ */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ sgi_whack_gamma(dpy, screen, &info[screen], 0.0);
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ XSync(dpy, False);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nwindows; screen++)
+ sgi_whack_gamma (dpy, screen, &info[screen], ratio);
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* I can't explain this; without this delay, we get a flicker.
+ I suppose there's some lossage with stale bits being in the
+ hardware frame buffer or something, and this delay gives it
+ time to flush out. This sucks! */
+ usleep(100000); /* 1/10th second */
+
+ for (screen = 0; screen < nscreens; screen++)
+ sgi_whack_gamma(dpy, screen, &info[screen], 1.0);
+ XSync(dpy, False);
+
+ FAIL:
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (info[screen].red1) free (info[screen].red1);
+ if (info[screen].green1) free (info[screen].green1);
+ if (info[screen].blue1) free (info[screen].blue1);
+ if (info[screen].red2) free (info[screen].red2);
+ if (info[screen].green2) free (info[screen].green2);
+ if (info[screen].blue2) free (info[screen].blue2);
+ }
+ free(info);
+
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: SGI fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ if (error_handler_hit_p) status = -1;
+ return status;
+}
+
+static void
+sgi_whack_gamma (Display *dpy, int screen, struct screen_sgi_gamma_info *info,
+ float ratio)
+{
+ int k;
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+ for (k = 0; k < info->gamma_size; k++)
+ {
+ info->red2[k] = info->red1[k] * ratio;
+ info->green2[k] = info->green1[k] * ratio;
+ info->blue2[k] = info->blue1[k] * ratio;
+ }
+
+ XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
+ XSGIVC_MComponentRed, info->red2);
+ XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
+ XSGIVC_MComponentGreen, info->green2);
+ XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
+ XSGIVC_MComponentBlue, info->blue2);
+ XSync(dpy, False);
+}
+
+#endif /* HAVE_SGI_VC_EXTENSION */
+
+
+/****************************************************************************
+
+ XFree86 gamma fading
+
+ ****************************************************************************/
+
+#ifdef HAVE_XF86VMODE_GAMMA
+
+#include <X11/extensions/xf86vmode.h>
+
+typedef struct {
+ XF86VidModeGamma vmg;
+ int size;
+ unsigned short *r, *g, *b;
+} xf86_gamma_info;
+
+static int xf86_check_gamma_extension (Display *dpy);
+static Bool xf86_whack_gamma (Display *dpy, int screen,
+ xf86_gamma_info *ginfo, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+xf86_gamma_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p)
+{
+ int nscreens = ScreenCount(dpy);
+ int screen;
+ int status = -1;
+ xf86_gamma_info *info = 0;
+
+ static int ext_ok = -1;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: xf86 fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ /* Only probe the extension once: the answer isn't going to change. */
+ if (ext_ok == -1)
+ ext_ok = xf86_check_gamma_extension (dpy);
+
+ /* If this server doesn't have the gamma extension, bug out. */
+ if (ext_ok == 0)
+ goto FAIL;
+
+# ifndef HAVE_XF86VMODE_GAMMA_RAMP
+ if (ext_ok == 2) ext_ok = 1; /* server is newer than client! */
+# endif
+
+ info = (xf86_gamma_info *) calloc(nscreens, sizeof(*info));
+
+ /* Get the current gamma maps for all screens.
+ Bug out and return -1 if we can't get them for some screen.
+ */
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (ext_ok == 1) /* only have gamma parameter, not ramps. */
+ {
+ if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
+ goto FAIL;
+ }
+# ifdef HAVE_XF86VMODE_GAMMA_RAMP
+ else if (ext_ok == 2) /* have ramps */
+ {
+ if (!XF86VidModeGetGammaRampSize(dpy, screen, &info[screen].size))
+ goto FAIL;
+ if (info[screen].size <= 0)
+ goto FAIL;
+
+ info[screen].r = (unsigned short *)
+ calloc(info[screen].size, sizeof(unsigned short));
+ info[screen].g = (unsigned short *)
+ calloc(info[screen].size, sizeof(unsigned short));
+ info[screen].b = (unsigned short *)
+ calloc(info[screen].size, sizeof(unsigned short));
+
+ if (!(info[screen].r && info[screen].g && info[screen].b))
+ goto FAIL;
+
+# if 0
+ if (verbose_p > 1 && out_p)
+ {
+ int i;
+ fprintf (stderr, "%s: initial gamma ramps, size %d:\n",
+ blurb(), info[screen].size);
+ fprintf (stderr, "%s: R:", blurb());
+ for (i = 0; i < info[screen].size; i++)
+ fprintf (stderr, " %d", info[screen].r[i]);
+ fprintf (stderr, "\n%s: G:", blurb());
+ for (i = 0; i < info[screen].size; i++)
+ fprintf (stderr, " %d", info[screen].g[i]);
+ fprintf (stderr, "\n%s: B:", blurb());
+ for (i = 0; i < info[screen].size; i++)
+ fprintf (stderr, " %d", info[screen].b[i]);
+ fprintf (stderr, "\n");
+ }
+# endif /* 0 */
+
+ if (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
+ info[screen].r,
+ info[screen].g,
+ info[screen].b))
+ goto FAIL;
+ }
+# endif /* HAVE_XF86VMODE_GAMMA_RAMP */
+ else
+ abort();
+ }
+
+ /* If we're fading in (from black), then first crank the gamma all the
+ way down to 0, then take the windows off the screen.
+ */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ xf86_whack_gamma(dpy, screen, &info[screen], 0.0);
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ XSync(dpy, False);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nscreens; screen++)
+ xf86_whack_gamma (dpy, screen, &info[screen], ratio);
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* I can't explain this; without this delay, we get a flicker.
+ I suppose there's some lossage with stale bits being in the
+ hardware frame buffer or something, and this delay gives it
+ time to flush out. This sucks! */
+ usleep(100000); /* 1/10th second */
+
+ for (screen = 0; screen < nscreens; screen++)
+ xf86_whack_gamma(dpy, screen, &info[screen], 1.0);
+ XSync(dpy, False);
+
+ FAIL:
+ if (info)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (info[screen].r) free(info[screen].r);
+ if (info[screen].g) free(info[screen].g);
+ if (info[screen].b) free(info[screen].b);
+ }
+ free(info);
+ }
+
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: xf86 fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ if (error_handler_hit_p) status = -1;
+ return status;
+}
+
+
+static Bool
+safe_XF86VidModeQueryVersion (Display *dpy, int *majP, int *minP)
+{
+ Bool result;
+ XErrorHandler old_handler;
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ result = XF86VidModeQueryVersion (dpy, majP, minP);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ return (error_handler_hit_p
+ ? False
+ : result);
+}
+
+
+
+/* VidModeExtension version 2.0 or better is needed to do gamma.
+ 2.0 added gamma values; 2.1 added gamma ramps.
+ */
+# define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
+# define XF86_VIDMODE_GAMMA_MIN_MINOR 0
+# define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
+# define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1
+
+
+
+/* Returns 0 if gamma fading not available; 1 if only gamma value setting
+ is available; 2 if gamma ramps are available.
+ */
+static int
+xf86_check_gamma_extension (Display *dpy)
+{
+ int event, error, major, minor;
+
+ if (!XF86VidModeQueryExtension (dpy, &event, &error))
+ return 0; /* display doesn't have the extension. */
+
+ if (!safe_XF86VidModeQueryVersion (dpy, &major, &minor))
+ return 0; /* unable to get version number? */
+
+ if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR ||
+ (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
+ minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
+ return 0; /* extension is too old for gamma. */
+
+ if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR ||
+ (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
+ minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
+ return 1; /* extension is too old for gamma ramps. */
+
+ /* Copacetic */
+ return 2;
+}
+
+
+/* XFree doesn't let you set gamma to a value smaller than this.
+ Apparently they didn't anticipate the trick I'm doing here...
+ */
+#define XF86_MIN_GAMMA 0.1
+
+
+static Bool
+xf86_whack_gamma(Display *dpy, int screen, xf86_gamma_info *info,
+ float ratio)
+{
+ XErrorHandler old_handler;
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+
+ if (info->size == 0) /* we only have a gamma number, not a ramp. */
+ {
+ XF86VidModeGamma g2;
+
+ g2.red = info->vmg.red * ratio;
+ g2.green = info->vmg.green * ratio;
+ g2.blue = info->vmg.blue * ratio;
+
+# ifdef XF86_MIN_GAMMA
+ if (g2.red < XF86_MIN_GAMMA) g2.red = XF86_MIN_GAMMA;
+ if (g2.green < XF86_MIN_GAMMA) g2.green = XF86_MIN_GAMMA;
+ if (g2.blue < XF86_MIN_GAMMA) g2.blue = XF86_MIN_GAMMA;
+# endif
+
+ if (! XF86VidModeSetGamma (dpy, screen, &g2))
+ return -1;
+ }
+ else
+ {
+# ifdef HAVE_XF86VMODE_GAMMA_RAMP
+
+ unsigned short *r, *g, *b;
+ int i;
+ r = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+ g = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+ b = (unsigned short *) malloc(info->size * sizeof(unsigned short));
+
+ for (i = 0; i < info->size; i++)
+ {
+ r[i] = info->r[i] * ratio;
+ g[i] = info->g[i] * ratio;
+ b[i] = info->b[i] * ratio;
+ }
+
+ if (! XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b))
+ return -1;
+
+ free (r);
+ free (g);
+ free (b);
+
+# else /* !HAVE_XF86VMODE_GAMMA_RAMP */
+ abort();
+# endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
+ }
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ return status;
+}
+
+#endif /* HAVE_XF86VMODE_GAMMA */
+
+
+/****************************************************************************
+
+ RANDR gamma fading
+
+ ****************************************************************************
+
+
+ Dec 2020: I noticed that gamma fading was not working on a Raspberry Pi
+ with Raspbian 10.6, and wrote this under the hypothesis that the XF86
+ gamma fade code was failing and maybe the RANDR version would work better.
+ Then I discovered that gamma simply isn't supported by the Raspberry Pi
+ HDMI driver:
+
+ https://github.com/raspberrypi/firmware/issues/1274
+
+ I should have tried this first and seen it not work:
+
+ xrandr --output HDMI-1 --brightness .1
+
+ Since I still don't have an answer to the question of whether the XF86
+ gamma fading method works on modern Linux systems that also have RANDR,
+ I'm leaving this new code turned off for now, as it is largely untested.
+ The new code would be useful if:
+
+ A) The XF86 way doesn't work but the RANDR way does, or
+ B) There exist systems that have RANDR but do not have XF86.
+
+ But until Raspberry Pi supports gamma, both gamma methods fail to work
+ for far too many users for them to be used in XScreenSaver.
+ */
+#ifdef HAVE_RANDR_12
+
+# include <X11/extensions/Xrandr.h>
+
+typedef struct {
+ RRCrtc crtc;
+ Bool enabled_p;
+ XRRCrtcGamma *gamma;
+} randr_gamma_info;
+
+
+static int
+randr_check_gamma_extension (Display *dpy)
+{
+ int event, error, major, minor;
+ if (! XRRQueryExtension (dpy, &event, &error))
+ return 0;
+
+ if (! XRRQueryVersion (dpy, &major, &minor)) {
+ if (verbose_p > 1) fprintf (stderr, "%s: no randr ext\n", blurb());
+ return 0;
+ }
+
+ /* Reject if < 1.5. It's possible that 1.2 - 1.4 work, but untested. */
+ if (major < 1 || (major == 1 && minor < 5)) {
+ if (verbose_p > 1) fprintf (stderr, "%s: randr ext only version %d.%d\n",
+ blurb(), major, minor);
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static void randr_whack_gamma (Display *dpy, int screen,
+ randr_gamma_info *ginfo, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unable to fade because the extension isn't supported.
+ */
+static int
+randr_gamma_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows,
+ double seconds, Bool out_p)
+{
+ int xsc = ScreenCount (dpy);
+ int nscreens = 0;
+ int j, screen;
+ int status = -1;
+ randr_gamma_info *info = 0;
+
+ static int ext_ok = -1;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: randr fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ /* Only probe the extension once: the answer isn't going to change. */
+ if (ext_ok == -1)
+ ext_ok = randr_check_gamma_extension (dpy);
+
+ /* If this server doesn't have the RANDR extension, bug out. */
+ if (ext_ok == 0)
+ goto FAIL;
+
+ /* Add up the virtual screens on each X screen. */
+ for (screen = 0; screen < xsc; screen++)
+ {
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindow (dpy, screen));
+ nscreens += res->noutput;
+ XRRFreeScreenResources (res);
+ }
+
+ if (nscreens <= 0)
+ goto FAIL;
+
+ info = (randr_gamma_info *) calloc(nscreens, sizeof(*info));
+
+ /* Get the current gamma maps for all screens.
+ Bug out and return -1 if we can't get them for some screen.
+ */
+ for (screen = 0, j = 0; screen < xsc; screen++)
+ {
+ XRRScreenResources *res =
+ XRRGetScreenResources (dpy, RootWindow (dpy, screen));
+ int k;
+ for (k = 0; k < res->noutput; k++, j++)
+ {
+ XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, res->outputs[j]);
+ RRCrtc crtc = (rroi->crtc ? rroi->crtc :
+ rroi->ncrtc ? rroi->crtcs[0] : 0);
+
+ info[j].crtc = crtc;
+ info[j].gamma = XRRGetCrtcGamma (dpy, crtc);
+
+ /* #### is this test sufficient? */
+ info[j].enabled_p = (rroi->connection != RR_Disconnected);
+
+# if 0
+ if (verbose_p > 1 && out_p)
+ {
+ int m;
+ fprintf (stderr, "%s: initial gamma ramps, size %d:\n",
+ blurb(), info[j].gamma->size);
+ fprintf (stderr, "%s: R:", blurb());
+ for (m = 0; m < info[j].gamma->size; m++)
+ fprintf (stderr, " %d", info[j].gamma->red[m]);
+ fprintf (stderr, "\n%s: G:", blurb());
+ for (m = 0; m < info[j].gamma->size; m++)
+ fprintf (stderr, " %d", info[j].gamma->green[m]);
+ fprintf (stderr, "\n%s: B:", blurb());
+ for (m = 0; m < info[j].gamma->size; m++)
+ fprintf (stderr, " %d", info[j].gamma->blue[m]);
+ fprintf (stderr, "\n");
+ }
+# endif /* 0 */
+
+ XRRFreeOutputInfo (rroi);
+ }
+ XRRFreeScreenResources (res);
+ }
+
+ /* If we're fading in (from black), then first crank the gamma all the
+ way down to 0, then take the windows off the screen.
+ */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ randr_whack_gamma(dpy, screen, &info[screen], 0.0);
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ XSync(dpy, False);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ if (!info[screen].enabled_p)
+ continue;
+
+ randr_whack_gamma (dpy, screen, &info[screen], ratio);
+ }
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ }
+ XSync(dpy, False);
+ }
+
+ /* I can't explain this; without this delay, we get a flicker.
+ I suppose there's some lossage with stale bits being in the
+ hardware frame buffer or something, and this delay gives it
+ time to flush out. This sucks! */
+ /* #### That comment was about XF86, not verified with randr. */
+ usleep(100000); /* 1/10th second */
+
+ for (screen = 0; screen < nscreens; screen++)
+ randr_whack_gamma (dpy, screen, &info[screen], 1.0);
+ XSync(dpy, False);
+
+ FAIL:
+ if (info)
+ {
+ for (screen = 0; screen < nscreens; screen++)
+ {
+ if (info[screen].gamma) XRRFreeGamma (info[screen].gamma);
+ }
+ free(info);
+ }
+
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: randr fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ return status;
+}
+
+
+static void
+randr_whack_gamma (Display *dpy, int screen, randr_gamma_info *info,
+ float ratio)
+{
+ XErrorHandler old_handler;
+ XRRCrtcGamma *g2;
+ int i;
+
+ XSync (dpy, False);
+ error_handler_hit_p = False;
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+
+ g2 = XRRAllocGamma (info->gamma->size);
+ for (i = 0; i < info->gamma->size; i++)
+ {
+ g2->red[i] = ratio * info->gamma->red[i];
+ g2->green[i] = ratio * info->gamma->green[i];
+ g2->blue[i] = ratio * info->gamma->blue[i];
+ }
+
+ XRRSetCrtcGamma (dpy, info->crtc, g2);
+ XRRFreeGamma (g2);
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+ XSync (dpy, False);
+
+ return 0;
+}
+
+#endif /* HAVE_RANDR_12 */
+
+
+/****************************************************************************
+
+ XSHM screen-shot fading
+
+ ****************************************************************************/
+
+typedef struct {
+ GC gc;
+ Window window;
+ Pixmap screenshot;
+ XImage *src, *intermediate;
+} xshm_fade_info;
+
+
+static int xshm_whack (Display *, XShmSegmentInfo *,
+ xshm_fade_info *, float ratio);
+
+/* Returns:
+ 0: faded normally
+ 1: canceled by user activity
+ -1: unknown error
+ */
+static int
+xshm_fade (XtAppContext app, Display *dpy,
+ Window *saver_windows, int nwindows, double seconds,
+ Bool out_p, Bool from_desktop_p, fade_state *state)
+{
+ int screen;
+ int status = -1;
+ xshm_fade_info *info = 0;
+ XShmSegmentInfo shm_info;
+ Window saver_window = 0;
+ XErrorHandler old_handler = 0;
+
+ XSync (dpy, False);
+ old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
+ error_handler_hit_p = False;
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: SHM fade %s\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ info = (xshm_fade_info *) calloc(nwindows, sizeof(*info));
+ if (!info) goto FAIL;
+
+ saver_window = find_screensaver_window (dpy, 0);
+ if (!saver_window) goto FAIL;
+
+ /* Retrieve a screenshot of the area covered by each window.
+ Windows might not be mapped.
+ Bug out and return -1 if we can't get one for some screen.
+ */
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XWindowAttributes xgwa;
+ Window root;
+ XGCValues gcv;
+ GC gc;
+ unsigned long attrmask = 0;
+ XSetWindowAttributes attrs;
+
+ XGetWindowAttributes (dpy, saver_windows[screen], &xgwa);
+ root = RootWindowOfScreen (xgwa.screen);
+
+ info[screen].src =
+ create_xshm_image (dpy, xgwa.visual, xgwa.depth,
+ ZPixmap, &shm_info, xgwa.width, xgwa.height);
+ if (!info[screen].src) goto FAIL;
+
+ info[screen].intermediate =
+ create_xshm_image (dpy, xgwa.visual, xgwa.depth,
+ ZPixmap, &shm_info, xgwa.width, xgwa.height);
+ if (!info[screen].intermediate) goto FAIL;
+
+ if (!out_p)
+ {
+ /* If we are fading in, retrieve the saved screenshot from
+ before we faded out. */
+ if (state->nscreens <= screen) goto FAIL;
+ info[screen].screenshot = state->screenshots[screen];
+ }
+ else
+ {
+ /* Create a pixmap and grab a screenshot into it. */
+ info[screen].screenshot =
+ XCreatePixmap (dpy, root, xgwa.width, xgwa.height, xgwa.depth);
+ if (!info[screen].screenshot) goto FAIL;
+
+ gcv.function = GXcopy;
+ gcv.subwindow_mode = IncludeInferiors;
+ gc = XCreateGC (dpy, root, GCFunction | GCSubwindowMode, &gcv);
+ XCopyArea (dpy, root, info[screen].screenshot, gc,
+ xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
+ XFreeGC (dpy, gc);
+ }
+
+ /* Create the fader window for the animation. */
+ attrmask = CWOverrideRedirect;
+ attrs.override_redirect = True;
+ info[screen].window =
+ XCreateWindow (dpy, root, xgwa.x, xgwa.y,
+ xgwa.width, xgwa.height, xgwa.border_width, xgwa.depth,
+ InputOutput, xgwa.visual,
+ attrmask, &attrs);
+ if (!info[screen].window) goto FAIL;
+ /* XSelectInput (dpy, info[screen].window,
+ KeyPressMask | ButtonPressMask); */
+
+ /* Copy the screenshot pixmap to the source image */
+ if (! get_xshm_image (dpy, info[screen].screenshot, info[screen].src,
+ 0, 0, ~0L, &shm_info))
+ goto FAIL;
+
+ gcv.function = GXcopy;
+ info[screen].gc = XCreateGC (dpy, info[screen].window, GCFunction, &gcv);
+ }
+
+ /* If we're fading out from the desktop, save our screen shots for later use.
+ But not if we're fading out from the savers to black. In that case we
+ don't want to overwrite the desktop screenshot with the current screenshot
+ which is of the final frames of the just-killed graphics hacks. */
+ if (from_desktop_p)
+ {
+ if (!out_p) abort();
+ for (screen = 0; screen < state->nscreens; screen++)
+ if (state->screenshots[screen])
+ XFreePixmap (dpy, state->screenshots[screen]);
+ if (state->screenshots)
+ free (state->screenshots);
+ state->nscreens = nwindows;
+ state->screenshots = calloc (nwindows, sizeof(*state->screenshots));
+ if (!state->screenshots)
+ state->nscreens = 0;
+ for (screen = 0; screen < state->nscreens; screen++)
+ state->screenshots[screen] = info[screen].screenshot;
+ }
+
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ if (out_p)
+ /* Copy the screenshot to the fader window */
+ XSetWindowBackgroundPixmap (dpy, info[screen].window,
+ info[screen].screenshot);
+ else
+ {
+ XSetWindowBackgroundPixmap (dpy, info[screen].window, None);
+ XSetWindowBackground (dpy, info[screen].window, BlackPixel (dpy, 0));
+ }
+
+ XMapRaised (dpy, info[screen].window);
+
+ /* Now that we have mapped the screenshot on the fader windows,
+ take the saver windows off the screen. */
+ if (out_p)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ }
+ }
+
+ /* Run the animation at the maximum frame rate in the time allotted. */
+ {
+ double start_time = double_time();
+ double end_time = start_time + seconds;
+ double prev = 0;
+ double now;
+ int frames = 0;
+ double max = 1/60.0; /* max FPS */
+ while ((now = double_time()) < end_time)
+ {
+ double ratio = (end_time - now) / seconds;
+ if (!out_p) ratio = 1-ratio;
+
+ for (screen = 0; screen < nwindows; screen++)
+ if (xshm_whack (dpy, &shm_info, &info[screen], ratio))
+ goto FAIL;
+
+ if (error_handler_hit_p)
+ goto FAIL;
+ if (user_active_p (app, dpy, out_p))
+ {
+ status = 1; /* user activity status code */
+ goto DONE;
+ }
+ frames++;
+
+ if (now < prev + max)
+ usleep (1000000 * (prev + max - now));
+ prev = now;
+ }
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
+ }
+
+ status = 0; /* completed fade with no user activity */
+
+ DONE:
+
+ /* If we're fading out, we have completed the transition from what was
+ on the screen to black, using our fader windows. Now raise the saver
+ windows and take the fader windows off the screen. Since they're both
+ black, that will be imperceptible.
+ */
+ if (out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XClearWindow (dpy, saver_windows[screen]);
+ XMapRaised (dpy, saver_windows[screen]);
+ if (info[screen].window)
+ XUnmapWindow (dpy, info[screen].window);
+ }
+ }
+
+ XSync (dpy, False);
+
+ FAIL:
+
+ /* After fading in, take the saver windows off the screen before
+ destroying the occluding screenshot windows. */
+ if (!out_p)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ XUnmapWindow (dpy, saver_windows[screen]);
+ XClearWindow (dpy, saver_windows[screen]);
+ }
+ }
+
+ if (info)
+ {
+ for (screen = 0; screen < nwindows; screen++)
+ {
+ if (info[screen].src)
+ destroy_xshm_image (dpy, info[screen].src, &shm_info);
+ if (info[screen].intermediate)
+ destroy_xshm_image (dpy, info[screen].intermediate, &shm_info);
+ if (info[screen].window)
+ XDestroyWindow (dpy, info[screen].window);
+ if (info[screen].gc)
+ XFreeGC (dpy, info[screen].gc);
+ }
+ free (info);
+ }
+
+ /* If fading in, delete the screenshot pixmaps, and the list of them. */
+ if (!out_p && saver_window)
+ {
+ for (screen = 0; screen < state->nscreens; screen++)
+ if (state->screenshots[screen])
+ XFreePixmap (dpy, state->screenshots[screen]);
+ if (state->screenshots)
+ free (state->screenshots);
+ state->nscreens = 0;
+ state->screenshots = 0;
+ }
+
+ XSync (dpy, False);
+ XSetErrorHandler (old_handler);
+
+ if (error_handler_hit_p) status = -1;
+ if (verbose_p > 1 && status)
+ fprintf (stderr, "%s: SHM fade %s failed\n",
+ blurb(), (out_p ? "out" : "in"));
+
+ return status;
+}
+
+
+static int
+xshm_whack (Display *dpy, XShmSegmentInfo *shm_info,
+ xshm_fade_info *info, float ratio)
+{
+ unsigned char *inbits = (unsigned char *) info->src->data;
+ unsigned char *outbits = (unsigned char *) info->intermediate->data;
+ unsigned char *end = (outbits +
+ info->intermediate->bytes_per_line *
+ info->intermediate->height);
+ unsigned char ramp[256];
+ int i;
+
+ XSync (dpy, False);
+
+ if (ratio < 0) ratio = 0;
+ if (ratio > 1) ratio = 1;
+
+ for (i = 0; i < sizeof(ramp); i++)
+ ramp[i] = i * ratio;
+ while (outbits < end)
+ *outbits++ = ramp[*inbits++];
+
+ put_xshm_image (dpy, info->window, info->gc, info->intermediate, 0, 0, 0, 0,
+ info->intermediate->width, info->intermediate->height,
+ shm_info);
+ XSync (dpy, False);
+ return 0;
+}