"Fossies" - the Fresh Open Source Software Archive

Member "xscreensaver-6.01/driver/fade.c" (21 May 2021, 53729 Bytes) of package /linux/misc/xscreensaver-6.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "fade.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.00_vs_6.01.

    1 /* xscreensaver, Copyright © 1992-2021 Jamie Zawinski <jwz@jwz.org>
    2  *
    3  * Permission to use, copy, modify, distribute, and sell this software and its
    4  * documentation for any purpose is hereby granted without fee, provided that
    5  * the above copyright notice appear in all copies and that both that
    6  * copyright notice and this permission notice appear in supporting
    7  * documentation.  No representations are made about the suitability of this
    8  * software for any purpose.  It is provided "as is" without express or 
    9  * implied warranty.
   10  */
   11 
   12 /* There are several different mechanisms here for fading the desktop to
   13    black, and then fading it back in again.
   14 
   15    - Colormaps: This only works on 8-bit displays, which basically haven't
   16      existed since the 90s.  It takes the current colormap, makes a writable
   17      copy of it, and then animates the color cells to fade and unfade.
   18 
   19    - XF86 Gamma or RANDR Gamma: These do the fade by altering the brightness
   20      settings of the screen.  This works on any system that has the "XF86
   21      Video-Mode" extension (which is every modern system) AND ALSO has gamma
   22      support in the video driver.  But it turns out that as of 2021, the
   23      Raspberry Pi HDMI video driver still does not support gamma.  And there's
   24      no way to determine that the video driver lacks gamma support even though
   25      the extension exists.  Since the Pi is probably the single most popular
   26      platform for running X11 on the desktop these days, that makes this
   27      method pretty much useless now.
   28 
   29    - SGI VC: Same as the above, but only works on SGI.
   30 
   31    - XSHM: This works by taking a screenshot and hacking the bits by hand.
   32      It's slow.  Also, in order to fade in from black to the desktop (possibly
   33      hours after it faded out) it has to retain that first screenshot of the
   34      desktop to fade back to.  But if the desktop had changed in the meantime,
   35      there will be a glitch at the end as it snaps from the screenshot to the
   36      new current reality.
   37 
   38    In summary, everything is terrible because X11 doesn't support alpha.
   39 
   40 
   41    The fade process goes like this:
   42 
   43    Screen saver activates:
   44    - Fade out:
   45      - Desktop is visible
   46      - Save screenshot for later
   47      - Map invisible temp windows
   48      - Fade from desktop to black
   49      - Erase saver windows to black and raise them
   50      - Destroy temp windows
   51 
   52    Screen saver deactivates:
   53    - Fade out:
   54      - Saver graphics are visible
   55      - Map invisible temp windows
   56      - Do not save a screenshot
   57      - Fade from graphics to black
   58      - Erase saver windows to black and raise them
   59      - Destroy temp windows
   60 
   61    - Fade in:
   62      - Screen is black
   63      - Map invisible temp windows
   64      - Do not save a screenshot
   65      - Unmap saver windows
   66      - Fade from black to saved screenshot
   67      - Destroy temp windows
   68  */
   69 
   70 #ifdef HAVE_CONFIG_H
   71 # include "config.h"
   72 #endif
   73 
   74 #include <stdlib.h>
   75 #include <stdio.h>
   76 #include <string.h>
   77 #include <sys/time.h>
   78 
   79 #ifdef HAVE_JWXYZ
   80 # include "jwxyz.h"
   81 #else /* real X11 */
   82 # include <X11/Xlib.h>
   83 # include <X11/Xatom.h>
   84 # include <X11/Xproto.h>
   85 # include <X11/Intrinsic.h>
   86 #endif /* !HAVE_JWXYZ */
   87 
   88 #include "blurb.h"
   89 #include "visual.h"
   90 #include "usleep.h"
   91 #include "fade.h"
   92 #include "xshm.h"
   93 #include "atoms.h"
   94 #include "clientmsg.h"
   95 #include "xmu.h"
   96 
   97 /* Since gamma fading doesn't work on the Raspberry Pi, probably the single
   98    most popular desktop Linux system these days, let's not use this fade
   99    method even if the extension exists (which it does).
  100  */
  101 #undef HAVE_XF86VMODE_GAMMA
  102 
  103 /* I'm not sure that the RANDR fade method brings anything to the party
  104    that the XF86 method does  See below.
  105  */
  106 #undef HAVE_RANDR_12
  107 
  108 #define HAVE_XINPUT2 1  /* Mandatory */
  109 
  110 
  111 #ifdef HAVE_XINPUT2
  112 # include <X11/extensions/XInput2.h>
  113 # include "xinput.h"
  114 #endif
  115 
  116 
  117 typedef struct {
  118   int nscreens;
  119   Pixmap *screenshots;
  120 } fade_state;
  121 
  122 
  123 /* #### There's a bunch of duplicated code in the back half of the
  124    four _fade and _whack functions that could probably be combined.
  125  */
  126 #ifdef HAVE_SGI_VC_EXTENSION
  127 static int sgi_gamma_fade (XtAppContext, Display *, Window *wins, int count,
  128                            double secs, Bool out_p);
  129 #endif
  130 #ifdef HAVE_XF86VMODE_GAMMA
  131 static int xf86_gamma_fade (XtAppContext, Display *, Window *wins, int count,
  132                             double secs, Bool out_p);
  133 #endif
  134 #ifdef HAVE_RANDR_12
  135 static int randr_gamma_fade (XtAppContext, Display *, Window *wins, int count,
  136                              double secs, Bool out_p);
  137 #endif
  138 static int colormap_fade (XtAppContext, Display *, Window *wins, int count, 
  139                           double secs, Bool out_p, Bool from_desktop_p);
  140 static int xshm_fade (XtAppContext, Display *,
  141                       Window *wins, int count, double secs,
  142                       Bool out_p, Bool from_desktop_p, fade_state *);
  143 
  144 
  145 static double
  146 double_time (void)
  147 {
  148   struct timeval now;
  149 # ifdef GETTIMEOFDAY_TWO_ARGS
  150   struct timezone tzp;
  151   gettimeofday(&now, &tzp);
  152 # else
  153   gettimeofday(&now);
  154 # endif
  155 
  156   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
  157 }
  158 
  159 
  160 #ifdef HAVE_XINPUT2
  161 static int xi_opcode = -1;
  162 #endif
  163 
  164 /* Closure arg points to a Bool saying whether motion events count.
  165    Motion aborts fade-out, but only clicks and keys abort fade-in.
  166  */
  167 static Bool
  168 user_event_p (Display *dpy, XEvent *event, XPointer arg)
  169 {
  170   Bool motion_p = *((Bool *) arg);
  171 
  172   switch (event->xany.type) {
  173   case KeyPress: case ButtonPress:
  174     return True;
  175     break;
  176   case MotionNotify:
  177     if (motion_p) return True;
  178     break;
  179 # ifdef HAVE_XINPUT2
  180   case GenericEvent:
  181     {
  182       XIRawEvent *re;
  183       if (event->xcookie.extension != xi_opcode)
  184         return False;
  185       if (! event->xcookie.data)
  186         XGetEventData (dpy, &event->xcookie);
  187       if (! event->xcookie.data)
  188         return False;
  189       re = event->xcookie.data;    
  190 
  191       if (re->evtype == XI_RawKeyPress ||
  192           re->evtype == XI_RawButtonPress)
  193         return True;
  194       else if (motion_p && re->evtype == XI_RawMotion)
  195         return True;
  196 
  197       /* Calling XFreeEventData here is bad news */
  198     }
  199     break;
  200 # endif /* HAVE_XINPUT2 */
  201   default: break;
  202   }
  203 
  204   return False;
  205 }
  206 
  207 
  208 static Bool
  209 user_active_p (XtAppContext app, Display *dpy, Bool fade_out_p)
  210 {
  211   XEvent event;
  212   XtInputMask m;
  213   Bool motion_p = fade_out_p;   /* Motion aborts fade-out, not fade-in. */
  214   motion_p = False;     /* Naah, never abort on motion only */
  215 
  216 # ifdef HAVE_XINPUT2
  217   if (xi_opcode == -1)
  218     {
  219       Bool ov = verbose_p;
  220       xi_opcode = 0; /* only init once */
  221       verbose_p = False;  /* xscreensaver already printed this */
  222       init_xinput (dpy, &xi_opcode);
  223       verbose_p = ov;
  224     }
  225 # endif
  226 
  227   m = XtAppPending (app);
  228   if (m & ~XtIMXEvent)
  229     {
  230       /* Process timers and signals only, don't block. */
  231       if (verbose_p > 1)
  232         fprintf (stderr, "%s: Xt pending %ld\n", blurb(), m);
  233       XtAppProcessEvent (app, m);
  234     }
  235 
  236   /* If there is user activity, bug out.  (Bug out on keypresses or
  237      mouse presses, but not motion, and not release events.  Bugging
  238      out on motion made the unfade hack be totally useless, I think.)
  239    */
  240   if (XCheckIfEvent (dpy, &event, &user_event_p, (XPointer) &motion_p))
  241     {
  242       if (verbose_p > 1)
  243         {
  244           XIRawEvent *re = 0;
  245           if (event.xany.type == GenericEvent && !event.xcookie.data)
  246             {
  247               XGetEventData (dpy, &event.xcookie);
  248               re = event.xcookie.data;
  249             }
  250           fprintf (stderr, "%s: user input %d %d\n", blurb(),
  251                    event.xany.type,
  252                    (re ? re->evtype : -1));
  253         }
  254       XPutBackEvent (dpy, &event);
  255       return True;
  256     }
  257 
  258   return False;
  259 }
  260 
  261 
  262 static void
  263 flush_user_input (Display *dpy)
  264 {
  265   XEvent event;
  266   Bool motion_p = True;
  267   while (XCheckIfEvent (dpy, &event, &user_event_p, (XPointer) &motion_p))
  268     if (verbose_p > 1)
  269       {
  270         XIRawEvent *re = 0;
  271         if (event.xany.type == GenericEvent && !event.xcookie.data)
  272           {
  273             XGetEventData (dpy, &event.xcookie);
  274             re = event.xcookie.data;
  275           }
  276         fprintf (stderr, "%s: flushed user event %d %d\n", blurb(),
  277                  event.xany.type,
  278                  (re ? re->evtype : -1));
  279       }
  280 }
  281 
  282 
  283 /* This bullshit is needed because the VidMode and SHM extensions don't work
  284    on remote displays. */
  285 static Bool error_handler_hit_p = False;
  286 
  287 static int
  288 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
  289 {
  290   if (verbose_p > 1)
  291     XmuPrintDefaultErrorMessage (dpy, error, stderr);
  292   error_handler_hit_p = True;
  293   return 0;
  294 }
  295 
  296 
  297 /* Like XDestroyWindow, but destroys the window later, on a timer.  This is
  298    necessary to work around a KDE 5 compositor bug.  Without this, destroying
  299    an old window causes the desktop to briefly become visible, even though a
  300    new window has already been mapped that is obscuring both of them!
  301  */
  302 typedef struct {
  303   XtAppContext app;
  304   Display *dpy;
  305   Window window;
  306 } defer_destroy_closure;
  307 
  308 static void
  309 defer_destroy_handler (XtPointer closure, XtIntervalId *id)
  310 {
  311   defer_destroy_closure *c = (defer_destroy_closure *) closure;
  312   XErrorHandler old_handler;
  313   XSync (c->dpy, False);
  314   error_handler_hit_p = False;
  315   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
  316   XDestroyWindow (c->dpy, c->window);
  317   XSync (c->dpy, False);
  318   XSetErrorHandler (old_handler);
  319   if (verbose_p > 1 && !error_handler_hit_p)
  320     fprintf (stderr, "%s: destroyed old window 0x%lx\n",
  321              blurb(), (unsigned long) c->window);
  322   free (c);
  323 }
  324 
  325 /* Used here and in windows.c */
  326 void
  327 defer_XDestroyWindow (XtAppContext app, Display *dpy, Window w)
  328 {
  329   defer_destroy_closure *c = (defer_destroy_closure *) malloc (sizeof (*c));
  330   c->app = app;
  331   c->dpy = dpy;
  332   c->window = w;
  333   XtAppAddTimeOut (app, 5 * 1000, defer_destroy_handler, (XtPointer) c);
  334 }
  335 
  336 
  337 /* Returns true if canceled by user activity. */
  338 Bool
  339 fade_screens (XtAppContext app, Display *dpy,
  340               Window *saver_windows, int nwindows,
  341               double seconds, Bool out_p, Bool from_desktop_p,
  342               void **closureP)
  343 {
  344   int status = False;
  345   fade_state *state = 0;
  346 
  347   if (nwindows <= 0) abort();
  348   if (!saver_windows) abort();
  349 
  350   if (!closureP) abort();
  351   state = (fade_state *) *closureP;
  352   if (!state)
  353     {
  354       state = (fade_state *) calloc (1, sizeof (*state));
  355       *closureP = state;
  356     }
  357 
  358   if (from_desktop_p && !out_p)
  359     abort();  /* Fading in from desktop makes no sense */
  360 
  361   if (out_p)
  362     flush_user_input (dpy);    /* Flush at start of cycle */
  363 
  364 # ifdef HAVE_SGI_VC_EXTENSION
  365   /* First try to do it by fading the gamma in an SGI-specific way... */
  366   status = sgi_gamma_fade (app, dpy, saver_windows, nwindows, seconds, out_p);
  367   if (status == 0 || status == 1)
  368     return status;  /* faded, possibly canceled */
  369 # endif
  370 
  371 # ifdef HAVE_RANDR_12
  372   /* Then try to do it by fading the gamma in an RANDR-specific way... */
  373   status = randr_gamma_fade (app, dpy, saver_windows, nwindows, seconds, out_p);
  374   if (status == 0 || status == 1)
  375     return status;  /* faded, possibly canceled */
  376 # endif
  377 
  378 # ifdef HAVE_XF86VMODE_GAMMA
  379   /* Then try to do it by fading the gamma in an XFree86-specific way... */
  380   status = xf86_gamma_fade(app, dpy, saver_windows, nwindows, seconds, out_p);
  381   if (status == 0 || status == 1)
  382     return status;  /* faded, possibly canceled */
  383 # endif
  384 
  385   if (has_writable_cells (DefaultScreenOfDisplay (dpy),
  386                           DefaultVisual (dpy, 0)))
  387     {
  388       /* Do it the old-fashioned way, which only really worked on
  389          8-bit displays. */
  390       status = colormap_fade (app, dpy, saver_windows, nwindows, seconds,
  391                               out_p, from_desktop_p);
  392       if (status == 0 || status == 1)
  393         return status;  /* faded, possibly canceled */
  394     }
  395 
  396   /* Else do it the hard way, by hacking a screenshot. */
  397   status = xshm_fade (app, dpy, saver_windows, nwindows, seconds, out_p,
  398                       from_desktop_p, state);
  399   status = (status ? True : False);
  400 
  401   return status;
  402 }
  403 
  404 /****************************************************************************
  405 
  406     Colormap fading
  407 
  408  ****************************************************************************/
  409 
  410 
  411 /* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
  412    hardware, which is capable of installing multiple (4) colormaps
  413    simultaneously.  We have to install multiple copies of the same set of
  414    colors in order to fill up all the available slots in the hardware color
  415    lookup table, so we install an extra N colormaps per screen to make sure
  416    that all screens really go black.
  417 
  418    I'm told that this trick also works with XInside's AcceleratedX when using
  419    the Matrox Millennium card (which also allows multiple PseudoColor and
  420    TrueColor visuals to co-exist and display properly at the same time.)  
  421 
  422    This trick works ok on the 24-bit Indy video hardware, but doesn't work at
  423    all on the O2 24-bit hardware.  I guess the higher-end hardware is too
  424    "good" for this to work (dammit.)  So... I figured out the "right" way to
  425    do this on SGIs, which is to ramp the monitor's gamma down to 0.  That's
  426    what is implemented in sgi_gamma_fade(), so we use that if we can.
  427 
  428    Returns:
  429    0: faded normally
  430    1: canceled by user activity
  431   -1: unable to fade because the extension isn't supported.
  432  */
  433 static int
  434 colormap_fade (XtAppContext app, Display *dpy,
  435                Window *saver_windows, int nwindows,
  436                double seconds, Bool out_p, Bool from_desktop_p)
  437 {
  438   int status = -1;
  439   Colormap *window_cmaps = 0;
  440   int i, j, k;
  441   int cmaps_per_screen = 5;
  442   int nscreens = ScreenCount(dpy);
  443   int ncmaps = nscreens * cmaps_per_screen;
  444   Colormap *fade_cmaps = 0;
  445   Bool installed = False;
  446   int total_ncolors;
  447   XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
  448   int screen;
  449 
  450   window_cmaps = (Colormap *) calloc(sizeof(Colormap), nwindows);
  451   if (!window_cmaps) abort();
  452   for (screen = 0; screen < nwindows; screen++)
  453     {
  454       XWindowAttributes xgwa;
  455       XGetWindowAttributes (dpy, saver_windows[screen], &xgwa);
  456       window_cmaps[screen] = xgwa.colormap;
  457     }
  458 
  459   error_handler_hit_p = False;
  460 
  461   if (verbose_p > 1)
  462     fprintf (stderr, "%s: colormap fade %s\n",
  463              blurb(), (out_p ? "out" : "in"));
  464 
  465   total_ncolors = 0;
  466   for (i = 0; i < nscreens; i++)
  467     total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
  468 
  469   orig_colors    = (XColor *) calloc(sizeof(XColor), total_ncolors);
  470   current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
  471 
  472   /* Get the contents of the colormap we are fading from or to. */
  473   screen_colors = orig_colors;
  474   for (i = 0; i < nscreens; i++)
  475     {
  476       Screen *sc = ScreenOfDisplay (dpy, i);
  477       int ncolors = CellsOfScreen (sc);
  478       Colormap cmap = (from_desktop_p || !out_p
  479                        ? DefaultColormapOfScreen(sc)
  480                        : window_cmaps[i]);
  481       for (j = 0; j < ncolors; j++)
  482     screen_colors[j].pixel = j;
  483       XQueryColors (dpy, cmap, screen_colors, ncolors);
  484 
  485       screen_colors += ncolors;
  486     }
  487 
  488   memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
  489 
  490 
  491   /* Make the writable colormaps (we keep these around and reuse them.) */
  492   if (!fade_cmaps)
  493     {
  494       fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
  495       for (i = 0; i < nscreens; i++)
  496     {
  497       Visual *v = DefaultVisual(dpy, i);
  498       Screen *s = ScreenOfDisplay(dpy, i);
  499       if (has_writable_cells (s, v))
  500         for (j = 0; j < cmaps_per_screen; j++)
  501           fade_cmaps[(i * cmaps_per_screen) + j] =
  502         XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
  503     }
  504     }
  505 
  506   /* Run the animation at the maximum frame rate in the time allotted. */
  507   {
  508     double start_time = double_time();
  509     double end_time = start_time + seconds;
  510     double prev = 0;
  511     double now;
  512     int frames = 0;
  513     double max = 1/60.0;  /* max FPS */
  514     while ((now = double_time()) < end_time)
  515       {
  516         double ratio = (end_time - now) / seconds;
  517         if (!out_p) ratio = 1-ratio;
  518 
  519         /* For each screen, compute the current value of each color...
  520          */
  521         orig_screen_colors = orig_colors;
  522         screen_colors = current_colors;
  523         for (j = 0; j < nscreens; j++)
  524           {
  525             int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
  526             for (k = 0; k < ncolors; k++)
  527               {
  528                 /* This doesn't take into account the relative luminance of the
  529                    RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
  530                    the difference is imperceptible for this application... */
  531                 screen_colors[k].red   = orig_screen_colors[k].red   * ratio;
  532                 screen_colors[k].green = orig_screen_colors[k].green * ratio;
  533                 screen_colors[k].blue  = orig_screen_colors[k].blue  * ratio;
  534               }
  535             screen_colors      += ncolors;
  536             orig_screen_colors += ncolors;
  537           }
  538 
  539         /* Put the colors into the maps...
  540          */
  541         screen_colors = current_colors;
  542         for (j = 0; j < nscreens; j++)
  543           {
  544             int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
  545             for (k = 0; k < cmaps_per_screen; k++)
  546               {
  547                 Colormap c = fade_cmaps[j * cmaps_per_screen + k];
  548                 if (c)
  549                   XStoreColors (dpy, c, screen_colors, ncolors);
  550               }
  551             screen_colors += ncolors;
  552           }
  553 
  554         /* Put the maps on the screens, and then take the windows off the
  555            screen.  (only need to do this the first time through the loop.)
  556          */
  557         if (!installed)
  558           {
  559             for (j = 0; j < ncmaps; j++)
  560               if (fade_cmaps[j])
  561                 XInstallColormap (dpy, fade_cmaps[j]);
  562             installed = True;
  563 
  564             if (!out_p)
  565               for (j = 0; j < nwindows; j++)
  566                 {
  567                   XUnmapWindow (dpy, saver_windows[j]);
  568                   XClearWindow (dpy, saver_windows[j]);
  569                 }
  570           }
  571 
  572         XSync (dpy, False);
  573 
  574         if (error_handler_hit_p)
  575           goto DONE;
  576         if (user_active_p (app, dpy, out_p))
  577           {
  578             status = 1;   /* user activity status code */
  579             goto DONE;
  580           }
  581         frames++;
  582 
  583         if (now < prev + max)
  584           usleep (1000000 * (prev + max - now));
  585         prev = now;
  586       }
  587 
  588     if (verbose_p > 1)
  589       fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
  590   }
  591 
  592   status = 0;   /* completed fade with no user activity */
  593 
  594  DONE:
  595 
  596   if (orig_colors)    free (orig_colors);
  597   if (current_colors) free (current_colors);
  598 
  599   /* If we've been given windows to raise after blackout, raise them before
  600      releasing the colormaps.
  601    */
  602   if (out_p)
  603     {
  604       for (i = 0; i < nwindows; i++)
  605     {
  606           XClearWindow (dpy, saver_windows[i]);
  607       XMapRaised (dpy, saver_windows[i]);
  608     }
  609       XSync(dpy, False);
  610     }
  611 
  612   /* Now put the target maps back.
  613      If we're fading out, use the given cmap (or the default cmap, if none.)
  614      If we're fading in, always use the default cmap.
  615    */
  616   for (i = 0; i < nscreens; i++)
  617     {
  618       Colormap cmap = window_cmaps[i];
  619       if (!cmap || !out_p)
  620     cmap = DefaultColormap(dpy, i);
  621       XInstallColormap (dpy, cmap);
  622     }
  623 
  624   /* The fade (in or out) is complete, so we don't need the black maps on
  625      stage any more.
  626    */
  627   for (i = 0; i < ncmaps; i++)
  628     if (fade_cmaps[i])
  629       {
  630     XUninstallColormap(dpy, fade_cmaps[i]);
  631     XFreeColormap(dpy, fade_cmaps[i]);
  632     fade_cmaps[i] = 0;
  633       }
  634   free (window_cmaps);
  635   free(fade_cmaps);
  636   fade_cmaps = 0;
  637 
  638   if (error_handler_hit_p) status = -1;
  639   return status;
  640 }
  641 
  642 
  643 /****************************************************************************
  644 
  645     SGI gamma fading
  646 
  647  ****************************************************************************/
  648 
  649 #ifdef HAVE_SGI_VC_EXTENSION
  650 
  651 # include <X11/extensions/XSGIvc.h>
  652 
  653 struct screen_sgi_gamma_info {
  654   int gamma_map;  /* ??? always using 0 */
  655   int nred, ngreen, nblue;
  656   unsigned short *red1, *green1, *blue1;
  657   unsigned short *red2, *green2, *blue2;
  658   int gamma_size;
  659   int gamma_precision;
  660   Bool alpha_p;
  661 };
  662 
  663 
  664 static void sgi_whack_gamma(Display *dpy, int screen,
  665                             struct screen_sgi_gamma_info *info, float ratio);
  666 
  667 /* Returns:
  668    0: faded normally
  669    1: canceled by user activity
  670   -1: unable to fade because the extension isn't supported.
  671  */
  672 static int
  673 sgi_gamma_fade (XtAppContext app, Display *dpy,
  674         Window *saver_windows, int nwindows,
  675                 double seconds, Bool out_p)
  676 {
  677   int nscreens = ScreenCount(dpy);
  678   struct timeval then, now;
  679   int i, screen;
  680   int status = -1;
  681   struct screen_sgi_gamma_info *info = (struct screen_sgi_gamma_info *)
  682     calloc(nscreens, sizeof(*info));
  683 
  684   if (verbose_p > 1)
  685     fprintf (stderr, "%s: sgi fade %s\n",
  686              blurb(), (out_p ? "out" : "in"));
  687 
  688   /* Get the current gamma maps for all screens.
  689      Bug out and return -1 if we can't get them for some screen.
  690    */
  691   for (screen = 0; screen < nscreens; screen++)
  692     {
  693       if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
  694                    &info[screen].gamma_size,
  695                    &info[screen].gamma_precision,
  696                    &info[screen].alpha_p))
  697     goto FAIL;
  698 
  699       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
  700                   XSGIVC_COMPONENT_RED,
  701                   &info[screen].nred, &info[screen].red1))
  702     goto FAIL;
  703       if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
  704                    XSGIVC_COMPONENT_GREEN,
  705                    &info[screen].ngreen, &info[screen].green1))
  706     goto FAIL;
  707       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
  708                   XSGIVC_COMPONENT_BLUE,
  709                   &info[screen].nblue, &info[screen].blue1))
  710     goto FAIL;
  711 
  712       if (info[screen].gamma_precision == 8)    /* Scale it up to 16 bits. */
  713     {
  714       int j;
  715       for(j = 0; j < info[screen].nred; j++)
  716         info[screen].red1[j]   =
  717           ((info[screen].red1[j]   << 8) | info[screen].red1[j]);
  718       for(j = 0; j < info[screen].ngreen; j++)
  719         info[screen].green1[j] =
  720           ((info[screen].green1[j] << 8) | info[screen].green1[j]);
  721       for(j = 0; j < info[screen].nblue; j++)
  722         info[screen].blue1[j]  =
  723           ((info[screen].blue1[j]  << 8) | info[screen].blue1[j]);
  724     }
  725 
  726       info[screen].red2   = (unsigned short *)
  727     malloc(sizeof(*info[screen].red2)   * (info[screen].nred+1));
  728       info[screen].green2 = (unsigned short *)
  729     malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
  730       info[screen].blue2  = (unsigned short *)
  731     malloc(sizeof(*info[screen].blue2)  * (info[screen].nblue+1));
  732     }
  733 
  734 #ifdef GETTIMEOFDAY_TWO_ARGS
  735   gettimeofday(&then, &tzp);
  736 #else
  737   gettimeofday(&then);
  738 #endif
  739 
  740   /* If we're fading in (from black), then first crank the gamma all the
  741      way down to 0, then take the windows off the screen.
  742    */
  743   if (!out_p)
  744     {
  745       for (screen = 0; screen < nscreens; screen++)
  746     sgi_whack_gamma(dpy, screen, &info[screen], 0.0);
  747       
  748       for (screen = 0; screen < nwindows; screen++)
  749         {
  750           XUnmapWindow (dpy, saver_windows[screen]);
  751           XClearWindow (dpy, saver_windows[screen]);
  752           XSync(dpy, False);
  753         }
  754     }
  755 
  756   /* Run the animation at the maximum frame rate in the time allotted. */
  757   {
  758     double start_time = double_time();
  759     double end_time = start_time + seconds;
  760     double prev = 0;
  761     double now;
  762     int frames = 0;
  763     double max = 1/60.0;  /* max FPS */
  764     while ((now = double_time()) < end_time)
  765       {
  766         double ratio = (end_time - now) / seconds;
  767         if (!out_p) ratio = 1-ratio;
  768 
  769         for (screen = 0; screen < nwindows; screen++)
  770       sgi_whack_gamma (dpy, screen, &info[screen], ratio);
  771 
  772         if (error_handler_hit_p)
  773           goto FAIL;
  774         if (user_active_p (app, dpy, out_p))
  775           {
  776             status = 1;   /* user activity status code */
  777             goto DONE;
  778           }
  779         frames++;
  780 
  781         if (now < prev + max)
  782           usleep (1000000 * (prev + max - now));
  783         prev = now;
  784       }
  785 
  786     if (verbose_p > 1)
  787       fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
  788   }
  789 
  790   status = 0;   /* completed fade with no user activity */
  791 
  792  DONE:
  793 
  794   if (out_p)
  795     {
  796       for (screen = 0; screen < nwindows; screen++)
  797     {
  798           XClearWindow (dpy, saver_windows[screen]);
  799       XMapRaised (dpy, saver_windows[screen]);
  800     }
  801       XSync(dpy, False);
  802     }
  803 
  804   /* I can't explain this; without this delay, we get a flicker.
  805      I suppose there's some lossage with stale bits being in the
  806      hardware frame buffer or something, and this delay gives it
  807      time to flush out.  This sucks! */
  808   usleep(100000);  /* 1/10th second */
  809 
  810   for (screen = 0; screen < nscreens; screen++)
  811     sgi_whack_gamma(dpy, screen, &info[screen], 1.0);
  812   XSync(dpy, False);
  813 
  814  FAIL:
  815   for (screen = 0; screen < nscreens; screen++)
  816     {
  817       if (info[screen].red1)   free (info[screen].red1);
  818       if (info[screen].green1) free (info[screen].green1);
  819       if (info[screen].blue1)  free (info[screen].blue1);
  820       if (info[screen].red2)   free (info[screen].red2);
  821       if (info[screen].green2) free (info[screen].green2);
  822       if (info[screen].blue2)  free (info[screen].blue2);
  823     }
  824   free(info);
  825 
  826   if (verbose_p > 1 && status)
  827     fprintf (stderr, "%s: SGI fade %s failed\n",
  828              blurb(), (out_p ? "out" : "in"));
  829 
  830   if (error_handler_hit_p) status = -1;
  831   return status;
  832 }
  833 
  834 static void
  835 sgi_whack_gamma (Display *dpy, int screen, struct screen_sgi_gamma_info *info,
  836                  float ratio)
  837 {
  838   int k;
  839 
  840   if (ratio < 0) ratio = 0;
  841   if (ratio > 1) ratio = 1;
  842   for (k = 0; k < info->gamma_size; k++)
  843     {
  844       info->red2[k]   = info->red1[k]   * ratio;
  845       info->green2[k] = info->green1[k] * ratio;
  846       info->blue2[k]  = info->blue1[k]  * ratio;
  847     }
  848 
  849   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
  850                XSGIVC_MComponentRed, info->red2);
  851   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
  852                XSGIVC_MComponentGreen, info->green2);
  853   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
  854                XSGIVC_MComponentBlue, info->blue2);
  855   XSync(dpy, False);
  856 }
  857 
  858 #endif /* HAVE_SGI_VC_EXTENSION */
  859 
  860 
  861 /****************************************************************************
  862 
  863     XFree86 gamma fading
  864 
  865  ****************************************************************************/
  866 
  867 #ifdef HAVE_XF86VMODE_GAMMA
  868 
  869 #include <X11/extensions/xf86vmode.h>
  870 
  871 typedef struct {
  872   XF86VidModeGamma vmg;
  873   int size;
  874   unsigned short *r, *g, *b;
  875 } xf86_gamma_info;
  876 
  877 static int xf86_check_gamma_extension (Display *dpy);
  878 static Bool xf86_whack_gamma (Display *dpy, int screen,
  879                               xf86_gamma_info *ginfo, float ratio);
  880 
  881 /* Returns:
  882    0: faded normally
  883    1: canceled by user activity
  884   -1: unable to fade because the extension isn't supported.
  885  */
  886 static int
  887 xf86_gamma_fade (XtAppContext app, Display *dpy,
  888                  Window *saver_windows, int nwindows,
  889                  double seconds, Bool out_p)
  890 {
  891   int nscreens = ScreenCount(dpy);
  892   int screen;
  893   int status = -1;
  894   xf86_gamma_info *info = 0;
  895 
  896   static int ext_ok = -1;
  897 
  898   if (verbose_p > 1)
  899     fprintf (stderr, "%s: xf86 fade %s\n",
  900              blurb(), (out_p ? "out" : "in"));
  901 
  902   /* Only probe the extension once: the answer isn't going to change. */
  903   if (ext_ok == -1)
  904     ext_ok = xf86_check_gamma_extension (dpy);
  905 
  906   /* If this server doesn't have the gamma extension, bug out. */
  907   if (ext_ok == 0)
  908     goto FAIL;
  909 
  910 # ifndef HAVE_XF86VMODE_GAMMA_RAMP
  911   if (ext_ok == 2) ext_ok = 1;  /* server is newer than client! */
  912 # endif
  913 
  914   info = (xf86_gamma_info *) calloc(nscreens, sizeof(*info));
  915 
  916   /* Get the current gamma maps for all screens.
  917      Bug out and return -1 if we can't get them for some screen.
  918    */
  919   for (screen = 0; screen < nscreens; screen++)
  920     {
  921       if (ext_ok == 1)  /* only have gamma parameter, not ramps. */
  922         {
  923           if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
  924             goto FAIL;
  925         }
  926 # ifdef HAVE_XF86VMODE_GAMMA_RAMP
  927       else if (ext_ok == 2)  /* have ramps */
  928         {
  929           if (!XF86VidModeGetGammaRampSize(dpy, screen, &info[screen].size))
  930             goto FAIL;
  931           if (info[screen].size <= 0)
  932             goto FAIL;
  933 
  934           info[screen].r = (unsigned short *)
  935             calloc(info[screen].size, sizeof(unsigned short));
  936           info[screen].g = (unsigned short *)
  937             calloc(info[screen].size, sizeof(unsigned short));
  938           info[screen].b = (unsigned short *)
  939             calloc(info[screen].size, sizeof(unsigned short));
  940 
  941           if (!(info[screen].r && info[screen].g && info[screen].b))
  942             goto FAIL;
  943 
  944 # if 0
  945           if (verbose_p > 1 && out_p)
  946             {
  947               int i;
  948               fprintf (stderr, "%s: initial gamma ramps, size %d:\n",
  949                        blurb(), info[screen].size);
  950               fprintf (stderr, "%s:   R:", blurb());
  951               for (i = 0; i < info[screen].size; i++)
  952                 fprintf (stderr, " %d", info[screen].r[i]);
  953               fprintf (stderr, "\n%s:   G:", blurb());
  954               for (i = 0; i < info[screen].size; i++)
  955                 fprintf (stderr, " %d", info[screen].g[i]);
  956               fprintf (stderr, "\n%s:   B:", blurb());
  957               for (i = 0; i < info[screen].size; i++)
  958                 fprintf (stderr, " %d", info[screen].b[i]);
  959               fprintf (stderr, "\n");
  960             }
  961 # endif /* 0 */
  962 
  963           if (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
  964                                        info[screen].r,
  965                                        info[screen].g,
  966                                        info[screen].b))
  967             goto FAIL;
  968         }
  969 # endif /* HAVE_XF86VMODE_GAMMA_RAMP */
  970       else
  971         abort();
  972     }
  973 
  974   /* If we're fading in (from black), then first crank the gamma all the
  975      way down to 0, then take the windows off the screen.
  976    */
  977   if (!out_p)
  978     {
  979       for (screen = 0; screen < nscreens; screen++)
  980     xf86_whack_gamma(dpy, screen, &info[screen], 0.0);
  981       for (screen = 0; screen < nwindows; screen++)
  982         {
  983           XUnmapWindow (dpy, saver_windows[screen]);
  984           XClearWindow (dpy, saver_windows[screen]);
  985           XSync(dpy, False);
  986         }
  987     }
  988 
  989   /* Run the animation at the maximum frame rate in the time allotted. */
  990   {
  991     double start_time = double_time();
  992     double end_time = start_time + seconds;
  993     double prev = 0;
  994     double now;
  995     int frames = 0;
  996     double max = 1/60.0;  /* max FPS */
  997     while ((now = double_time()) < end_time)
  998       {
  999         double ratio = (end_time - now) / seconds;
 1000         if (!out_p) ratio = 1-ratio;
 1001 
 1002         for (screen = 0; screen < nscreens; screen++)
 1003           xf86_whack_gamma (dpy, screen, &info[screen], ratio);
 1004 
 1005         if (error_handler_hit_p)
 1006           goto FAIL;
 1007         if (user_active_p (app, dpy, out_p))
 1008           {
 1009             status = 1;   /* user activity status code */
 1010             goto DONE;
 1011           }
 1012         frames++;
 1013 
 1014         if (now < prev + max)
 1015           usleep (1000000 * (prev + max - now));
 1016         prev = now;
 1017       }
 1018 
 1019     if (verbose_p > 1)
 1020       fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
 1021   }
 1022 
 1023   status = 0;   /* completed fade with no user activity */
 1024 
 1025  DONE:
 1026 
 1027   if (out_p)
 1028     {
 1029       for (screen = 0; screen < nwindows; screen++)
 1030     {
 1031           XClearWindow (dpy, saver_windows[screen]);
 1032       XMapRaised (dpy, saver_windows[screen]);
 1033     }
 1034       XSync(dpy, False);
 1035     }
 1036 
 1037   /* I can't explain this; without this delay, we get a flicker.
 1038      I suppose there's some lossage with stale bits being in the
 1039      hardware frame buffer or something, and this delay gives it
 1040      time to flush out.  This sucks! */
 1041   usleep(100000);  /* 1/10th second */
 1042 
 1043   for (screen = 0; screen < nscreens; screen++)
 1044     xf86_whack_gamma(dpy, screen, &info[screen], 1.0);
 1045   XSync(dpy, False);
 1046 
 1047  FAIL:
 1048   if (info)
 1049     {
 1050       for (screen = 0; screen < nscreens; screen++)
 1051         {
 1052           if (info[screen].r) free(info[screen].r);
 1053           if (info[screen].g) free(info[screen].g);
 1054           if (info[screen].b) free(info[screen].b);
 1055         }
 1056       free(info);
 1057     }
 1058 
 1059   if (verbose_p > 1 && status)
 1060     fprintf (stderr, "%s: xf86 fade %s failed\n",
 1061              blurb(), (out_p ? "out" : "in"));
 1062 
 1063   if (error_handler_hit_p) status = -1;
 1064   return status;
 1065 }
 1066 
 1067 
 1068 static Bool
 1069 safe_XF86VidModeQueryVersion (Display *dpy, int *majP, int *minP)
 1070 {
 1071   Bool result;
 1072   XErrorHandler old_handler;
 1073   XSync (dpy, False);
 1074   error_handler_hit_p = False;
 1075   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
 1076 
 1077   result = XF86VidModeQueryVersion (dpy, majP, minP);
 1078 
 1079   XSync (dpy, False);
 1080   XSetErrorHandler (old_handler);
 1081   XSync (dpy, False);
 1082 
 1083   return (error_handler_hit_p
 1084           ? False
 1085           : result);
 1086 }
 1087 
 1088 
 1089 
 1090 /* VidModeExtension version 2.0 or better is needed to do gamma.
 1091    2.0 added gamma values; 2.1 added gamma ramps.
 1092  */
 1093 # define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
 1094 # define XF86_VIDMODE_GAMMA_MIN_MINOR 0
 1095 # define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
 1096 # define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1
 1097 
 1098 
 1099 
 1100 /* Returns 0 if gamma fading not available; 1 if only gamma value setting
 1101    is available; 2 if gamma ramps are available.
 1102  */
 1103 static int
 1104 xf86_check_gamma_extension (Display *dpy)
 1105 {
 1106   int event, error, major, minor;
 1107 
 1108   if (!XF86VidModeQueryExtension (dpy, &event, &error))
 1109     return 0;  /* display doesn't have the extension. */
 1110 
 1111   if (!safe_XF86VidModeQueryVersion (dpy, &major, &minor))
 1112     return 0;  /* unable to get version number? */
 1113 
 1114   if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR || 
 1115       (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
 1116        minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
 1117     return 0;  /* extension is too old for gamma. */
 1118 
 1119   if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR || 
 1120       (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
 1121        minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
 1122     return 1;  /* extension is too old for gamma ramps. */
 1123 
 1124   /* Copacetic */
 1125   return 2;
 1126 }
 1127 
 1128 
 1129 /* XFree doesn't let you set gamma to a value smaller than this.
 1130    Apparently they didn't anticipate the trick I'm doing here...
 1131  */
 1132 #define XF86_MIN_GAMMA  0.1
 1133 
 1134 
 1135 static Bool
 1136 xf86_whack_gamma(Display *dpy, int screen, xf86_gamma_info *info,
 1137                  float ratio)
 1138 {
 1139   XErrorHandler old_handler;
 1140   XSync (dpy, False);
 1141   error_handler_hit_p = False;
 1142   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
 1143 
 1144   if (ratio < 0) ratio = 0;
 1145   if (ratio > 1) ratio = 1;
 1146 
 1147   if (info->size == 0)    /* we only have a gamma number, not a ramp. */
 1148     {
 1149       XF86VidModeGamma g2;
 1150 
 1151       g2.red   = info->vmg.red   * ratio;
 1152       g2.green = info->vmg.green * ratio;
 1153       g2.blue  = info->vmg.blue  * ratio;
 1154 
 1155 # ifdef XF86_MIN_GAMMA
 1156       if (g2.red   < XF86_MIN_GAMMA) g2.red   = XF86_MIN_GAMMA;
 1157       if (g2.green < XF86_MIN_GAMMA) g2.green = XF86_MIN_GAMMA;
 1158       if (g2.blue  < XF86_MIN_GAMMA) g2.blue  = XF86_MIN_GAMMA;
 1159 # endif
 1160 
 1161       if (! XF86VidModeSetGamma (dpy, screen, &g2))
 1162         return -1;
 1163     }
 1164   else
 1165     {
 1166 # ifdef HAVE_XF86VMODE_GAMMA_RAMP
 1167 
 1168       unsigned short *r, *g, *b;
 1169       int i;
 1170       r = (unsigned short *) malloc(info->size * sizeof(unsigned short));
 1171       g = (unsigned short *) malloc(info->size * sizeof(unsigned short));
 1172       b = (unsigned short *) malloc(info->size * sizeof(unsigned short));
 1173 
 1174       for (i = 0; i < info->size; i++)
 1175         {
 1176           r[i] = info->r[i] * ratio;
 1177           g[i] = info->g[i] * ratio;
 1178           b[i] = info->b[i] * ratio;
 1179         }
 1180 
 1181       if (! XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b))
 1182         return -1;
 1183 
 1184       free (r);
 1185       free (g);
 1186       free (b);
 1187 
 1188 # else  /* !HAVE_XF86VMODE_GAMMA_RAMP */
 1189       abort();
 1190 # endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
 1191     }
 1192 
 1193   XSync (dpy, False);
 1194   XSetErrorHandler (old_handler);
 1195   XSync (dpy, False);
 1196 
 1197   return status;
 1198 }
 1199 
 1200 #endif /* HAVE_XF86VMODE_GAMMA */
 1201 
 1202 
 1203 /****************************************************************************
 1204 
 1205     RANDR gamma fading
 1206 
 1207  ****************************************************************************
 1208 
 1209 
 1210    Dec 2020: I noticed that gamma fading was not working on a Raspberry Pi
 1211    with Raspbian 10.6, and wrote this under the hypothesis that the XF86
 1212    gamma fade code was failing and maybe the RANDR version would work better.
 1213    Then I discovered that gamma simply isn't supported by the Raspberry Pi
 1214    HDMI driver:
 1215 
 1216        https://github.com/raspberrypi/firmware/issues/1274
 1217 
 1218    I should have tried this first and seen it not work:
 1219 
 1220        xrandr --output HDMI-1 --brightness .1
 1221 
 1222    Since I still don't have an answer to the question of whether the XF86
 1223    gamma fading method works on modern Linux systems that also have RANDR,
 1224    I'm leaving this new code turned off for now, as it is largely untested.
 1225    The new code would be useful if:
 1226 
 1227      A) The XF86 way doesn't work but the RANDR way does, or
 1228      B) There exist systems that have RANDR but do not have XF86.
 1229 
 1230    But until Raspberry Pi supports gamma, both gamma methods fail to work
 1231    for far too many users for them to be used in XScreenSaver.
 1232  */
 1233 #ifdef HAVE_RANDR_12
 1234 
 1235 # include <X11/extensions/Xrandr.h>
 1236 
 1237 typedef struct {
 1238   RRCrtc crtc;
 1239   Bool enabled_p;
 1240   XRRCrtcGamma *gamma;
 1241 } randr_gamma_info;
 1242 
 1243 
 1244 static int
 1245 randr_check_gamma_extension (Display *dpy)
 1246 {
 1247   int event, error, major, minor;
 1248   if (! XRRQueryExtension (dpy, &event, &error))
 1249     return 0;
 1250   
 1251   if (! XRRQueryVersion (dpy, &major, &minor)) {
 1252     if (verbose_p > 1) fprintf (stderr, "%s: no randr ext\n", blurb());
 1253     return 0;
 1254   }
 1255 
 1256   /* Reject if < 1.5. It's possible that 1.2 - 1.4 work, but untested. */
 1257   if (major < 1 || (major == 1 && minor < 5)) {
 1258     if (verbose_p > 1) fprintf (stderr, "%s: randr ext only version %d.%d\n",
 1259                                blurb(), major, minor);
 1260     return 0;
 1261   }
 1262 
 1263   return 1;
 1264 }
 1265 
 1266 
 1267 static void randr_whack_gamma (Display *dpy, int screen,
 1268                                randr_gamma_info *ginfo, float ratio);
 1269 
 1270 /* Returns:
 1271    0: faded normally
 1272    1: canceled by user activity
 1273   -1: unable to fade because the extension isn't supported.
 1274  */
 1275 static int
 1276 randr_gamma_fade (XtAppContext app, Display *dpy,
 1277                    Window *saver_windows, int nwindows,
 1278                    double seconds, Bool out_p)
 1279 {
 1280   int xsc = ScreenCount (dpy);
 1281   int nscreens = 0;
 1282   int j, screen;
 1283   int status = -1;
 1284   randr_gamma_info *info = 0;
 1285 
 1286   static int ext_ok = -1;
 1287 
 1288   if (verbose_p > 1)
 1289     fprintf (stderr, "%s: randr fade %s\n",
 1290              blurb(), (out_p ? "out" : "in"));
 1291 
 1292   /* Only probe the extension once: the answer isn't going to change. */
 1293   if (ext_ok == -1)
 1294     ext_ok = randr_check_gamma_extension (dpy);
 1295 
 1296   /* If this server doesn't have the RANDR extension, bug out. */
 1297   if (ext_ok == 0)
 1298     goto FAIL;
 1299 
 1300   /* Add up the virtual screens on each X screen. */
 1301   for (screen = 0; screen < xsc; screen++)
 1302     {
 1303       XRRScreenResources *res = 
 1304         XRRGetScreenResources (dpy, RootWindow (dpy, screen));
 1305       nscreens += res->noutput;
 1306       XRRFreeScreenResources (res);
 1307     }
 1308 
 1309   if (nscreens <= 0)
 1310     goto FAIL;
 1311 
 1312   info = (randr_gamma_info *) calloc(nscreens, sizeof(*info));
 1313 
 1314   /* Get the current gamma maps for all screens.
 1315      Bug out and return -1 if we can't get them for some screen.
 1316   */
 1317   for (screen = 0, j = 0; screen < xsc; screen++)
 1318     {
 1319       XRRScreenResources *res =
 1320         XRRGetScreenResources (dpy, RootWindow (dpy, screen));
 1321       int k;
 1322       for (k = 0; k < res->noutput; k++, j++)
 1323         {
 1324           XRROutputInfo *rroi = XRRGetOutputInfo (dpy, res, res->outputs[j]);
 1325           RRCrtc crtc = (rroi->crtc  ? rroi->crtc :
 1326                          rroi->ncrtc ? rroi->crtcs[0] : 0);
 1327 
 1328           info[j].crtc = crtc;
 1329           info[j].gamma = XRRGetCrtcGamma (dpy, crtc);
 1330 
 1331           /* #### is this test sufficient? */
 1332           info[j].enabled_p = (rroi->connection != RR_Disconnected);
 1333 
 1334 # if 0
 1335           if (verbose_p > 1 && out_p)
 1336             {
 1337               int m;
 1338               fprintf (stderr, "%s: initial gamma ramps, size %d:\n",
 1339                        blurb(), info[j].gamma->size);
 1340               fprintf (stderr, "%s:   R:", blurb());
 1341               for (m = 0; m < info[j].gamma->size; m++)
 1342                 fprintf (stderr, " %d", info[j].gamma->red[m]);
 1343               fprintf (stderr, "\n%s:   G:", blurb());
 1344               for (m = 0; m < info[j].gamma->size; m++)
 1345                 fprintf (stderr, " %d", info[j].gamma->green[m]);
 1346               fprintf (stderr, "\n%s:   B:", blurb());
 1347               for (m = 0; m < info[j].gamma->size; m++)
 1348                 fprintf (stderr, " %d", info[j].gamma->blue[m]);
 1349               fprintf (stderr, "\n");
 1350             }
 1351 # endif /* 0 */
 1352 
 1353           XRRFreeOutputInfo (rroi);
 1354         }
 1355       XRRFreeScreenResources (res);
 1356     }
 1357 
 1358   /* If we're fading in (from black), then first crank the gamma all the
 1359      way down to 0, then take the windows off the screen.
 1360   */
 1361   if (!out_p)
 1362     {
 1363       for (screen = 0; screen < nscreens; screen++)
 1364     randr_whack_gamma(dpy, screen, &info[screen], 0.0);
 1365       for (screen = 0; screen < nwindows; screen++)
 1366         {
 1367           XUnmapWindow (dpy, saver_windows[screen]);
 1368           XClearWindow (dpy, saver_windows[screen]);
 1369           XSync(dpy, False);
 1370         }
 1371     }
 1372 
 1373   /* Run the animation at the maximum frame rate in the time allotted. */
 1374   {
 1375     double start_time = double_time();
 1376     double end_time = start_time + seconds;
 1377     double prev = 0;
 1378     double now;
 1379     int frames = 0;
 1380     double max = 1/60.0;  /* max FPS */
 1381     while ((now = double_time()) < end_time)
 1382       {
 1383         double ratio = (end_time - now) / seconds;
 1384         if (!out_p) ratio = 1-ratio;
 1385 
 1386         for (screen = 0; screen < nwindows; screen++)
 1387           {
 1388             if (!info[screen].enabled_p)
 1389               continue;
 1390 
 1391             randr_whack_gamma (dpy, screen, &info[screen], ratio);
 1392           }
 1393 
 1394         if (error_handler_hit_p)
 1395           goto FAIL;
 1396         if (user_active_p (app, dpy, out_p))
 1397           {
 1398             status = 1;   /* user activity status code */
 1399             goto DONE;
 1400           }
 1401         frames++;
 1402 
 1403         if (now < prev + max)
 1404           usleep (1000000 * (prev + max - now));
 1405         prev = now;
 1406       }
 1407 
 1408     if (verbose_p > 1)
 1409       fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
 1410   }
 1411 
 1412   status = 0;   /* completed fade with no user activity */
 1413 
 1414  DONE:
 1415 
 1416   if (out_p)
 1417     {
 1418       for (screen = 0; screen < nwindows; screen++)
 1419     {
 1420           XClearWindow (dpy, saver_windows[screen]);
 1421       XMapRaised (dpy, saver_windows[screen]);
 1422     }
 1423       XSync(dpy, False);
 1424     }
 1425 
 1426   /* I can't explain this; without this delay, we get a flicker.
 1427      I suppose there's some lossage with stale bits being in the
 1428      hardware frame buffer or something, and this delay gives it
 1429      time to flush out.  This sucks! */
 1430   /* #### That comment was about XF86, not verified with randr. */
 1431   usleep(100000);  /* 1/10th second */
 1432 
 1433   for (screen = 0; screen < nscreens; screen++)
 1434     randr_whack_gamma (dpy, screen, &info[screen], 1.0);
 1435   XSync(dpy, False);
 1436 
 1437  FAIL:
 1438   if (info)
 1439     {
 1440       for (screen = 0; screen < nscreens; screen++)
 1441         {
 1442           if (info[screen].gamma) XRRFreeGamma (info[screen].gamma);
 1443         }
 1444       free(info);
 1445     }
 1446 
 1447   if (verbose_p > 1 && status)
 1448     fprintf (stderr, "%s: randr fade %s failed\n",
 1449              blurb(), (out_p ? "out" : "in"));
 1450 
 1451   return status;
 1452 }
 1453 
 1454 
 1455 static void
 1456 randr_whack_gamma (Display *dpy, int screen, randr_gamma_info *info,
 1457                    float ratio)
 1458 {
 1459   XErrorHandler old_handler;
 1460   XRRCrtcGamma *g2;
 1461   int i;
 1462 
 1463   XSync (dpy, False);
 1464   error_handler_hit_p = False;
 1465   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
 1466 
 1467   if (ratio < 0) ratio = 0;
 1468   if (ratio > 1) ratio = 1;
 1469 
 1470   g2 = XRRAllocGamma (info->gamma->size);
 1471   for (i = 0; i < info->gamma->size; i++)
 1472     {
 1473       g2->red[i]   = ratio * info->gamma->red[i];
 1474       g2->green[i] = ratio * info->gamma->green[i];
 1475       g2->blue[i]  = ratio * info->gamma->blue[i];
 1476     }
 1477 
 1478   XRRSetCrtcGamma (dpy, info->crtc, g2);
 1479   XRRFreeGamma (g2);
 1480 
 1481   XSync (dpy, False);
 1482   XSetErrorHandler (old_handler);
 1483   XSync (dpy, False);
 1484 
 1485   return 0;
 1486 }
 1487 
 1488 #endif /* HAVE_RANDR_12 */
 1489 
 1490 
 1491 /****************************************************************************
 1492 
 1493     XSHM screen-shot fading
 1494 
 1495  ****************************************************************************/
 1496 
 1497 typedef struct {
 1498   GC gc;
 1499   Window window;
 1500   Pixmap screenshot;
 1501   XImage *src, *intermediate;
 1502 } xshm_fade_info;
 1503 
 1504 
 1505 static int xshm_whack (Display *, XShmSegmentInfo *,
 1506                        xshm_fade_info *, float ratio);
 1507 
 1508 /* Returns:
 1509    0: faded normally
 1510    1: canceled by user activity
 1511   -1: unknown error
 1512  */
 1513 static int
 1514 xshm_fade (XtAppContext app, Display *dpy,
 1515            Window *saver_windows, int nwindows, double seconds, 
 1516            Bool out_p, Bool from_desktop_p, fade_state *state)
 1517 {
 1518   int screen;
 1519   int status = -1;
 1520   xshm_fade_info *info = 0;
 1521   XShmSegmentInfo shm_info;
 1522   Window saver_window = 0;
 1523   XErrorHandler old_handler = 0;
 1524 
 1525   XSync (dpy, False);
 1526   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
 1527   error_handler_hit_p = False;  
 1528 
 1529   if (verbose_p > 1)
 1530     fprintf (stderr, "%s: SHM fade %s\n",
 1531              blurb(), (out_p ? "out" : "in"));
 1532 
 1533   info = (xshm_fade_info *) calloc(nwindows, sizeof(*info));
 1534   if (!info) goto FAIL;
 1535 
 1536   saver_window = find_screensaver_window (dpy, 0);
 1537   if (!saver_window) goto FAIL;
 1538 
 1539   /* Retrieve a screenshot of the area covered by each window.
 1540      Windows might not be mapped.
 1541      Bug out and return -1 if we can't get one for some screen.
 1542   */
 1543 
 1544   for (screen = 0; screen < nwindows; screen++)
 1545     {
 1546       XWindowAttributes xgwa;
 1547       Window root;
 1548       XGCValues gcv;
 1549       GC gc;
 1550       unsigned long attrmask = 0;
 1551       XSetWindowAttributes attrs;
 1552 
 1553       XGetWindowAttributes (dpy, saver_windows[screen], &xgwa);
 1554       root = RootWindowOfScreen (xgwa.screen);
 1555 
 1556       info[screen].src =
 1557         create_xshm_image (dpy, xgwa.visual, xgwa.depth,
 1558                            ZPixmap, &shm_info, xgwa.width, xgwa.height);
 1559       if (!info[screen].src) goto FAIL;
 1560 
 1561       info[screen].intermediate =
 1562         create_xshm_image (dpy, xgwa.visual, xgwa.depth,
 1563                            ZPixmap, &shm_info, xgwa.width, xgwa.height);
 1564       if (!info[screen].intermediate) goto FAIL;
 1565 
 1566       if (!out_p)
 1567         {
 1568           /* If we are fading in, retrieve the saved screenshot from
 1569              before we faded out. */
 1570           if (state->nscreens <= screen) goto FAIL;
 1571           info[screen].screenshot = state->screenshots[screen];
 1572         }
 1573       else
 1574         {
 1575           /* Create a pixmap and grab a screenshot into it. */
 1576           info[screen].screenshot =
 1577             XCreatePixmap (dpy, root, xgwa.width, xgwa.height, xgwa.depth);
 1578           if (!info[screen].screenshot) goto FAIL;
 1579 
 1580           gcv.function = GXcopy;
 1581           gcv.subwindow_mode = IncludeInferiors;
 1582           gc = XCreateGC (dpy, root, GCFunction | GCSubwindowMode, &gcv);
 1583           XCopyArea (dpy, root, info[screen].screenshot, gc,
 1584                      xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
 1585           XFreeGC (dpy, gc);
 1586         }
 1587 
 1588       /* Create the fader window for the animation. */
 1589       attrmask = CWOverrideRedirect;
 1590       attrs.override_redirect = True;
 1591       info[screen].window = 
 1592         XCreateWindow (dpy, root, xgwa.x, xgwa.y,
 1593                        xgwa.width, xgwa.height, xgwa.border_width, xgwa.depth,
 1594                        InputOutput, xgwa.visual,
 1595                        attrmask, &attrs);
 1596       if (!info[screen].window) goto FAIL;
 1597       /* XSelectInput (dpy, info[screen].window,
 1598                        KeyPressMask | ButtonPressMask); */
 1599 
 1600       /* Copy the screenshot pixmap to the source image */
 1601       if (! get_xshm_image (dpy, info[screen].screenshot, info[screen].src,
 1602                             0, 0, ~0L, &shm_info))
 1603         goto FAIL;
 1604 
 1605       gcv.function = GXcopy;
 1606       info[screen].gc = XCreateGC (dpy, info[screen].window, GCFunction, &gcv);
 1607     }
 1608 
 1609   /* If we're fading out from the desktop, save our screen shots for later use.
 1610      But not if we're fading out from the savers to black.  In that case we
 1611      don't want to overwrite the desktop screenshot with the current screenshot
 1612      which is of the final frames of the just-killed graphics hacks. */
 1613   if (from_desktop_p)
 1614     {
 1615       if (!out_p) abort();
 1616       for (screen = 0; screen < state->nscreens; screen++)
 1617         if (state->screenshots[screen])
 1618           XFreePixmap (dpy, state->screenshots[screen]);
 1619       if (state->screenshots)
 1620         free (state->screenshots);
 1621       state->nscreens = nwindows;
 1622       state->screenshots = calloc (nwindows, sizeof(*state->screenshots));
 1623       if (!state->screenshots)
 1624         state->nscreens = 0;
 1625       for (screen = 0; screen < state->nscreens; screen++)
 1626         state->screenshots[screen] = info[screen].screenshot;
 1627     }
 1628 
 1629   for (screen = 0; screen < nwindows; screen++)
 1630     {
 1631       if (out_p)
 1632         /* Copy the screenshot to the fader window */
 1633         XSetWindowBackgroundPixmap (dpy, info[screen].window,
 1634                                     info[screen].screenshot);
 1635       else
 1636         {
 1637           XSetWindowBackgroundPixmap (dpy, info[screen].window, None);
 1638           XSetWindowBackground (dpy, info[screen].window, BlackPixel (dpy, 0));
 1639         }
 1640 
 1641       XMapRaised (dpy, info[screen].window);
 1642 
 1643       /* Now that we have mapped the screenshot on the fader windows,
 1644          take the saver windows off the screen. */
 1645       if (out_p)
 1646         {
 1647           XUnmapWindow (dpy, saver_windows[screen]);
 1648           XClearWindow (dpy, saver_windows[screen]);
 1649         }
 1650     }
 1651 
 1652   /* Run the animation at the maximum frame rate in the time allotted. */
 1653   {
 1654     double start_time = double_time();
 1655     double end_time = start_time + seconds;
 1656     double prev = 0;
 1657     double now;
 1658     int frames = 0;
 1659     double max = 1/60.0;  /* max FPS */
 1660     while ((now = double_time()) < end_time)
 1661       {
 1662         double ratio = (end_time - now) / seconds;
 1663         if (!out_p) ratio = 1-ratio;
 1664 
 1665         for (screen = 0; screen < nwindows; screen++)
 1666           if (xshm_whack (dpy, &shm_info, &info[screen], ratio))
 1667             goto FAIL;
 1668 
 1669         if (error_handler_hit_p)
 1670           goto FAIL;
 1671         if (user_active_p (app, dpy, out_p))
 1672           {
 1673             status = 1;   /* user activity status code */
 1674             goto DONE;
 1675           }
 1676         frames++;
 1677 
 1678         if (now < prev + max)
 1679           usleep (1000000 * (prev + max - now));
 1680         prev = now;
 1681       }
 1682 
 1683     if (verbose_p > 1)
 1684       fprintf (stderr, "%s: %.0f FPS\n", blurb(), frames / (now - start_time));
 1685   }
 1686 
 1687   status = 0;   /* completed fade with no user activity */
 1688 
 1689  DONE:
 1690 
 1691   /* If we're fading out, we have completed the transition from what was
 1692      on the screen to black, using our fader windows.  Now raise the saver
 1693      windows and take the fader windows off the screen.  Since they're both
 1694      black, that will be imperceptible.   
 1695    */
 1696   if (out_p)
 1697     {
 1698       for (screen = 0; screen < nwindows; screen++)
 1699     {
 1700           XClearWindow (dpy, saver_windows[screen]);
 1701       XMapRaised (dpy, saver_windows[screen]);
 1702           /* Doing this here triggers the same KDE 5 compositor bug that
 1703              defer_XDestroyWindow is to work around. */
 1704           /* if (info[screen].window)
 1705             XUnmapWindow (dpy, info[screen].window); */
 1706     }
 1707     }
 1708 
 1709   XSync (dpy, False);
 1710 
 1711  FAIL:
 1712 
 1713   /* After fading in, take the saver windows off the screen before
 1714      destroying the occluding screenshot windows. */
 1715   if (!out_p)
 1716     {
 1717       for (screen = 0; screen < nwindows; screen++)
 1718     {
 1719           XUnmapWindow (dpy, saver_windows[screen]);
 1720           XClearWindow (dpy, saver_windows[screen]);
 1721     }
 1722     }
 1723 
 1724   if (info)
 1725     {
 1726       for (screen = 0; screen < nwindows; screen++)
 1727         {
 1728           if (info[screen].src)
 1729             destroy_xshm_image (dpy, info[screen].src, &shm_info);
 1730           if (info[screen].intermediate)
 1731             destroy_xshm_image (dpy, info[screen].intermediate, &shm_info);
 1732           if (info[screen].window)
 1733             defer_XDestroyWindow (app, dpy, info[screen].window);
 1734           if (info[screen].gc)
 1735             XFreeGC (dpy, info[screen].gc);
 1736         }
 1737       free (info);
 1738     }
 1739 
 1740   /* If fading in, delete the screenshot pixmaps, and the list of them. */
 1741   if (!out_p && saver_window)
 1742     {
 1743       for (screen = 0; screen < state->nscreens; screen++)
 1744         if (state->screenshots[screen])
 1745           XFreePixmap (dpy, state->screenshots[screen]);
 1746       if (state->screenshots)
 1747         free (state->screenshots);
 1748       state->nscreens = 0;
 1749       state->screenshots = 0;
 1750     }
 1751 
 1752   XSync (dpy, False);
 1753   XSetErrorHandler (old_handler);
 1754 
 1755   if (error_handler_hit_p) status = -1;
 1756   if (verbose_p > 1 && status)
 1757     fprintf (stderr, "%s: SHM fade %s failed\n",
 1758              blurb(), (out_p ? "out" : "in"));
 1759 
 1760   return status;
 1761 }
 1762 
 1763 
 1764 static int
 1765 xshm_whack (Display *dpy, XShmSegmentInfo *shm_info,
 1766             xshm_fade_info *info, float ratio)
 1767 {
 1768   unsigned char *inbits  = (unsigned char *) info->src->data;
 1769   unsigned char *outbits = (unsigned char *) info->intermediate->data;
 1770   unsigned char *end = (outbits + 
 1771                         info->intermediate->bytes_per_line *
 1772                         info->intermediate->height);
 1773   unsigned char ramp[256];
 1774   int i;
 1775 
 1776   XSync (dpy, False);
 1777 
 1778   if (ratio < 0) ratio = 0;
 1779   if (ratio > 1) ratio = 1;
 1780 
 1781   for (i = 0; i < sizeof(ramp); i++)
 1782     ramp[i] = i * ratio;
 1783   while (outbits < end)
 1784     *outbits++ = ramp[*inbits++];
 1785 
 1786   put_xshm_image (dpy, info->window, info->gc, info->intermediate, 0, 0, 0, 0,
 1787                   info->intermediate->width, info->intermediate->height,
 1788                   shm_info);
 1789   XSync (dpy, False);
 1790   return 0;
 1791 }