X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=driver%2Fxscreensaver-getimage.c;h=092540d478695bc297a1ffd5b209ef33ea7a87ec;hb=refs%2Fheads%2Fmaster;hp=8dc800a40ed97709d542c4106d46dce3258cab1b;hpb=6a1da724858673ac40aa13a9612340d8bed8c7b9;p=xscreensaver diff --git a/driver/xscreensaver-getimage.c b/driver/xscreensaver-getimage.c index 8dc800a4..092540d4 100644 --- a/driver/xscreensaver-getimage.c +++ b/driver/xscreensaver-getimage.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2001, 2002, 2003 by Jamie Zawinski +/* xscreensaver, Copyright (c) 2001-2018 by Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -46,6 +46,10 @@ #include "version.h" #include "vroot.h" +#ifndef _XSCREENSAVER_VROOT_H_ +# error Error! You have an old version of vroot.h! Check -I args. +#endif /* _XSCREENSAVER_VROOT_H_ */ + #ifdef HAVE_GDK_PIXBUF # undef HAVE_JPEGLIB # ifdef HAVE_GTK2 @@ -61,6 +65,16 @@ #endif +#ifdef __APPLE__ + /* On MacOS under X11, the usual X11 mechanism of getting a screen shot + doesn't work, and we need to use an external program. This is only + used when running under X11 on MacOS. If it's a Cocoa build, this + path is not taken, and OSX/grabclient-osx.m is used instead. + */ +# define USE_EXTERNAL_SCREEN_GRABBER +#endif + + #ifdef __GNUC__ __extension__ /* shut up about "string length is greater than the length ISO C89 compilers are required to support" when including @@ -81,9 +95,16 @@ XtAppContext app; extern void grabscreen_verbose (void); +typedef enum { + GRAB_DESK, GRAB_VIDEO, GRAB_FILE, GRAB_BARS +} grab_type; + -#define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video" -#define GETIMAGE_FILE_PROGRAM "xscreensaver-getimage-file" +#define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video" +#define GETIMAGE_FILE_PROGRAM "xscreensaver-getimage-file" +#define GETIMAGE_SCREEN_PROGRAM "xscreensaver-getimage-desktop" + +extern const char *blurb (void); const char * blurb (void) @@ -95,8 +116,17 @@ blurb (void) static int x_ehandler (Display *dpy, XErrorEvent *error) { - fprintf (stderr, "\nX error in %s:\n", progname); - XmuPrintDefaultErrorMessage (dpy, error, stderr); + if (error->error_code == BadWindow || error->error_code == BadDrawable) + { + fprintf (stderr, "%s: target %s 0x%lx unexpectedly deleted\n", progname, + (error->error_code == BadWindow ? "window" : "pixmap"), + (unsigned long) error->resourceid); + } + else + { + fprintf (stderr, "\nX error in %s:\n", progname); + XmuPrintDefaultErrorMessage (dpy, error, stderr); + } exit (-1); return 0; } @@ -111,6 +141,17 @@ ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) return 0; } +#ifndef USE_EXTERNAL_SCREEN_GRABBER +static int +ignore_badmatch_ehandler (Display *dpy, XErrorEvent *error) +{ + if (error->error_code == BadMatch) + return ignore_all_errors_ehandler (dpy, error); + else + return x_ehandler (dpy, error); +} +#endif /* ! USE_EXTERNAL_SCREEN_GRABBER */ + /* Returns True if the given Drawable is a Window; False if it's a Pixmap. */ @@ -135,6 +176,35 @@ drawable_window_p (Display *dpy, Drawable d) } +/* Returns true if the window is the root window, or a virtual root window, + but *not* the xscreensaver window. That is, if it's a "real" desktop + root window of some kind. + */ +static Bool +root_window_p (Screen *screen, Window window) +{ + Display *dpy = DisplayOfScreen (screen); + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *version; + + if (window != RootWindowOfScreen (screen)) + return False; + + if (XGetWindowProperty (dpy, window, + XInternAtom (dpy, "_SCREENSAVER_VERSION", False), + 0, 1, False, XA_STRING, + &type, &format, &nitems, &bytesafter, + &version) + == Success + && type != None) + return False; + + return True; +} + + /* Clear the window or pixmap to black, or its background color. */ static void @@ -186,9 +256,24 @@ compute_image_scaling (int src_w, int src_h, float rw = (float) dest_w / src_w; float rh = (float) dest_h / src_h; float r = (rw < rh ? rw : rh); - int tw = src_w * r; - int th = src_h * r; - int pct = (r * 100); + int tw, th, pct; + + /* If the window is a goofy aspect ratio, take a middle slice of + the image instead. */ + if (dest_w > dest_h * 5 || dest_h > dest_w * 5) + { + double r2 = (dest_w > dest_h + ? dest_w / (double) dest_h + : dest_h / (double) dest_w); + r *= r2; + if (verbose_p) + fprintf (stderr, "%s: weird aspect: scaling by %.1f\n", + progname, r2); + } + + tw = src_w * r; + th = src_h * r; + pct = (r * 100); #if 0 /* this optimization breaks things */ @@ -211,8 +296,8 @@ compute_image_scaling (int src_w, int src_h, if (destx < 0) srcx = -destx, destx = 0; if (desty < 0) srcy = -desty, desty = 0; - if (dest_w < src_w) src_w = dest_w; - if (dest_h < src_h) src_h = dest_h; + /* if (dest_w < src_w) src_w = dest_w; + if (dest_h < src_h) src_h = dest_h; */ *scaled_w_ret = src_w; *scaled_h_ret = src_h; @@ -222,9 +307,80 @@ compute_image_scaling (int src_w, int src_h, *scaled_to_y_ret = desty; if (verbose_p) - fprintf (stderr, "%s: displaying %dx%d image at %d,%d.\n", - progname, src_w, src_h, destx, desty); + fprintf (stderr, "%s: displaying %dx%d+%d+%d image at %d,%d in %dx%d.\n", + progname, src_w, src_h, srcx, srcy, destx, desty, dest_w, dest_h); +} + + +static void +colorbars (Screen *screen, Visual *visual, Drawable drawable, Colormap cmap) +{ + Pixmap mask = 0; + unsigned long *pixels; /* ignored - unfreed */ + int npixels; + Pixmap logo = xscreensaver_logo (screen, visual, drawable, cmap, + BlackPixelOfScreen (screen), + &pixels, &npixels, &mask, True); + draw_colorbars (screen, visual, drawable, cmap, 0, 0, 0, 0, logo, mask); + XFreePixmap (DisplayOfScreen (screen), logo); + XFreePixmap (DisplayOfScreen (screen), mask); +} + + +/* Scales an XImage, modifying it in place. + This doesn't do dithering or smoothing, so it might have artifacts. + If out of memory, returns False, and the XImage will have been + destroyed and freed. + */ +#if !defined(USE_EXTERNAL_SCREEN_GRABBER) || defined(HAVE_JPEGLIB) +static Bool +scale_ximage (Screen *screen, Visual *visual, + XImage *ximage, int new_width, int new_height) +{ + Display *dpy = DisplayOfScreen (screen); + int depth = visual_depth (screen, visual); + int x, y; + double xscale, yscale; + + XImage *ximage2 = XCreateImage (dpy, visual, depth, + ZPixmap, 0, 0, + new_width, new_height, 8, 0); + ximage2->data = (char *) calloc (ximage2->height, ximage2->bytes_per_line); + + if (!ximage2->data) + { + fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n", + progname, + ximage->width, ximage->height, + ximage2->width, ximage2->height); + if (ximage->data) free (ximage->data); + if (ximage2->data) free (ximage2->data); + ximage->data = 0; + ximage2->data = 0; + XDestroyImage (ximage); + XDestroyImage (ximage2); + return False; + } + + /* Brute force scaling... */ + xscale = (double) ximage->width / ximage2->width; + yscale = (double) ximage->height / ximage2->height; + for (y = 0; y < ximage2->height; y++) + for (x = 0; x < ximage2->width; x++) + XPutPixel (ximage2, x, y, + XGetPixel (ximage, x * xscale, y * yscale)); + + free (ximage->data); + ximage->data = 0; + + (*ximage) = (*ximage2); + + ximage2->data = 0; + XDestroyImage (ximage2); + + return True; } +#endif /* !USE_EXTERNAL_SCREEN_GRABBER || HAVE_JPEGLIB */ #ifdef HAVE_GDK_PIXBUF @@ -234,29 +390,30 @@ compute_image_scaling (int src_w, int src_h, */ static Bool read_file_gdk (Screen *screen, Window window, Drawable drawable, - const char *filename, Bool verbose_p) + const char *filename, Bool verbose_p, + XRectangle *geom_ret) { GdkPixbuf *pb; Display *dpy = DisplayOfScreen (screen); - unsigned int win_width, win_height; + unsigned int win_width, win_height, win_depth; # ifdef HAVE_GTK2 GError *gerr = 0; # endif /* HAVE_GTK2 */ + /* Find the size of the Drawable. */ { Window root; int x, y; - unsigned int bw, d; - XWindowAttributes xgwa; - XGetWindowAttributes (dpy, window, &xgwa); - screen = xgwa.screen; + unsigned int bw; XGetGeometry (dpy, drawable, - &root, &x, &y, &win_width, &win_height, &bw, &d); + &root, &x, &y, &win_width, &win_height, &bw, &win_depth); } - gdk_pixbuf_xlib_init (dpy, screen_number (screen)); + gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), win_depth); # ifdef HAVE_GTK2 +# if !GLIB_CHECK_VERSION(2, 36 ,0) g_type_init(); +# endif # else /* !HAVE_GTK2 */ xlib_rgb_init (dpy, screen); # endif /* !HAVE_GTK2 */ @@ -281,6 +438,21 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable, int w = gdk_pixbuf_get_width (pb); int h = gdk_pixbuf_get_height (pb); int srcx, srcy, destx, desty, w2, h2; + Bool bg_p = False; + +# ifdef HAVE_GDK_PIXBUF_APPLY_EMBEDDED_ORIENTATION + { + int ow = w, oh = h; + GdkPixbuf *opb = pb; + pb = gdk_pixbuf_apply_embedded_orientation (opb); + g_object_unref (opb); + w = gdk_pixbuf_get_width (pb); + h = gdk_pixbuf_get_height (pb); + if (verbose_p && (w != ow || h != oh)) + fprintf (stderr, "%s: rotated %dx%d to %dx%d\n", + progname, ow, oh, w, h); + } +# endif compute_image_scaling (w, h, win_width, win_height, verbose_p, &srcx, &srcy, &destx, &desty, &w2, &h2); @@ -290,7 +462,7 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable, GDK_INTERP_BILINEAR); if (pb2) { - gdk_pixbuf_unref (pb); + g_object_unref (pb); pb = pb2; w = w2; h = h2; @@ -299,22 +471,55 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable, fprintf (stderr, "%s: out of memory when scaling?\n", progname); } - clear_drawable (screen, drawable); + /* If we're rendering onto the root window (and it's not the + xscreensaver pseudo-root) then put the image in the window's + background. Otherwise, just paint the image onto the window. + */ + bg_p = (window == drawable && root_window_p (screen, window)); + + if (bg_p) + { + XGCValues gcv; + GC gc; + drawable = XCreatePixmap (dpy, window, + win_width, win_height, win_depth); + gcv.foreground = BlackPixelOfScreen (screen); + gc = XCreateGC (dpy, drawable, GCForeground, &gcv); + XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height); + XFreeGC (dpy, gc); + } + else + clear_drawable (screen, drawable); /* #### Note that this always uses the default colormap! Morons! Owen says that in Gnome 2.0, I should try using gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead. But I haven't tried. */ + if (srcx > 0) w -= srcx; + if (srcy > 0) h -= srcy; gdk_pixbuf_xlib_render_to_drawable_alpha (pb, drawable, srcx, srcy, destx, desty, w, h, GDK_PIXBUF_ALPHA_FULL, 127, XLIB_RGB_DITHER_NORMAL, 0, 0); - XSync (dpy, False); + if (bg_p) + { + XSetWindowBackgroundPixmap (dpy, window, drawable); + XClearWindow (dpy, window); + } + + if (geom_ret) + { + geom_ret->x = destx; + geom_ret->y = desty; + geom_ret->width = w; + geom_ret->height = h; + } } + XSync (dpy, False); return True; } @@ -326,6 +531,8 @@ read_file_gdk (Screen *screen, Window window, Drawable drawable, /* Allocates a colormap that makes a PseudoColor or DirectColor visual behave like a TrueColor visual of the same depth. + + #### Duplicated in utils/grabscreen.c */ static void allocate_cubic_colormap (Screen *screen, Visual *visual, Colormap cmap, @@ -392,6 +599,8 @@ allocate_cubic_colormap (Screen *screen, Visual *visual, Colormap cmap, /* 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 utils/grabscreen.c */ static unsigned long find_closest_pixel (XColor *colors, int ncolors, @@ -432,6 +641,8 @@ find_closest_pixel (XColor *colors, int ncolors, 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 utils/grabscreen.c */ static void remap_image (Screen *screen, Colormap cmap, XImage *image, Bool verbose_p) @@ -661,8 +872,7 @@ jpg_error_exit (j_common_ptr cinfo) { getimg_jpg_error_mgr *err = (getimg_jpg_error_mgr *) cinfo->err; cinfo->err->output_message (cinfo); - draw_colorbars (err->screen, err->visual, err->drawable, err->cmap, - 0, 0, 0, 0); + colorbars (err->screen, err->visual, err->drawable, err->cmap); XSync (DisplayOfScreen (err->screen), False); exit (1); } @@ -755,7 +965,7 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable, int x; for (x = 0; x < ximage->width; x++) { - int j = x * cinfo.num_components; + int j = x * cinfo.output_components; unsigned char r = scanbuf[i][j]; unsigned char g = scanbuf[i][j+1]; unsigned char b = scanbuf[i][j+2]; @@ -767,10 +977,12 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable, pixel = ((r >> 5) | ((g >> 5) << 3) | ((b >> 6) << 6)); else if (depth == 12) pixel = ((r >> 4) | ((g >> 4) << 4) | ((b >> 4) << 8)); - else if (depth == 16 || depth == 15) + else if (depth == 15) /* Gah! I don't understand why these are in the other order. */ pixel = (((r >> 3) << 10) | ((g >> 3) << 5) | ((b >> 3))); + else if (depth == 16) + pixel = (((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3))); else abort(); @@ -805,65 +1017,13 @@ read_jpeg_ximage (Screen *screen, Visual *visual, Drawable drawable, } -/* Scales an XImage, modifying it in place. - If out of memory, returns False, and the XImage will have been - destroyed and freed. - */ -static Bool -scale_ximage (Screen *screen, Visual *visual, - XImage *ximage, int new_width, int new_height) -{ - Display *dpy = DisplayOfScreen (screen); - int depth = visual_depth (screen, visual); - int x, y; - double xscale, yscale; - - XImage *ximage2 = XCreateImage (dpy, visual, depth, - ZPixmap, 0, 0, - new_width, new_height, 8, 0); - ximage2->data = (char *) calloc (ximage2->height, ximage2->bytes_per_line); - - if (!ximage2->data) - { - fprintf (stderr, "%s: out of memory scaling %dx%d image to %dx%d\n", - progname, - ximage->width, ximage->height, - ximage2->width, ximage2->height); - if (ximage->data) free (ximage->data); - if (ximage2->data) free (ximage2->data); - ximage->data = 0; - ximage2->data = 0; - XDestroyImage (ximage); - XDestroyImage (ximage2); - return False; - } - - /* Brute force scaling... */ - xscale = (double) ximage->width / ximage2->width; - yscale = (double) ximage->height / ximage2->height; - for (y = 0; y < ximage2->height; y++) - for (x = 0; x < ximage2->width; x++) - XPutPixel (ximage2, x, y, - XGetPixel (ximage, x * xscale, y * yscale)); - - free (ximage->data); - ximage->data = 0; - - (*ximage) = (*ximage2); - - ximage2->data = 0; - XDestroyImage (ximage2); - - return True; -} - - /* Reads the given image file and renders it on the Drawable, using JPEG lib. Returns False if it fails. */ static Bool read_file_jpeglib (Screen *screen, Window window, Drawable drawable, - const char *filename, Bool verbose_p) + const char *filename, Bool verbose_p, + XRectangle *geom_ret) { Display *dpy = DisplayOfScreen (screen); XImage *ximage; @@ -873,17 +1033,15 @@ read_file_jpeglib (Screen *screen, Window window, Drawable drawable, unsigned int win_width, win_height, win_depth; int srcx, srcy, destx, desty, w2, h2; + /* Find the size of the Drawable, and the Visual/Colormap of the Window. */ { Window root; int x, y; unsigned int bw; XWindowAttributes xgwa; - XGetWindowAttributes (dpy, window, &xgwa); - screen = xgwa.screen; visual = xgwa.visual; cmap = xgwa.colormap; - XGetGeometry (dpy, drawable, &root, &x, &y, &win_width, &win_height, &bw, &win_depth); } @@ -925,16 +1083,45 @@ read_file_jpeglib (Screen *screen, Window window, Drawable drawable, /* Finally, put the resized image on the window. */ - clear_drawable (screen, drawable); { GC gc; XGCValues gcv; - gc = XCreateGC (dpy, drawable, 0, &gcv); - XPutImage (dpy, drawable, gc, ximage, - srcx, srcy, destx, desty, ximage->width, ximage->height); + + /* If we're rendering onto the root window (and it's not the xscreensaver + pseudo-root) then put the image in the window's background. Otherwise, + just paint the image onto the window. + */ + if (window == drawable && root_window_p (screen, window)) + { + Pixmap bg = XCreatePixmap (dpy, window, + win_width, win_height, win_depth); + gcv.foreground = BlackPixelOfScreen (screen); + gc = XCreateGC (dpy, drawable, GCForeground, &gcv); + XFillRectangle (dpy, bg, gc, 0, 0, win_width, win_height); + XPutImage (dpy, bg, gc, ximage, + srcx, srcy, destx, desty, ximage->width, ximage->height); + XSetWindowBackgroundPixmap (dpy, window, bg); + XClearWindow (dpy, window); + } + else + { + gc = XCreateGC (dpy, drawable, 0, &gcv); + clear_drawable (screen, drawable); + XPutImage (dpy, drawable, gc, ximage, + srcx, srcy, destx, desty, ximage->width, ximage->height); + } + XFreeGC (dpy, gc); } + if (geom_ret) + { + geom_ret->x = destx; + geom_ret->y = desty; + geom_ret->width = ximage->width; + geom_ret->height = ximage->height; + } + free (ximage->data); ximage->data = 0; XDestroyImage (ximage); @@ -950,16 +1137,18 @@ read_file_jpeglib (Screen *screen, Window window, Drawable drawable, */ static Bool display_file (Screen *screen, Window window, Drawable drawable, - const char *filename, Bool verbose_p) + const char *filename, Bool verbose_p, + XRectangle *geom_ret) { if (verbose_p) fprintf (stderr, "%s: loading \"%s\"\n", progname, filename); # if defined(HAVE_GDK_PIXBUF) - if (read_file_gdk (screen, window, drawable, filename, verbose_p)) + if (read_file_gdk (screen, window, drawable, filename, verbose_p, geom_ret)) return True; # elif defined(HAVE_JPEGLIB) - if (read_file_jpeglib (screen, window, drawable, filename, verbose_p)) + if (read_file_jpeglib (screen, window, drawable, filename, verbose_p, + geom_ret)) return True; # else /* !(HAVE_GDK_PIXBUF || HAVE_JPEGLIB) */ /* shouldn't get here if we have no image-loading methods available. */ @@ -971,35 +1160,49 @@ display_file (Screen *screen, Window window, Drawable drawable, /* Invokes a sub-process and returns its output (presumably, a file to - load.) Free the string when done. video_p controls which program - to run. + load.) Free the string when done. 'grab_type' controls which program + to run. Returned pathname may be relative to 'directory', or absolute. */ static char * -get_filename_1 (Screen *screen, const char *directory, Bool video_p, +get_filename_1 (Screen *screen, const char *directory, grab_type type, Bool verbose_p) { Display *dpy = DisplayOfScreen (screen); pid_t forked; int fds [2]; int in, out; - char buf[1024]; + char buf[10240]; char *av[20]; int ac = 0; - if (!video_p) + switch (type) { + case GRAB_FILE: av[ac++] = GETIMAGE_FILE_PROGRAM; if (verbose_p) av[ac++] = "--verbose"; av[ac++] = "--name"; av[ac++] = (char *) directory; - } - else - { + break; + + case GRAB_VIDEO: av[ac++] = GETIMAGE_VIDEO_PROGRAM; if (verbose_p) av[ac++] = "--verbose"; av[ac++] = "--name"; + break; + +# ifdef USE_EXTERNAL_SCREEN_GRABBER + case GRAB_DESK: + av[ac++] = GETIMAGE_SCREEN_PROGRAM; + if (verbose_p) + av[ac++] = "--verbose"; + av[ac++] = "--name"; + break; +# endif + + default: + abort(); } av[ac] = 0; @@ -1053,10 +1256,12 @@ get_filename_1 (Screen *screen, const char *directory, Bool video_p, int wait_status = 0; FILE *f = fdopen (in, "r"); int L; + char *ret = 0; close (out); /* don't need this one */ *buf = 0; - fgets (buf, sizeof(buf)-1, f); + if (! fgets (buf, sizeof(buf)-1, f)) + *buf = 0; fclose (f); /* Wait for the child to die. */ @@ -1068,14 +1273,28 @@ get_filename_1 (Screen *screen, const char *directory, Bool video_p, if (!*buf) return 0; + + ret = strdup (buf); + + if (*ret != '/') + { + /* Program returned path relative to directory. Prepend dir + to buf so that we can properly stat it. */ + strcpy (buf, directory); + if (directory[strlen(directory)-1] != '/') + strcat (buf, "/"); + strcat (buf, ret); + } + if (stat(buf, &st)) { fprintf (stderr, "%s: file does not exist: \"%s\"\n", progname, buf); + free (ret); return 0; } else - return strdup (buf); + return ret; } } @@ -1088,7 +1307,7 @@ get_filename_1 (Screen *screen, const char *directory, Bool video_p, static char * get_filename (Screen *screen, const char *directory, Bool verbose_p) { - return get_filename_1 (screen, directory, False, verbose_p); + return get_filename_1 (screen, directory, GRAB_FILE, verbose_p); } @@ -1098,8 +1317,19 @@ get_filename (Screen *screen, const char *directory, Bool verbose_p) static char * get_video_filename (Screen *screen, Bool verbose_p) { - return get_filename_1 (screen, 0, True, verbose_p); + return get_filename_1 (screen, 0, GRAB_VIDEO, verbose_p); +} + +/* Grabs a desktop image to a file, and returns a pathname to that file. + Delete that file when you are done with it (and free the string.) + */ +# ifdef USE_EXTERNAL_SCREEN_GRABBER +static char * +get_desktop_filename (Screen *screen, Bool verbose_p) +{ + return get_filename_1 (screen, 0, GRAB_DESK, verbose_p); } +#endif /* USE_EXTERNAL_SCREEN_GRABBER */ /* Grabs a video frame, and renders it on the Drawable. @@ -1107,7 +1337,7 @@ get_video_filename (Screen *screen, Bool verbose_p) */ static Bool display_video (Screen *screen, Window window, Drawable drawable, - Bool verbose_p) + Bool verbose_p, XRectangle *geom_ret) { char *filename = get_video_filename (screen, verbose_p); Bool status; @@ -1119,7 +1349,68 @@ display_video (Screen *screen, Window window, Drawable drawable, return False; } - status = display_file (screen, window, drawable, filename, verbose_p); + status = display_file (screen, window, drawable, filename, verbose_p, + geom_ret); + + if (unlink (filename)) + { + char buf[512]; + sprintf (buf, "%s: rm %.100s", progname, filename); + perror (buf); + } + else if (verbose_p) + fprintf (stderr, "%s: rm %s\n", progname, filename); + + if (filename) free (filename); + return status; +} + + +/* Grabs a desktop screen shot onto the window and the drawable. + If the window and drawable are not the same size, the image in + the drawable is scaled to fit. + Returns False if it fails. + */ +static Bool +display_desktop (Screen *screen, Window window, Drawable drawable, + Bool verbose_p, XRectangle *geom_ret) +{ +# ifdef USE_EXTERNAL_SCREEN_GRABBER + + Display *dpy = DisplayOfScreen (screen); + Bool top_p = top_level_window_p (screen, window); + char *filename; + Bool status; + + if (top_p) + { + if (verbose_p) + fprintf (stderr, "%s: unmapping 0x%lx.\n", progname, + (unsigned long) window); + XUnmapWindow (dpy, window); + XSync (dpy, False); + } + + filename = get_desktop_filename (screen, verbose_p); + + if (top_p) + { + if (verbose_p) + fprintf (stderr, "%s: mapping 0x%lx.\n", progname, + (unsigned long) window); + XMapRaised (dpy, window); + XSync (dpy, False); + } + + if (!filename) + { + if (verbose_p) + fprintf (stderr, "%s: desktop grab failed.\n", progname); + return False; + } + + status = display_file (screen, window, drawable, filename, verbose_p, + geom_ret); if (unlink (filename)) { @@ -1132,6 +1423,106 @@ display_video (Screen *screen, Window window, Drawable drawable, if (filename) free (filename); return status; + +# else /* !USE_EXTERNAL_SCREEN_GRABBER */ + + Display *dpy = DisplayOfScreen (screen); + XGCValues gcv; + XWindowAttributes xgwa; + Window root; + int px, py; + unsigned int pw, ph, pbw, pd; + int srcx, srcy, destx, desty, w2, h2; + + if (verbose_p) + { + fprintf (stderr, "%s: grabbing desktop image\n", progname); + grabscreen_verbose(); + } + + XGetWindowAttributes (dpy, window, &xgwa); + XGetGeometry (dpy, drawable, &root, &px, &py, &pw, &ph, &pbw, &pd); + + grab_screen_image_internal (screen, window); + + compute_image_scaling (xgwa.width, xgwa.height, + pw, ph, verbose_p, + &srcx, &srcy, &destx, &desty, &w2, &h2); + + if (pw == w2 && ph == h2) /* it fits -- just copy server-side pixmaps */ + { + GC gc = XCreateGC (dpy, drawable, 0, &gcv); + XCopyArea (dpy, window, drawable, gc, + 0, 0, xgwa.width, xgwa.height, 0, 0); + XFreeGC (dpy, gc); + } + else /* size mismatch -- must scale client-side images to fit drawable */ + { + GC gc; + XImage *ximage = 0; + XErrorHandler old_handler; + + XSync (dpy, False); + old_handler = XSetErrorHandler (ignore_badmatch_ehandler); + error_handler_hit_p = False; + + /* This can return BadMatch if the window is not fully on screen. + Trap that error and return color bars in that case. + (Note that this only happens with XGetImage, not with XCopyArea: + yet another totally gratuitous inconsistency in X, thanks.) + */ + ximage = XGetImage (dpy, window, 0, 0, xgwa.width, xgwa.height, + ~0L, ZPixmap); + + XSync (dpy, False); + XSetErrorHandler (old_handler); + XSync (dpy, False); + + if (error_handler_hit_p) + { + ximage = 0; + if (verbose_p) + fprintf (stderr, "%s: BadMatch reading window 0x%x contents!\n", + progname, (unsigned int) window); + } + + if (!ximage || + !scale_ximage (xgwa.screen, xgwa.visual, ximage, w2, h2)) + return False; + + gc = XCreateGC (dpy, drawable, 0, &gcv); + clear_drawable (screen, drawable); + XPutImage (dpy, drawable, gc, ximage, + srcx, srcy, destx, desty, ximage->width, ximage->height); + XDestroyImage (ximage); + XFreeGC (dpy, gc); + } + + if (geom_ret) + { + geom_ret->x = destx; + geom_ret->y = desty; + geom_ret->width = w2; + geom_ret->height = h2; + } + + XSync (dpy, False); + return True; + +# endif /* !USE_EXTERNAL_SCREEN_GRABBER */ +} + + +/* Whether the given Drawable is unreasonably small. + */ +static Bool +drawable_miniscule_p (Display *dpy, Drawable drawable) +{ + Window root; + int xx, yy; + unsigned int bw, d, w = 0, h = 0; + XGetGeometry (dpy, drawable, &root, &xx, &yy, &w, &h, &bw, &d); + return (w < 32 || h < 30); } @@ -1150,9 +1541,11 @@ get_image (Screen *screen, const char *file) { Display *dpy = DisplayOfScreen (screen); - enum { do_desk, do_video, do_image, do_bars } which = do_bars; - int count = 0; + grab_type which = GRAB_BARS; struct stat st; + const char *file_prop = 0; + char *absfile = 0; + XRectangle geom = { 0, 0, 0, 0 }; if (! drawable_window_p (dpy, window)) { @@ -1161,6 +1554,13 @@ get_image (Screen *screen, exit (1); } + /* Make sure the Screen and the Window correspond. */ + { + XWindowAttributes xgwa; + XGetWindowAttributes (dpy, window, &xgwa); + screen = xgwa.screen; + } + if (file && stat (file, &st)) { fprintf (stderr, "%s: file \"%s\" does not exist\n", progname, file); @@ -1181,6 +1581,10 @@ get_image (Screen *screen, # if !(defined(HAVE_GDK_PIXBUF) || defined(HAVE_JPEGLIB)) image_p = False; /* can't load images from files... */ +# ifdef USE_EXTERNAL_SCREEN_GRABBER + desk_p = False; /* ...or from desktops grabbed to files. */ +# endif + if (file) { fprintf (stderr, @@ -1197,8 +1601,7 @@ get_image (Screen *screen, video_p = False; image_p = True; } - - if (!dir || !*dir) + else if (!dir || !*dir) { if (verbose_p && image_p) fprintf (stderr, @@ -1207,18 +1610,30 @@ get_image (Screen *screen, image_p = False; } + /* If the target drawable is really small, no good can come of that. + Always do colorbars in that case. + */ + if (drawable_miniscule_p (dpy, drawable)) + { + desk_p = False; + video_p = False; + image_p = False; + } # ifndef _VROOT_H_ # error Error! This file definitely needs vroot.h! # endif - /* We can grab desktop images if: + /* We can grab desktop images (using the normal X11 method) if: - the window is the real root window; - - the window is the virtal root window; - the window is a toplevel window. - We cannot grab desktop images if: + We cannot grab desktop images that way if: - the window is a non-top-level window. + + Under X11 on MacOS, desktops are just like loaded image files. + Under Cocoa on MacOS, this code is not used at all. */ +# ifndef USE_EXTERNAL_SCREEN_GRABBER if (desk_p) { if (!top_level_window_p (screen, window)) @@ -1230,36 +1645,41 @@ get_image (Screen *screen, progname, (unsigned int) window); } } +# endif /* !USE_EXTERNAL_SCREEN_GRABBER */ - count = 0; - if (desk_p) count++; - if (video_p) count++; - if (image_p) count++; - - if (count == 0) - which = do_bars; + if (! (desk_p || video_p || image_p)) + which = GRAB_BARS; else { int i = 0; - while (1) /* loop until we get one that's permitted */ - { - which = (random() % 3); - if (which == do_desk && desk_p) break; - if (which == do_video && video_p) break; - if (which == do_image && image_p) break; - if (++i > 200) abort(); - } + int n; + /* Loop until we get one that's permitted. + If files or video are permitted, do them more often + than desktop. + + D+V+I: 10% + 45% + 45%. + V+I: 50% + 50% + D+V: 18% + 82% + D+I: 18% + 82% + */ + AGAIN: + n = (random() % 100); + if (++i > 300) abort(); + else if (desk_p && n < 10) which = GRAB_DESK; /* 10% */ + else if (video_p && n < 55) which = GRAB_VIDEO; /* 45% */ + else if (image_p) which = GRAB_FILE; /* 45% */ + else goto AGAIN; } /* If we're to search a directory to find an image file, do so now. */ - if (which == do_image && !file) + if (which == GRAB_FILE && !file) { file = get_filename (screen, dir, verbose_p); if (!file) { - which = do_bars; + which = GRAB_BARS; if (verbose_p) fprintf (stderr, "%s: no image files found.\n", progname); } @@ -1267,48 +1687,93 @@ get_image (Screen *screen, /* Now actually render something. */ - if (which == do_bars) - { - XWindowAttributes xgwa; - COLORBARS: - if (verbose_p) - fprintf (stderr, "%s: drawing colorbars.\n", progname); - XGetWindowAttributes (dpy, window, &xgwa); - draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap, - 0, 0, 0, 0); - XSync (dpy, False); - } - else if (which == do_desk) + switch (which) { - GC gc; - XGCValues gcv; - XWindowAttributes xgwa; + case GRAB_BARS: + { + XWindowAttributes xgwa; + COLORBARS: + if (verbose_p) + fprintf (stderr, "%s: drawing colorbars.\n", progname); + XGetWindowAttributes (dpy, window, &xgwa); + colorbars (screen, xgwa.visual, drawable, xgwa.colormap); + XSync (dpy, False); + if (! file_prop) file_prop = ""; - if (verbose_p) + } + break; + + case GRAB_DESK: + if (! display_desktop (screen, window, drawable, verbose_p, &geom)) + goto COLORBARS; + file_prop = "desktop"; + break; + + case GRAB_FILE: + if (*file && *file != '/') /* pathname is relative to dir. */ { - fprintf (stderr, "%s: grabbing desktop image\n", progname); - grabscreen_verbose(); + if (absfile) free (absfile); + absfile = malloc (strlen(dir) + strlen(file) + 10); + strcpy (absfile, dir); + if (dir[strlen(dir)-1] != '/') + strcat (absfile, "/"); + strcat (absfile, file); } - gc = XCreateGC (dpy, drawable, 0, &gcv); - XGetWindowAttributes (dpy, window, &xgwa); - grab_screen_image (screen, window); - XCopyArea (dpy, window, drawable, gc, - 0, 0, xgwa.width, xgwa.height, 0, 0); - XFreeGC (dpy, gc); - XSync (dpy, False); - } - else if (which == do_image) - { - if (! display_file (screen, window, drawable, file, verbose_p)) + if (! display_file (screen, window, drawable, + (absfile ? absfile : file), + verbose_p, &geom)) goto COLORBARS; - } - else if (which == do_video) - { - if (! display_video (screen, window, drawable, verbose_p)) + file_prop = file; + break; + + case GRAB_VIDEO: + if (! display_video (screen, window, drawable, verbose_p, &geom)) goto COLORBARS; + file_prop = "video"; + break; + + default: + abort(); + break; } - else - abort(); + + { + Atom a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False); + if (file_prop && *file_prop) + { + char *f2 = strdup (file_prop); + + /* Take the extension off of the file name. */ + /* Duplicated in utils/grabclient.c. */ + char *slash = strrchr (f2, '/'); + char *dot = strrchr ((slash ? slash : f2), '.'); + if (dot) *dot = 0; + /* Replace slashes with newlines */ + /* while ((dot = strchr(f2, '/'))) *dot = '\n'; */ + /* Replace slashes with spaces */ + /* while ((dot = strchr(f2, '/'))) *dot = ' '; */ + + XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace, + (unsigned char *) f2, strlen(f2)); + free (f2); + } + else + XDeleteProperty (dpy, window, a); + + a = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False); + if (geom.width > 0) + { + char gstr[30]; + sprintf (gstr, "%dx%d+%d+%d", geom.width, geom.height, geom.x, geom.y); + XChangeProperty (dpy, window, a, XA_STRING, 8, PropModeReplace, + (unsigned char *) gstr, strlen (gstr)); + } + else + XDeleteProperty (dpy, window, a); + } + + if (absfile) free (absfile); + XSync (dpy, False); } @@ -1433,7 +1898,7 @@ main (int argc, char **argv) memset (&P, 0, sizeof(P)); P.db = db; - load_init_file (&P); + load_init_file (dpy, &P); progname = argv[0] = oprogname; @@ -1465,7 +1930,7 @@ main (int argc, char **argv) goto LOSE; } window_str = argv[i]; - window = (Window) RootWindowOfScreen (screen); + window = VirtualRootWindowOfScreen (screen); } else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) || 1 == sscanf (argv[i], " %lu %c", &w, &dummy)) &&