X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=utils%2Fgrabclient.c;h=a1a360e476d8dbc23dc5b866250a325da96cc7fb;hb=d6b0217f2417bd19187f0ebc389d6c5c2233b11c;hp=dbf0aec7858e7eff5d3254cf239208e48c99d513;hpb=2d04c4f22466851aedb6ed0f2919d148f726b889;p=xscreensaver diff --git a/utils/grabclient.c b/utils/grabclient.c index dbf0aec7..a1a360e4 100644 --- a/utils/grabclient.c +++ b/utils/grabclient.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1992-2005 Jamie Zawinski +/* xscreensaver, Copyright (c) 1992-2016 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 @@ -18,15 +18,85 @@ same API as this program (utils/grabclient.c). */ +/* This code is a mess. There's two decades of history in this file. + There are several distinct paths through this file depending on what + platform it's being compiled for: + + + X11 execution path: + + load_image_async CB + load_random_image_x11 + fork_exec_cb + "xscreensaver-getimage 0xWINDOW 0xPIXMAP" + "xscreensaver-getimage-file --name /DIR" + draw_colorbars + XPutImage + XtAppAddInput xscreensaver_getimage_cb + ... + xscreensaver_getimage_cb + get_name_from_xprops + get_original_geometry_from_xprops + CB name, geom, closure + + + MacOS execution path: + + load_image_async CB + load_random_image_cocoa + osx_grab_desktop_image (grabclient-osx.m, MacOS version) + copy_framebuffer_to_ximage + XPutImage + draw_colorbars + osx_load_image_file_async + open_image_name_pipe + "xscreensaver-getimage-file --name /DIR" + XtAppAddInput xscreensaver_getimage_file_cb + ... + xscreensaver_getimage_file_cb + osx_load_image_file + CB name, geom, closure + + + iOS execution path: + + load_image_async CB + load_random_image_cocoa + osx_grab_desktop_image (grabclient-osx.m, iOS version) + CGWindowListCreateImage + jwxyz_draw_NSImage_or_CGImage + draw_colorbars + ios_load_random_image + ios_load_random_image_cb + jwxyz_draw_NSImage_or_CGImage + CB name, geom, closure + + + Andrid execution path: + + load_image_async CB + load_random_image_android + jwxyz_load_random_image (jwxyz-android.c) + XPutImage + draw_colorbars + CB name, geom, closure + */ + #include "utils.h" #include "grabscreen.h" #include "resources.h" +#include "yarandom.h" -#include "vroot.h" -#include - -#include /* for XtInputId, etc */ +#ifdef HAVE_JWXYZ +# include "jwxyz.h" +# include "colorbars.h" +#else /* !HAVE_COCOA -- real Xlib */ +# include "vroot.h" +# include +# include /* for XtInputId, etc */ +#endif /* !HAVE_COCOA */ +#include #ifdef HAVE_UNISTD_H # include @@ -37,8 +107,38 @@ extern char *progname; -extern XtAppContext app; +static void print_loading_msg (Screen *, Window); + + +/* Used for pipe callbacks in X11 or OSX mode. + X11: this is the xscreensaver_getimage_cb closure, + when waiting on the fork of "xscreensaver-getimage" + OSX: this is the xscreensaver_getimage_file_cb closure, + when waiting on the fork of "xscreensaver-getimage-file" + */ +typedef struct { + void (*callback) (Screen *, Window, Drawable, + const char *name, XRectangle *geom, void *closure); + Screen *screen; + Window window; + Drawable drawable; + void *closure; + XtInputId pipe_id; + FILE *pipe; + +# if !defined(USE_IPHONE) && !defined(HAVE_COCOA) /* Real X11 */ + pid_t pid; +# endif + +# if !defined(USE_IPHONE) && defined(HAVE_COCOA) /* Desktop OSX */ + char *directory; +# endif + +} xscreensaver_getimage_data; + + +#if !defined(HAVE_COCOA) && !defined(HAVE_ANDROID) /* Real X11 */ static Bool error_handler_hit_p = False; @@ -100,7 +200,7 @@ xscreensaver_window_p (Display *dpy, Window window) explain. */ Bool -use_subwindow_mode_p(Screen *screen, Window window) +use_subwindow_mode_p (Screen *screen, Window window) { if (window != VirtualRootWindowOfScreen(screen)) return False; @@ -165,11 +265,15 @@ checkerboard (Screen *screen, Drawable drawable) XFillRectangle (dpy, drawable, gc, x, y, size, size); XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size); } + XFreeGC (dpy, gc); } +/* Read the image's original name off of the window's X properties. + Used only when running "real" X11, not jwxyz. + */ static char * -get_name (Display *dpy, Window window) +get_name_from_xprops (Display *dpy, Window window) { Atom type; int format; @@ -182,13 +286,17 @@ get_name (Display *dpy, Window window) &name) == Success && type != None) - return strdup((char *) name); + return (char *) name; else return 0; } + +/* Read the image's original geometry off of the window's X properties. + Used only when running "real" X11, not jwxyz. + */ static Bool -get_geometry (Display *dpy, Window window, XRectangle *ret) +get_original_geometry_from_xprops (Display *dpy, Window window, XRectangle *ret) { Atom type; int format; @@ -205,6 +313,7 @@ get_geometry (Display *dpy, Window window, XRectangle *ret) && type != None) { int flags = XParseGeometry ((char *) name, &x, &y, &w, &h); + free (name); /* Require all four, and don't allow negative positions. */ if (flags == (XValue|YValue|WidthValue|HeightValue)) { @@ -222,7 +331,6 @@ get_geometry (Display *dpy, Window window, XRectangle *ret) } - static void hack_subproc_environment (Display *dpy) { @@ -236,10 +344,15 @@ hack_subproc_environment (Display *dpy) /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems any more, right? It's not Posix, but everyone seems to have it. */ -#ifdef HAVE_PUTENV +# ifdef HAVE_PUTENV if (putenv (ndpy)) abort (); -#endif /* HAVE_PUTENV */ +# endif /* HAVE_PUTENV */ + + /* don't free (ndpy) -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2, MacOS) + do not. So we must leak it (and/or the previous setting). Yay. + */ } @@ -249,8 +362,10 @@ hack_subproc_environment (Display *dpy) this is due to the intermediate /bin/sh that system() uses to parse arguments? I'm not sure. But using fork() and execvp() here seems to close the race. - */ + Used to execute "xscreensaver-getimage". + Used only when running "real" X11, not jwxyz. + */ static void exec_simple_command (const char *command) { @@ -267,47 +382,14 @@ exec_simple_command (const char *command) execvp (av[0], av); /* shouldn't return. */ } -static void -fork_exec_wait (const char *command) -{ - char buf [255]; - pid_t forked; - int status; - - switch ((int) (forked = fork ())) - { - case -1: - sprintf (buf, "%s: couldn't fork", progname); - perror (buf); - return; - - case 0: - exec_simple_command (command); - exit (1); /* exits child fork */ - break; - - default: - waitpid (forked, &status, 0); - break; - } -} - - -typedef struct { - void (*callback) (Screen *, Window, Drawable, - const char *name, XRectangle *geom, void *closure); - Screen *screen; - Window window; - Drawable drawable; - void *closure; - FILE *read_pipe; - FILE *write_pipe; - XtInputId pipe_id; -} grabclient_data; - -static void finalize_cb (XtPointer closure, int *fd, XtIntervalId *id); +static void xscreensaver_getimage_cb (XtPointer closure, + int *fd, XtIntervalId *id); +/* Spawn a program, and run the callback when it finishes. + Used to execute "xscreensaver-getimage". + Used only when running "real" X11, not jwxyz. + */ static void fork_exec_cb (const char *command, Screen *screen, Window window, Drawable drawable, @@ -316,9 +398,11 @@ fork_exec_cb (const char *command, void *closure), void *closure) { - grabclient_data *data; + XtAppContext app = XtDisplayToApplicationContext (DisplayOfScreen (screen)); + xscreensaver_getimage_data *data; char buf [255]; pid_t forked; + FILE *wpipe; int fds [2]; @@ -329,16 +413,16 @@ fork_exec_cb (const char *command, exit (1); } - data = (grabclient_data *) calloc (1, sizeof(*data)); + data = (xscreensaver_getimage_data *) calloc (1, sizeof(*data)); data->callback = callback; data->closure = closure; data->screen = screen; data->window = window; data->drawable = drawable; - data->read_pipe = fdopen (fds[0], "r"); - data->write_pipe = fdopen (fds[1], "w"); + data->pipe = fdopen (fds[0], "r"); + wpipe = fdopen (fds[1], "w"); /* Is this necessary? */ - if (!data->read_pipe || !data->write_pipe) + if (!data->pipe || !wpipe) { sprintf (buf, "%s: fdopen", progname); perror (buf); @@ -346,11 +430,12 @@ fork_exec_cb (const char *command, } data->pipe_id = - XtAppAddInput (app, fileno (data->read_pipe), + XtAppAddInput (app, fileno (data->pipe), (XtPointer) (XtInputReadMask | XtInputExceptMask), - finalize_cb, (XtPointer) data); + xscreensaver_getimage_cb, (XtPointer) data); - switch ((int) (forked = fork ())) + forked = fork (); + switch ((int) forked) { case -1: sprintf (buf, "%s: couldn't fork", progname); @@ -359,8 +444,8 @@ fork_exec_cb (const char *command, case 0: /* child */ - fclose (data->read_pipe); - data->read_pipe = 0; + fclose (data->pipe); + data->pipe = 0; /* clone the write pipe onto stdout so that it gets closed when the fork exits. This will shut down the pipe and @@ -377,8 +462,8 @@ fork_exec_cb (const char *command, break; default: /* parent */ - fclose (data->write_pipe); - data->write_pipe = 0; + fclose (wpipe); + data->pid = forked; break; } } @@ -386,25 +471,35 @@ fork_exec_cb (const char *command, /* Called in the parent when the forked process dies. Runs the caller's callback, and cleans up. + This runs when "xscreensaver-getimage" exits. + Used only when running "real" X11, not jwxyz. */ static void -finalize_cb (XtPointer closure, int *fd, XtIntervalId *id) +xscreensaver_getimage_cb (XtPointer closure, int *fd, XtIntervalId *id) { - grabclient_data *data = (grabclient_data *) closure; + xscreensaver_getimage_data *data = (xscreensaver_getimage_data *) closure; Display *dpy = DisplayOfScreen (data->screen); char *name; XRectangle geom = { 0, 0, 0, 0 }; XtRemoveInput (*id); - name = get_name (dpy, data->window); - get_geometry (dpy, data->window, &geom); + name = get_name_from_xprops (dpy, data->window); + get_original_geometry_from_xprops (dpy, data->window, &geom); data->callback (data->screen, data->window, data->drawable, name, &geom, data->closure); if (name) free (name); - fclose (data->read_pipe); + fclose (data->pipe); + + if (data->pid) /* reap zombies */ + { + int status; + waitpid (data->pid, &status, 0); + data->pid = 0; + } + memset (data, 0, sizeof (*data)); free (data); } @@ -412,18 +507,17 @@ finalize_cb (XtPointer closure, int *fd, XtIntervalId *id) /* Loads an image into the Drawable. When grabbing desktop images, the Window will be unmapped first. + Used only when running "real" X11, not jwxyz. */ static void -load_random_image_1 (Screen *screen, Window window, Drawable drawable, - void (*callback) (Screen *, Window, Drawable, - const char *name, XRectangle *geom, - void *closure), - void *closure, - char **name_ret, - XRectangle *geom_ret) +load_random_image_x11 (Screen *screen, Window window, Drawable drawable, + void (*callback) (Screen *, Window, Drawable, + const char *name, XRectangle *geom, + void *closure), + void *closure) { Display *dpy = DisplayOfScreen (screen); - char *grabber = get_string_resource ("desktopGrabber", "DesktopGrabber"); + char *grabber = get_string_resource(dpy, "desktopGrabber", "DesktopGrabber"); char *cmd; char id[200]; @@ -448,100 +542,505 @@ load_random_image_1 (Screen *screen, Window window, Drawable drawable, then the game is already over. */ sprintf (cmd, grabber, id); + free (grabber); + grabber = 0; /* In case "cmd" fails, leave some random image on the screen, not just black or white, so that it's more obvious what went wrong. */ checkerboard (screen, drawable); + if (window == drawable) + print_loading_msg (screen, window); XSync (dpy, True); hack_subproc_environment (dpy); - if (callback) + /* Start the image loading in another fork and return immediately. + Invoke the callback function when done. */ + fork_exec_cb (cmd, screen, window, drawable, callback, closure); + + free (cmd); + XSync (dpy, True); +} + +#elif defined (HAVE_COCOA) /* OSX or iOS */ + +# ifndef USE_IPHONE /* HAVE_COCOA && !USE_IPHONE -- desktop OSX */ + +# define BACKSLASH(c) \ + (! ((c >= 'a' && c <= 'z') || \ + (c >= 'A' && c <= 'Z') || \ + (c >= '0' && c <= '9') || \ + c == '.' || c == '_' || c == '-' || c == '+' || c == '/')) + +/* Gets the name of an image file to load by running xscreensaver-getimage-file + at the end of a pipe. This can be very slow! + */ +static FILE * +open_image_name_pipe (const char *dir) +{ + char *s; + + /* /bin/sh on OS X 10.10 wipes out the PATH. */ + const char *path = getenv("PATH"); + char *cmd = s = malloc ((strlen(dir) + strlen(path)) * 2 + 100); + strcpy (s, "/bin/sh -c 'export PATH="); + s += strlen (s); + while (*path) { + char c = *path++; + if (BACKSLASH(c)) *s++ = '\\'; + *s++ = c; + } + strcpy (s, "; "); + s += strlen (s); + + strcpy (s, "xscreensaver-getimage-file --name "); + s += strlen (s); + while (*dir) { + char c = *dir++; + if (BACKSLASH(c)) *s++ = '\\'; + *s++ = c; + } + + strcpy (s, "'"); + s += strlen (s); + + *s = 0; + + FILE *pipe = popen (cmd, "r"); + free (cmd); + return pipe; +} + + +static void +xscreensaver_getimage_file_cb (XtPointer closure, int *source, XtInputId *id) +{ + /* This is not called from a signal handler, so doing stuff here is fine. + */ + xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure; + char buf[10240]; + const char *dir = clo2->directory; + char *absfile = 0; + *buf = 0; + fgets (buf, sizeof(buf)-1, clo2->pipe); + pclose (clo2->pipe); + clo2->pipe = 0; + XtRemoveInput (clo2->pipe_id); + clo2->pipe_id = 0; + + /* strip trailing newline */ + int L = strlen(buf); + while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n')) + buf[--L] = 0; + + Display *dpy = DisplayOfScreen (clo2->screen); + XRectangle geom; + + if (*buf && *buf != '/') /* pathname is relative to dir. */ { - /* Start the image loading in another fork and return immediately. - Invoke the callback function when done. - */ - if (name_ret) abort(); - fork_exec_cb (cmd, screen, window, drawable, callback, closure); + absfile = malloc (strlen(dir) + strlen(buf) + 10); + strcpy (absfile, dir); + if (dir[strlen(dir)-1] != '/') + strcat (absfile, "/"); + strcat (absfile, buf); } - else + + if (! osx_load_image_file (clo2->screen, clo2->window, clo2->drawable, + (absfile ? absfile : buf), &geom)) { + /* unable to load image - draw colorbars + */ + XWindowAttributes xgwa; + XGetWindowAttributes (dpy, clo2->window, &xgwa); + Window r; + int x, y; + unsigned int w, h, bbw, d; + struct stat st; + + /* Log something to syslog so we can tell the difference between + corrupted images and broken symlinks. */ + if (!*buf) + fprintf (stderr, "%s: no image filename found\n", progname); + else if (! stat (buf, &st)) + fprintf (stderr, "%s: %s: unparsable\n", progname, buf); + else + { + char buf2[2048]; + sprintf (buf2, "%.255s: %.1024s", progname, buf); + perror (buf2); + } + + XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d); + draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap, + 0, 0, w, h); + geom.x = geom.y = 0; + geom.width = w; + geom.height = h; + } + + /* Take the extension off of the file name. */ + /* Duplicated in driver/xscreensaver-getimage.c. */ + if (*buf) { - /* Wait for the image to load, and return it immediately. - */ - fork_exec_wait (cmd); - if (name_ret) - *name_ret = get_name (dpy, window); - if (geom_ret) - get_geometry (dpy, window, geom_ret); + char *slash = strrchr (buf, '/'); + char *dot = strrchr ((slash ? slash : buf), '.'); + if (dot) *dot = 0; + /* Replace slashes with newlines */ + /* while (dot = strchr(buf, '/')) *dot = '\n'; */ + /* Replace slashes with spaces */ + /* while ((dot = strchr(buf, '/'))) *dot = ' '; */ } - free (cmd); - XSync (dpy, True); + if (absfile) free (absfile); + clo2->callback (clo2->screen, clo2->window, clo2->drawable, buf, &geom, + clo2->closure); + clo2->callback = 0; + free (clo2->directory); + free (clo2); } -/* Loads an image into the Drawable in the background; - when the image is fully loaded, runs the callback. - When grabbing desktop images, the Window will be unmapped first. +# else /* HAVE_COCOA && USE_IPHONE -- iOS */ + +/* Callback for ios_load_random_image(), called after we have loaded an + image from the iOS device's Photo Library. See grabclient-ios.m. */ -void -fork_load_random_image (Screen *screen, Window window, Drawable drawable, - void (*callback) (Screen *, Window, Drawable, - const char *name, XRectangle *geom, - void *closure), - void *closure) +static void +ios_load_random_image_cb (void *uiimage, const char *filename, + int width, int height, void *closure) { - load_random_image_1 (screen, window, drawable, callback, closure, 0, 0); + xscreensaver_getimage_data *clo2 = (xscreensaver_getimage_data *) closure; + Display *dpy = DisplayOfScreen (clo2->screen); + XRectangle geom; + XWindowAttributes xgwa; + Window r; + int x, y; + unsigned int w, h, bbw, d; + int rot = 0; + + XGetWindowAttributes (dpy, clo2->window, &xgwa); + XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d); + + /* If the image is portrait and the window is landscape, or vice versa, + rotate the image. The idea is to fill up as many pixels as possible, + and assume the user will just rotate their phone until it looks right. + This makes "decayscreen", etc. much more easily viewable. + */ + if (get_boolean_resource (dpy, "rotateImages", "RotateImages")) { + if ((width > height) != (w > h)) + rot = 5; + } + + if (uiimage) + { + jwxyz_draw_NSImage_or_CGImage (DisplayOfScreen (clo2->screen), + clo2->drawable, + True, uiimage, &geom, + rot); + } + else /* Probably means no images in the gallery. */ + { + draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap, + 0, 0, w, h); + geom.x = geom.y = 0; + geom.width = w; + geom.height = h; + filename = 0; + } + + clo2->callback (clo2->screen, clo2->window, clo2->drawable, + filename, &geom, clo2->closure); + clo2->callback = 0; + free (clo2); } +# endif /* HAVE_COCOA && USE_IPHONE */ + + +static void +osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable, + const char *dir, + void (*callback) (Screen *, Window, Drawable, + const char *name, + XRectangle *geom, + void *closure), + void *closure) +{ + xscreensaver_getimage_data *clo2 = + (xscreensaver_getimage_data *) calloc (1, sizeof(*clo2)); + + clo2->screen = screen; + clo2->window = xwindow; + clo2->drawable = drawable; + clo2->callback = callback; + clo2->closure = closure; + +# ifndef USE_IPHONE /* Desktop OSX */ + clo2->directory = strdup (dir); + clo2->pipe = open_image_name_pipe (dir); + clo2->pipe_id = XtAppAddInput (XtDisplayToApplicationContext ( + DisplayOfScreen (screen)), + fileno (clo2->pipe), + (XtPointer) (XtInputReadMask | XtInputExceptMask), + xscreensaver_getimage_file_cb, (XtPointer) clo2); +# else /* USE_IPHONE */ + ios_load_random_image (ios_load_random_image_cb, clo2); +# endif /* USE_IPHONE */ +} -#ifndef DEBUG /* Loads an image into the Drawable, returning once the image is loaded. - When grabbing desktop images, the Window will be unmapped first. */ -void -load_random_image (Screen *screen, Window window, Drawable drawable, - char **name_ret, XRectangle *geom_ret) +static void +load_random_image_cocoa (Screen *screen, Window window, Drawable drawable, + void (*callback) (Screen *, Window, Drawable, + const char *name, XRectangle *geom, + void *closure), + void *closure) { - load_random_image_1 (screen, window, drawable, 0, 0, name_ret, geom_ret); + Display *dpy = DisplayOfScreen (screen); + XWindowAttributes xgwa; + Bool deskp = get_boolean_resource (dpy, "grabDesktopImages", "Boolean"); + Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean"); + const char *dir = 0; + Bool done = False; + XRectangle geom; + char *name = 0; + + if (!drawable) abort(); + + XGetWindowAttributes (dpy, window, &xgwa); + { + Window r; + int x, y; + unsigned int w, h, bbw, d; + XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d); + xgwa.width = w; + xgwa.height = h; + } + + geom.x = 0; + geom.y = 0; + geom.width = xgwa.width; + geom.height = xgwa.height; + +# ifndef USE_IPHONE + if (filep) + dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory"); + + if (!dir || !*dir) + filep = False; +# endif /* ! USE_IPHONE */ + + if (deskp && filep) { + deskp = !(random() & 5); /* if both, desktop 1/5th of the time */ + filep = !deskp; + } + + if (filep && !done) { + osx_load_image_file_async (screen, window, drawable, dir, + callback, closure); + return; + } + + if (deskp && !done) { + if (osx_grab_desktop_image (screen, window, drawable, &geom)) { + name = strdup ("desktop"); + done = True; + } + } + + if (! done) + draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap, + 0, 0, xgwa.width, xgwa.height); + + /* If we got here, we loaded synchronously, so we're done. */ + callback (screen, window, drawable, name, &geom, closure); + if (name) free (name); } -#else /* DEBUG */ -typedef struct { - char **name_ret; - Bool done; -} debug_closure; +#elif defined(HAVE_ANDROID) +/* Loads an image into the Drawable, returning once the image is loaded. + */ static void -debug_cb (Screen *screen, Window window, Drawable drawable, - const char *name, void *closure) +load_random_image_android (Screen *screen, Window window, Drawable drawable, + void (*callback) (Screen *, Window, Drawable, + const char *name, + XRectangle *geom, void *closure), + void *closure) { - debug_closure *data = (debug_closure *) closure; - fprintf (stderr, "%s: GRAB DEBUG: callback\n", progname); - if (data->name_ret) - *data->name_ret = (name ? strdup (name) : 0); - data->done = True; + Display *dpy = DisplayOfScreen (screen); + XWindowAttributes xgwa; + XRectangle geom; + char *name = 0; + char *data = 0; + int width = 0; + int height = 0; + + if (!drawable) abort(); + + XGetWindowAttributes (dpy, window, &xgwa); + { + Window r; + int x, y; + unsigned int w, h, bbw, d; + XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d); + xgwa.width = w; + xgwa.height = h; + } + + geom.x = 0; + geom.y = 0; + geom.width = xgwa.width; + geom.height = xgwa.height; + + data = jwxyz_load_random_image (dpy, &width, &height, &name); + if (! data) + draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap, + 0, 0, xgwa.width, xgwa.height); + else + { + XImage *img = XCreateImage (dpy, xgwa.visual, 32, + ZPixmap, 0, data, width, height, 0, 0); + XGCValues gcv; + GC gc; + gcv.foreground = BlackPixelOfScreen (screen); + gc = XCreateGC (dpy, drawable, GCForeground, &gcv); + XFillRectangle (dpy, drawable, gc, 0, 0, xgwa.width, xgwa.height); + XPutImage (dpy, drawable, gc, img, 0, 0, + (xgwa.width - width) / 2, + (xgwa.height - height) / 2, + width, height); + XDestroyImage (img); + XFreeGC (dpy, gc); + } + + callback (screen, window, drawable, name, &geom, closure); + if (name) free (name); } +#endif /* HAVE_ANDROID */ + + + +/* Writes the string "Loading..." in the middle of the screen. + This will presumably get blown away when the image finally loads, + minutes or hours later... + + This is called by load_image_async_simple() but not by load_image_async(), + since it is assumed that hacks that are loading more than one image + *at one time* will be doing something more clever than just blocking + with a blank screen. + */ +static void +print_loading_msg (Screen *screen, Window window) +{ + Display *dpy = DisplayOfScreen (screen); + XWindowAttributes xgwa; + XGCValues gcv; + XFontStruct *f = 0; + GC gc; + char *fn = get_string_resource (dpy, "labelFont", "Font"); + const char *text = "Loading..."; + int w; + + if (!fn) fn = get_string_resource (dpy, "titleFont", "Font"); + if (!fn) fn = get_string_resource (dpy, "font", "Font"); + if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*"); + f = XLoadQueryFont (dpy, fn); + if (!f) f = XLoadQueryFont (dpy, "fixed"); + if (!f) abort(); + free (fn); + fn = 0; + + XGetWindowAttributes (dpy, window, &xgwa); + w = XTextWidth (f, text, (int) strlen(text)); + + gcv.foreground = get_pixel_resource (dpy, xgwa.colormap, + "foreground", "Foreground"); + gcv.background = get_pixel_resource (dpy, xgwa.colormap, + "background", "Background"); + gcv.font = f->fid; + gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv); + XDrawImageString (dpy, window, gc, + (xgwa.width - w) / 2, + (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent, + text, (int) strlen(text)); + XFreeFont (dpy, f); + XFreeGC (dpy, gc); + XSync (dpy, False); +} + + +/* Loads an image into the Drawable in the background; + when the image is fully loaded, runs the callback. + When grabbing desktop images, the Window will be unmapped first. + */ void -load_random_image (Screen *screen, Window window, Drawable drawable, - char **name_ret) +load_image_async (Screen *screen, Window window, Drawable drawable, + void (*callback) (Screen *, Window, Drawable, + const char *name, XRectangle *geom, + void *closure), + void *closure) { - debug_closure data; - data.name_ret = name_ret; - data.done = False; - fprintf (stderr, "%s: GRAB DEBUG: forking\n", progname); - fork_load_random_image (screen, window, drawable, debug_cb, &data); - while (! data.done) + if (!callback) abort(); +# if defined(HAVE_COCOA) + load_random_image_cocoa (screen, window, drawable, callback, closure); +# elif defined(HAVE_ANDROID) + load_random_image_android (screen, window, drawable, callback, closure); +# else /* real X11 */ + load_random_image_x11 (screen, window, drawable, callback, closure); +# endif +} + +struct async_load_state { + Bool done_p; + char *filename; + XRectangle geom; +}; + +static void +load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable, + const char *name, XRectangle *geom, void *closure) +{ + async_load_state *state = (async_load_state *) closure; + state->done_p = True; + state->filename = (name ? strdup (name) : 0); + state->geom = *geom; +} + +async_load_state * +load_image_async_simple (async_load_state *state, + Screen *screen, + Window window, + Drawable drawable, + char **filename_ret, + XRectangle *geometry_ret) +{ + if (state && state->done_p) /* done! */ { - fprintf (stderr, "%s: GRAB DEBUG: waiting\n", progname); - if (XtAppPending (app) & XtIMAlternateInput) - XtAppProcessEvent (app, XtIMAlternateInput); - usleep (50000); + if (filename_ret) + *filename_ret = state->filename; + else if (state->filename) + free (state->filename); + + if (geometry_ret) + *geometry_ret = state->geom; + + free (state); + return 0; + } + else if (! state) /* first time */ + { + state = (async_load_state *) calloc (1, sizeof(*state)); + state->done_p = False; + print_loading_msg (screen, window); + load_image_async (screen, window, drawable, + load_image_async_simple_cb, + state); + return state; } - fprintf (stderr, "%s: GRAB DEBUG: done\n", progname); + else /* still waiting */ + return state; } - -#endif /* DEBUG */