http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.34.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 typedef struct {
610   XF86VidModeGamma vmg;
611   int size;
612   unsigned short *r, *g, *b;
613 } xf86_gamma_info;
614
615 static int xf86_check_gamma_extension (Display *dpy);
616 static Bool xf86_whack_gamma (Display *dpy, int screen,
617                               xf86_gamma_info *ginfo, float ratio);
618
619 static int
620 xf86_gamma_fade (Display *dpy,
621                  Window *black_windows, int seconds, int ticks,
622                  Bool out_p, Bool clear_windows)
623 {
624   int steps = seconds * ticks;
625   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
626   XEvent dummy_event;
627   int nscreens = ScreenCount(dpy);
628   struct timeval then, now;
629 #ifdef GETTIMEOFDAY_TWO_ARGS
630   struct timezone tzp;
631 #endif
632   int i, screen;
633   int status = -1;
634   xf86_gamma_info *info = 0;
635
636   static int ext_ok = -1;
637
638   /* Only probe the extension once: the answer isn't going to change. */
639   if (ext_ok == -1)
640     ext_ok = xf86_check_gamma_extension (dpy);
641
642   /* If this server doesn't have the gamma extension, bug out. */
643   if (ext_ok == 0)
644     goto FAIL;
645
646 # ifndef HAVE_XF86VMODE_GAMMA_RAMP
647   if (ext_ok == 2) ext_ok = 1;  /* server is newer than client! */
648 # endif
649
650   info = (xf86_gamma_info *) calloc(nscreens, sizeof(*info));
651
652   /* Get the current gamma maps for all screens.
653      Bug out and return -1 if we can't get them for some screen.
654    */
655   for (screen = 0; screen < nscreens; screen++)
656     {
657       if (ext_ok == 1)  /* only have gamma parameter, not ramps. */
658         {
659           if (!XF86VidModeGetGamma(dpy, screen, &info[screen].vmg))
660             goto FAIL;
661         }
662       else if (ext_ok == 2)  /* have ramps */
663         {
664           if (!XF86VidModeGetGammaRampSize(dpy, screen, &info[screen].size))
665             goto FAIL;
666           if (info[screen].size <= 0)
667             goto FAIL;
668
669           info[screen].r = (unsigned short *)
670             calloc(info[screen].size, sizeof(unsigned short));
671           info[screen].g = (unsigned short *)
672             calloc(info[screen].size, sizeof(unsigned short));
673           info[screen].b = (unsigned short *)
674             calloc(info[screen].size, sizeof(unsigned short));
675
676           if (!(info[screen].r && info[screen].g && info[screen].b))
677             goto FAIL;
678
679           if (!XF86VidModeGetGammaRamp(dpy, screen, info[screen].size,
680                                        info[screen].r,
681                                        info[screen].g,
682                                        info[screen].b))
683             goto FAIL;
684         }
685       else
686         abort();
687     }
688
689 #ifdef GETTIMEOFDAY_TWO_ARGS
690   gettimeofday(&then, &tzp);
691 #else
692   gettimeofday(&then);
693 #endif
694
695   /* If we're fading in (from black), then first crank the gamma all the
696      way down to 0, then take the windows off the screen.
697    */
698   if (!out_p)
699     for (screen = 0; screen < nscreens; screen++)
700       {
701         xf86_whack_gamma(dpy, screen, &info[screen], 0.0);
702         if (black_windows && black_windows[screen])
703           {
704             XUnmapWindow (dpy, black_windows[screen]);
705             XClearWindow (dpy, black_windows[screen]);
706             XSync(dpy, False);
707           }
708       }
709
710
711   /* Iterate by steps of the animation... */
712   for (i = (out_p ? steps : 0);
713        (out_p ? i > 0 : i < steps);
714        (out_p ? i-- : i++))
715     {
716       for (screen = 0; screen < nscreens; screen++)
717         {
718           xf86_whack_gamma(dpy, screen, &info[screen],
719                            (((float)i) / ((float)steps)));
720
721           /* If there is user activity, bug out.  (Bug out on keypresses or
722              mouse presses, but not motion, and not release events.  Bugging
723              out on motion made the unfade hack be totally useless, I think.)
724
725              We put the event back so that the calling code can notice it too.
726              It would be better to not remove it at all, but that's harder
727              because Xlib has such a non-design for this kind of crap, and
728              in this application it doesn't matter if the events end up out
729              of order, so in the grand unix tradition we say "fuck it" and
730              do something that mostly works for the time being.
731            */
732           if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
733                                &dummy_event))
734             {
735               XPutBackEvent (dpy, &dummy_event);
736               goto DONE;
737             }
738
739 #ifdef GETTIMEOFDAY_TWO_ARGS
740           gettimeofday(&now, &tzp);
741 #else
742           gettimeofday(&now);
743 #endif
744
745           /* If we haven't already used up our alotted time, sleep to avoid
746              changing the colormap too fast. */
747           {
748             long diff = (((now.tv_sec - then.tv_sec) * 1000000) +
749                          now.tv_usec - then.tv_usec);
750             then.tv_sec = now.tv_sec;
751             then.tv_usec = now.tv_usec;
752             if (usecs_per_step > diff)
753               usleep (usecs_per_step - diff);
754           }
755         }
756     }
757   
758
759  DONE:
760
761   if (out_p && black_windows)
762     {
763       for (screen = 0; screen < nscreens; screen++)
764         {
765           if (clear_windows)
766             XClearWindow (dpy, black_windows[screen]);
767           XMapRaised (dpy, black_windows[screen]);
768         }
769       XSync(dpy, False);
770     }
771
772   /* I can't explain this; without this delay, we get a flicker.
773      I suppose there's some lossage with stale bits being in the
774      hardware frame buffer or something, and this delay gives it
775      time to flush out.  This sucks! */
776   usleep(100000);  /* 1/10th second */
777
778   for (screen = 0; screen < nscreens; screen++)
779     xf86_whack_gamma(dpy, screen, &info[screen], 1.0);
780   XSync(dpy, False);
781
782   status = 0;
783
784  FAIL:
785   if (info)
786     {
787       for (screen = 0; screen < nscreens; screen++)
788         {
789           if (info[screen].r) free(info[screen].r);
790           if (info[screen].g) free(info[screen].g);
791           if (info[screen].b) free(info[screen].b);
792         }
793       free(info);
794     }
795
796   return status;
797 }
798
799
800 /* VidModeExtension version 2.0 or better is needed to do gamma.
801    2.0 added gamma values; 2.1 added gamma ramps.
802  */
803 # define XF86_VIDMODE_NAME "XFree86-VidModeExtension"
804 # define XF86_VIDMODE_GAMMA_MIN_MAJOR 2
805 # define XF86_VIDMODE_GAMMA_MIN_MINOR 0
806 # define XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR 2
807 # define XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR 1
808
809 /* Returns 0 if gamma fading not available; 1 if only gamma value setting
810    is available; 2 if gamma ramps are available.
811  */
812 static int
813 xf86_check_gamma_extension (Display *dpy)
814 {
815   int op, event, error, major, minor;
816
817   if (!XQueryExtension (dpy, XF86_VIDMODE_NAME, &op, &event, &error))
818     return 0;  /* display doesn't have the extension. */
819
820   if (!XF86VidModeQueryVersion (dpy, &major, &minor))
821     return 0;  /* unable to get version number? */
822
823   if (major < XF86_VIDMODE_GAMMA_MIN_MAJOR || 
824       (major == XF86_VIDMODE_GAMMA_MIN_MAJOR &&
825        minor < XF86_VIDMODE_GAMMA_MIN_MINOR))
826     return 0;  /* extension is too old for gamma. */
827
828   if (major < XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR || 
829       (major == XF86_VIDMODE_GAMMA_RAMP_MIN_MAJOR &&
830        minor < XF86_VIDMODE_GAMMA_RAMP_MIN_MINOR))
831     return 1;  /* extension is too old for gamma ramps. */
832
833   /* Copacetic */
834   return 2;
835 }
836
837
838 /* XFree doesn't let you set gamma to a value smaller than this.
839    Apparently they didn't anticipate the trick I'm doing here...
840  */
841 #define XF86_MIN_GAMMA  0.1
842
843
844 static Bool
845 xf86_whack_gamma(Display *dpy, int screen, xf86_gamma_info *info,
846                  float ratio)
847 {
848   Bool status;
849
850   if (ratio < 0) ratio = 0;
851   if (ratio > 1) ratio = 1;
852
853   if (info->size == 0)    /* we only have a gamma number, not a ramp. */
854     {
855       XF86VidModeGamma g2;
856
857       g2.red   = info->vmg.red   * ratio;
858       g2.green = info->vmg.green * ratio;
859       g2.blue  = info->vmg.blue  * ratio;
860
861 # ifdef XF86_MIN_GAMMA
862       if (g2.red   < XF86_MIN_GAMMA) g2.red   = XF86_MIN_GAMMA;
863       if (g2.green < XF86_MIN_GAMMA) g2.green = XF86_MIN_GAMMA;
864       if (g2.blue  < XF86_MIN_GAMMA) g2.blue  = XF86_MIN_GAMMA;
865 # endif
866
867       status = XF86VidModeSetGamma (dpy, screen, &g2);
868     }
869   else
870     {
871 # ifdef HAVE_XF86VMODE_GAMMA_RAMP
872
873       unsigned short *r, *g, *b;
874       int i;
875       r = (unsigned short *) malloc(info->size * sizeof(unsigned short));
876       g = (unsigned short *) malloc(info->size * sizeof(unsigned short));
877       b = (unsigned short *) malloc(info->size * sizeof(unsigned short));
878
879       for (i = 0; i < info->size; i++)
880         {
881           r[i] = info->r[i] * ratio;
882           g[i] = info->g[i] * ratio;
883           b[i] = info->b[i] * ratio;
884         }
885
886       status = XF86VidModeSetGammaRamp(dpy, screen, info->size, r, g, b);
887
888       free (r);
889       free (g);
890       free (b);
891
892 # else  /* !HAVE_XF86VMODE_GAMMA_RAMP */
893       abort();
894 # endif /* !HAVE_XF86VMODE_GAMMA_RAMP */
895     }
896
897   XSync(dpy, False);
898   return status;
899 }
900
901 #endif /* HAVE_XF86VMODE_GAMMA */