1 /* xscreensaver, Copyright (c) 1992-2005 Jamie Zawinski <jwz@jwz.org>
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
12 /* This file contains code for running an external program to grab an image
13 onto the given window. The external program in question must take a
14 window ID as its argument, e.g., the "xscreensaver-getimage" program
15 in the hacks/ directory.
17 That program links against utils/grabimage.c, which happens to export the
18 same API as this program (utils/grabclient.c).
22 #include "grabscreen.h"
23 #include "resources.h"
26 #include <X11/Xatom.h>
28 #include <X11/Intrinsic.h> /* for XtInputId, etc */
34 #ifdef HAVE_SYS_WAIT_H
35 # include <sys/wait.h> /* for waitpid() and associated macros */
39 extern char *progname;
40 extern XtAppContext app;
43 static Bool error_handler_hit_p = False;
46 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
48 error_handler_hit_p = True;
53 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
56 drawable_window_p (Display *dpy, Drawable d)
58 XErrorHandler old_handler;
59 XWindowAttributes xgwa;
62 old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
63 error_handler_hit_p = False;
64 XGetWindowAttributes (dpy, d, &xgwa);
66 XSetErrorHandler (old_handler);
69 if (!error_handler_hit_p)
70 return True; /* It's a Window. */
72 return False; /* It's a Pixmap, or an invalid ID. */
77 xscreensaver_window_p (Display *dpy, Window window)
81 unsigned long nitems, bytesafter;
82 unsigned char *version;
83 if (XGetWindowProperty (dpy, window,
84 XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
85 0, 1, False, XA_STRING,
86 &type, &format, &nitems, &bytesafter,
95 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
96 on a window whose depth is not the maximal depth of the screen? Or
97 something. Anyway, things don't work unless we: use SubwindowMode for
98 the real root window (or a legitimate virtual root window); but do not
99 use SubwindowMode for the xscreensaver window. I make no attempt to
103 use_subwindow_mode_p(Screen *screen, Window window)
105 if (window != VirtualRootWindowOfScreen(screen))
107 else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
115 checkerboard (Screen *screen, Drawable drawable)
117 Display *dpy = DisplayOfScreen (screen);
122 GC gc = XCreateGC (dpy, drawable, 0, &gcv);
124 unsigned int win_width, win_height;
126 fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
127 fg.red = fg.green = fg.blue = 0x0000;
128 bg.red = bg.green = bg.blue = 0x4444;
132 if (drawable_window_p (dpy, drawable))
134 XWindowAttributes xgwa;
135 XGetWindowAttributes (dpy, drawable, &xgwa);
136 win_width = xgwa.width;
137 win_height = xgwa.height;
138 cmap = xgwa.colormap;
139 screen = xgwa.screen;
141 else /* it's a pixmap */
143 XWindowAttributes xgwa;
147 XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
148 cmap = xgwa.colormap;
149 XGetGeometry (dpy, drawable,
150 &root, &x, &y, &win_width, &win_height, &bw, &d);
153 /* Allocate black and gray, but don't hold them locked. */
154 if (XAllocColor (dpy, cmap, &fg))
155 XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
156 if (XAllocColor (dpy, cmap, &bg))
157 XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
159 XSetForeground (dpy, gc, bg.pixel);
160 XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
161 XSetForeground (dpy, gc, fg.pixel);
162 for (y = 0; y < win_height; y += size+size)
163 for (x = 0; x < win_width; x += size+size)
165 XFillRectangle (dpy, drawable, gc, x, y, size, size);
166 XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
172 get_name (Display *dpy, Window window)
176 unsigned long nitems, bytesafter;
177 unsigned char *name = 0;
178 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
179 if (XGetWindowProperty (dpy, window, atom,
180 0, 1024, False, XA_STRING,
181 &type, &format, &nitems, &bytesafter,
185 return strdup((char *) name);
191 get_geometry (Display *dpy, Window window, XRectangle *ret)
195 unsigned long nitems, bytesafter;
196 unsigned char *name = 0;
197 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
200 if (XGetWindowProperty (dpy, window, atom,
201 0, 1024, False, XA_STRING,
202 &type, &format, &nitems, &bytesafter,
207 int flags = XParseGeometry ((char *) name, &x, &y, &w, &h);
208 /* Require all four, and don't allow negative positions. */
209 if (flags == (XValue|YValue|WidthValue|HeightValue))
227 hack_subproc_environment (Display *dpy)
229 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
230 the spawned processes inherit is what we are actually using.
232 const char *odpy = DisplayString (dpy);
233 char *ndpy = (char *) malloc(strlen(odpy) + 20);
234 strcpy (ndpy, "DISPLAY=");
237 /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
238 any more, right? It's not Posix, but everyone seems to have it. */
242 #endif /* HAVE_PUTENV */
246 /* Spawn a program, and wait for it to finish.
247 If we just use system() for this, then sometimes the subprocess
248 doesn't die when *this* process is sent a TERM signal. Perhaps
249 this is due to the intermediate /bin/sh that system() uses to
250 parse arguments? I'm not sure. But using fork() and execvp()
251 here seems to close the race.
255 exec_simple_command (const char *command)
259 char *token = strtok (strdup(command), " \t");
263 token = strtok(0, " \t");
267 execvp (av[0], av); /* shouldn't return. */
271 fork_exec_wait (const char *command)
277 switch ((int) (forked = fork ()))
280 sprintf (buf, "%s: couldn't fork", progname);
285 exec_simple_command (command);
286 exit (1); /* exits child fork */
290 waitpid (forked, &status, 0);
297 void (*callback) (Screen *, Window, Drawable,
298 const char *name, XRectangle *geom, void *closure);
309 static void finalize_cb (XtPointer closure, int *fd, XtIntervalId *id);
312 fork_exec_cb (const char *command,
313 Screen *screen, Window window, Drawable drawable,
314 void (*callback) (Screen *, Window, Drawable,
315 const char *name, XRectangle *geom,
319 grabclient_data *data;
327 sprintf (buf, "%s: creating pipe", progname);
332 data = (grabclient_data *) calloc (1, sizeof(*data));
333 data->callback = callback;
334 data->closure = closure;
335 data->screen = screen;
336 data->window = window;
337 data->drawable = drawable;
338 data->read_pipe = fdopen (fds[0], "r");
339 data->write_pipe = fdopen (fds[1], "w");
341 if (!data->read_pipe || !data->write_pipe)
343 sprintf (buf, "%s: fdopen", progname);
349 XtAppAddInput (app, fileno (data->read_pipe),
350 (XtPointer) (XtInputReadMask | XtInputExceptMask),
351 finalize_cb, (XtPointer) data);
353 switch ((int) (forked = fork ()))
356 sprintf (buf, "%s: couldn't fork", progname);
362 fclose (data->read_pipe);
365 /* clone the write pipe onto stdout so that it gets closed
366 when the fork exits. This will shut down the pipe and
369 close (fileno (stdout));
370 dup2 (fds[1], fileno (stdout));
373 close (fileno (stdin)); /* for kicks */
375 exec_simple_command (command);
376 exit (1); /* exits child fork */
379 default: /* parent */
380 fclose (data->write_pipe);
381 data->write_pipe = 0;
387 /* Called in the parent when the forked process dies.
388 Runs the caller's callback, and cleans up.
391 finalize_cb (XtPointer closure, int *fd, XtIntervalId *id)
393 grabclient_data *data = (grabclient_data *) closure;
394 Display *dpy = DisplayOfScreen (data->screen);
396 XRectangle geom = { 0, 0, 0, 0 };
400 name = get_name (dpy, data->window);
401 get_geometry (dpy, data->window, &geom);
403 data->callback (data->screen, data->window, data->drawable,
404 name, &geom, data->closure);
405 if (name) free (name);
407 fclose (data->read_pipe);
408 memset (data, 0, sizeof (*data));
413 /* Loads an image into the Drawable.
414 When grabbing desktop images, the Window will be unmapped first.
417 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
418 void (*callback) (Screen *, Window, Drawable,
419 const char *name, XRectangle *geom,
423 XRectangle *geom_ret)
425 Display *dpy = DisplayOfScreen (screen);
426 char *grabber = get_string_resource ("desktopGrabber", "DesktopGrabber");
430 if (!grabber || !*grabber)
433 "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
438 sprintf (id, "0x%lx 0x%lx",
439 (unsigned long) window,
440 (unsigned long) drawable);
441 cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
443 /* Needn't worry about buffer overflows here, because the buffer is
444 longer than the length of the format string, and the length of what
445 we're putting into it. So the only way to crash would be if the
446 format string itself was corrupted, but that comes from the
447 resource database, and if hostile forces have access to that,
448 then the game is already over.
450 sprintf (cmd, grabber, id);
452 /* In case "cmd" fails, leave some random image on the screen, not just
453 black or white, so that it's more obvious what went wrong. */
454 checkerboard (screen, drawable);
457 hack_subproc_environment (dpy);
461 /* Start the image loading in another fork and return immediately.
462 Invoke the callback function when done.
464 if (name_ret) abort();
465 fork_exec_cb (cmd, screen, window, drawable, callback, closure);
469 /* Wait for the image to load, and return it immediately.
471 fork_exec_wait (cmd);
473 *name_ret = get_name (dpy, window);
475 get_geometry (dpy, window, geom_ret);
483 /* Loads an image into the Drawable in the background;
484 when the image is fully loaded, runs the callback.
485 When grabbing desktop images, the Window will be unmapped first.
488 fork_load_random_image (Screen *screen, Window window, Drawable drawable,
489 void (*callback) (Screen *, Window, Drawable,
490 const char *name, XRectangle *geom,
494 load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
500 /* Loads an image into the Drawable, returning once the image is loaded.
501 When grabbing desktop images, the Window will be unmapped first.
504 load_random_image (Screen *screen, Window window, Drawable drawable,
505 char **name_ret, XRectangle *geom_ret)
507 load_random_image_1 (screen, window, drawable, 0, 0, name_ret, geom_ret);
518 debug_cb (Screen *screen, Window window, Drawable drawable,
519 const char *name, void *closure)
521 debug_closure *data = (debug_closure *) closure;
522 fprintf (stderr, "%s: GRAB DEBUG: callback\n", progname);
524 *data->name_ret = (name ? strdup (name) : 0);
529 load_random_image (Screen *screen, Window window, Drawable drawable,
533 data.name_ret = name_ret;
535 fprintf (stderr, "%s: GRAB DEBUG: forking\n", progname);
536 fork_load_random_image (screen, window, drawable, debug_cb, &data);
539 fprintf (stderr, "%s: GRAB DEBUG: waiting\n", progname);
540 if (XtAppPending (app) & XtIMAlternateInput)
541 XtAppProcessEvent (app, XtIMAlternateInput);
544 fprintf (stderr, "%s: GRAB DEBUG: done\n", progname);