/* xscreensaver, Copyright (c) 1992-2016 Jamie Zawinski <jwz@jwz.org>
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
/* This file contains code for grabbing an image of the screen to hack its
bits. This is a little tricky, since doing this involves the need to tell
the difference between drawing on the actual root window, and on the fake
root window used by the screensaver, since at this level the illusion
The hacks themselves use utils/grabclient.c to invoke the
"xscreensaver-getimage" program as a sub-process.
"driver/xscreensaver-getimage" runs the code in this file to grab
the X11 root window image as a Pixmap.
On MacOS systems running X11, which nobody does any more:
"driver/xscreensaver-getimage" runs the Perl script
"driver/xscreensaver-getimage-desktop", which in turn runs the MacOS
program "/usr/sbin/screencapture" to get the Mac desktop image as a
On MacOS systems running the native Cocoa build, or on iOS or Android
"driver/xscreensaver-getimage" is not used. Instead, each saver's
"utils/grabclient.c" links against "OSX/grabclient-osx.m",
"OSX/grabclient-ios.m" or "jwxyz/jwxyz-android.c" to grab
screenshots directly without invoking a sub-process to do it.
See the comment at the top of utils/grabclient.c for a more detailed
# include <X11/Xmu/WinUtil.h>
# include <Xmu/WinUtil.h>
#undef RootWindowOfScreen
#ifdef HAVE_READ_DISPLAY_EXTENSION
# include <X11/extensions/readdisplay.h>
static Bool
read_display (Screen
*, Window
, Pixmap
, Bool
);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
static void copy_default_colormap_contents (Screen
*, Colormap
, Visual
*);
#ifdef HAVE_READ_DISPLAY_EXTENSION
static void allocate_cubic_colormap (Screen
*, Window
, Visual
*);
void remap_image (Screen
*, Window
, Colormap
, XImage
*);
MapNotify_event_p (Display
*dpy
, XEvent
*event
, XPointer window
)
return (event
->xany
.type
== MapNotify
&&
event
->xvisibility
.window
== (Window
) window
);
Bool grab_verbose_p
= False
;
raise_window(Display
*dpy
, Window window
, Bool dont_wait
)
fprintf(stderr
, "%s: raising window 0x%08lX (%s)\n",
progname
, (unsigned long) window
,
(dont_wait
? "not waiting" : "waiting"));
memset(&hints
, 0, sizeof(hints
));
XGetWMNormalHints(dpy
, window
, &hints
, &supplied
);
XGetWindowAttributes (dpy
, window
, &xgwa
);
hints
.width
= xgwa
.width
;
hints
.height
= xgwa
.height
;
hints
.flags
|= (PPosition
|USPosition
|PSize
|USSize
);
XSetWMNormalHints(dpy
, window
, &hints
);
XSelectInput (dpy
, window
, (xgwa
.your_event_mask
| StructureNotifyMask
));
XIfEvent (dpy
, &event
, MapNotify_event_p
, (XPointer
) window
);
xscreensaver_window_p (Display
*dpy
, Window window
)
unsigned long nitems
, bytesafter
;
if (XGetWindowProperty (dpy
, window
,
XInternAtom (dpy
, "_SCREENSAVER_VERSION", False
),
&type
, &format
, &nitems
, &bytesafter
,
/* Whether the given window is:
- a direct child of the root window;
- a direct child of the window manager's decorations.
top_level_window_p (Screen
*screen
, Window window
)
Display
*dpy
= DisplayOfScreen (screen
);
Window root
, parent
, *kids
;
if (!XQueryTree (dpy
, window
, &root
, &parent
, &kids
, &nkids
))
/* If our direct parent is the real root window, then yes. */
unsigned long nitems
, bytesafter
;
/* If our direct parent has the WM_STATE property, then it is a
window manager decoration -- yes.
if (XGetWindowProperty (dpy
, window
,
XInternAtom (dpy
, "WM_STATE", True
),
0, 0, False
, AnyPropertyType
,
&type
, &format
, &nitems
, &bytesafter
,
(unsigned char **) &data
)
/* Else, no. We're deep in a tree somewhere.
static Bool error_handler_hit_p
= False
;
static XErrorHandler old_ehandler
= 0;
BadWindow_ehandler (Display
*dpy
, XErrorEvent
*error
)
error_handler_hit_p
= True
;
if (error
->error_code
== BadWindow
|| error
->error_code
== BadDrawable
)
return (*old_ehandler
) (dpy
, error
);
/* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
on a window whose depth is not the maximal depth of the screen? Or
something. Anyway, things don't work unless we: use SubwindowMode for
the real root window (or a legitimate virtual root window); but do not
use SubwindowMode for the xscreensaver window. I make no attempt to
use_subwindow_mode_p(Screen
*screen
, Window window
)
if (window
!= VirtualRootWindowOfScreen(screen
))
else if (xscreensaver_window_p(DisplayOfScreen(screen
), window
))
/* Install the colormaps of all visible windows, deepest first.
This should leave the colormaps of the topmost windows installed
(if only N colormaps can be installed at a time, then only the
topmost N windows will be shown in the right colors.)
install_screen_colormaps (Screen
*screen
)
Display
*dpy
= DisplayOfScreen (screen
);
Window parent
, *kids
= 0;
old_ehandler
= XSetErrorHandler (BadWindow_ehandler
);
error_handler_hit_p
= False
;
real_root
= XRootWindowOfScreen (screen
); /* not vroot */
if (XQueryTree (dpy
, real_root
, &real_root
, &parent
, &kids
, &nkids
))
for (i
= 0; i
< nkids
; i
++)
/* #### need to put XmuClientWindow() in xmu.c, sigh... */
if (! (client
= XmuClientWindow (dpy
, kids
[i
])))
XGetWindowAttributes (dpy
, client
, &xgwa
);
if (xgwa
.colormap
&& xgwa
.map_state
== IsViewable
)
XInstallColormap (dpy
, xgwa
.colormap
);
XInstallColormap (dpy
, DefaultColormapOfScreen (screen
));
XSetErrorHandler (old_ehandler
);
grab_screen_image_internal (Screen
*screen
, Window window
)
Display
*dpy
= DisplayOfScreen (screen
);
Bool grab_mouse_p
= False
;
real_root
= XRootWindowOfScreen (screen
); /* not vroot */
root_p
= (window
== real_root
);
saver_p
= xscreensaver_window_p (dpy
, window
);
XGetWindowAttributes (dpy
, window
, &xgwa
);
/* I think this is redundant, but just to be safe... */
/* The only time grabbing the mouse is important is if this program
is being run while the saver is locking the screen. */
unmap
= get_float_resource(dpy
, "grabRootDelay", "Seconds");
if (unmap
<= 0.00001 || unmap
> 20) unmap
= 2.5;
unmap
= get_float_resource(dpy
, "grabWindowDelay", "Seconds");
if (unmap
<= 0.00001 || unmap
> 20) unmap
= 0.66;
unmap_time
= unmap
* 100000;
"\n%s: window 0x%08lX root: %d saver: %d grab: %d wait: %.1f\n",
progname
, (unsigned long) window
,
root_p
, saver_p
, grab_mouse_p
, ((double)unmap_time
)/1000000.0);
fprintf(stderr
, "%s: ", progname
);
describe_visual(stderr
, screen
, xgwa
.visual
, False
);
if (!root_p
&& !top_level_window_p (screen
, window
))
fprintf (stderr
, "%s: not a top-level window: 0x%08lX: not grabbing\n",
progname
, (unsigned long) window
);
XSetWindowBackgroundPixmap (dpy
, window
, None
);
/* prevent random viewer of the screen saver (locker) from messing
with windows. We don't check whether it succeeded, because what
are our options, really... */
XGrabPointer (dpy
, real_root
, True
, ButtonPressMask
|ButtonReleaseMask
,
GrabModeAsync
, GrabModeAsync
, None
, None
, CurrentTime
);
XGrabKeyboard (dpy
, real_root
, True
, GrabModeSync
, GrabModeAsync
,
XUnmapWindow (dpy
, window
);
install_screen_colormaps (screen
);
usleep(unmap_time
); /* wait for everyone to swap in and handle exposes */
#ifdef HAVE_READ_DISPLAY_EXTENSION
if (! read_display(screen
, window
, 0, saver_p
))
#endif /* HAVE_READ_DISPLAY_EXTENSION */
#ifdef HAVE_READ_DISPLAY_EXTENSION
fprintf(stderr
, "%s: read_display() failed\n", progname
);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
copy_default_colormap_contents (screen
, xgwa
.colormap
, xgwa
.visual
);
raise_window(dpy
, window
, saver_p
);
/* Generally it's bad news to call XInstallColormap() explicitly,
but this file does a lot of sleazy stuff already... This is to
make sure that the window's colormap is installed, even in the
case where the window is OverrideRedirect. */
if (xgwa
.colormap
) XInstallColormap (dpy
, xgwa
.colormap
);
pixmap
= XCreatePixmap(dpy
, window
, xgwa
.width
, xgwa
.height
, xgwa
.depth
);
#ifdef HAVE_READ_DISPLAY_EXTENSION
if (! read_display(screen
, window
, pixmap
, True
))
Window real_root
= XRootWindowOfScreen (screen
); /* not vroot */
#ifdef HAVE_READ_DISPLAY_EXTENSION
fprintf(stderr
, "%s: read_display() failed\n", progname
);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
copy_default_colormap_contents (screen
, xgwa
.colormap
, xgwa
.visual
);
gcv
.subwindow_mode
= IncludeInferiors
;
gc
= XCreateGC (dpy
, window
, GCFunction
| GCSubwindowMode
, &gcv
);
XCopyArea (dpy
, real_root
, pixmap
, gc
,
xgwa
.x
, xgwa
.y
, xgwa
.width
, xgwa
.height
, 0, 0);
XSetWindowBackgroundPixmap (dpy
, window
, pixmap
);
XFreePixmap (dpy
, pixmap
);
fprintf (stderr
, "%s: grabbed %d bit screen image to %swindow.\n",
(root_p
? "real root " : ""));
XUngrabPointer (dpy
, CurrentTime
);
XUngrabKeyboard (dpy
, CurrentTime
);
/* When we are grabbing and manipulating a screen image, it's important that
we use the same colormap it originally had. So, if the screensaver was
started with -install, we need to copy the contents of the default colormap
into the screensaver's colormap.
copy_default_colormap_contents (Screen
*screen
,
Display
*dpy
= DisplayOfScreen (screen
);
Visual
*from_visual
= DefaultVisualOfScreen (screen
);
Colormap from_cmap
= XDefaultColormapOfScreen (screen
);
XColor
*old_colors
, *new_colors
;
XVisualInfo vi_in
, *vi_out
;
int from_cells
, to_cells
, max_cells
, got_cells
;
if (from_cmap
== to_cmap
)
vi_in
.screen
= XScreenNumberOfScreen (screen
);
vi_in
.visualid
= XVisualIDFromVisual (from_visual
);
vi_out
= XGetVisualInfo (dpy
, VisualScreenMask
|VisualIDMask
,
from_cells
= vi_out
[0].colormap_size
;
vi_in
.screen
= XScreenNumberOfScreen (screen
);
vi_in
.visualid
= XVisualIDFromVisual (to_visual
);
vi_out
= XGetVisualInfo (dpy
, VisualScreenMask
|VisualIDMask
,
to_cells
= vi_out
[0].colormap_size
;
max_cells
= (from_cells
> to_cells
? to_cells
: from_cells
);
old_colors
= (XColor
*) calloc (sizeof (XColor
), max_cells
);
new_colors
= (XColor
*) calloc (sizeof (XColor
), max_cells
);
pixels
= (unsigned long *) calloc (sizeof (unsigned long), max_cells
);
for (i
= 0; i
< max_cells
; i
++)
XQueryColors (dpy
, from_cmap
, old_colors
, max_cells
);
allocate_writable_colors (screen
, to_cmap
, pixels
, &got_cells
);
if (grab_verbose_p
&& got_cells
!= max_cells
)
fprintf(stderr
, "%s: got only %d of %d cells\n", progname
,
if (got_cells
<= 0) /* we're screwed */
else if (got_cells
== max_cells
&& /* we're golden */
XStoreColors (dpy
, to_cmap
, old_colors
, got_cells
);
else /* try to cope... */
for (i
= 0; i
< got_cells
; i
++)
XColor
*c
= old_colors
+ i
;
for (j
= 0; j
< got_cells
; j
++)
if (pixels
[j
] == c
->pixel
)
/* only store this color value if this is one of the pixels
we were able to allocate. */
XStoreColors (dpy
, to_cmap
, c
, 1);
fprintf(stderr
, "%s: installing copy of default colormap\n", progname
);
/* The SGI ReadDisplay extension.
This extension lets you get back a 24-bit image of the screen, taking into
account the colors with which all windows are *currently* displayed, even
if those windows have different visuals. Without this extension, presence
of windows with different visuals or colormaps will result in technicolor
when one tries to grab the screen image.
#ifdef HAVE_READ_DISPLAY_EXTENSION
read_display (Screen
*screen
, Window window
, Pixmap into_pixmap
,
Display
*dpy
= DisplayOfScreen (screen
);
/* Check to see if the server supports the extension, and bug out if not.
if (! XReadDisplayQueryExtension (dpy
, &rd_event_base
, &rd_error_base
))
fprintf(stderr
, "%s: no XReadDisplay extension\n", progname
);
/* If this isn't a visual we know how to handle, bug out. We handle:
= TrueColor in depths 8, 12, 15, 16, and 32;
= PseudoColor and DirectColor in depths 8 and 12.
XGetWindowAttributes(dpy
, window
, &xgwa
);
class = visual_class (screen
, xgwa
.visual
);
if (xgwa
.depth
!= 8 && xgwa
.depth
!= 12 && xgwa
.depth
!= 15 &&
xgwa
.depth
!= 16 && xgwa
.depth
!= 24 && xgwa
.depth
!= 32)
fprintf(stderr
, "%s: TrueColor depth %d unsupported\n",
else if (class == PseudoColor
|| class == DirectColor
)
if (xgwa
.depth
!= 8 && xgwa
.depth
!= 12)
fprintf(stderr
, "%s: Pseudo/DirectColor depth %d unsupported\n",
/* Allocate a TrueColor-like spread of colors for the image. */
/* Try and read the screen.
hints
= (XRD_TRANSPARENT
| XRD_READ_POINTER
);
image
= XReadDisplay (dpy
, window
, xgwa
.x
, xgwa
.y
, xgwa
.width
, xgwa
.height
,
fprintf(stderr
, "%s: XReadDisplay() failed\n", progname
);
fprintf(stderr
, "%s: XReadDisplay() returned no data\n", progname
);
/* XReadDisplay tends to LIE about the depth of the image it read.
It is returning an XImage which has `depth' and `bits_per_pixel'
That is, on a 24-bit display, where all visuals claim depth 24, and
where XGetImage would return an XImage with depth 24, and where
XPutImage will get a BadMatch with images that are not depth 24,
XReadDisplay is returning images with depth 32! Fuckwits!
So if the visual is of depth 24, but the image came back as depth 32,
hack it to be 24 lest we get a BadMatch from XPutImage.
I wonder what happens on an 8-bit SGI... Probably it still returns
an image claiming depth 32? Certainly it can't be 8. So, let's just
if (image
->depth
== 32 /* && xgwa.depth == 24 */ )
/* If the visual of the window/pixmap into which we're going to draw is
less deep than the screen itself, then we need to convert the grabbed bits
to match the depth by clipping off the less significant bit-planes of each
if (image
->depth
> xgwa
.depth
)
/* We use the same image->data in both images -- that's ok, because
since we're reading from B and writing to A, and B uses more bytes
per pixel than A, the write pointer won't overrun the read pointer.
XImage
*image2
= XCreateImage (dpy
, xgwa
.visual
, xgwa
.depth
,
fprintf(stderr
, "%s: out of memory?\n", progname
);
fprintf(stderr
, "%s: converting from depth %d to depth %d\n",
progname
, image
->depth
, xgwa
.depth
);
for (y
= 0; y
< image
->height
; y
++)
for (x
= 0; x
< image
->width
; x
++)
/* #### really these shift values should be determined from the
mask values -- but that's a pain in the ass, and anyway,
this is an SGI-specific extension so hardcoding assumptions
about the SGI server's behavior isn't *too* heinous... */
unsigned long pixel
= XGetPixel(image
, x
, y
);
unsigned int r
= (pixel
& image
->red_mask
);
unsigned int g
= (pixel
& image
->green_mask
) >> 8;
unsigned int b
= (pixel
& image
->blue_mask
) >> 16;
pixel
= ((r
>> 5) | ((g
>> 5) << 3) | ((b
>> 6) << 6));
else if (xgwa
.depth
== 12)
pixel
= ((r
>> 4) | ((g
>> 4) << 4) | ((b
>> 4) << 8));
else if (xgwa
.depth
== 16 || xgwa
.depth
== 15)
/* Gah! I don't understand why these are in the other order. */
pixel
= (((r
>> 3) << 10) | ((g
>> 3) << 5) | ((b
>> 3)));
XPutPixel(image2
, x
, y
, pixel
);
allocate_cubic_colormap (screen
, window
, xgwa
.visual
);
remap_image (screen
, window
, xgwa
.colormap
, image
);
/* Now actually put the bits into the window or pixmap -- note the design
bogosity of this extension, where we've been forced to take 24 bit data
from the server to the client, and then push it back from the client to
the server, *without alteration*. We should have just been able to tell
the server, "put a screen image in this drawable", instead of having to
go through the intermediate step of converting it to an Image. Geez.
(Assuming that the window is of screen depth; we happen to handle less
deep windows, but that's beside the point.)
gc
= XCreateGC (dpy
, window
, GCFunction
, &gcv
);
gc
= XCreateGC (dpy
, into_pixmap
, GCFunction
, &gcv
);
XPutImage (dpy
, into_pixmap
, gc
, image
, 0, 0, 0, 0,
xgwa
.width
, xgwa
.height
);
gc
= XCreateGC (dpy
, window
, GCFunction
, &gcv
);
/* Ok, now we'll be needing that window on the screen... */
raise_window(dpy
, window
, dont_wait
);
/* Plop down the bits... */
XPutImage (dpy
, window
, gc
, image
, 0, 0, 0, 0, xgwa
.width
, xgwa
.height
);
#endif /* HAVE_READ_DISPLAY_EXTENSION */
#ifdef HAVE_READ_DISPLAY_EXTENSION
/* Makes and installs a colormap that makes a PseudoColor or DirectColor
visual behave like a TrueColor visual of the same depth.
#### Duplicated in driver/xscreensaver-getimage.c
allocate_cubic_colormap (Screen
*screen
, Window window
, Visual
*visual
)
Display
*dpy
= DisplayOfScreen (screen
);
XGetWindowAttributes (dpy
, window
, &xgwa
);
depth
= visual_depth (screen
, visual
);
case 8: nr
= 3; ng
= 3; nb
= 2; cells
= 256; break;
case 12: nr
= 4; ng
= 4; nb
= 4; cells
= 4096; break;
memset(colors
, 0, sizeof(colors
));
for (r
= 0; r
< (1 << nr
); r
++)
for (g
= 0; g
< (1 << ng
); g
++)
for (b
= 0; b
< (1 << nb
); b
++)
i
= (r
| (g
<< nr
) | (b
<< (nr
+ ng
)));
colors
[i
].flags
= DoRed
|DoGreen
|DoBlue
;
colors
[i
].red
= ((r
<< 13) | (r
<< 10) | (r
<< 7) |
colors
[i
].green
= ((g
<< 13) | (g
<< 10) | (g
<< 7) |
colors
[i
].blue
= ((b
<< 14) | (b
<< 12) | (b
<< 10) |
(b
<< 8) | (b
<< 6) | (b
<< 4) |
colors
[i
].red
= (r
<< 12) | (r
<< 8) | (r
<< 4) | r
;
colors
[i
].green
= (g
<< 12) | (g
<< 8) | (g
<< 4) | g
;
colors
[i
].blue
= (b
<< 12) | (b
<< 8) | (b
<< 4) | b
;
int interleave
= cells
/ 8; /* skip around, rather than allocating in
order, so that we get better coverage if
we can't allocated all of them. */
for (j
= 0; j
< interleave
; j
++)
for (i
= 0; i
< cells
; i
+= interleave
)
if (XAllocColor (dpy
, cmap
, &colors
[i
+ j
]))
fprintf (stderr
, "%s: allocated %d of %d colors for cubic map\n",
progname
, allocated
, cells
);
/* Find the pixel index that is closest to the given color
(using linear distance in RGB space -- which is far from the best way.)
#### Duplicated in driver/xscreensaver-getimage.c
find_closest_pixel (XColor
*colors
, int ncolors
,
unsigned long r
, unsigned long g
, unsigned long b
)
unsigned long distance
= ~0;
for (i
= 0; i
< ncolors
; i
++)
gd
= g
- colors
[i
].green
;
d
= (rd
<< 1) + (gd
<< 2) + bd
;
/* Given an XImage with 8-bit or 12-bit RGB data, convert it to be
displayable with the given X colormap. The farther from a perfect
color cube the contents of the colormap are, the lossier the
transformation will be. No dithering is done.
#### Duplicated in driver/xscreensaver-getimage.c
remap_image (Screen
*screen
, Window window
, Colormap cmap
, XImage
*image
)
Display
*dpy
= DisplayOfScreen (screen
);
else if (image
->depth
== 12)
memset(map
, -1, sizeof(*map
));
memset(colors
, -1, sizeof(*colors
));
for (i
= 0; i
< cells
; i
++)
XQueryColors (dpy
, cmap
, colors
, cells
);
fprintf(stderr
, "%s: building table for %d bit image\n",
for (i
= 0; i
< cells
; i
++)
/* "RRR GGG BB" In an 8 bit map. Convert that to
"RRR RRR RR" "GGG GGG GG" "BB BB BB BB" to give
r
= ((r
<< 13) | (r
<< 10) | (r
<< 7) | (r
<< 4) | (r
<< 1));
g
= ((g
<< 13) | (g
<< 10) | (g
<< 7) | (g
<< 4) | (g
<< 1));
b
= ((b
<< 14) | (b
<< 12) | (b
<< 10) | (b
<< 8) |
(b
<< 6) | (b
<< 4) | (b
<< 2) | b
);
/* "RRRR GGGG BBBB" In a 12 bit map. Convert that to
"RRRR RRRR" "GGGG GGGG" "BBBB BBBB" to give an even
r
= (r
<< 12) | (r
<< 8) | (r
<< 4) | r
;
g
= (g
<< 12) | (g
<< 8) | (g
<< 4) | g
;
b
= (b
<< 12) | (b
<< 8) | (b
<< 4) | b
;
map
[i
] = find_closest_pixel (colors
, cells
, r
, g
, b
);
fprintf(stderr
, "%s: remapping colors in %d bit image\n",
for (y
= 0; y
< image
->height
; y
++)
for (x
= 0; x
< image
->width
; x
++)
unsigned long pixel
= XGetPixel(image
, x
, y
);
if (pixel
>= cells
) abort();
XPutPixel(image
, x
, y
, map
[pixel
]);
#endif /* HAVE_READ_DISPLAY_EXTENSION */