| 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 */ |