ftp://ftp.demon.nl/disk1/redhat-contrib/libc5/SRPMS/xscreensaver-2.14-1.src.rpm
[xscreensaver] / utils / fade.c
1 /* xscreensaver, Copyright (c) 1992-1997 Jamie Zawinski <jwz@netscape.com>
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
25 Colormap
26 copy_colormap (Screen *screen, Visual *visual,
27                Colormap cmap, Colormap into_cmap)
28 {
29   int i;
30   Display *dpy = DisplayOfScreen (screen);
31   Window window = RootWindowOfScreen (screen);
32   int ncolors = CellsOfScreen (screen);
33   XColor *colors = 0;
34
35   /* If this is a colormap on a mono visual, or one with insanely many
36      color cells, bug out. */
37   if (ncolors <= 2 || ncolors > 4096)
38     return 0;
39   /* If this is a non-writable visual, bug out. */
40   if (!has_writable_cells (screen, visual))
41     return 0;
42
43   if (! into_cmap)
44     into_cmap = XCreateColormap (dpy, window, visual, AllocAll);
45   if (! cmap)
46     cmap = DefaultColormapOfScreen (screen);
47
48   colors = (XColor *) calloc(sizeof(XColor), ncolors);
49   for (i = 0; i < ncolors; i++)
50     colors [i].pixel = i;
51   XQueryColors (dpy, cmap, colors, ncolors);
52   XStoreColors (dpy, into_cmap, colors, ncolors);
53   free (colors);
54   return into_cmap;
55 }
56
57
58 void
59 blacken_colormap (Screen *screen, Colormap cmap)
60 {
61   Display *dpy = DisplayOfScreen (screen);
62   int ncolors = CellsOfScreen (screen);
63   XColor *colors;
64   int i;
65   if (ncolors > 4096)
66     return;
67   colors = (XColor *) calloc(sizeof(XColor), ncolors);
68   for (i = 0; i < ncolors; i++)
69     colors[i].pixel = i;
70   XStoreColors (dpy, cmap, colors, ncolors);
71   free (colors);
72 }
73
74
75
76 static void fade_screens_1 (Display *dpy, Colormap *cmaps,
77                             Window *black_windows, int seconds, int ticks,
78                             Bool out_p, Bool clear_windows);
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
86
87 void
88 fade_screens (Display *dpy, Colormap *cmaps, Window *black_windows,
89               int seconds, int ticks,
90               Bool out_p, Bool clear_windows)
91 {
92 #ifdef HAVE_SGI_VC_EXTENSION
93   /* First try to do it by fading the gamma in an SGI-specific way... */
94   if (0 != sgi_gamma_fade(dpy, black_windows, seconds, ticks, out_p,
95                           clear_windows))
96 #endif /* HAVE_SGI_VC_EXTENSION */
97     /* Else, do it the old-fashioned way, which (somewhat) loses if
98        there are TrueColor windows visible. */
99     fade_screens_1 (dpy, cmaps, black_windows, seconds, ticks,
100                     out_p, clear_windows);
101 }
102
103
104 /* The business with `cmaps_per_screen' is to fake out the SGI 8-bit video
105    hardware, which is capable of installing multiple (4) colormaps
106    simultaniously.  We have to install multiple copies of the same set of
107    colors in order to fill up all the available slots in the hardware color
108    lookup table, so we install an extra N colormaps per screen to make sure
109    that all screens really go black.
110
111    I'm told that this trick also works with XInside's AcceleratedX when using
112    the Matrox Millenium card (which also allows multiple PseudoColor and
113    TrueColor visuals to co-exist and display properly at the same time.)  
114
115    This trick works ok on the 24-bit Indy video hardware, but doesn't work at
116    all on the O2 24-bit hardware.  I guess the higher-end hardware is too
117    "good" for this to work (dammit.)  So... I figured out the "right" way to
118    do this on SGIs, which is to ramp the monitor's gamma down to 0.  That's
119    what is implemented in sgi_gamma_fade(), so we use that if we can.
120  */
121 static void
122 fade_screens_1 (Display *dpy, Colormap *cmaps, Window *black_windows,
123                 int seconds, int ticks,
124                 Bool out_p, Bool clear_windows)
125 {
126   int i, j, k;
127   int steps = seconds * ticks;
128   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
129   XEvent dummy_event;
130   int cmaps_per_screen = 5;
131   int nscreens = ScreenCount(dpy);
132   int ncmaps = nscreens * cmaps_per_screen;
133   Colormap *fade_cmaps = 0;
134   Bool installed = False;
135   int total_ncolors;
136   XColor *orig_colors, *current_colors, *screen_colors, *orig_screen_colors;
137   struct timeval then, now;
138 #ifdef GETTIMEOFDAY_TWO_ARGS
139   struct timezone tzp;
140 #endif
141
142   total_ncolors = 0;
143   for (i = 0; i < nscreens; i++)
144     total_ncolors += CellsOfScreen (ScreenOfDisplay(dpy, i));
145
146   orig_colors    = (XColor *) calloc(sizeof(XColor), total_ncolors);
147   current_colors = (XColor *) calloc(sizeof(XColor), total_ncolors);
148
149   /* Get the contents of the colormap we are fading from or to. */
150   screen_colors = orig_colors;
151   for (i = 0; i < nscreens; i++)
152     {
153       int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, i));
154       Colormap cmap = (cmaps ? cmaps[i] : 0);
155       if (!cmap) cmap = DefaultColormap(dpy, i);
156
157       for (j = 0; j < ncolors; j++)
158         screen_colors[j].pixel = j;
159       XQueryColors (dpy, cmap, screen_colors, ncolors);
160
161       screen_colors += ncolors;
162     }
163
164   memcpy (current_colors, orig_colors, total_ncolors * sizeof (XColor));
165
166
167   /* Make the writable colormaps (we keep these around and reuse them.) */
168   if (!fade_cmaps)
169     {
170       fade_cmaps = (Colormap *) calloc(sizeof(Colormap), ncmaps);
171       for (i = 0; i < nscreens; i++)
172         {
173           Visual *v = DefaultVisual(dpy, i);
174           Screen *s = ScreenOfDisplay(dpy, i);
175           if (has_writable_cells (s, v))
176             for (j = 0; j < cmaps_per_screen; j++)
177               fade_cmaps[(i * cmaps_per_screen) + j] =
178                 XCreateColormap (dpy, RootWindowOfScreen (s), v, AllocAll);
179         }
180     }
181
182 #ifdef GETTIMEOFDAY_TWO_ARGS
183   gettimeofday(&then, &tzp);
184 #else
185   gettimeofday(&then);
186 #endif
187
188   /* Iterate by steps of the animation... */
189   for (i = (out_p ? steps : 0);
190        (out_p ? i > 0 : i < steps);
191        (out_p ? i-- : i++))
192     {
193
194       /* For each screen, compute the current value of each color...
195        */
196       orig_screen_colors = orig_colors;
197       screen_colors = current_colors;
198       for (j = 0; j < nscreens; j++)
199         {
200           int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
201           for (k = 0; k < ncolors; k++)
202             {
203               /* This doesn't take into account the relative luminance of the
204                  RGB components (0.299, 0.587, and 0.114 at gamma 2.2) but
205                  the difference is imperceptible for this application... */
206               screen_colors[k].red   = orig_screen_colors[k].red   * i / steps;
207               screen_colors[k].green = orig_screen_colors[k].green * i / steps;
208               screen_colors[k].blue  = orig_screen_colors[k].blue  * i / steps;
209             }
210           screen_colors      += ncolors;
211           orig_screen_colors += ncolors;
212         }
213
214       /* Put the colors into the maps...
215        */
216       screen_colors = current_colors;
217       for (j = 0; j < nscreens; j++)
218         {
219           int ncolors = CellsOfScreen (ScreenOfDisplay (dpy, j));
220           for (k = 0; k < cmaps_per_screen; k++)
221             {
222               Colormap c = fade_cmaps[j * cmaps_per_screen + k];
223               if (c)
224                 XStoreColors (dpy, c, screen_colors, ncolors);
225             }
226           screen_colors += ncolors;
227         }
228
229       /* Put the maps on the screens, and then take the windows off the screen.
230          (only need to do this the first time through the loop.)
231        */
232       if (!installed)
233         {
234           for (j = 0; j < ncmaps; j++)
235             if (fade_cmaps[j])
236               XInstallColormap (dpy, fade_cmaps[j]);
237           installed = True;
238
239           if (black_windows && !out_p)
240             for (j = 0; j < nscreens; j++)
241               if (black_windows[j])
242                 {
243                   XUnmapWindow (dpy, black_windows[j]);
244                   XClearWindow (dpy, black_windows[j]);
245                 }
246         }
247
248       XSync (dpy, False);
249
250       /* If there is user activity, bug out.  (Bug out on keypresses or
251          mouse presses, but not motion, and not release events.  Bugging
252          out on motion made the unfade hack be totally useless, I think.)
253
254          We put the event back so that the calling code can notice it too.
255          It would be better to not remove it at all, but that's harder
256          because Xlib has such a non-design for this kind of crap, and
257          in this application it doesn't matter if the events end up out
258          of order, so in the grand unix tradition we say "fuck it" and
259          do something that mostly works for the time being.
260        */
261       if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask), &dummy_event))
262         {
263           XPutBackEvent (dpy, &dummy_event);
264           goto DONE;
265         }
266
267 #ifdef GETTIMEOFDAY_TWO_ARGS
268       gettimeofday(&now, &tzp);
269 #else
270       gettimeofday(&now);
271 #endif
272
273       /* If we haven't already used up our alotted time, sleep to avoid
274          changing the colormap too fast. */
275       {
276         long diff = (((now.tv_sec - then.tv_sec) * 1000000) +
277                      now.tv_usec - then.tv_usec);
278         then.tv_sec = now.tv_sec;
279         then.tv_usec = now.tv_usec;
280         if (usecs_per_step > diff)
281           usleep (usecs_per_step - diff);
282       }
283     }
284
285  DONE:
286
287   if (orig_colors)    free (orig_colors);
288   if (current_colors) free (current_colors);
289
290   /* If we've been given windows to raise after blackout, raise them before
291      releasing the colormaps.
292    */
293   if (out_p && black_windows)
294     for (i = 0; i < nscreens; i++)
295       {
296         if (clear_windows)
297           XClearWindow (dpy, black_windows[i]);
298         XMapRaised (dpy, black_windows[i]);
299       }
300
301
302   /* Now put the target maps back.
303      If we're fading out, use the given cmap (or the default cmap, if none.)
304      If we're fading in, always use the default cmap.
305    */
306   for (i = 0; i < nscreens; i++)
307     {
308       Colormap cmap = (cmaps ? cmaps[i] : 0);
309       if (!cmap || !out_p)
310         cmap = DefaultColormap(dpy, i);
311       XInstallColormap (dpy, cmap);
312     }
313
314   /* The fade (in or out) is complete, so we don't need the black maps on
315      stage any more.
316    */
317   for (i = 0; i < ncmaps; i++)
318     if (fade_cmaps[i])
319       {
320         XUninstallColormap(dpy, fade_cmaps[i]);
321         XFreeColormap(dpy, fade_cmaps[i]);
322         fade_cmaps[i] = 0;
323       }
324   free(fade_cmaps);
325   fade_cmaps = 0;
326 }
327
328
329 #ifdef HAVE_SGI_VC_EXTENSION
330
331 # include <X11/extensions/XSGIvc.h>
332
333 struct screen_gamma_info {
334   int gamma_map;  /* ??? always using 0 */
335   int nred, ngreen, nblue;
336   unsigned short *red1, *green1, *blue1;
337   unsigned short *red2, *green2, *blue2;
338   int gamma_size;
339   int gamma_precision;
340   Bool alpha_p;
341 };
342
343
344 static void whack_gamma(Display *dpy, int screen,
345                         struct screen_gamma_info *info, float ratio);
346
347 static int
348 sgi_gamma_fade (Display *dpy,
349                 Window *black_windows, int seconds, int ticks,
350                 Bool out_p, Bool clear_windows)
351 {
352   int steps = seconds * ticks;
353   long usecs_per_step = (long)(seconds * 1000000) / (long)steps;
354   XEvent dummy_event;
355   int nscreens = ScreenCount(dpy);
356   struct timeval then, now;
357 #ifdef GETTIMEOFDAY_TWO_ARGS
358   struct timezone tzp;
359 #endif
360   int i, screen;
361   int status = -1;
362   struct screen_gamma_info *info = (struct screen_gamma_info *)
363     calloc(nscreens, sizeof(*info));
364
365   /* Get the current gamma maps for all screens.
366      Bug out and return -1 if we can't get them for some screen.
367    */
368   for (screen = 0; screen < nscreens; screen++)
369     {
370       if (!XSGIvcQueryGammaMap(dpy, screen, info[screen].gamma_map,
371                                &info[screen].gamma_size,
372                                &info[screen].gamma_precision,
373                                &info[screen].alpha_p))
374         goto FAIL;
375
376       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
377                                   XSGIVC_COMPONENT_RED,
378                                   &info[screen].nred, &info[screen].red1))
379         goto FAIL;
380       if (! XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
381                                    XSGIVC_COMPONENT_GREEN,
382                                    &info[screen].ngreen, &info[screen].green1))
383         goto FAIL;
384       if (!XSGIvcQueryGammaColors(dpy, screen, info[screen].gamma_map,
385                                   XSGIVC_COMPONENT_BLUE,
386                                   &info[screen].nblue, &info[screen].blue1))
387         goto FAIL;
388
389       if (info[screen].gamma_precision == 8)    /* Scale it up to 16 bits. */
390         {
391           int j;
392           for(j = 0; j < info[screen].nred; j++)
393             info[screen].red1[j]   =
394               ((info[screen].red1[j]   << 8) | info[screen].red1[j]);
395           for(j = 0; j < info[screen].ngreen; j++)
396             info[screen].green1[j] =
397               ((info[screen].green1[j] << 8) | info[screen].green1[j]);
398           for(j = 0; j < info[screen].nblue; j++)
399             info[screen].blue1[j]  =
400               ((info[screen].blue1[j]  << 8) | info[screen].blue1[j]);
401         }
402
403       info[screen].red2   = (unsigned short *)
404         malloc(sizeof(*info[screen].red2)   * (info[screen].nred+1));
405       info[screen].green2 = (unsigned short *)
406         malloc(sizeof(*info[screen].green2) * (info[screen].ngreen+1));
407       info[screen].blue2  = (unsigned short *)
408         malloc(sizeof(*info[screen].blue2)  * (info[screen].nblue+1));
409     }
410
411 #ifdef GETTIMEOFDAY_TWO_ARGS
412   gettimeofday(&then, &tzp);
413 #else
414   gettimeofday(&then);
415 #endif
416
417   /* If we're fading in (from black), then first crank the gamma all the
418      way down to 0, then take the windows off the screen.
419    */
420   if (!out_p)
421     for (screen = 0; screen < nscreens; screen++)
422       {
423         whack_gamma(dpy, screen, &info[screen], 0.0);
424         if (black_windows && black_windows[screen])
425           {
426             XUnmapWindow (dpy, black_windows[screen]);
427             XClearWindow (dpy, black_windows[screen]);
428           }
429       }
430
431
432   /* Iterate by steps of the animation... */
433   for (i = (out_p ? steps : 0);
434        (out_p ? i > 0 : i < steps);
435        (out_p ? i-- : i++))
436     {
437       for (screen = 0; screen < nscreens; screen++)
438         {
439           whack_gamma(dpy, screen, &info[screen],
440                       (((float)i) / ((float)steps)));
441
442           /* If there is user activity, bug out.  (Bug out on keypresses or
443              mouse presses, but not motion, and not release events.  Bugging
444              out on motion made the unfade hack be totally useless, I think.)
445
446              We put the event back so that the calling code can notice it too.
447              It would be better to not remove it at all, but that's harder
448              because Xlib has such a non-design for this kind of crap, and
449              in this application it doesn't matter if the events end up out
450              of order, so in the grand unix tradition we say "fuck it" and
451              do something that mostly works for the time being.
452            */
453           if (XCheckMaskEvent (dpy, (KeyPressMask|ButtonPressMask),
454                                &dummy_event))
455             {
456               XPutBackEvent (dpy, &dummy_event);
457               goto DONE;
458             }
459
460 #ifdef GETTIMEOFDAY_TWO_ARGS
461           gettimeofday(&now, &tzp);
462 #else
463           gettimeofday(&now);
464 #endif
465
466           /* If we haven't already used up our alotted time, sleep to avoid
467              changing the colormap too fast. */
468           {
469             long diff = (((now.tv_sec - then.tv_sec) * 1000000) +
470                          now.tv_usec - then.tv_usec);
471             then.tv_sec = now.tv_sec;
472             then.tv_usec = now.tv_usec;
473             if (usecs_per_step > diff)
474               usleep (usecs_per_step - diff);
475           }
476         }
477     }
478   
479
480  DONE:
481
482   if (out_p && black_windows)
483     {
484       for (screen = 0; screen < nscreens; screen++)
485         {
486           if (clear_windows)
487             XClearWindow (dpy, black_windows[i]);
488           XMapRaised (dpy, black_windows[i]);
489         }
490       XSync(dpy, False);
491     }
492
493   for (screen = 0; screen < nscreens; screen++)
494     whack_gamma(dpy, screen, &info[screen], 1.0);
495   XSync(dpy, False);
496
497   status = 0;
498
499  FAIL:
500   for (screen = 0; screen < nscreens; screen++)
501     {
502       if (info[screen].red1)   free (info[screen].red1);
503       if (info[screen].green1) free (info[screen].green1);
504       if (info[screen].blue1)  free (info[screen].blue1);
505       if (info[screen].red2)   free (info[screen].red2);
506       if (info[screen].green2) free (info[screen].green2);
507       if (info[screen].blue2)  free (info[screen].blue2);
508     }
509   free(info);
510   return status;
511 }
512
513 static void
514 whack_gamma(Display *dpy, int screen, struct screen_gamma_info *info,
515             float ratio)
516 {
517   int k;
518   if (ratio < 0) ratio = 0;
519   if (ratio > 1) ratio = 1;
520   for (k = 0; k < info->gamma_size; k++)
521     {
522       info->red2[k]   = info->red1[k]   * ratio;
523       info->green2[k] = info->green1[k] * ratio;
524       info->blue2[k]  = info->blue1[k]  * ratio;
525     }
526   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nred,
527                            XSGIVC_MComponentRed, info->red2);
528   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->ngreen,
529                            XSGIVC_MComponentGreen, info->green2);
530   XSGIvcStoreGammaColors16(dpy, screen, info->gamma_map, info->nblue,
531                            XSGIVC_MComponentBlue, info->blue2);
532   XSync(dpy, False);
533 }
534
535 #endif /* HAVE_SGI_VC_EXTENSION */
536
537
538
539 \f
540 #if 0
541 #include "screenhack.h"
542
543 char *progclass = "foo";
544 char *defaults [] = {
545   0
546 };
547
548 XrmOptionDescRec options [] = {0};
549 int options_size = 0;
550
551 void
552 screenhack (dpy, w)
553      Display *dpy;
554      Window w;
555 {
556   int seconds = 3;
557   int ticks = 20;
558   int delay = 1;
559
560   while (1)
561     {
562       XSync (dpy, False);
563
564       fprintf(stderr,"out..."); fflush(stderr);
565       fade_screens (dpy, 0, seconds, ticks, True);
566       fprintf(stderr, "done.\n"); fflush(stderr);
567
568       if (delay) sleep (delay);
569
570       fprintf(stderr,"in..."); fflush(stderr);
571       fade_screens (dpy, 0, seconds, ticks, False);
572       fprintf(stderr, "done.\n"); fflush(stderr);
573
574       if (delay) sleep (delay);
575     }
576 }
577
578 #endif