http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / utils / fade.c
1 /* xscreensaver, Copyright (c) 1992-2008 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 #include "utils.h"
13
14 #include <sys/time.h> /* for gettimeofday() */
15
16 #ifdef VMS
17 # include "vms-gtod.h"
18 #endif /* VMS */
19
20 #include "visual.h"
21 #include "usleep.h"
22 #include "fade.h"
23
24 Colormap
25 copy_colormap (Screen *screen, Visual *visual,
26                Colormap cmap, Colormap into_cmap)
27 {
28   int i;
29   Display *dpy = DisplayOfScreen (screen);
30   Window window = RootWindowOfScreen (screen);
31   int ncolors = CellsOfScreen (screen);
32   XColor *colors = 0;
33
34   /* If this is a colormap on a mono visual, or one with insanely many
35      color cells, bug out. */
36   if (ncolors <= 2 || ncolors > 4096)
37     return 0;
38   /* If this is a non-writable visual, bug out. */
39   if (!has_writable_cells (screen, visual))
40     return 0;
41
42   if (! into_cmap)
43     into_cmap = XCreateColormap (dpy, window, visual, AllocAll);
44   if (! cmap)
45     cmap = DefaultColormapOfScreen (screen);
46
47   colors = (XColor *) calloc(sizeof(XColor), ncolors);
48   for (i = 0; i < ncolors; i++)
49     colors [i].pixel = i;
50   XQueryColors (dpy, cmap, colors, ncolors);
51   XStoreColors (dpy, into_cmap, colors, ncolors);
52   free (colors);
53   return into_cmap;
54 }
55
56
57 void
58 blacken_colormap (Screen *screen, Colormap cmap)
59 {
60   Display *dpy = DisplayOfScreen (screen);
61   int ncolors = CellsOfScreen (screen);
62   XColor *colors;
63   int i;
64   if (ncolors > 4096)
65     return;
66   colors = (XColor *) calloc(sizeof(XColor), ncolors);
67   for (i = 0; i < ncolors; i++)
68     colors[i].pixel = i;
69   XStoreColors (dpy, cmap, colors, ncolors);
70   free (colors);
71 }
72
73
74
75 static void fade_screens_1 (Display *dpy, Colormap *cmaps,
76                             Window *black_windows, int nwindows,
77                             int seconds, int ticks,
78                             Bool out_p, Bool clear_windows);
79
80 #ifdef HAVE_SGI_VC_EXTENSION
81 static int sgi_gamma_fade (Display *dpy,
82                            Window *black_windows, int nwindows,
83                            int seconds, int ticks,
84                            Bool out_p, Bool clear_windows);
85 #endif /* HAVE_SGI_VC_EXTENSION */
86
87 #ifdef HAVE_XF86VMODE_GAMMA
88 static int xf86_gamma_fade (Display *dpy,
89                             Window *black_windows, int nwindows,
90                             int seconds, int ticks,
91                             Bool out_p, Bool clear_windows);
92 #endif /* HAVE_XF86VMODE_GAMMA */
93
94
95 void
96 fade_screens (Display *dpy, Colormap *cmaps,
97               Window *black_windows, int nwindows,
98               int seconds, int ticks,
99               Bool out_p, Bool clear_windows)
100 {
101   int oseconds = seconds;
102   Bool was_in_p = !out_p;
103
104   /* When we're asked to fade in, first fade out, then fade in.
105      That way all the transitions are smooth -- from what's on the
106      screen, to black, to the desktop.
107    */
108   if (was_in_p)
109     {
110       clear_windows = True;
111       out_p = True;
112       seconds /= 3;
113       if (seconds == 0)
114         seconds = 1;
115     }
116
117  AGAIN:
118
119 /* #### printf("\n\nfade_screens %d %d %d\n", seconds, ticks, out_p); */
120
121 #ifdef HAVE_SGI_VC_EXTENSION
122   /* First try to do it by fading the gamma in an SGI-specific way... */
123   if (0 == sgi_gamma_fade(dpy, black_windows, nwindows,
124                           seconds, ticks, out_p,
125                           clear_windows))
126     ;
127   else
128 #endif /* HAVE_SGI_VC_EXTENSION */
129
130 #ifdef HAVE_XF86VMODE_GAMMA
131   /* Then try to do it by fading the gamma in an XFree86-specific way... */
132   if (0 == xf86_gamma_fade(dpy, black_windows, nwindows,
133                            seconds, ticks, out_p,
134                            clear_windows))
135     ;
136   else
137 #endif /* HAVE_XF86VMODE_GAMMA */
138
139     /* Else, do it the old-fashioned way, which (somewhat) loses if
140        there are TrueColor windows visible. */
141     fade_screens_1 (dpy, cmaps, black_windows, nwindows,
142                     seconds, ticks,
143                     out_p, clear_windows);
144
145   /* If we were supposed to be fading in, do so now (we just faded out,
146      so now fade back in.)
147    */
148   if (was_in_p)
149     {
150       was_in_p = False;
151       out_p = False;
152       seconds = oseconds * 2 / 3;
153       if (seconds == 0)
154         seconds = 1;
155       goto AGAIN;
156     }
157 }
158
159
160 static void
161 sleep_from (struct timeval *now, struct timeval *then, long usecs_per_step)
162 {
163   /* If several seconds have passed, the machine must have been asleep
164      or thrashing or something.  Don't sleep in that case, to avoid
165      overflowing and sleeping for an unconscionably long time.  This
166      function should only be sleeping for very short periods.
167    */
168   if (now->tv_sec - then->tv_sec < 5)
169     {
170       long diff = (((now->tv_sec - then->tv_sec) * 1000000) +
171                    now->tv_usec - then->tv_usec);
172       if (usecs_per_step > diff)
173         usleep (usecs_per_step - diff);
174     }
175
176   then->tv_sec  = now->tv_sec;
177   then->tv_usec = now->tv_usec;
178 }
179
180
181
182 /* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
183    hardware, which is capable of installing multiple (4) colormaps
184    simultaneously.  We have to install multiple copies of the same set of
185    colors in order to fill up all the available slots in the hardware color
186    lookup table, so we install an extra N colormaps per screen to make sure
187    that all screens really go black.
188
189    I'm told that this trick also works with XInside's AcceleratedX when using
190    the Matrox Millennium card (which also allows multiple PseudoColor and
191    TrueColor visuals to co-exist and display properly at the same time.)  
192
193    This trick works ok on the 24-bit Indy video hardware, but doesn't work at
194    all on the O2 24-bit hardware.  I guess the higher-end hardware is too
195    "good" for this to work (dammit.)  So... I figured out the "right" way to
196    do this on SGIs, which is to ramp the monitor's gamma down to 0.  That's
197    what is implemented in sgi_gamma_fade(), so we use that if we can.
198  */
199 static void
200 fade_screens_1 (Display *dpy, Colormap *cmaps,
201                 Window *black_windows, int nwindows,
202                 int seconds, int ticks,
203                 Bool out_p, Bool clear_windows)
204 {
205   int i, j, k;
206   int steps = seconds * ticks;
207   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
208   XEvent dummy_event;
209   int cmaps_per_screen = 5;
210   int nscreens = ScreenCount(dpy);
211   int ncmaps = nscreens * cmaps_per_screen;
212   Colormap *fade_cmaps = 0;
213   Bool installed = False;
214   int total_ncolors;
215   XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
216   struct timeval then, now;
217 #ifdef GETTIMEOFDAY_TWO_ARGS
218   struct timezone tzp;
219 #endif
220
221   total_ncolors = 0;
222   for (i = 0; i < nscreens; i++)
223     total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
224
225   orig_colors    = (XColor *) calloc(sizeof(XColor), total_ncolors);
226   current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
227
228   /* Get the contents of the colormap we are fading from or to. */
229   screen_colors = orig_colors;
230   for (i = 0; i < nscreens; i++)
231     {
232       int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, i));
233       Colormap cmap = (cmaps ? cmaps[i] : 0);
234       if (!cmap) cmap = DefaultColormap(dpy, i);
235
236       for (j = 0; j < ncolors; j++)
237         screen_colors[j].pixel = j;
238       XQueryColors (dpy, cmap, screen_colors, ncolors);
239
240       screen_colors += ncolors;
241     }
242
243   memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
244
245
246   /* Make the writable colormaps (we keep these around and reuse them.) */
247   if (!fade_cmaps)
248     {
249       fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
250       for (i = 0; i < nscreens; i++)
251         {
252           Visual *v = DefaultVisual(dpy, i);
253           Screen *s = ScreenOfDisplay(dpy, i);
254           if (has_writable_cells (s, v))
255             for (j = 0; j < cmaps_per_screen; j++)
256               fade_cmaps[(i * cmaps_per_screen) + j] =
257                 XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
258         }
259     }
260
261 #ifdef GETTIMEOFDAY_TWO_ARGS
262   gettimeofday(&then, &tzp);
263 #else
264   gettimeofday(&then);
265 #endif
266
267   /* Iterate by steps of the animation... */
268   for (i = (out_p ? steps : 0);
269        (out_p ? i > 0 : i < steps);
270        (out_p ? i-- : i++))
271     {
272
273       /* For each screen, compute the current value of each color...
274        */
275       orig_screen_colors = orig_colors;
276       screen_colors = current_colors;
277       for (j = 0; j < nscreens; j++)
278         {
279           int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
280           for (k = 0; k < ncolors; k++)
281             {
282               /* This doesn't take into account the relative luminance of the
283                  RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
284                  the difference is imperceptible for this application... */
285               screen_colors[k].red   = orig_screen_colors[k].red   * i / steps;
286               screen_colors[k].green = orig_screen_colors[k].green * i / steps;
287               screen_colors[k].blue  = orig_screen_colors[k].blue  * i / steps;
288             }
289           screen_colors      += ncolors;
290           orig_screen_colors += ncolors;
291         }
292
293       /* Put the colors into the maps...
294        */
295       screen_colors = current_colors;
296       for (j = 0; j < nscreens; j++)
297         {
298           int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
299           for (k = 0; k < cmaps_per_screen; k++)
300             {
301               Colormap c = fade_cmaps[j * cmaps_per_screen + k];
302               if (c)
303                 XStoreColors (dpy, c, screen_colors, ncolors);
304             }
305           screen_colors += ncolors;
306         }
307
308       /* Put the maps on the screens, and then take the windows off the screen.
309          (only need to do this the first time through the loop.)
310        */
311       if (!installed)
312         {
313           for (j = 0; j < ncmaps; j++)
314             if (fade_cmaps[j])
315               XInstallColormap (dpy, fade_cmaps[j]);
316           installed = True;
317
318           if (black_windows && !out_p)
319             for (j = 0; j < nwindows; j++)
320               if (black_windows[j])
321                 {
322                   XUnmapWindow (dpy, black_windows[j]);
323                   XClearWindow (dpy, black_windows[j]);
324                 }
325         }
326
327       XSync (dpy, False);
328
329       /* If there is user activity, bug out.  (Bug out on keypresses or
330          mouse presses, but not motion, and not release events.  Bugging
331          out on motion made the unfade hack be totally useless, I think.)
332
333          We put the event back so that the calling code can notice it too.
334          It would be better to not remove it at all, but that's harder
335          because Xlib has such a non-design for this kind of crap, and
336          in this application it doesn't matter if the events end up out
337          of order, so in the grand unix tradition we say "fuck it" and
338          do something that mostly works for the time being.
339        */
340       if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask), &dummy_event))
341         {
342           XPutBackEvent (dpy, &dummy_event);
343           goto DONE;
344         }
345
346 #ifdef GETTIMEOFDAY_TWO_ARGS
347       gettimeofday(&now, &tzp);
348 #else
349       gettimeofday(&now);
350 #endif
351
352       /* If we haven't already used up our alotted time, sleep to avoid
353          changing the colormap too fast. */
354       sleep_from (&now, &then, usecs_per_step);
355     }
356
357  DONE:
358
359   if (orig_colors)    free (orig_colors);
360   if (current_colors) free (current_colors);
361
362   /* If we've been given windows to raise after blackout, raise them before
363      releasing the colormaps.
364    */
365   if (out_p && black_windows)
366     {
367       for (i = 0; i < nwindows; i++)
368         {
369           if (clear_windows)
370             XClearWindow (dpy, black_windows[i]);
371           XMapRaised (dpy, black_windows[i]);
372         }
373       XSync(dpy, False);
374     }
375
376   /* Now put the target maps back.
377      If we're fading out, use the given cmap (or the default cmap, if none.)
378      If we're fading in, always use the default cmap.
379    */
380   for (i = 0; i < nscreens; i++)
381     {
382       Colormap cmap = (cmaps ? cmaps[i] : 0);
383       if (!cmap || !out_p)
384         cmap = DefaultColormap(dpy, i);
385       XInstallColormap (dpy, cmap);
386     }
387
388   /* The fade (in or out) is complete, so we don't need the black maps on
389      stage any more.
390    */
391   for (i = 0; i < ncmaps; i++)
392     if (fade_cmaps[i])
393       {
394         XUninstallColormap(dpy, fade_cmaps[i]);
395         XFreeColormap(dpy, fade_cmaps[i]);
396         fade_cmaps[i] = 0;
397       }
398   free(fade_cmaps);
399   fade_cmaps = 0;
400 }
401
402
403 \f
404 /* SGI Gamma fading */
405
406 #ifdef HAVE_SGI_VC_EXTENSION
407
408 # include <X11/extensions/XSGIvc.h>
409
410 struct screen_sgi_gamma_info {
411   int gamma_map;  /* ??? always using 0 */
412   int nred, ngreen, nblue;
413   unsigned short *red1, *green1, *blue1;
414   unsigned short *red2, *green2, *blue2;
415   int gamma_size;
416   int gamma_precision;
417   Bool alpha_p;
418 };
419
420
421 static void sgi_whack_gamma(Display *dpy, int screen,
422                             struct screen_sgi_gamma_info *info, float ratio);
423
424 static int
425 sgi_gamma_fade (Display *dpy,
426                 Window *black_windows, int nwindows,
427                 int seconds, int ticks,
428                 Bool out_p, Bool clear_windows)
429 {
430   int steps = seconds * ticks;
431   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
432   XEvent dummy_event;
433   int nscreens = ScreenCount(dpy);
434   struct timeval then, now;
435 #ifdef GETTIMEOFDAY_TWO_ARGS
436   struct timezone tzp;
437 #endif
438   int i, screen;
439   int status = -1;
440   struct screen_sgi_gamma_info *info = (struct screen_sgi_gamma_info *)
441     calloc(nscreens, sizeof(*info));
442
443   /* Get the current gamma maps for all screens.
444      Bug out and return -1 if we can't get them for some screen.
445    */
446   for (screen = 0; screen < nscreens; screen++)
447     {
448       if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
449                                &info[screen].gamma_size,
450                                &info[screen].gamma_precision,
451                                &info[screen].alpha_p))
452         goto FAIL;
453
454       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
455                                   XSGIVC_COMPONENT_RED,
456                                   &info[screen].nred, &info[screen].red1))
457         goto FAIL;
458       if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
459                                    XSGIVC_COMPONENT_GREEN,
460                                    &info[screen].ngreen, &info[screen].green1))
461         goto FAIL;
462       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
463                                   XSGIVC_COMPONENT_BLUE,
464                                   &info[screen].nblue, &info[screen].blue1))
465         goto FAIL;
466
467       if (info[screen].gamma_precision == 8)    /* Scale it up to 16 bits. */
468         {
469           int j;
470           for(j = 0; j < info[screen].nred; j++)
471             info[screen].red1[j]   =
472               ((info[screen].red1[j]   << 8) | info[screen].red1[j]);
473           for(j = 0; j < info[screen].ngreen; j++)
474             info[screen].green1[j] =
475               ((info[screen].green1[j] << 8) | info[screen].green1[j]);
476           for(j = 0; j < info[screen].nblue; j++)
477             info[screen].blue1[j]  =
478               ((info[screen].blue1[j]  << 8) | info[screen].blue1[j]);
479         }
480
481       info[screen].red2   = (unsigned short *)
482         malloc(sizeof(*info[screen].red2)   * (info[screen].nred+1));
483       info[screen].green2 = (unsigned short *)
484         malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
485       info[screen].blue2  = (unsigned short *)
486         malloc(sizeof(*info[screen].blue2)  * (info[screen].nblue+1));
487     }
488
489 #ifdef GETTIMEOFDAY_TWO_ARGS
490   gettimeofday(&then, &tzp);
491 #else
492   gettimeofday(&then);
493 #endif
494
495   /* If we're fading in (from black), then first crank the gamma all the
496      way down to 0, then take the windows off the screen.
497    */
498   if (!out_p)
499     {
500       for (screen = 0; screen < nscreens; screen++)
501         sgi_whack_gamma(dpy, screen, &info[screen], 0.0);
502       
503       for (screen = 0; screen < nwindows; screen++)
504         if (black_windows && black_windows[screen])
505           {
506             XUnmapWindow (dpy, black_windows[screen]);
507             XClearWindow (dpy, black_windows[screen]);
508             XSync(dpy, False);
509           }
510     }
511
512   /* Iterate by steps of the animation... */
513   for (i = (out_p ? steps : 0);
514        (out_p ? i > 0 : i < steps);
515        (out_p ? i-- : i++))
516     {
517       for (screen = 0; screen < nscreens; screen++)
518         {
519           sgi_whack_gamma(dpy, screen, &info[screen],
520                           (((float)i) / ((float)steps)));
521
522           /* If there is user activity, bug out.  (Bug out on keypresses or
523              mouse presses, but not motion, and not release events.  Bugging
524              out on motion made the unfade hack be totally useless, I think.)
525
526              We put the event back so that the calling code can notice it too.
527              It would be better to not remove it at all, but that's harder
528              because Xlib has such a non-design for this kind of crap, and
529              in this application it doesn't matter if the events end up out
530              of order, so in the grand unix tradition we say "fuck it" and
531              do something that mostly works for the time being.
532            */
533           if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
534                                &dummy_event))
535             {
536               XPutBackEvent (dpy, &dummy_event);
537               goto DONE;
538             }
539
540 #ifdef GETTIMEOFDAY_TWO_ARGS
541           gettimeofday(&now, &tzp);
542 #else
543           gettimeofday(&now);
544 #endif
545
546           /* If we haven't already used up our alotted time, sleep to avoid
547              changing the colormap too fast. */
548           sleep_from (&now, &then, usecs_per_step);
549         }
550     }
551   
552
553  DONE:
554
555   if (out_p && black_windows)
556     {
557       for (screen = 0; screen < nwindows; screen++)
558         {
559           if (clear_windows)
560             XClearWindow (dpy, black_windows[screen]);
561           XMapRaised (dpy, black_windows[screen]);
562         }
563       XSync(dpy, False);
564     }
565
566   /* I can't explain this; without this delay, we get a flicker.
567      I suppose there's some lossage with stale bits being in the
568      hardware frame buffer or something, and this delay gives it
569      time to flush out.  This sucks! */
570   usleep(100000);  /* 1/10th second */
571
572   for (screen = 0; screen < nscreens; screen++)
573     sgi_whack_gamma(dpy, screen, &info[screen], 1.0);
574   XSync(dpy, False);
575
576   status = 0;
577
578  FAIL:
579   for (screen = 0; screen < nscreens; screen++)
580     {
581       if (info[screen].red1)   free (info[screen].red1);
582       if (info[screen].green1) free (info[screen].green1);
583       if (info[screen].blue1)  free (info[screen].blue1);
584       if (info[screen].red2)   free (info[screen].red2);
585       if (info[screen].green2) free (info[screen].green2);
586       if (info[screen].blue2)  free (info[screen].blue2);
587     }
588   free(info);
589
590   return status;
591 }
592
593 static void
594 sgi_whack_gamma(Display *dpy, int screen, struct screen_sgi_gamma_info *info,
595                 float ratio)
596 {
597   int k;
598
599   if (ratio < 0) ratio = 0;
600   if (ratio > 1) ratio = 1;
601   for (k = 0; k < info->gamma_size; k++)
602     {
603       info->red2[k]   = info->red1[k]   * ratio;
604       info->green2[k] = info->green1[k] * ratio;
605       info->blue2[k]  = info->blue1[k]  * ratio;
606     }
607
608   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
609                            XSGIVC_MComponentRed, info->red2);
610   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
611                            XSGIVC_MComponentGreen, info->green2);
612   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
613                            XSGIVC_MComponentBlue, info->blue2);
614   XSync(dpy, False);
615 }
616
617 #endif /* HAVE_SGI_VC_EXTENSION */
618
619
620 \f
621 /* XFree86 4.x+ Gamma fading */
622
623 #ifdef HAVE_XF86VMODE_GAMMA
624
625 #include <X11/extensions/xf86vmode.h>
626
627 typedef struct {
628   XF86VidModeGamma vmg;
629   int size;
630   unsigned short *r, *g, *b;
631 } xf86_gamma_info;
632
633 static int xf86_check_gamma_extension (Display *dpy);
634 static Bool xf86_whack_gamma (Display *dpy, int screen,
635                               xf86_gamma_info *ginfo, float ratio);
636
637 static int
638 xf86_gamma_fade (Display *dpy,
639                  Window *black_windows, int nwindows,
640                  int seconds, int ticks,
641                  Bool out_p, Bool clear_windows)
642 {
643   int steps = seconds * ticks;
644   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
645   XEvent dummy_event;
646   int nscreens = ScreenCount(dpy);
647   struct timeval then, now;
648 #ifdef GETTIMEOFDAY_TWO_ARGS
649   struct timezone tzp;
650 #endif
651   int i, screen;
652   int status = -1;
653   xf86_gamma_info *info = 0;
654
655   static int ext_ok = -1;
656
657   /* Only probe the extension once: the answer isn't going to change. */
658   if (ext_ok == -1)
659     ext_ok = xf86_check_gamma_extension (dpy);
660
661   /* If this server doesn't have the gamma extension, bug out. */
662   if (ext_ok == 0)
663     goto FAIL;
664
665 # ifndef HAVE_XF86VMODE_GAMMA_RAMP
666   if (ext_ok == 2) ext_ok = 1;  /* server is newer than client! */
667 # endif
668
669   info = (xf86_gamma_info *) calloc(nscreens, sizeof(*info));
670
671   /* Get the current gamma maps for all screens.
672      Bug out and return -1 if we can't get them for some screen.
673    */
674   for (screen = 0; screen < nscreens; screen++)
675     {
676       if (ext_ok == 1)  /* only have gamma parameter, not ramps. */
677         {
678           if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
679             goto FAIL;
680         }
681 # ifdef HAVE_XF86VMODE_GAMMA_RAMP
682       else if (ext_ok == 2)  /* have ramps */
683         {
684           if (!XF86VidModeGetGammaRampSize(dpy, screen, &info[screen].size))
685             goto FAIL;
686           if (info[screen].size <= 0)
687             goto FAIL;
688
689           info[screen].r = (unsigned short *)
690             calloc(info[screen].size, sizeof(unsigned short));
691           info[screen].g = (unsigned short *)
692             calloc(info[screen].size, sizeof(unsigned short));
693           info[screen].b = (unsigned short *)
694             calloc(info[screen].size, sizeof(unsigned short));
695
696           if (!(info[screen].r && info[screen].g && info[screen].b))
697             goto FAIL;
698
699           if (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
700                                        info[screen].r,
701                                        info[screen].g,
702                                        info[screen].b))
703             goto FAIL;
704         }
705 # endif /* HAVE_XF86VMODE_GAMMA_RAMP */
706       else
707         abort();
708     }
709
710 #ifdef GETTIMEOFDAY_TWO_ARGS
711   gettimeofday(&then, &tzp);
712 #else
713   gettimeofday(&then);
714 #endif
715
716   /* If we're fading in (from black), then first crank the gamma all the
717      way down to 0, then take the windows off the screen.
718    */
719   if (!out_p)
720     {
721       for (screen = 0; screen < nscreens; screen++)
722         xf86_whack_gamma(dpy, screen, &info[screen], 0.0);
723       for (screen = 0; screen < nwindows; screen++)
724         if (black_windows && black_windows[screen])
725           {
726             XUnmapWindow (dpy, black_windows[screen]);
727             XClearWindow (dpy, black_windows[screen]);
728             XSync(dpy, False);
729           }
730     }
731
732   /* Iterate by steps of the animation... */
733   for (i = (out_p ? steps : 0);
734        (out_p ? i > 0 : i < steps);
735        (out_p ? i-- : i++))
736     {
737       for (screen = 0; screen < nscreens; screen++)
738         {
739           xf86_whack_gamma(dpy, screen, &info[screen],
740                            (((float)i) / ((float)steps)));
741
742           /* If there is user activity, bug out.  (Bug out on keypresses or
743              mouse presses, but not motion, and not release events.  Bugging
744              out on motion made the unfade hack be totally useless, I think.)
745
746              We put the event back so that the calling code can notice it too.
747              It would be better to not remove it at all, but that's harder
748              because Xlib has such a non-design for this kind of crap, and
749              in this application it doesn't matter if the events end up out
750              of order, so in the grand unix tradition we say "fuck it" and
751              do something that mostly works for the time being.
752            */
753           if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
754                                &dummy_event))
755             {
756               XPutBackEvent (dpy, &dummy_event);
757               goto DONE;
758             }
759
760 #ifdef GETTIMEOFDAY_TWO_ARGS
761           gettimeofday(&now, &tzp);
762 #else
763           gettimeofday(&now);
764 #endif
765
766           /* If we haven't already used up our alotted time, sleep to avoid
767              changing the colormap too fast. */
768           sleep_from (&now, &then, usecs_per_step);
769         }
770     }
771   
772
773  DONE:
774
775   if (out_p && black_windows)
776     {
777       for (screen = 0; screen < nwindows; screen++)
778         {
779           if (clear_windows)
780             XClearWindow (dpy, black_windows[screen]);
781           XMapRaised (dpy, black_windows[screen]);
782         }
783       XSync(dpy, False);
784     }
785
786   /* I can't explain this; without this delay, we get a flicker.
787      I suppose there's some lossage with stale bits being in the
788      hardware frame buffer or something, and this delay gives it
789      time to flush out.  This sucks! */
790   usleep(100000);  /* 1/10th second */
791
792   for (screen = 0; screen < nscreens; screen++)
793     xf86_whack_gamma(dpy, screen, &info[screen], 1.0);
794   XSync(dpy, False);
795
796   status = 0;
797
798  FAIL:
799   if (info)
800     {
801       for (screen = 0; screen < nscreens; screen++)
802         {
803           if (info[screen].r) free(info[screen].r);
804           if (info[screen].g) free(info[screen].g);
805           if (info[screen].b) free(info[screen].b);
806         }
807       free(info);
808     }
809
810   return status;
811 }
812
813
814 /* This bullshit is needed because the VidMode extension doesn't work
815    on remote displays -- but if the remote display has the extension
816    at all, XF86VidModeQueryExtension returns true, and then
817    XF86VidModeQueryVersion dies with an X error.  Thank you XFree,
818    may I have another.
819  */
820
821 static Bool error_handler_hit_p = False;
822
823 static int
824 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
825 {
826   error_handler_hit_p = True;
827   return 0;
828 }
829
830 static Bool
831 safe_XF86VidModeQueryVersion (Display *dpy, int *majP, int *minP)
832 {
833   Bool result;
834   XErrorHandler old_handler;
835   XSync (dpy, False);
836   error_handler_hit_p = False;
837   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
838
839   result = XF86VidModeQueryVersion (dpy, majP, minP);
840
841   XSync (dpy, False);
842   XSetErrorHandler (old_handler);
843   XSync (dpy, False);
844
845   return (error_handler_hit_p
846           ? False
847           : result);
848 }
849
850
851
852 /* VidModeExtension version 2.0 or better is needed to do gamma.
853    2.0 added gamma values; 2.1 added gamma ramps.
854  */
855 # define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
856 # define XF86_VIDMODE_GAMMA_MIN_MINOR 0
857 # define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
858 # define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1
859
860
861
862 /* Returns 0 if gamma fading not available; 1 if only gamma value setting
863    is available; 2 if gamma ramps are available.
864  */
865 static int
866 xf86_check_gamma_extension (Display *dpy)
867 {
868   int event, error, major, minor;
869
870   if (!XF86VidModeQueryExtension (dpy, &event, &error))
871     return 0;  /* display doesn't have the extension. */
872
873   if (!safe_XF86VidModeQueryVersion (dpy, &major, &minor))
874     return 0;  /* unable to get version number? */
875
876   if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR || 
877       (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
878        minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
879     return 0;  /* extension is too old for gamma. */
880
881   if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR || 
882       (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
883        minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
884     return 1;  /* extension is too old for gamma ramps. */
885
886   /* Copacetic */
887   return 2;
888 }
889
890
891 /* XFree doesn't let you set gamma to a value smaller than this.
892    Apparently they didn't anticipate the trick I'm doing here...
893  */
894 #define XF86_MIN_GAMMA  0.1
895
896
897 static Bool
898 xf86_whack_gamma(Display *dpy, int screen, xf86_gamma_info *info,
899                  float ratio)
900 {
901   Bool status;
902
903   if (ratio < 0) ratio = 0;
904   if (ratio > 1) ratio = 1;
905
906   if (info->size == 0)    /* we only have a gamma number, not a ramp. */
907     {
908       XF86VidModeGamma g2;
909
910       g2.red   = info->vmg.red   * ratio;
911       g2.green = info->vmg.green * ratio;
912       g2.blue  = info->vmg.blue  * ratio;
913
914 # ifdef XF86_MIN_GAMMA
915       if (g2.red   < XF86_MIN_GAMMA) g2.red   = XF86_MIN_GAMMA;
916       if (g2.green < XF86_MIN_GAMMA) g2.green = XF86_MIN_GAMMA;
917       if (g2.blue  < XF86_MIN_GAMMA) g2.blue  = XF86_MIN_GAMMA;
918 # endif
919
920       status = XF86VidModeSetGamma (dpy, screen, &g2);
921     }
922   else
923     {
924 # ifdef HAVE_XF86VMODE_GAMMA_RAMP
925
926       unsigned short *r, *g, *b;
927       int i;
928       r = (unsigned short *) malloc(info->size * sizeof(unsigned short));
929       g = (unsigned short *) malloc(info->size * sizeof(unsigned short));
930       b = (unsigned short *) malloc(info->size * sizeof(unsigned short));
931
932       for (i = 0; i < info->size; i++)
933         {
934           r[i] = info->r[i] * ratio;
935           g[i] = info->g[i] * ratio;
936           b[i] = info->b[i] * ratio;
937         }
938
939       status = XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b);
940
941       free (r);
942       free (g);
943       free (b);
944
945 # else  /* !HAVE_XF86VMODE_GAMMA_RAMP */
946       abort();
947 # endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
948     }
949
950   XSync(dpy, False);
951   return status;
952 }
953
954 #endif /* HAVE_XF86VMODE_GAMMA */