1 /* xscreensaver, Copyright (c) 1992-2008 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;
660 if (!drawable) abort();
663 geom_ret = &geom_ret_2;
664 name_ret = &name_ret_2;
667 XGetWindowAttributes (dpy, window, &xgwa);
671 unsigned int w, h, bbw, d;
672 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
683 geom_ret->width = xgwa.width;
684 geom_ret->height = xgwa.height;
688 dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
693 if (deskp && filep) {
694 deskp = !(random() & 5); /* if both, desktop 1/5th of the time */
698 if (filep && !done) {
699 osx_load_image_file_async (screen, window, drawable, dir,
704 if (deskp && !done) {
705 osx_grab_desktop_image (screen, window, drawable);
707 *name_ret = strdup ("desktop");
712 draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
713 0, 0, xgwa.width, xgwa.height);
718 /* If we got here, we loaded synchronously even though they wanted async.
720 callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
724 #endif /* HAVE_COCOA */
727 /* Writes the string "Loading..." in the middle of the screen.
728 This will presumably get blown away when the image finally loads,
729 minutes or hours later...
731 This is called by load_image_async_simple() but not by load_image_async(),
732 since it is assumed that hacks that are loading more than one image
733 *at one time* will be doing something more clever than just blocking
737 print_loading_msg (Screen *screen, Window window)
739 Display *dpy = DisplayOfScreen (screen);
740 XWindowAttributes xgwa;
744 char *fn = get_string_resource (dpy, "labelFont", "Font");
745 const char *text = "Loading...";
748 if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
749 if (!fn) fn = get_string_resource (dpy, "font", "Font");
750 if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
751 f = XLoadQueryFont (dpy, fn);
752 if (!f) f = XLoadQueryFont (dpy, "fixed");
757 XGetWindowAttributes (dpy, window, &xgwa);
758 w = XTextWidth (f, text, strlen(text));
760 gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
761 "foreground", "Foreground");
762 gcv.background = get_pixel_resource (dpy, xgwa.colormap,
763 "background", "Background");
765 gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
766 XDrawImageString (dpy, window, gc,
767 (xgwa.width - w) / 2,
768 (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
776 /* Loads an image into the Drawable in the background;
777 when the image is fully loaded, runs the callback.
778 When grabbing desktop images, the Window will be unmapped first.
781 load_image_async (Screen *screen, Window window, Drawable drawable,
782 void (*callback) (Screen *, Window, Drawable,
783 const char *name, XRectangle *geom,
787 load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
790 struct async_load_state {
797 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
798 const char *name, XRectangle *geom, void *closure)
800 async_load_state *state = (async_load_state *) closure;
801 state->done_p = True;
802 state->filename = (name ? strdup (name) : 0);
807 load_image_async_simple (async_load_state *state,
812 XRectangle *geometry_ret)
814 if (state && state->done_p) /* done! */
817 *filename_ret = state->filename;
818 else if (state->filename)
819 free (state->filename);
822 *geometry_ret = state->geom;
827 else if (! state) /* first time */
829 state = (async_load_state *) calloc (1, sizeof(*state));
830 state->done_p = False;
831 print_loading_msg (screen, window);
832 load_image_async (screen, window, drawable,
833 load_image_async_simple_cb,
837 else /* still waiting */