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