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