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