Commit | Line | Data |
---|---|---|
3144ee8a AT |
1 | /* xscreensaver, Copyright (c) 1992-2016 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 | /* This file contains code for grabbing an image of the screen to hack its | |
13 | bits. This is a little tricky, since doing this involves the need to tell | |
14 | the difference between drawing on the actual root window, and on the fake | |
15 | root window used by the screensaver, since at this level the illusion | |
16 | breaks down... | |
17 | ||
18 | The hacks themselves use utils/grabclient.c to invoke the | |
19 | "xscreensaver-getimage" program as a sub-process. | |
20 | ||
21 | On "real" X11 systems: | |
22 | ||
23 | "driver/xscreensaver-getimage" runs the code in this file to grab | |
24 | the X11 root window image as a Pixmap. | |
25 | ||
26 | On MacOS systems running X11, which nobody does any more: | |
27 | ||
28 | "driver/xscreensaver-getimage" runs the Perl script | |
29 | "driver/xscreensaver-getimage-desktop", which in turn runs the MacOS | |
30 | program "/usr/sbin/screencapture" to get the Mac desktop image as a | |
31 | PNG file. | |
32 | ||
33 | On MacOS systems running the native Cocoa build, or on iOS or Android | |
34 | systems: | |
35 | ||
36 | "driver/xscreensaver-getimage" is not used. Instead, each saver's | |
37 | "utils/grabclient.c" links against "OSX/grabclient-osx.m", | |
38 | "OSX/grabclient-ios.m" or "jwxyz/jwxyz-android.c" to grab | |
39 | screenshots directly without invoking a sub-process to do it. | |
40 | ||
41 | See the comment at the top of utils/grabclient.c for a more detailed | |
42 | explanation. | |
43 | */ | |
44 | ||
45 | #include "utils.h" | |
46 | #include "yarandom.h" | |
47 | ||
48 | #include <X11/Xatom.h> | |
49 | #include <X11/Xutil.h> | |
50 | ||
51 | #ifdef HAVE_XMU | |
52 | # ifndef VMS | |
53 | # include <X11/Xmu/WinUtil.h> | |
54 | # else /* VMS */ | |
55 | # include <Xmu/WinUtil.h> | |
56 | # endif /* VMS */ | |
57 | #endif | |
58 | ||
59 | #include "usleep.h" | |
60 | #include "colors.h" | |
61 | #include "grabscreen.h" | |
62 | #include "visual.h" | |
63 | #include "resources.h" | |
64 | ||
65 | #include "vroot.h" | |
66 | #undef RootWindowOfScreen | |
67 | #undef RootWindow | |
68 | #undef DefaultRootWindow | |
69 | ||
70 | ||
71 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
72 | # include <X11/extensions/readdisplay.h> | |
73 | static Bool read_display (Screen *, Window, Pixmap, Bool); | |
74 | #endif /* HAVE_READ_DISPLAY_EXTENSION */ | |
75 | ||
76 | ||
77 | static void copy_default_colormap_contents (Screen *, Colormap, Visual *); | |
78 | ||
79 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
80 | static void allocate_cubic_colormap (Screen *, Window, Visual *); | |
81 | void remap_image (Screen *, Window, Colormap, XImage *); | |
82 | #endif | |
83 | ||
84 | ||
85 | static Bool | |
86 | MapNotify_event_p (Display *dpy, XEvent *event, XPointer window) | |
87 | { | |
88 | return (event->xany.type == MapNotify && | |
89 | event->xvisibility.window == (Window) window); | |
90 | } | |
91 | ||
92 | extern char *progname; | |
93 | Bool grab_verbose_p = False; | |
94 | ||
95 | void | |
96 | grabscreen_verbose(void) | |
97 | { | |
98 | grab_verbose_p = True; | |
99 | } | |
100 | ||
101 | ||
102 | static void | |
103 | raise_window(Display *dpy, Window window, Bool dont_wait) | |
104 | { | |
105 | if (grab_verbose_p) | |
106 | fprintf(stderr, "%s: raising window 0x%08lX (%s)\n", | |
107 | progname, (unsigned long) window, | |
108 | (dont_wait ? "not waiting" : "waiting")); | |
109 | ||
110 | if (! dont_wait) | |
111 | { | |
112 | XWindowAttributes xgwa; | |
113 | XSizeHints hints; | |
114 | long supplied = 0; | |
115 | memset(&hints, 0, sizeof(hints)); | |
116 | XGetWMNormalHints(dpy, window, &hints, &supplied); | |
117 | XGetWindowAttributes (dpy, window, &xgwa); | |
118 | hints.x = xgwa.x; | |
119 | hints.y = xgwa.y; | |
120 | hints.width = xgwa.width; | |
121 | hints.height = xgwa.height; | |
122 | hints.flags |= (PPosition|USPosition|PSize|USSize); | |
123 | XSetWMNormalHints(dpy, window, &hints); | |
124 | ||
125 | XSelectInput (dpy, window, (xgwa.your_event_mask | StructureNotifyMask)); | |
126 | } | |
127 | ||
128 | XMapRaised(dpy, window); | |
129 | ||
130 | if (! dont_wait) | |
131 | { | |
132 | XEvent event; | |
133 | XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window); | |
134 | XSync (dpy, True); | |
135 | } | |
136 | } | |
137 | ||
138 | ||
139 | static Bool | |
140 | xscreensaver_window_p (Display *dpy, Window window) | |
141 | { | |
142 | Atom type; | |
143 | int format; | |
144 | unsigned long nitems, bytesafter; | |
145 | unsigned char *version; | |
146 | if (XGetWindowProperty (dpy, window, | |
147 | XInternAtom (dpy, "_SCREENSAVER_VERSION", False), | |
148 | 0, 1, False, XA_STRING, | |
149 | &type, &format, &nitems, &bytesafter, | |
150 | &version) | |
151 | == Success | |
152 | && type != None) | |
153 | return True; | |
154 | return False; | |
155 | } | |
156 | ||
157 | ||
158 | ||
159 | /* Whether the given window is: | |
160 | - the real root window; | |
161 | - a direct child of the root window; | |
162 | - a direct child of the window manager's decorations. | |
163 | */ | |
164 | Bool | |
165 | top_level_window_p (Screen *screen, Window window) | |
166 | { | |
167 | Display *dpy = DisplayOfScreen (screen); | |
168 | Window root, parent, *kids; | |
169 | unsigned int nkids; | |
170 | ||
171 | if (!XQueryTree (dpy, window, &root, &parent, &kids, &nkids)) | |
172 | return False; | |
173 | ||
174 | if (window == root) | |
175 | return True; | |
176 | ||
177 | /* If our direct parent is the real root window, then yes. */ | |
178 | if (parent == root) | |
179 | return True; | |
180 | else | |
181 | { | |
182 | Atom type = None; | |
183 | int format; | |
184 | unsigned long nitems, bytesafter; | |
185 | unsigned char *data; | |
186 | ||
187 | /* If our direct parent has the WM_STATE property, then it is a | |
188 | window manager decoration -- yes. | |
189 | */ | |
190 | if (XGetWindowProperty (dpy, window, | |
191 | XInternAtom (dpy, "WM_STATE", True), | |
192 | 0, 0, False, AnyPropertyType, | |
193 | &type, &format, &nitems, &bytesafter, | |
194 | (unsigned char **) &data) | |
195 | == Success | |
196 | && type != None) | |
197 | return True; | |
198 | } | |
199 | ||
200 | /* Else, no. We're deep in a tree somewhere. | |
201 | */ | |
202 | return False; | |
203 | } | |
204 | ||
205 | ||
206 | static Bool error_handler_hit_p = False; | |
207 | static XErrorHandler old_ehandler = 0; | |
208 | static int | |
209 | BadWindow_ehandler (Display *dpy, XErrorEvent *error) | |
210 | { | |
211 | error_handler_hit_p = True; | |
212 | if (error->error_code == BadWindow || error->error_code == BadDrawable) | |
213 | return 0; | |
214 | else if (!old_ehandler) | |
215 | { | |
216 | abort(); | |
217 | return 0; | |
218 | } | |
219 | else | |
220 | return (*old_ehandler) (dpy, error); | |
221 | } | |
222 | ||
223 | ||
224 | /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode | |
225 | on a window whose depth is not the maximal depth of the screen? Or | |
226 | something. Anyway, things don't work unless we: use SubwindowMode for | |
227 | the real root window (or a legitimate virtual root window); but do not | |
228 | use SubwindowMode for the xscreensaver window. I make no attempt to | |
229 | explain. | |
230 | */ | |
231 | Bool | |
232 | use_subwindow_mode_p(Screen *screen, Window window) | |
233 | { | |
234 | if (window != VirtualRootWindowOfScreen(screen)) | |
235 | return False; | |
236 | else if (xscreensaver_window_p(DisplayOfScreen(screen), window)) | |
237 | return False; | |
238 | else | |
239 | return True; | |
240 | } | |
241 | ||
242 | ||
243 | /* Install the colormaps of all visible windows, deepest first. | |
244 | This should leave the colormaps of the topmost windows installed | |
245 | (if only N colormaps can be installed at a time, then only the | |
246 | topmost N windows will be shown in the right colors.) | |
247 | */ | |
248 | static void | |
249 | install_screen_colormaps (Screen *screen) | |
250 | { | |
251 | unsigned int i; | |
252 | Display *dpy = DisplayOfScreen (screen); | |
253 | Window real_root; | |
254 | Window parent, *kids = 0; | |
255 | unsigned int nkids = 0; | |
256 | ||
257 | XSync (dpy, False); | |
258 | old_ehandler = XSetErrorHandler (BadWindow_ehandler); | |
259 | error_handler_hit_p = False; | |
260 | ||
261 | real_root = XRootWindowOfScreen (screen); /* not vroot */ | |
262 | if (XQueryTree (dpy, real_root, &real_root, &parent, &kids, &nkids)) | |
263 | for (i = 0; i < nkids; i++) | |
264 | { | |
265 | XWindowAttributes xgwa; | |
266 | Window client; | |
267 | #ifdef HAVE_XMU | |
268 | /* #### need to put XmuClientWindow() in xmu.c, sigh... */ | |
269 | if (! (client = XmuClientWindow (dpy, kids[i]))) | |
270 | #endif | |
271 | client = kids[i]; | |
272 | xgwa.colormap = 0; | |
273 | XGetWindowAttributes (dpy, client, &xgwa); | |
274 | if (xgwa.colormap && xgwa.map_state == IsViewable) | |
275 | XInstallColormap (dpy, xgwa.colormap); | |
276 | } | |
277 | XInstallColormap (dpy, DefaultColormapOfScreen (screen)); | |
278 | XSync (dpy, False); | |
279 | XSetErrorHandler (old_ehandler); | |
280 | XSync (dpy, False); | |
281 | ||
282 | if (kids) | |
283 | XFree ((char *) kids); | |
284 | } | |
285 | ||
286 | ||
287 | void | |
288 | grab_screen_image_internal (Screen *screen, Window window) | |
289 | { | |
290 | Display *dpy = DisplayOfScreen (screen); | |
291 | XWindowAttributes xgwa; | |
292 | Window real_root; | |
293 | Bool root_p; | |
294 | Bool saver_p; | |
295 | Bool grab_mouse_p = False; | |
296 | int unmap_time = 0; | |
297 | ||
298 | real_root = XRootWindowOfScreen (screen); /* not vroot */ | |
299 | root_p = (window == real_root); | |
300 | saver_p = xscreensaver_window_p (dpy, window); | |
301 | ||
302 | XGetWindowAttributes (dpy, window, &xgwa); | |
303 | screen = xgwa.screen; | |
304 | ||
305 | if (saver_p) | |
306 | /* I think this is redundant, but just to be safe... */ | |
307 | root_p = False; | |
308 | ||
309 | if (saver_p) | |
310 | /* The only time grabbing the mouse is important is if this program | |
311 | is being run while the saver is locking the screen. */ | |
312 | grab_mouse_p = True; | |
313 | ||
314 | if (!root_p) | |
315 | { | |
316 | double unmap = 0; | |
317 | if (saver_p) | |
318 | { | |
319 | unmap = get_float_resource(dpy, "grabRootDelay", "Seconds"); | |
320 | if (unmap <= 0.00001 || unmap > 20) unmap = 2.5; | |
321 | } | |
322 | else | |
323 | { | |
324 | unmap = get_float_resource(dpy, "grabWindowDelay", "Seconds"); | |
325 | if (unmap <= 0.00001 || unmap > 20) unmap = 0.66; | |
326 | } | |
327 | unmap_time = unmap * 100000; | |
328 | } | |
329 | ||
330 | if (grab_verbose_p) | |
331 | { | |
332 | fprintf(stderr, | |
333 | "\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n", | |
334 | progname, (unsigned long) window, | |
335 | root_p, saver_p, grab_mouse_p, ((double)unmap_time)/1000000.0); | |
336 | ||
337 | fprintf(stderr, "%s: ", progname); | |
338 | describe_visual(stderr, screen, xgwa.visual, False); | |
339 | fprintf (stderr, "\n"); | |
340 | } | |
341 | ||
342 | ||
343 | if (!root_p && !top_level_window_p (screen, window)) | |
344 | { | |
345 | if (grab_verbose_p) | |
346 | fprintf (stderr, "%s: not a top-level window: 0x%08lX: not grabbing\n", | |
347 | progname, (unsigned long) window); | |
348 | return; | |
349 | } | |
350 | ||
351 | ||
352 | if (!root_p) | |
353 | XSetWindowBackgroundPixmap (dpy, window, None); | |
354 | ||
355 | if (grab_mouse_p) | |
356 | { | |
357 | /* prevent random viewer of the screen saver (locker) from messing | |
358 | with windows. We don't check whether it succeeded, because what | |
359 | are our options, really... */ | |
360 | XGrabPointer (dpy, real_root, True, ButtonPressMask|ButtonReleaseMask, | |
361 | GrabModeAsync, GrabModeAsync, None, None, CurrentTime); | |
362 | XGrabKeyboard (dpy, real_root, True, GrabModeSync, GrabModeAsync, | |
363 | CurrentTime); | |
364 | } | |
365 | ||
366 | if (unmap_time > 0) | |
367 | { | |
368 | XUnmapWindow (dpy, window); | |
369 | install_screen_colormaps (screen); | |
370 | XSync (dpy, True); | |
371 | usleep(unmap_time); /* wait for everyone to swap in and handle exposes */ | |
372 | } | |
373 | ||
374 | if (!root_p) | |
375 | { | |
376 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
377 | if (! read_display(screen, window, 0, saver_p)) | |
378 | #endif /* HAVE_READ_DISPLAY_EXTENSION */ | |
379 | { | |
380 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
381 | if (grab_verbose_p) | |
382 | fprintf(stderr, "%s: read_display() failed\n", progname); | |
383 | #endif /* HAVE_READ_DISPLAY_EXTENSION */ | |
384 | ||
385 | copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual); | |
386 | raise_window(dpy, window, saver_p); | |
387 | ||
388 | /* Generally it's bad news to call XInstallColormap() explicitly, | |
389 | but this file does a lot of sleazy stuff already... This is to | |
390 | make sure that the window's colormap is installed, even in the | |
391 | case where the window is OverrideRedirect. */ | |
392 | if (xgwa.colormap) XInstallColormap (dpy, xgwa.colormap); | |
393 | XSync (dpy, False); | |
394 | } | |
395 | } | |
396 | else /* root_p */ | |
397 | { | |
398 | Pixmap pixmap; | |
399 | pixmap = XCreatePixmap(dpy, window, xgwa.width, xgwa.height, xgwa.depth); | |
400 | ||
401 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
402 | if (! read_display(screen, window, pixmap, True)) | |
403 | #endif | |
404 | { | |
405 | Window real_root = XRootWindowOfScreen (screen); /* not vroot */ | |
406 | XGCValues gcv; | |
407 | GC gc; | |
408 | ||
409 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
410 | if (grab_verbose_p) | |
411 | fprintf(stderr, "%s: read_display() failed\n", progname); | |
412 | #endif /* HAVE_READ_DISPLAY_EXTENSION */ | |
413 | ||
414 | copy_default_colormap_contents (screen, xgwa.colormap, xgwa.visual); | |
415 | ||
416 | gcv.function = GXcopy; | |
417 | gcv.subwindow_mode = IncludeInferiors; | |
418 | gc = XCreateGC (dpy, window, GCFunction | GCSubwindowMode, &gcv); | |
419 | XCopyArea (dpy, real_root, pixmap, gc, | |
420 | xgwa.x, xgwa.y, xgwa.width, xgwa.height, 0, 0); | |
421 | XFreeGC (dpy, gc); | |
422 | } | |
423 | XSetWindowBackgroundPixmap (dpy, window, pixmap); | |
424 | XFreePixmap (dpy, pixmap); | |
425 | } | |
426 | ||
427 | if (grab_verbose_p) | |
428 | fprintf (stderr, "%s: grabbed %d bit screen image to %swindow.\n", | |
429 | progname, xgwa.depth, | |
430 | (root_p ? "real root " : "")); | |
431 | ||
432 | if (grab_mouse_p) | |
433 | { | |
434 | XUngrabPointer (dpy, CurrentTime); | |
435 | XUngrabKeyboard (dpy, CurrentTime); | |
436 | } | |
437 | ||
438 | XSync (dpy, True); | |
439 | } | |
440 | ||
441 | ||
442 | /* When we are grabbing and manipulating a screen image, it's important that | |
443 | we use the same colormap it originally had. So, if the screensaver was | |
444 | started with -install, we need to copy the contents of the default colormap | |
445 | into the screensaver's colormap. | |
446 | */ | |
447 | static void | |
448 | copy_default_colormap_contents (Screen *screen, | |
449 | Colormap to_cmap, | |
450 | Visual *to_visual) | |
451 | { | |
452 | Display *dpy = DisplayOfScreen (screen); | |
453 | Visual *from_visual = DefaultVisualOfScreen (screen); | |
454 | Colormap from_cmap = XDefaultColormapOfScreen (screen); | |
455 | ||
456 | XColor *old_colors, *new_colors; | |
457 | unsigned long *pixels; | |
458 | XVisualInfo vi_in, *vi_out; | |
459 | int out_count; | |
460 | int from_cells, to_cells, max_cells, got_cells; | |
461 | int i; | |
462 | ||
463 | if (from_cmap == to_cmap) | |
464 | return; | |
465 | ||
466 | vi_in.screen = XScreenNumberOfScreen (screen); | |
467 | vi_in.visualid = XVisualIDFromVisual (from_visual); | |
468 | vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask, | |
469 | &vi_in, &out_count); | |
470 | if (! vi_out) abort (); | |
471 | from_cells = vi_out [0].colormap_size; | |
472 | XFree ((char *) vi_out); | |
473 | ||
474 | vi_in.screen = XScreenNumberOfScreen (screen); | |
475 | vi_in.visualid = XVisualIDFromVisual (to_visual); | |
476 | vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask, | |
477 | &vi_in, &out_count); | |
478 | if (! vi_out) abort (); | |
479 | to_cells = vi_out [0].colormap_size; | |
480 | XFree ((char *) vi_out); | |
481 | ||
482 | max_cells = (from_cells > to_cells ? to_cells : from_cells); | |
483 | ||
484 | old_colors = (XColor *) calloc (sizeof (XColor), max_cells); | |
485 | new_colors = (XColor *) calloc (sizeof (XColor), max_cells); | |
486 | pixels = (unsigned long *) calloc (sizeof (unsigned long), max_cells); | |
487 | for (i = 0; i < max_cells; i++) | |
488 | old_colors[i].pixel = i; | |
489 | XQueryColors (dpy, from_cmap, old_colors, max_cells); | |
490 | ||
491 | got_cells = max_cells; | |
492 | allocate_writable_colors (screen, to_cmap, pixels, &got_cells); | |
493 | ||
494 | if (grab_verbose_p && got_cells != max_cells) | |
495 | fprintf(stderr, "%s: got only %d of %d cells\n", progname, | |
496 | got_cells, max_cells); | |
497 | ||
498 | if (got_cells <= 0) /* we're screwed */ | |
499 | ; | |
500 | else if (got_cells == max_cells && /* we're golden */ | |
501 | from_cells == to_cells) | |
502 | XStoreColors (dpy, to_cmap, old_colors, got_cells); | |
503 | else /* try to cope... */ | |
504 | { | |
505 | for (i = 0; i < got_cells; i++) | |
506 | { | |
507 | XColor *c = old_colors + i; | |
508 | int j; | |
509 | for (j = 0; j < got_cells; j++) | |
510 | if (pixels[j] == c->pixel) | |
511 | { | |
512 | /* only store this color value if this is one of the pixels | |
513 | we were able to allocate. */ | |
514 | XStoreColors (dpy, to_cmap, c, 1); | |
515 | break; | |
516 | } | |
517 | } | |
518 | } | |
519 | ||
520 | ||
521 | if (grab_verbose_p) | |
522 | fprintf(stderr, "%s: installing copy of default colormap\n", progname); | |
523 | ||
524 | free (old_colors); | |
525 | free (new_colors); | |
526 | free (pixels); | |
527 | } | |
528 | ||
529 | ||
530 | \f | |
531 | /* The SGI ReadDisplay extension. | |
532 | This extension lets you get back a 24-bit image of the screen, taking into | |
533 | account the colors with which all windows are *currently* displayed, even | |
534 | if those windows have different visuals. Without this extension, presence | |
535 | of windows with different visuals or colormaps will result in technicolor | |
536 | when one tries to grab the screen image. | |
537 | */ | |
538 | ||
539 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
540 | ||
541 | static Bool | |
542 | read_display (Screen *screen, Window window, Pixmap into_pixmap, | |
543 | Bool dont_wait) | |
544 | { | |
545 | Display *dpy = DisplayOfScreen (screen); | |
546 | XWindowAttributes xgwa; | |
547 | int rd_event_base = 0; | |
548 | int rd_error_base = 0; | |
549 | unsigned long hints = 0; | |
550 | XImage *image = 0; | |
551 | XGCValues gcv; | |
552 | int class; | |
553 | GC gc; | |
554 | Bool remap_p = False; | |
555 | ||
556 | /* Check to see if the server supports the extension, and bug out if not. | |
557 | */ | |
558 | if (! XReadDisplayQueryExtension (dpy, &rd_event_base, &rd_error_base)) | |
559 | { | |
560 | if (grab_verbose_p) | |
561 | fprintf(stderr, "%s: no XReadDisplay extension\n", progname); | |
562 | return False; | |
563 | } | |
564 | ||
565 | /* If this isn't a visual we know how to handle, bug out. We handle: | |
566 | = TrueColor in depths 8, 12, 15, 16, and 32; | |
567 | = PseudoColor and DirectColor in depths 8 and 12. | |
568 | */ | |
569 | XGetWindowAttributes(dpy, window, &xgwa); | |
570 | class = visual_class (screen, xgwa.visual); | |
571 | if (class == TrueColor) | |
572 | { | |
573 | if (xgwa.depth != 8 && xgwa.depth != 12 && xgwa.depth != 15 && | |
574 | xgwa.depth != 16 && xgwa.depth != 24 && xgwa.depth != 32) | |
575 | { | |
576 | if (grab_verbose_p) | |
577 | fprintf(stderr, "%s: TrueColor depth %d unsupported\n", | |
578 | progname, xgwa.depth); | |
579 | return False; | |
580 | } | |
581 | } | |
582 | else if (class == PseudoColor || class == DirectColor) | |
583 | { | |
584 | if (xgwa.depth != 8 && xgwa.depth != 12) | |
585 | { | |
586 | if (grab_verbose_p) | |
587 | fprintf(stderr, "%s: Pseudo/DirectColor depth %d unsupported\n", | |
588 | progname, xgwa.depth); | |
589 | return False; | |
590 | } | |
591 | else | |
592 | /* Allocate a TrueColor-like spread of colors for the image. */ | |
593 | remap_p = True; | |
594 | } | |
595 | ||
596 | ||
597 | /* Try and read the screen. | |
598 | */ | |
599 | hints = (XRD_TRANSPARENT | XRD_READ_POINTER); | |
600 | image = XReadDisplay (dpy, window, xgwa.x, xgwa.y, xgwa.width, xgwa.height, | |
601 | hints, &hints); | |
602 | if (!image) | |
603 | { | |
604 | if (grab_verbose_p) | |
605 | fprintf(stderr, "%s: XReadDisplay() failed\n", progname); | |
606 | return False; | |
607 | } | |
608 | if (!image->data) | |
609 | { | |
610 | if (grab_verbose_p) | |
611 | fprintf(stderr, "%s: XReadDisplay() returned no data\n", progname); | |
612 | XDestroyImage(image); | |
613 | return False; | |
614 | } | |
615 | ||
616 | /* XReadDisplay tends to LIE about the depth of the image it read. | |
617 | It is returning an XImage which has `depth' and `bits_per_pixel' | |
618 | confused! | |
619 | ||
620 | That is, on a 24-bit display, where all visuals claim depth 24, and | |
621 | where XGetImage would return an XImage with depth 24, and where | |
622 | XPutImage will get a BadMatch with images that are not depth 24, | |
623 | XReadDisplay is returning images with depth 32! Fuckwits! | |
624 | ||
625 | So if the visual is of depth 24, but the image came back as depth 32, | |
626 | hack it to be 24 lest we get a BadMatch from XPutImage. | |
627 | ||
628 | I wonder what happens on an 8-bit SGI... Probably it still returns | |
629 | an image claiming depth 32? Certainly it can't be 8. So, let's just | |
630 | smash it to 32... | |
631 | */ | |
632 | if (image->depth == 32 /* && xgwa.depth == 24 */ ) | |
633 | image->depth = 24; | |
634 | ||
635 | /* If the visual of the window/pixmap into which we're going to draw is | |
636 | less deep than the screen itself, then we need to convert the grabbed bits | |
637 | to match the depth by clipping off the less significant bit-planes of each | |
638 | color component. | |
639 | */ | |
640 | if (image->depth > xgwa.depth) | |
641 | { | |
642 | int x, y; | |
643 | /* We use the same image->data in both images -- that's ok, because | |
644 | since we're reading from B and writing to A, and B uses more bytes | |
645 | per pixel than A, the write pointer won't overrun the read pointer. | |
646 | */ | |
647 | XImage *image2 = XCreateImage (dpy, xgwa.visual, xgwa.depth, | |
648 | ZPixmap, 0, image->data, | |
649 | xgwa.width, xgwa.height, | |
650 | 8, 0); | |
651 | if (!image2) | |
652 | { | |
653 | if (grab_verbose_p) | |
654 | fprintf(stderr, "%s: out of memory?\n", progname); | |
655 | return False; | |
656 | } | |
657 | ||
658 | if (grab_verbose_p) | |
659 | fprintf(stderr, "%s: converting from depth %d to depth %d\n", | |
660 | progname, image->depth, xgwa.depth); | |
661 | ||
662 | for (y = 0; y < image->height; y++) | |
663 | for (x = 0; x < image->width; x++) | |
664 | { | |
665 | /* #### really these shift values should be determined from the | |
666 | mask values -- but that's a pain in the ass, and anyway, | |
667 | this is an SGI-specific extension so hardcoding assumptions | |
668 | about the SGI server's behavior isn't *too* heinous... */ | |
669 | unsigned long pixel = XGetPixel(image, x, y); | |
670 | unsigned int r = (pixel & image->red_mask); | |
671 | unsigned int g = (pixel & image->green_mask) >> 8; | |
672 | unsigned int b = (pixel & image->blue_mask) >> 16; | |
673 | ||
674 | if (xgwa.depth == 8) | |
675 | pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6)); | |
676 | else if (xgwa.depth == 12) | |
677 | pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8)); | |
678 | else if (xgwa.depth == 16 || xgwa.depth == 15) | |
679 | /* Gah! I don't understand why these are in the other order. */ | |
680 | pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3))); | |
681 | else | |
682 | abort(); | |
683 | ||
684 | XPutPixel(image2, x, y, pixel); | |
685 | } | |
686 | image->data = 0; | |
687 | XDestroyImage(image); | |
688 | image = image2; | |
689 | } | |
690 | ||
691 | if (remap_p) | |
692 | { | |
693 | allocate_cubic_colormap (screen, window, xgwa.visual); | |
694 | remap_image (screen, window, xgwa.colormap, image); | |
695 | } | |
696 | ||
697 | /* Now actually put the bits into the window or pixmap -- note the design | |
698 | bogosity of this extension, where we've been forced to take 24 bit data | |
699 | from the server to the client, and then push it back from the client to | |
700 | the server, *without alteration*. We should have just been able to tell | |
701 | the server, "put a screen image in this drawable", instead of having to | |
702 | go through the intermediate step of converting it to an Image. Geez. | |
703 | (Assuming that the window is of screen depth; we happen to handle less | |
704 | deep windows, but that's beside the point.) | |
705 | */ | |
706 | gcv.function = GXcopy; | |
707 | gc = XCreateGC (dpy, window, GCFunction, &gcv); | |
708 | ||
709 | if (into_pixmap) | |
710 | { | |
711 | gcv.function = GXcopy; | |
712 | gc = XCreateGC (dpy, into_pixmap, GCFunction, &gcv); | |
713 | XPutImage (dpy, into_pixmap, gc, image, 0, 0, 0, 0, | |
714 | xgwa.width, xgwa.height); | |
715 | } | |
716 | else | |
717 | { | |
718 | gcv.function = GXcopy; | |
719 | gc = XCreateGC (dpy, window, GCFunction, &gcv); | |
720 | ||
721 | /* Ok, now we'll be needing that window on the screen... */ | |
722 | raise_window(dpy, window, dont_wait); | |
723 | ||
724 | /* Plop down the bits... */ | |
725 | XPutImage (dpy, window, gc, image, 0, 0, 0, 0, xgwa.width, xgwa.height); | |
726 | } | |
727 | XFreeGC (dpy, gc); | |
728 | ||
729 | if (image->data) | |
730 | { | |
731 | free(image->data); | |
732 | image->data = 0; | |
733 | } | |
734 | XDestroyImage(image); | |
735 | ||
736 | return True; | |
737 | } | |
738 | #endif /* HAVE_READ_DISPLAY_EXTENSION */ | |
739 | ||
740 | ||
741 | #ifdef HAVE_READ_DISPLAY_EXTENSION | |
742 | ||
743 | /* Makes and installs a colormap that makes a PseudoColor or DirectColor | |
744 | visual behave like a TrueColor visual of the same depth. | |
745 | ||
746 | #### Duplicated in driver/xscreensaver-getimage.c | |
747 | */ | |
748 | static void | |
749 | allocate_cubic_colormap (Screen *screen, Window window, Visual *visual) | |
750 | { | |
751 | Display *dpy = DisplayOfScreen (screen); | |
752 | XWindowAttributes xgwa; | |
753 | Colormap cmap; | |
754 | int nr, ng, nb, cells; | |
755 | int r, g, b; | |
756 | int depth; | |
757 | XColor colors[4097]; | |
758 | int i; | |
759 | ||
760 | XGetWindowAttributes (dpy, window, &xgwa); | |
761 | cmap = xgwa.colormap; | |
762 | depth = visual_depth (screen, visual); | |
763 | ||
764 | switch (depth) | |
765 | { | |
766 | case 8: nr = 3; ng = 3; nb = 2; cells = 256; break; | |
767 | case 12: nr = 4; ng = 4; nb = 4; cells = 4096; break; | |
768 | default: abort(); break; | |
769 | } | |
770 | ||
771 | memset(colors, 0, sizeof(colors)); | |
772 | for (r = 0; r < (1 << nr); r++) | |
773 | for (g = 0; g < (1 << ng); g++) | |
774 | for (b = 0; b < (1 << nb); b++) | |
775 | { | |
776 | i = (r | (g << nr) | (b << (nr + ng))); | |
777 | colors[i].pixel = i; | |
778 | colors[i].flags = DoRed|DoGreen|DoBlue; | |
779 | if (depth == 8) | |
780 | { | |
781 | colors[i].red = ((r << 13) | (r << 10) | (r << 7) | | |
782 | (r << 4) | (r << 1)); | |
783 | colors[i].green = ((g << 13) | (g << 10) | (g << 7) | | |
784 | (g << 4) | (g << 1)); | |
785 | colors[i].blue = ((b << 14) | (b << 12) | (b << 10) | | |
786 | (b << 8) | (b << 6) | (b << 4) | | |
787 | (b << 2) | b); | |
788 | } | |
789 | else | |
790 | { | |
791 | colors[i].red = (r << 12) | (r << 8) | (r << 4) | r; | |
792 | colors[i].green = (g << 12) | (g << 8) | (g << 4) | g; | |
793 | colors[i].blue = (b << 12) | (b << 8) | (b << 4) | b; | |
794 | } | |
795 | } | |
796 | ||
797 | { | |
798 | int j; | |
799 | int allocated = 0; | |
800 | int interleave = cells / 8; /* skip around, rather than allocating in | |
801 | order, so that we get better coverage if | |
802 | we can't allocated all of them. */ | |
803 | for (j = 0; j < interleave; j++) | |
804 | for (i = 0; i < cells; i += interleave) | |
805 | if (XAllocColor (dpy, cmap, &colors[i + j])) | |
806 | allocated++; | |
807 | ||
808 | if (grab_verbose_p) | |
809 | fprintf (stderr, "%s: allocated %d of %d colors for cubic map\n", | |
810 | progname, allocated, cells); | |
811 | } | |
812 | } | |
813 | ||
814 | /* Find the pixel index that is closest to the given color | |
815 | (using linear distance in RGB space -- which is far from the best way.) | |
816 | ||
817 | #### Duplicated in driver/xscreensaver-getimage.c | |
818 | */ | |
819 | static unsigned long | |
820 | find_closest_pixel (XColor *colors, int ncolors, | |
821 | unsigned long r, unsigned long g, unsigned long b) | |
822 | { | |
823 | unsigned long distance = ~0; | |
824 | int i, found = 0; | |
825 | ||
826 | if (ncolors == 0) | |
827 | abort(); | |
828 | for (i = 0; i < ncolors; i++) | |
829 | { | |
830 | unsigned long d; | |
831 | int rd, gd, bd; | |
832 | ||
833 | rd = r - colors[i].red; | |
834 | gd = g - colors[i].green; | |
835 | bd = b - colors[i].blue; | |
836 | if (rd < 0) rd = -rd; | |
837 | if (gd < 0) gd = -gd; | |
838 | if (bd < 0) bd = -bd; | |
839 | d = (rd << 1) + (gd << 2) + bd; | |
840 | ||
841 | if (d < distance) | |
842 | { | |
843 | distance = d; | |
844 | found = i; | |
845 | if (distance == 0) | |
846 | break; | |
847 | } | |
848 | } | |
849 | ||
850 | return found; | |
851 | } | |
852 | ||
853 | ||
854 | /* Given an XImage with 8-bit or 12-bit RGB data, convert it to be | |
855 | displayable with the given X colormap. The farther from a perfect | |
856 | color cube the contents of the colormap are, the lossier the | |
857 | transformation will be. No dithering is done. | |
858 | ||
859 | #### Duplicated in driver/xscreensaver-getimage.c | |
860 | */ | |
861 | void | |
862 | remap_image (Screen *screen, Window window, Colormap cmap, XImage *image) | |
863 | { | |
864 | Display *dpy = DisplayOfScreen (screen); | |
865 | unsigned long map[4097]; | |
866 | int x, y, i; | |
867 | int cells; | |
868 | XColor colors[4097]; | |
869 | ||
870 | if (image->depth == 8) | |
871 | cells = 256; | |
872 | else if (image->depth == 12) | |
873 | cells = 4096; | |
874 | else | |
875 | abort(); | |
876 | ||
877 | memset(map, -1, sizeof(*map)); | |
878 | memset(colors, -1, sizeof(*colors)); | |
879 | ||
880 | for (i = 0; i < cells; i++) | |
881 | colors[i].pixel = i; | |
882 | XQueryColors (dpy, cmap, colors, cells); | |
883 | ||
884 | if (grab_verbose_p) | |
885 | fprintf(stderr, "%s: building table for %d bit image\n", | |
886 | progname, image->depth); | |
887 | ||
888 | for (i = 0; i < cells; i++) | |
889 | { | |
890 | unsigned short r, g, b; | |
891 | ||
892 | if (cells == 256) | |
893 | { | |
894 | /* "RRR GGG BB" In an 8 bit map. Convert that to | |
895 | "RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give | |
896 | an even spread. */ | |
897 | r = (i & 0x07); | |
898 | g = (i & 0x38) >> 3; | |
899 | b = (i & 0xC0) >> 6; | |
900 | ||
901 | r = ((r << 13) | (r << 10) | (r << 7) | (r << 4) | (r << 1)); | |
902 | g = ((g << 13) | (g << 10) | (g << 7) | (g << 4) | (g << 1)); | |
903 | b = ((b << 14) | (b << 12) | (b << 10) | (b << 8) | | |
904 | (b << 6) | (b << 4) | (b << 2) | b); | |
905 | } | |
906 | else | |
907 | { | |
908 | /* "RRRR GGGG BBBB" In a 12 bit map. Convert that to | |
909 | "RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even | |
910 | spread. */ | |
911 | r = (i & 0x00F); | |
912 | g = (i & 0x0F0) >> 4; | |
913 | b = (i & 0xF00) >> 8; | |
914 | ||
915 | r = (r << 12) | (r << 8) | (r << 4) | r; | |
916 | g = (g << 12) | (g << 8) | (g << 4) | g; | |
917 | b = (b << 12) | (b << 8) | (b << 4) | b; | |
918 | } | |
919 | ||
920 | map[i] = find_closest_pixel (colors, cells, r, g, b); | |
921 | } | |
922 | ||
923 | if (grab_verbose_p) | |
924 | fprintf(stderr, "%s: remapping colors in %d bit image\n", | |
925 | progname, image->depth); | |
926 | ||
927 | for (y = 0; y < image->height; y++) | |
928 | for (x = 0; x < image->width; x++) | |
929 | { | |
930 | unsigned long pixel = XGetPixel(image, x, y); | |
931 | if (pixel >= cells) abort(); | |
932 | XPutPixel(image, x, y, map[pixel]); | |
933 | } | |
934 | } | |
935 | ||
936 | ||
937 | #endif /* HAVE_READ_DISPLAY_EXTENSION */ |