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