ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-2.07.tar.gz
[xscreensaver] / utils / grabscreen.c
1 /* xscreensaver, Copyright (c) 1992, 1993, 1994, 1997
2  *  Jamie Zawinski <jwz@netscape.com>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* This file contains code for grabbing an image of the screen to hack its
14    bits.  This is a little tricky, since doing this involves the need to tell
15    the difference between drawing on the actual root window, and on the fake
16    root window used by the screensaver, since at this level the illusion 
17    breaks down...
18  */
19
20 #include "utils.h"
21
22 #include <X11/Xatom.h>
23 #include <X11/Xutil.h>
24
25 #ifdef HAVE_XMU
26 # ifndef VMS
27 #  include <X11/Xmu/WinUtil.h>
28 # else  /* VMS */
29 #  include <Xmu/WinUtil.h>
30 # endif /* VMS */
31 #endif
32
33 #include "usleep.h"
34 #include "colors.h"
35 #include "grabscreen.h"
36
37 #include "vroot.h"
38 #undef RootWindowOfScreen
39 #undef RootWindow
40 #undef DefaultRootWindow
41
42
43 #ifdef HAVE_READ_DISPLAY_EXTENSION
44 # include "visual.h"
45 # include <X11/extensions/readdisplay.h>
46   static Bool read_display (Screen *, Window, Pixmap, Bool);
47 #endif /* HAVE_READ_DISPLAY_EXTENSION */
48
49
50 static void copy_default_colormap_contents (Screen *, Colormap, Visual *);
51
52
53 static Bool
54 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
55 {
56   return (event->xany.type == MapNotify &&
57           event->xvisibility.window == (Window) window);
58 }
59
60 #ifdef DEBUG
61 extern char *progname;
62 #endif /* DEBUG */
63
64
65 static void
66 raise_window(Display *dpy, Window window, Bool dont_wait)
67 {
68 #ifdef DEBUG
69   fprintf(stderr, "%s: raising window 0x%08lX (%s)\n",
70           progname, (unsigned long) window,
71           (dont_wait ? "not waiting" : "waiting"));
72 #endif /* DEBUG */
73
74   if (! dont_wait)
75     {
76       XWindowAttributes xgwa;
77       XSizeHints hints;
78       long supplied = 0;
79       memset(&hints, 0, sizeof(hints));
80       XGetWMNormalHints(dpy, window, &hints, &supplied);
81       XGetWindowAttributes (dpy, window, &xgwa);
82       hints.x = xgwa.x;
83       hints.y = xgwa.y;
84       hints.width = xgwa.width;
85       hints.height = xgwa.height;
86       hints.flags |= (PPosition|USPosition|PSize|USSize);
87       XSetWMNormalHints(dpy, window, &hints);
88     }
89
90   XMapRaised(dpy, window);
91
92   if (! dont_wait)
93     {
94       XEvent event;
95       XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
96       XSync (dpy, True);
97     }
98 }
99
100
101 static Bool
102 xscreensaver_window_p (Display *dpy, Window window)
103 {
104   Atom type;
105   int format;
106   unsigned long nitems, bytesafter;
107   char *version;
108   if (XGetWindowProperty (dpy, window,
109                           XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
110                           0, 1, False, XA_STRING,
111                           &type, &format, &nitems, &bytesafter,
112                           (unsigned char **) &version)
113       == Success
114       && type != None)
115     return True;
116   return False;
117 }
118
119
120
121 static XErrorHandler old_ehandler = 0;
122 static int
123 BadWindow_ehandler (Display *dpy, XErrorEvent *error)
124 {
125   if (error->error_code == BadWindow || error->error_code == BadDrawable)
126     return 0;
127   else if (!old_ehandler)
128     abort();
129   else
130     return (*old_ehandler) (dpy, error);
131 }
132
133
134 /* Install the colormaps of all visible windows, deepest first.
135    This should leave the colormaps of the topmost windows installed
136    (if only N colormaps can be installed at a time, then only the
137    topmost N windows will be shown in the right colors.)
138  */
139 static void
140 install_screen_colormaps (Screen *screen)
141 {
142   int i;
143   Display *dpy = DisplayOfScreen (screen);
144   Window vroot, real_root;
145   Window parent, *kids = 0;
146   unsigned int nkids = 0;
147
148   XSync (dpy, False);
149   old_ehandler = XSetErrorHandler (BadWindow_ehandler);
150
151   vroot = VirtualRootWindowOfScreen (screen);
152   if (XQueryTree (dpy, vroot, &real_root, &parent, &kids, &nkids))
153     for (i = 0; i < nkids; i++)
154       {
155         XWindowAttributes xgwa;
156         Window client;
157 #ifdef HAVE_XMU
158         /* #### need to put XmuClientWindow() in xmu.c, sigh... */
159         if (! (client = XmuClientWindow (dpy, kids[i])))
160 #endif
161           client = kids[i];
162         xgwa.colormap = 0;
163         XGetWindowAttributes (dpy, client, &xgwa);
164         if (xgwa.colormap && xgwa.map_state == IsViewable)
165           XInstallColormap (dpy, xgwa.colormap);
166       }
167   XInstallColormap (dpy, DefaultColormapOfScreen (screen));
168   XSync (dpy, False);
169   XSetErrorHandler (old_ehandler);
170   XSync (dpy, False);
171
172   if (kids)
173     XFree ((char *) kids);
174 }
175
176
177 void
178 grab_screen_image (Screen *screen, Window window)
179 {
180   Display *dpy = DisplayOfScreen (screen);
181   XWindowAttributes xgwa;
182   Window real_root = XRootWindowOfScreen (screen);  /* not vroot */
183   Bool root_p = (window == real_root);
184   Bool saver_p = xscreensaver_window_p (dpy, window);
185   Bool grab_mouse_p = False;
186   int unmap_time = 0;
187
188   if (saver_p)
189     /* I think this is redundant, but just to be safe... */
190     root_p = False;
191
192   if (saver_p)
193     /* The only time grabbing the mouse is important is if this program
194        is being run while the saver is locking the screen. */
195     grab_mouse_p = True;
196
197   if (!root_p)
198     {
199       if (saver_p)
200         unmap_time = 2500000;  /* 2 1/2 seconds */
201       else
202         unmap_time =  660000;  /* 2/3rd second */
203     }
204
205 #ifdef DEBUG
206   fprintf(stderr,
207           "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
208           progname, (unsigned long) window,
209           root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0);
210   {
211     XWindowAttributes xgwa2;
212     XGetWindowAttributes (dpy, window, &xgwa2);
213     fprintf(stderr, "%s: ", progname);
214     describe_visual(stderr, screen, xgwa2.visual);
215   }
216 #endif /* DEBUG */
217
218   if (!root_p)
219     XSetWindowBackgroundPixmap (dpy, window, None);
220
221   if (grab_mouse_p)
222     {
223       /* prevent random viewer of the screen saver (locker) from messing
224          with windows.   We don't check whether it succeeded, because what
225          are our options, really... */
226       XGrabPointer (dpy, real_root, True, ButtonPressMask|ButtonReleaseMask,
227                     GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
228       XGrabKeyboard (dpy, real_root, True, GrabModeSync, GrabModeAsync,
229                      CurrentTime);
230     }
231
232   if (unmap_time > 0)
233     {
234       XUnmapWindow (dpy, window);
235       install_screen_colormaps (screen);
236       XSync (dpy, True);
237       usleep(unmap_time); /* wait for everyone to swap in and handle exposes */
238     }
239
240   XGetWindowAttributes (dpy, window, &xgwa);
241
242   if (!root_p)
243     {
244 #ifdef HAVE_READ_DISPLAY_EXTENSION
245       if (! read_display(screen, window, 0, saver_p))
246 #endif /* HAVE_READ_DISPLAY_EXTENSION */
247         {
248 #if defined(HAVE_READ_DISPLAY_EXTENSION) && defined(DEBUG)
249           fprintf(stderr, "%s: read_display() failed\n", progname);
250 #endif /* DEBUG */
251           copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
252           raise_window(dpy, window, saver_p);
253         }
254     }
255   else  /* root_p */
256     {
257       Pixmap pixmap;
258       XWindowAttributes xgwa;
259       XGetWindowAttributes(dpy, window, &xgwa);
260       pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth);
261
262 #ifdef HAVE_READ_DISPLAY_EXTENSION
263       if (! read_display(screen, window, pixmap, True))
264 #endif
265         {
266           Window real_root = XRootWindowOfScreen (xgwa.screen); /* not vroot */
267           XGCValues gcv;
268           GC gc;
269
270 #if defined(HAVE_READ_DISPLAY_EXTENSION) && defined(DEBUG)
271           fprintf(stderr, "%s: read_display() failed\n", progname);
272 #endif /* DEBUG */
273
274           copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual);
275
276           gcv.function = GXcopy;
277           gcv.subwindow_mode = IncludeInferiors;
278           gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv);
279           XCopyArea (dpy, real_root, pixmap, gc,
280                      xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0);
281           XFreeGC (dpy, gc);
282         }
283       XSetWindowBackgroundPixmap (dpy, window, pixmap);
284       XFreePixmap (dpy, pixmap);
285     }
286
287   if (grab_mouse_p)
288     {
289       XUngrabPointer (dpy, CurrentTime);
290       XUngrabKeyboard (dpy, CurrentTime);
291     }
292
293   XSync (dpy, True);
294 }
295
296
297 /* When we are grabbing and manipulating a screen image, it's important that
298    we use the same colormap it originally had.  So, if the screensaver was
299    started with -install, we need to copy the contents of the default colormap
300    into the screensaver's colormap.
301  */
302 static void
303 copy_default_colormap_contents (Screen *screen,
304                                 Colormap to_cmap,
305                                 Visual *to_visual)
306 {
307   Display *dpy = DisplayOfScreen (screen);
308   Visual *from_visual = DefaultVisualOfScreen (screen);
309   Colormap from_cmap = XDefaultColormapOfScreen (screen);
310
311   XColor *old_colors, *new_colors;
312   unsigned long *pixels;
313   XVisualInfo vi_in, *vi_out;
314   int out_count;
315   int from_cells, to_cells, max_cells, got_cells;
316   int i;
317
318   if (from_cmap == to_cmap)
319     return;
320
321   vi_in.screen = XScreenNumberOfScreen (screen);
322   vi_in.visualid = XVisualIDFromVisual (from_visual);
323   vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
324                            &vi_in, &out_count);
325   if (! vi_out) abort ();
326   from_cells = vi_out [0].colormap_size;
327   XFree ((char *) vi_out);
328
329   vi_in.screen = XScreenNumberOfScreen (screen);
330   vi_in.visualid = XVisualIDFromVisual (to_visual);
331   vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
332                            &vi_in, &out_count);
333   if (! vi_out) abort ();
334   to_cells = vi_out [0].colormap_size;
335   XFree ((char *) vi_out);
336
337   max_cells = (from_cells > to_cells ? to_cells : from_cells);
338
339   old_colors = (XColor *) calloc (sizeof (XColor), max_cells);
340   new_colors = (XColor *) calloc (sizeof (XColor), max_cells);
341   pixels = (unsigned long *) calloc (sizeof (unsigned long), max_cells);
342   for (i = 0; i < max_cells; i++)
343     old_colors[i].pixel = i;
344   XQueryColors (dpy, from_cmap, old_colors, max_cells);
345
346   got_cells = max_cells;
347   allocate_writable_colors (dpy, to_cmap, pixels, &got_cells);
348   XStoreColors (dpy, to_cmap, old_colors, got_cells);
349
350 #ifdef DEBUG
351   fprintf(stderr, "%s: installing copy of default colormap\n", progname);
352 #endif /* DEBUG */
353
354   free (old_colors);
355   free (new_colors);
356   free (pixels);
357 }
358
359
360 \f
361 /* The SGI ReadDisplay extension.
362    This extension lets you get back a 24-bit image of the screen, taking into
363    account the colors with which all windows are *currently* displayed, even
364    if those windows have different visuals.  Without this extension, presence
365    of windows with different visuals or colormaps will result in technicolor
366    when one tries to grab the screen image.
367  */
368
369 #ifdef HAVE_READ_DISPLAY_EXTENSION
370
371 static void make_cubic_colormap (Screen *, Window, Visual *);
372
373 static Bool
374 read_display (Screen *screen, Window window, Pixmap into_pixmap,
375               Bool dont_wait)
376 {
377   Display *dpy = DisplayOfScreen (screen);
378   XWindowAttributes xgwa;
379   int rd_event_base = 0;
380   int rd_error_base = 0;
381   unsigned long hints = 0;
382   XImage *image = 0;
383   XGCValues gcv;
384   int class;
385   GC gc;
386   Bool install_p = False;
387
388   /* Check to see if the server supports the extension, and bug out if not.
389    */
390   if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base))
391     return False;
392
393   /* If this isn't a visual we know how to handle, bug out.  We handle:
394       = TrueColor in depths 8, 12, 16, and 32;
395       = PseudoColor and DirectColor in depths 8 and 12.
396    */
397   XGetWindowAttributes(dpy, window, &xgwa);
398   class = visual_class (screen, xgwa.visual);
399   if (class == TrueColor)
400     {
401       if (xgwa.depth != 8  && xgwa.depth != 12 && xgwa.depth != 16 &&
402           xgwa.depth != 24 && xgwa.depth != 32)
403         return False;
404     }
405   else if (class == PseudoColor || class == DirectColor)
406     {
407       if (xgwa.depth != 8 && xgwa.depth != 12)
408         return False;
409       else
410         /* Install a colormap that makes this visual behave like
411            a TrueColor visual of the same depth. */
412         install_p = True;
413     }
414
415
416   /* Try and read the screen.
417    */
418   hints = (XRD_TRANSPARENT | XRD_READ_POINTER);
419   image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height,
420                         hints, &hints);
421   if (!image)
422     return False;
423   if (!image->data)
424     {
425       XDestroyImage(image);
426       return False;
427     }
428
429   /* Uh, this can't be right, can it?  But it's necessary.  X sucks.
430      If the visual is of depth 24, but the image came back as depth 32,
431      hack it to be 24 lest we get a BadMatch from XPutImage.  (I presume
432      I'm expected to look at the server's pixmap formats or some such
433      nonsense... but fuck it.
434    */
435   if (xgwa.depth == 24 && image->depth == 32)
436     image->depth = 24;
437
438   /* If the visual of the window/pixmap into which we're going to draw is
439      less deep than the screen itself, then we need to convert the grabbed bits
440      to match the depth by clipping off the less significant bit-planes of each
441      color component.
442    */
443   if (image->depth > xgwa.depth)
444     {
445       int x, y;
446       /* We use the same image->data in both images -- that's ok, because
447          since we're reading from B and writing to A, and B uses more bytes
448          per pixel than A, the write pointer won't overrun the read pointer.
449        */
450       XImage *image2 = XCreateImage (dpy, xgwa.visual, xgwa.depth,
451                                      ZPixmap, 0, image->data,
452                                      xgwa.width, xgwa.height,
453                                      8, 0);
454       if (!image2)
455         return False;
456
457 #ifdef DEBUG
458       fprintf(stderr, "%s: converting from depth %d to depth %d\n",
459               progname, image->depth, xgwa.depth);
460 #endif /* DEBUG */
461
462       for (y = 0; y < image->height; y++)
463         for (x = 0; x < image->width; x++)
464           {
465             /* #### really these shift values should be determined from the
466                mask values -- but that's a pain in the ass, and anyway,
467                this is an SGI-specific extension so hardcoding assumptions
468                about the SGI server's behavior isn't *too* heinous... */
469             unsigned long pixel = XGetPixel(image, x, y);
470             unsigned int r = (pixel & image->red_mask);
471             unsigned int g = (pixel & image->green_mask) >> 8;
472             unsigned int b = (pixel & image->blue_mask) >> 16;
473
474             if (xgwa.depth == 8)
475               pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6));
476             else if (xgwa.depth == 12)
477               pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8));
478             else if (xgwa.depth == 16)
479               pixel = ((r >> 3) | ((g >> 3) << 5) | ((b >> 3) << 10));
480             else
481               abort();
482
483             XPutPixel(image2, x, y, pixel);
484           }
485       image->data = 0;
486       XDestroyImage(image);
487       image = image2;
488     }
489
490
491   /* Now actually put the bits into the window or pixmap -- note the design
492      bogosity of this extension, where we've been forced to take 24 bit data
493      from the server to the client, and then push it back from the client to
494      the server, *without alteration*.  We should have just been able to tell
495      the server, "put a screen image in this drawable", instead of having to
496      go through the intermediate step of converting it to an Image.  Geez.
497      (Assuming that the window is of screen depth; we happen to handle less
498      deep windows, but that's beside the point.)
499    */
500   gcv.function = GXcopy;
501   gc = XCreateGC (dpy, window, GCFunction, &gcv);
502
503   if (into_pixmap)
504     {
505       gcv.function = GXcopy;
506       gc = XCreateGC (dpy, into_pixmap, GCFunction, &gcv);
507       XPutImage (dpy, into_pixmap, gc, image, 0, 0, 0, 0,
508                  xgwa.width, xgwa.height);
509     }
510   else
511     {
512       gcv.function = GXcopy;
513       gc = XCreateGC (dpy, window, GCFunction, &gcv);
514
515       /* Ok, now we'll be needing that window on the screen... */
516       raise_window(dpy, window, dont_wait);
517
518       /* Plop down the bits... */
519       XPutImage (dpy, window, gc, image, 0, 0, 0, 0, xgwa.width, xgwa.height);
520     }
521   XFreeGC (dpy, gc);
522
523   if (image->data)
524     {
525       free(image->data);
526       image->data = 0;
527     }
528   XDestroyImage(image);
529
530   if (install_p)
531     make_cubic_colormap (screen, window, xgwa.visual);
532
533   return True;
534 }
535
536 static void
537 make_cubic_colormap (Screen *screen, Window window, Visual *visual)
538 {
539   Display *dpy = DisplayOfScreen (screen);
540   Colormap cmap = XCreateColormap(dpy, window, visual, AllocAll);
541   int nr, ng, nb, cells;
542   int r, g, b;
543   int depth;
544   XColor colors[4097];
545   int i;
546
547   depth = visual_depth(screen, visual);
548   switch (depth)
549     {
550     case 8:  nr = 3; ng = 3; nb = 2; cells = 256;  break;
551     case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break;
552     default: abort(); break;
553     }
554
555   memset(colors, 0, sizeof(colors));
556   for (i = 0; i < cells; i++)
557     {
558       colors[i].flags = DoRed|DoGreen|DoBlue;
559       colors[i].red = colors[i].green = colors[i].blue = 0;
560     }
561
562   for (r = 0; r < (1 << nr); r++)
563     for (g = 0; g < (1 << ng); g++)
564       for (b = 0; b < (1 << nb); b++)
565         {
566           i = (r | (g << nr) | (b << (nr + ng)));
567           colors[i].pixel = i;
568           if (depth == 8)
569             {
570               colors[i].red   = ((r << 13) | (r << 10) | (r << 7) |
571                                  (r <<  4) | (r <<  1));
572               colors[i].green = ((g << 13) | (g << 10) | (g << 7) |
573                                  (g <<  4) | (g <<  1));
574               colors[i].blue  = ((b << 14) | (b << 12) | (b << 10) |
575                                  (b <<  8) | (b <<  6) | (b <<  4) |
576                                  (b <<  2) | b);
577             }
578           else
579             {
580               colors[i].red   = (r << 12) | (r << 8) | (r << 4) | r;
581               colors[i].green = (g << 12) | (g << 8) | (g << 4) | g;
582               colors[i].blue  = (b << 12) | (b << 8) | (b << 4) | b;
583             }
584         }
585
586 #ifdef DEBUG
587   fprintf(stderr, "%s: installing cubic colormap\n", progname);
588 #endif /* DEBUG */
589
590   XStoreColors (dpy, cmap, colors, cells);
591   XSetWindowColormap (dpy, window, cmap);
592
593   /* Gag, install the colormap.
594      This is definitely right in the `if xscreensaver_window_p' case, since
595      it will never get installed otherwise.  But, if we don't do it
596      unconditionally, then the new colormap won't get installed until the
597      window (re-)gains focus.  It's generally very antisocial to install
598      the colormap of a non-OverrideRedirect window (that task belongs to
599      the WM) and if we were being kosher, we would only install this cmap
600      if the old cmap was already installed (or perhaps, if the window had
601      focus.)  But, since this extension only exists on SGIs, and since SGIs
602      can handle four colormaps at once, let's go ahead and install it all
603      the time, so that even if the window pops up and has never had focus,
604      it will still display in the proper colors.
605    */
606   XInstallColormap (dpy, cmap);
607 }
608
609
610 #endif /* HAVE_READ_DISPLAY_EXTENSION */