1 /* xscreensaver, Copyright (c) 1992-2006 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"
27 # include "colorbars.h"
28 #else /* !HAVE_COCOA -- real Xlib */
30 # include <X11/Xatom.h>
31 # include <X11/Intrinsic.h> /* for XtInputId, etc */
32 #endif /* !HAVE_COCOA */
37 #ifdef HAVE_SYS_WAIT_H
38 # include <sys/wait.h> /* for waitpid() and associated macros */
42 extern char *progname;
44 static void print_loading_msg (Screen *, Window);
48 static Bool error_handler_hit_p = False;
51 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
53 error_handler_hit_p = True;
58 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
61 drawable_window_p (Display *dpy, Drawable d)
63 XErrorHandler old_handler;
64 XWindowAttributes xgwa;
67 old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
68 error_handler_hit_p = False;
69 XGetWindowAttributes (dpy, d, &xgwa);
71 XSetErrorHandler (old_handler);
74 if (!error_handler_hit_p)
75 return True; /* It's a Window. */
77 return False; /* It's a Pixmap, or an invalid ID. */
82 xscreensaver_window_p (Display *dpy, Window window)
86 unsigned long nitems, bytesafter;
87 unsigned char *version;
88 if (XGetWindowProperty (dpy, window,
89 XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
90 0, 1, False, XA_STRING,
91 &type, &format, &nitems, &bytesafter,
100 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
101 on a window whose depth is not the maximal depth of the screen? Or
102 something. Anyway, things don't work unless we: use SubwindowMode for
103 the real root window (or a legitimate virtual root window); but do not
104 use SubwindowMode for the xscreensaver window. I make no attempt to
108 use_subwindow_mode_p (Screen *screen, Window window)
110 if (window != VirtualRootWindowOfScreen(screen))
112 else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
120 checkerboard (Screen *screen, Drawable drawable)
122 Display *dpy = DisplayOfScreen (screen);
127 GC gc = XCreateGC (dpy, drawable, 0, &gcv);
129 unsigned int win_width, win_height;
131 fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
132 fg.red = fg.green = fg.blue = 0x0000;
133 bg.red = bg.green = bg.blue = 0x4444;
137 if (drawable_window_p (dpy, drawable))
139 XWindowAttributes xgwa;
140 XGetWindowAttributes (dpy, drawable, &xgwa);
141 win_width = xgwa.width;
142 win_height = xgwa.height;
143 cmap = xgwa.colormap;
144 screen = xgwa.screen;
146 else /* it's a pixmap */
148 XWindowAttributes xgwa;
152 XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
153 cmap = xgwa.colormap;
154 XGetGeometry (dpy, drawable,
155 &root, &x, &y, &win_width, &win_height, &bw, &d);
158 /* Allocate black and gray, but don't hold them locked. */
159 if (XAllocColor (dpy, cmap, &fg))
160 XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
161 if (XAllocColor (dpy, cmap, &bg))
162 XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
164 XSetForeground (dpy, gc, bg.pixel);
165 XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
166 XSetForeground (dpy, gc, fg.pixel);
167 for (y = 0; y < win_height; y += size+size)
168 for (x = 0; x < win_width; x += size+size)
170 XFillRectangle (dpy, drawable, gc, x, y, size, size);
171 XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
178 get_name (Display *dpy, Window window)
182 unsigned long nitems, bytesafter;
183 unsigned char *name = 0;
184 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
185 if (XGetWindowProperty (dpy, window, atom,
186 0, 1024, False, XA_STRING,
187 &type, &format, &nitems, &bytesafter,
191 return (char *) name;
198 get_geometry (Display *dpy, Window window, XRectangle *ret)
202 unsigned long nitems, bytesafter;
203 unsigned char *name = 0;
204 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
207 if (XGetWindowProperty (dpy, window, atom,
208 0, 1024, False, XA_STRING,
209 &type, &format, &nitems, &bytesafter,
214 int flags = XParseGeometry ((char *) name, &x, &y, &w, &h);
216 /* Require all four, and don't allow negative positions. */
217 if (flags == (XValue|YValue|WidthValue|HeightValue))
234 hack_subproc_environment (Display *dpy)
236 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
237 the spawned processes inherit is what we are actually using.
239 const char *odpy = DisplayString (dpy);
240 char *ndpy = (char *) malloc(strlen(odpy) + 20);
241 strcpy (ndpy, "DISPLAY=");
244 /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
245 any more, right? It's not Posix, but everyone seems to have it. */
249 #endif /* HAVE_PUTENV */
253 /* Spawn a program, and wait for it to finish.
254 If we just use system() for this, then sometimes the subprocess
255 doesn't die when *this* process is sent a TERM signal. Perhaps
256 this is due to the intermediate /bin/sh that system() uses to
257 parse arguments? I'm not sure. But using fork() and execvp()
258 here seems to close the race.
261 exec_simple_command (const char *command)
265 char *token = strtok (strdup(command), " \t");
269 token = strtok(0, " \t");
273 execvp (av[0], av); /* shouldn't return. */
278 fork_exec_wait (const char *command)
284 switch ((int) (forked = fork ()))
287 sprintf (buf, "%s: couldn't fork", progname);
292 exec_simple_command (command);
293 exit (1); /* exits child fork */
297 waitpid (forked, &status, 0);
304 void (*callback) (Screen *, Window, Drawable,
305 const char *name, XRectangle *geom, void *closure);
317 static void finalize_cb (XtPointer closure, int *fd, XtIntervalId *id);
320 fork_exec_cb (const char *command,
321 Screen *screen, Window window, Drawable drawable,
322 void (*callback) (Screen *, Window, Drawable,
323 const char *name, XRectangle *geom,
327 XtAppContext app = XtDisplayToApplicationContext (DisplayOfScreen (screen));
328 grabclient_data *data;
336 sprintf (buf, "%s: creating pipe", progname);
341 data = (grabclient_data *) calloc (1, sizeof(*data));
342 data->callback = callback;
343 data->closure = closure;
344 data->screen = screen;
345 data->window = window;
346 data->drawable = drawable;
347 data->read_pipe = fdopen (fds[0], "r");
348 data->write_pipe = fdopen (fds[1], "w");
350 if (!data->read_pipe || !data->write_pipe)
352 sprintf (buf, "%s: fdopen", progname);
358 XtAppAddInput (app, fileno (data->read_pipe),
359 (XtPointer) (XtInputReadMask | XtInputExceptMask),
360 finalize_cb, (XtPointer) data);
363 switch ((int) forked)
366 sprintf (buf, "%s: couldn't fork", progname);
372 fclose (data->read_pipe);
375 /* clone the write pipe onto stdout so that it gets closed
376 when the fork exits. This will shut down the pipe and
379 close (fileno (stdout));
380 dup2 (fds[1], fileno (stdout));
383 close (fileno (stdin)); /* for kicks */
385 exec_simple_command (command);
386 exit (1); /* exits child fork */
389 default: /* parent */
390 fclose (data->write_pipe);
391 data->write_pipe = 0;
398 /* Called in the parent when the forked process dies.
399 Runs the caller's callback, and cleans up.
402 finalize_cb (XtPointer closure, int *fd, XtIntervalId *id)
404 grabclient_data *data = (grabclient_data *) closure;
405 Display *dpy = DisplayOfScreen (data->screen);
407 XRectangle geom = { 0, 0, 0, 0 };
411 name = get_name (dpy, data->window);
412 get_geometry (dpy, data->window, &geom);
414 data->callback (data->screen, data->window, data->drawable,
415 name, &geom, data->closure);
416 if (name) free (name);
418 fclose (data->read_pipe);
420 if (data->pid) /* reap zombies */
423 waitpid (data->pid, &status, 0);
427 memset (data, 0, sizeof (*data));
432 /* Loads an image into the Drawable.
433 When grabbing desktop images, the Window will be unmapped first.
436 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
437 void (*callback) (Screen *, Window, Drawable,
438 const char *name, XRectangle *geom,
442 XRectangle *geom_ret)
444 Display *dpy = DisplayOfScreen (screen);
445 char *grabber = get_string_resource(dpy, "desktopGrabber", "DesktopGrabber");
449 if (!grabber || !*grabber)
452 "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
457 sprintf (id, "0x%lx 0x%lx",
458 (unsigned long) window,
459 (unsigned long) drawable);
460 cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
462 /* Needn't worry about buffer overflows here, because the buffer is
463 longer than the length of the format string, and the length of what
464 we're putting into it. So the only way to crash would be if the
465 format string itself was corrupted, but that comes from the
466 resource database, and if hostile forces have access to that,
467 then the game is already over.
469 sprintf (cmd, grabber, id);
473 /* In case "cmd" fails, leave some random image on the screen, not just
474 black or white, so that it's more obvious what went wrong. */
475 checkerboard (screen, drawable);
476 if (window == drawable)
477 print_loading_msg (screen, window);
480 hack_subproc_environment (dpy);
484 /* Start the image loading in another fork and return immediately.
485 Invoke the callback function when done.
487 if (name_ret) abort();
488 fork_exec_cb (cmd, screen, window, drawable, callback, closure);
492 /* Wait for the image to load, and return it immediately.
494 fork_exec_wait (cmd);
496 *name_ret = get_name (dpy, window);
498 get_geometry (dpy, window, geom_ret);
505 #else /* HAVE_COCOA */
507 /* Gets the name of an image file to load by running xscreensaver-getimage-file
508 at the end of a pipe. This can be very slow!
511 open_image_name_pipe (const char *dir)
513 char *cmd = malloc (strlen(dir) * 2 + 100);
515 strcpy (cmd, "xscreensaver-getimage-file --name ");
516 s = cmd + strlen (cmd);
519 /* put a backslash in front of any character that might confuse sh. */
520 if (! ((c >= 'a' && c <= 'z') ||
521 (c >= 'A' && c <= 'Z') ||
522 (c >= '0' && c <= '9') ||
523 c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
529 FILE *pipe = popen (cmd, "r");
535 struct pipe_closure {
541 void (*callback) (Screen *, Window, Drawable,
542 const char *name, XRectangle *geom,
549 pipe_cb (XtPointer closure, int *source, XtInputId *id)
551 /* This is not called from a signal handler, so doing stuff here is fine.
553 struct pipe_closure *clo2 = (struct pipe_closure *) closure;
555 fgets (buf, sizeof(buf)-1, clo2->pipe);
558 XtRemoveInput (clo2->id);
561 /* strip trailing newline */
563 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
566 Display *dpy = DisplayOfScreen (clo2->screen);
569 if (! osx_load_image_file (clo2->screen, clo2->xwindow, clo2->drawable,
571 /* unable to load image - draw colorbars
573 XWindowAttributes xgwa;
574 XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
577 unsigned int w, h, bbw, d;
578 XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
579 draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
586 clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable, buf, &geom,
594 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
596 void (*callback) (Screen *, Window, Drawable,
602 #if 0 /* do it synchronously */
604 FILE *pipe = open_image_name_pipe (dir);
607 fgets (buf, sizeof(buf)-1, pipe);
610 /* strip trailing newline */
612 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
616 if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
620 callback (screen, xwindow, drawable, buf, &geom, closure);
622 #else /* do it asynchronously */
624 Display *dpy = DisplayOfScreen (screen);
625 struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
626 clo2->pipe = open_image_name_pipe (dir);
627 clo2->id = XtAppAddInput (XtDisplayToApplicationContext (dpy),
629 (XtPointer) (XtInputReadMask | XtInputExceptMask),
630 pipe_cb, (XtPointer) clo2);
631 clo2->screen = screen;
632 clo2->xwindow = xwindow;
633 clo2->drawable = drawable;
634 clo2->callback = callback;
635 clo2->closure = closure;
640 /* Loads an image into the Drawable, returning once the image is loaded.
643 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
644 void (*callback) (Screen *, Window, Drawable,
645 const char *name, XRectangle *geom,
649 XRectangle *geom_ret)
651 Display *dpy = DisplayOfScreen (screen);
652 XWindowAttributes xgwa;
653 Bool deskp = get_boolean_resource (dpy, "grabDesktopImages", "Boolean");
654 Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
657 XRectangle geom_ret_2;
658 char *name_ret_2 = 0;
661 geom_ret = &geom_ret_2;
662 name_ret = &name_ret_2;
665 XGetWindowAttributes (dpy, window, &xgwa);
669 unsigned int w, h, bbw, d;
670 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
681 geom_ret->width = xgwa.width;
682 geom_ret->height = xgwa.height;
686 dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
691 if (deskp && filep) {
692 deskp = !(random() & 5); /* if both, desktop 1/5th of the time */
696 if (filep && !done) {
697 osx_load_image_file_async (screen, window, drawable, dir,
702 if (deskp && !done) {
703 osx_grab_desktop_image (screen, window, drawable);
705 *name_ret = strdup ("desktop");
710 draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
711 0, 0, xgwa.width, xgwa.height);
716 /* If we got here, we loaded synchronously even though they wanted async.
718 callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
722 #endif /* HAVE_COCOA */
725 /* Writes the string "Loading..." in the middle of the screen.
726 This will presumably get blown away when the image finally loads,
727 minutes or hours later...
729 This is called by load_image_async_simple() but not by load_image_async(),
730 since it is assumed that hacks that are loading more than one image
731 *at one time* will be doing something more clever than just blocking
735 print_loading_msg (Screen *screen, Window window)
737 Display *dpy = DisplayOfScreen (screen);
738 XWindowAttributes xgwa;
742 char *fn = get_string_resource (dpy, "labelFont", "Font");
743 const char *text = "Loading...";
746 if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
747 if (!fn) fn = get_string_resource (dpy, "font", "Font");
748 if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
749 f = XLoadQueryFont (dpy, fn);
750 if (!f) f = XLoadQueryFont (dpy, "fixed");
755 XGetWindowAttributes (dpy, window, &xgwa);
756 w = XTextWidth (f, text, strlen(text));
758 gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
759 "foreground", "Foreground");
760 gcv.background = get_pixel_resource (dpy, xgwa.colormap,
761 "background", "Background");
763 gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
764 XDrawImageString (dpy, window, gc,
765 (xgwa.width - w) / 2,
766 (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
774 /* Loads an image into the Drawable in the background;
775 when the image is fully loaded, runs the callback.
776 When grabbing desktop images, the Window will be unmapped first.
779 load_image_async (Screen *screen, Window window, Drawable drawable,
780 void (*callback) (Screen *, Window, Drawable,
781 const char *name, XRectangle *geom,
785 load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
788 struct async_load_state {
795 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
796 const char *name, XRectangle *geom, void *closure)
798 async_load_state *state = (async_load_state *) closure;
799 state->done_p = True;
800 state->filename = (name ? strdup (name) : 0);
805 load_image_async_simple (async_load_state *state,
810 XRectangle *geometry_ret)
812 if (state && state->done_p) /* done! */
815 *filename_ret = state->filename;
816 else if (state->filename)
817 free (state->filename);
820 *geometry_ret = state->geom;
825 else if (! state) /* first time */
827 state = (async_load_state *) calloc (1, sizeof(*state));
828 state->done_p = False;
829 print_loading_msg (screen, window);
830 load_image_async (screen, window, drawable,
831 load_image_async_simple_cb,
835 else /* still waiting */