http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.34.tar.gz
[xscreensaver] / utils / fade.c
index eed532218ba77151667111a63d724ce4a6ace5c6..3df6bc9edfdb64abeecc5d0c2de616a1b03a4660 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 1992-1997 Jamie Zawinski <jwz@netscape.com>
+/* xscreensaver, Copyright (c) 1992-2001 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
@@ -21,7 +21,6 @@
 #include "usleep.h"
 #include "fade.h"
 
-
 Colormap
 copy_colormap (Screen *screen, Visual *visual,
               Colormap cmap, Colormap into_cmap)
@@ -76,12 +75,18 @@ blacken_colormap (Screen *screen, Colormap cmap)
 static void fade_screens_1 (Display *dpy, Colormap *cmaps,
                            Window *black_windows, int seconds, int ticks,
                            Bool out_p, Bool clear_windows);
+
 #ifdef HAVE_SGI_VC_EXTENSION
 static int sgi_gamma_fade (Display *dpy,
                           Window *black_windows, int seconds, int ticks,
                           Bool out_p, Bool clear_windows);
 #endif /* HAVE_SGI_VC_EXTENSION */
 
+#ifdef HAVE_XF86VMODE_GAMMA
+static int xf86_gamma_fade (Display *dpy,
+                            Window *black_windows, int seconds, int ticks,
+                            Bool out_p, Bool clear_windows);
+#endif /* HAVE_XF86VMODE_GAMMA */
 
 
 void
@@ -89,15 +94,59 @@ fade_screens (Display *dpy, Colormap *cmaps, Window *black_windows,
              int seconds, int ticks,
              Bool out_p, Bool clear_windows)
 {
+  int oseconds = seconds;
+  Bool was_in_p = !out_p;
+
+  /* When we're asked to fade in, first fade out, then fade in.
+     That way all the transitions are smooth -- from what's on the
+     screen, to black, to the desktop.
+   */
+  if (was_in_p)
+    {
+      clear_windows = True;
+      out_p = True;
+      seconds /= 3;
+      if (seconds == 0)
+       seconds = 1;
+    }
+
+ AGAIN:
+
+/* #### printf("\n\nfade_screens %d %d %d\n", seconds, ticks, out_p); */
+
 #ifdef HAVE_SGI_VC_EXTENSION
   /* First try to do it by fading the gamma in an SGI-specific way... */
-  if (0 != sgi_gamma_fade(dpy, black_windows, seconds, ticks, out_p,
+  if (0 == sgi_gamma_fade(dpy, black_windows, seconds, ticks, out_p,
                          clear_windows))
+    ;
+  else
 #endif /* HAVE_SGI_VC_EXTENSION */
+
+#ifdef HAVE_XF86VMODE_GAMMA
+  /* Then try to do it by fading the gamma in an XFree86-specific way... */
+  if (0 == xf86_gamma_fade(dpy, black_windows, seconds, ticks, out_p,
+                           clear_windows))
+    ;
+  else
+#endif /* HAVE_XF86VMODE_GAMMA */
+
     /* Else, do it the old-fashioned way, which (somewhat) loses if
        there are TrueColor windows visible. */
     fade_screens_1 (dpy, cmaps, black_windows, seconds, ticks,
                    out_p, clear_windows);
+
+  /* If we were supposed to be fading in, do so now (we just faded out,
+     so now fade back in.)
+   */
+  if (was_in_p)
+    {
+      was_in_p = False;
+      out_p = False;
+      seconds = oseconds * 2 / 3;
+      if (seconds == 0)
+       seconds = 1;
+      goto AGAIN;
+    }
 }
 
 
@@ -109,7 +158,7 @@ fade_screens (Display *dpy, Colormap *cmaps, Window *black_windows,
    that all screens really go black.
 
    I'm told that this trick also works with XInside's AcceleratedX when using
-   the Matrox Millenium card (which also allows multiple PseudoColor and
+   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
@@ -291,13 +340,15 @@ fade_screens_1 (Display *dpy, Colormap *cmaps, Window *black_windows,
      releasing the colormaps.
    */
   if (out_p && black_windows)
-    for (i = 0; i < nscreens; i++)
-      {
-       if (clear_windows)
-         XClearWindow (dpy, black_windows[i]);
-       XMapRaised (dpy, black_windows[i]);
-      }
-
+    {
+      for (i = 0; i < nscreens; i++)
+       {
+         if (clear_windows)
+           XClearWindow (dpy, black_windows[i]);
+         XMapRaised (dpy, black_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.)
@@ -326,11 +377,14 @@ fade_screens_1 (Display *dpy, Colormap *cmaps, Window *black_windows,
 }
 
 
+\f
+/* SGI Gamma fading */
+
 #ifdef HAVE_SGI_VC_EXTENSION
 
 # include <X11/extensions/XSGIvc.h>
 
-struct screen_gamma_info {
+struct screen_sgi_gamma_info {
   int gamma_map;  /* ??? always using 0 */
   int nred, ngreen, nblue;
   unsigned short *red1, *green1, *blue1;
@@ -341,8 +395,8 @@ struct screen_gamma_info {
 };
 
 
-static void whack_gamma(Display *dpy, int screen,
-                       struct screen_gamma_info *info, float ratio);
+static void sgi_whack_gamma(Display *dpy, int screen,
+                            struct screen_sgi_gamma_info *info, float ratio);
 
 static int
 sgi_gamma_fade (Display *dpy,
@@ -359,7 +413,7 @@ sgi_gamma_fade (Display *dpy,
 #endif
   int i, screen;
   int status = -1;
-  struct screen_gamma_info *info = (struct screen_gamma_info *)
+  struct screen_sgi_gamma_info *info = (struct screen_sgi_gamma_info *)
     calloc(nscreens, sizeof(*info));
 
   /* Get the current gamma maps for all screens.
@@ -420,11 +474,12 @@ sgi_gamma_fade (Display *dpy,
   if (!out_p)
     for (screen = 0; screen < nscreens; screen++)
       {
-       whack_gamma(dpy, screen, &info[screen], 0.0);
+       sgi_whack_gamma(dpy, screen, &info[screen], 0.0);
        if (black_windows && black_windows[screen])
          {
            XUnmapWindow (dpy, black_windows[screen]);
            XClearWindow (dpy, black_windows[screen]);
+           XSync(dpy, False);
          }
       }
 
@@ -436,8 +491,8 @@ sgi_gamma_fade (Display *dpy,
     {
       for (screen = 0; screen < nscreens; screen++)
        {
-         whack_gamma(dpy, screen, &info[screen],
-                     (((float)i) / ((float)steps)));
+         sgi_whack_gamma(dpy, screen, &info[screen],
+                          (((float)i) / ((float)steps)));
 
          /* If there is user activity, bug out.  (Bug out on keypresses or
             mouse presses, but not motion, and not release events.  Bugging
@@ -484,14 +539,20 @@ sgi_gamma_fade (Display *dpy,
       for (screen = 0; screen < nscreens; screen++)
        {
          if (clear_windows)
-           XClearWindow (dpy, black_windows[i]);
-         XMapRaised (dpy, black_windows[i]);
+           XClearWindow (dpy, black_windows[screen]);
+         XMapRaised (dpy, black_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++)
-    whack_gamma(dpy, screen, &info[screen], 1.0);
+    sgi_whack_gamma(dpy, screen, &info[screen], 1.0);
   XSync(dpy, False);
 
   status = 0;
@@ -507,14 +568,16 @@ sgi_gamma_fade (Display *dpy,
       if (info[screen].blue2)  free (info[screen].blue2);
     }
   free(info);
+
   return status;
 }
 
 static void
-whack_gamma(Display *dpy, int screen, struct screen_gamma_info *info,
-           float ratio)
+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++)
@@ -523,6 +586,7 @@ whack_gamma(Display *dpy, int screen, struct screen_gamma_info *info,
       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,
@@ -535,44 +599,303 @@ whack_gamma(Display *dpy, int screen, struct screen_gamma_info *info,
 #endif /* HAVE_SGI_VC_EXTENSION */
 
 
-
 \f
-#if 0
-#include "screenhack.h"
+/* XFree86 4.x+ Gamma fading */
 
-char *progclass = "foo";
-char *defaults [] = {
-  0
-};
+#ifdef HAVE_XF86VMODE_GAMMA
 
-XrmOptionDescRec options [] = {0};
-int options_size = 0;
+#include <X11/extensions/xf86vmode.h>
 
-void
-screenhack (dpy, w)
-     Display *dpy;
-     Window w;
+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);
+
+static int
+xf86_gamma_fade (Display *dpy,
+                 Window *black_windows, int seconds, int ticks,
+                 Bool out_p, Bool clear_windows)
 {
-  int seconds = 3;
-  int ticks = 20;
-  int delay = 1;
+  int steps = seconds * ticks;
+  long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
+  XEvent dummy_event;
+  int nscreens = ScreenCount(dpy);
+  struct timeval then, now;
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  struct timezone tzp;
+#endif
+  int i, screen;
+  int status = -1;
+  xf86_gamma_info *info = 0;
 
-  while (1)
+  static int ext_ok = -1;
+
+  /* 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++)
     {
-      XSync (dpy, False);
+      if (ext_ok == 1)  /* only have gamma parameter, not ramps. */
+        {
+          if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
+            goto FAIL;
+        }
+      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 (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
+                                       info[screen].r,
+                                       info[screen].g,
+                                       info[screen].b))
+            goto FAIL;
+        }
+      else
+        abort();
+    }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+  gettimeofday(&then, &tzp);
+#else
+  gettimeofday(&then);
+#endif
 
-      fprintf(stderr,"out..."); fflush(stderr);
-      fade_screens (dpy, 0, seconds, ticks, True);
-      fprintf(stderr, "done.\n"); fflush(stderr);
+  /* 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);
+       if (black_windows && black_windows[screen])
+         {
+           XUnmapWindow (dpy, black_windows[screen]);
+           XClearWindow (dpy, black_windows[screen]);
+           XSync(dpy, False);
+         }
+      }
 
-      if (delay) sleep (delay);
 
-      fprintf(stderr,"in..."); fflush(stderr);
-      fade_screens (dpy, 0, seconds, ticks, False);
-      fprintf(stderr, "done.\n"); fflush(stderr);
+  /* Iterate by steps of the animation... */
+  for (i = (out_p ? steps : 0);
+       (out_p ? i > 0 : i < steps);
+       (out_p ? i-- : i++))
+    {
+      for (screen = 0; screen < nscreens; screen++)
+       {
+         xf86_whack_gamma(dpy, screen, &info[screen],
+                           (((float)i) / ((float)steps)));
+
+         /* 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 (delay) sleep (delay);
+            We put the event back so that the calling code can notice it too.
+            It would be better to not remove it at all, but that's harder
+            because Xlib has such a non-design for this kind of crap, and
+            in this application it doesn't matter if the events end up out
+            of order, so in the grand unix tradition we say "fuck it" and
+            do something that mostly works for the time being.
+          */
+         if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
+                              &dummy_event))
+           {
+             XPutBackEvent (dpy, &dummy_event);
+             goto DONE;
+           }
+
+#ifdef GETTIMEOFDAY_TWO_ARGS
+         gettimeofday(&now, &tzp);
+#else
+         gettimeofday(&now);
+#endif
+
+         /* If we haven't already used up our alotted time, sleep to avoid
+            changing the colormap too fast. */
+         {
+           long diff = (((now.tv_sec - then.tv_sec) * 1000000) +
+                        now.tv_usec - then.tv_usec);
+           then.tv_sec = now.tv_sec;
+           then.tv_usec = now.tv_usec;
+           if (usecs_per_step > diff)
+             usleep (usecs_per_step - diff);
+         }
+       }
     }
+  
+
+ DONE:
+
+  if (out_p && black_windows)
+    {
+      for (screen = 0; screen < nscreens; screen++)
+       {
+         if (clear_windows)
+           XClearWindow (dpy, black_windows[screen]);
+         XMapRaised (dpy, black_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);
+
+  status = 0;
+
+ 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);
+    }
+
+  return status;
 }
 
-#endif
+
+/* VidModeExtension version 2.0 or better is needed to do gamma.
+   2.0 added gamma values; 2.1 added gamma ramps.
+ */
+# define XF86_VIDMODE_NAME "XFree86-VidModeExtension"
+# 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 op, event, error, major, minor;
+
+  if (!XQueryExtension (dpy, XF86_VIDMODE_NAME, &op, &event, &error))
+    return 0;  /* display doesn't have the extension. */
+
+  if (!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)
+{
+  Bool status;
+
+  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
+
+      status = XF86VidModeSetGamma (dpy, screen, &g2);
+    }
+  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;
+        }
+
+      status = XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b);
+
+      free (r);
+      free (g);
+      free (b);
+
+# else  /* !HAVE_XF86VMODE_GAMMA_RAMP */
+      abort();
+# endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
+    }
+
+  XSync(dpy, False);
+  return status;
+}
+
+#endif /* HAVE_XF86VMODE_GAMMA */