1 /* xscreensaver, Copyright (c) 1992-2011 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 */
39 #ifdef HAVE_SYS_WAIT_H
40 # include <sys/wait.h> /* for waitpid() and associated macros */
44 extern char *progname;
46 static void print_loading_msg (Screen *, Window);
50 static Bool error_handler_hit_p = False;
53 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
55 error_handler_hit_p = True;
60 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
63 drawable_window_p (Display *dpy, Drawable d)
65 XErrorHandler old_handler;
66 XWindowAttributes xgwa;
69 old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
70 error_handler_hit_p = False;
71 XGetWindowAttributes (dpy, d, &xgwa);
73 XSetErrorHandler (old_handler);
76 if (!error_handler_hit_p)
77 return True; /* It's a Window. */
79 return False; /* It's a Pixmap, or an invalid ID. */
84 xscreensaver_window_p (Display *dpy, Window window)
88 unsigned long nitems, bytesafter;
89 unsigned char *version;
90 if (XGetWindowProperty (dpy, window,
91 XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
92 0, 1, False, XA_STRING,
93 &type, &format, &nitems, &bytesafter,
102 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
103 on a window whose depth is not the maximal depth of the screen? Or
104 something. Anyway, things don't work unless we: use SubwindowMode for
105 the real root window (or a legitimate virtual root window); but do not
106 use SubwindowMode for the xscreensaver window. I make no attempt to
110 use_subwindow_mode_p (Screen *screen, Window window)
112 if (window != VirtualRootWindowOfScreen(screen))
114 else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
122 checkerboard (Screen *screen, Drawable drawable)
124 Display *dpy = DisplayOfScreen (screen);
129 GC gc = XCreateGC (dpy, drawable, 0, &gcv);
131 unsigned int win_width, win_height;
133 fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
134 fg.red = fg.green = fg.blue = 0x0000;
135 bg.red = bg.green = bg.blue = 0x4444;
139 if (drawable_window_p (dpy, drawable))
141 XWindowAttributes xgwa;
142 XGetWindowAttributes (dpy, drawable, &xgwa);
143 win_width = xgwa.width;
144 win_height = xgwa.height;
145 cmap = xgwa.colormap;
146 screen = xgwa.screen;
148 else /* it's a pixmap */
150 XWindowAttributes xgwa;
154 XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
155 cmap = xgwa.colormap;
156 XGetGeometry (dpy, drawable,
157 &root, &x, &y, &win_width, &win_height, &bw, &d);
160 /* Allocate black and gray, but don't hold them locked. */
161 if (XAllocColor (dpy, cmap, &fg))
162 XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
163 if (XAllocColor (dpy, cmap, &bg))
164 XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
166 XSetForeground (dpy, gc, bg.pixel);
167 XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
168 XSetForeground (dpy, gc, fg.pixel);
169 for (y = 0; y < win_height; y += size+size)
170 for (x = 0; x < win_width; x += size+size)
172 XFillRectangle (dpy, drawable, gc, x, y, size, size);
173 XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
180 get_name (Display *dpy, Window window)
184 unsigned long nitems, bytesafter;
185 unsigned char *name = 0;
186 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
187 if (XGetWindowProperty (dpy, window, atom,
188 0, 1024, False, XA_STRING,
189 &type, &format, &nitems, &bytesafter,
193 return (char *) name;
200 get_geometry (Display *dpy, Window window, XRectangle *ret)
204 unsigned long nitems, bytesafter;
205 unsigned char *name = 0;
206 Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_GEOMETRY, False);
209 if (XGetWindowProperty (dpy, window, atom,
210 0, 1024, False, XA_STRING,
211 &type, &format, &nitems, &bytesafter,
216 int flags = XParseGeometry ((char *) name, &x, &y, &w, &h);
218 /* Require all four, and don't allow negative positions. */
219 if (flags == (XValue|YValue|WidthValue|HeightValue))
236 hack_subproc_environment (Display *dpy)
238 /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
239 the spawned processes inherit is what we are actually using.
241 const char *odpy = DisplayString (dpy);
242 char *ndpy = (char *) malloc(strlen(odpy) + 20);
243 strcpy (ndpy, "DISPLAY=");
246 /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
247 any more, right? It's not Posix, but everyone seems to have it. */
251 #endif /* HAVE_PUTENV */
253 /* don't free (ndpy) -- some implementations of putenv (BSD 4.4,
254 glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2, MacOS)
255 do not. So we must leak it (and/or the previous setting). Yay.
260 /* Spawn a program, and wait for it to finish.
261 If we just use system() for this, then sometimes the subprocess
262 doesn't die when *this* process is sent a TERM signal. Perhaps
263 this is due to the intermediate /bin/sh that system() uses to
264 parse arguments? I'm not sure. But using fork() and execvp()
265 here seems to close the race.
268 exec_simple_command (const char *command)
272 char *token = strtok (strdup(command), " \t");
276 token = strtok(0, " \t");
280 execvp (av[0], av); /* shouldn't return. */
285 fork_exec_wait (const char *command)
291 switch ((int) (forked = fork ()))
294 sprintf (buf, "%s: couldn't fork", progname);
299 exec_simple_command (command);
300 exit (1); /* exits child fork */
304 waitpid (forked, &status, 0);
311 void (*callback) (Screen *, Window, Drawable,
312 const char *name, XRectangle *geom, void *closure);
324 static void finalize_cb (XtPointer closure, int *fd, XtIntervalId *id);
327 fork_exec_cb (const char *command,
328 Screen *screen, Window window, Drawable drawable,
329 void (*callback) (Screen *, Window, Drawable,
330 const char *name, XRectangle *geom,
334 XtAppContext app = XtDisplayToApplicationContext (DisplayOfScreen (screen));
335 grabclient_data *data;
343 sprintf (buf, "%s: creating pipe", progname);
348 data = (grabclient_data *) calloc (1, sizeof(*data));
349 data->callback = callback;
350 data->closure = closure;
351 data->screen = screen;
352 data->window = window;
353 data->drawable = drawable;
354 data->read_pipe = fdopen (fds[0], "r");
355 data->write_pipe = fdopen (fds[1], "w");
357 if (!data->read_pipe || !data->write_pipe)
359 sprintf (buf, "%s: fdopen", progname);
365 XtAppAddInput (app, fileno (data->read_pipe),
366 (XtPointer) (XtInputReadMask | XtInputExceptMask),
367 finalize_cb, (XtPointer) data);
370 switch ((int) forked)
373 sprintf (buf, "%s: couldn't fork", progname);
379 fclose (data->read_pipe);
382 /* clone the write pipe onto stdout so that it gets closed
383 when the fork exits. This will shut down the pipe and
386 close (fileno (stdout));
387 dup2 (fds[1], fileno (stdout));
390 close (fileno (stdin)); /* for kicks */
392 exec_simple_command (command);
393 exit (1); /* exits child fork */
396 default: /* parent */
397 fclose (data->write_pipe);
398 data->write_pipe = 0;
405 /* Called in the parent when the forked process dies.
406 Runs the caller's callback, and cleans up.
409 finalize_cb (XtPointer closure, int *fd, XtIntervalId *id)
411 grabclient_data *data = (grabclient_data *) closure;
412 Display *dpy = DisplayOfScreen (data->screen);
414 XRectangle geom = { 0, 0, 0, 0 };
418 name = get_name (dpy, data->window);
419 get_geometry (dpy, data->window, &geom);
421 data->callback (data->screen, data->window, data->drawable,
422 name, &geom, data->closure);
423 if (name) free (name);
425 fclose (data->read_pipe);
427 if (data->pid) /* reap zombies */
430 waitpid (data->pid, &status, 0);
434 memset (data, 0, sizeof (*data));
439 /* Loads an image into the Drawable.
440 When grabbing desktop images, the Window will be unmapped first.
443 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
444 void (*callback) (Screen *, Window, Drawable,
445 const char *name, XRectangle *geom,
449 XRectangle *geom_ret)
451 Display *dpy = DisplayOfScreen (screen);
452 char *grabber = get_string_resource(dpy, "desktopGrabber", "DesktopGrabber");
456 if (!grabber || !*grabber)
459 "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
464 sprintf (id, "0x%lx 0x%lx",
465 (unsigned long) window,
466 (unsigned long) drawable);
467 cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
469 /* Needn't worry about buffer overflows here, because the buffer is
470 longer than the length of the format string, and the length of what
471 we're putting into it. So the only way to crash would be if the
472 format string itself was corrupted, but that comes from the
473 resource database, and if hostile forces have access to that,
474 then the game is already over.
476 sprintf (cmd, grabber, id);
480 /* In case "cmd" fails, leave some random image on the screen, not just
481 black or white, so that it's more obvious what went wrong. */
482 checkerboard (screen, drawable);
483 if (window == drawable)
484 print_loading_msg (screen, window);
487 hack_subproc_environment (dpy);
491 /* Start the image loading in another fork and return immediately.
492 Invoke the callback function when done.
494 if (name_ret) abort();
495 fork_exec_cb (cmd, screen, window, drawable, callback, closure);
499 /* Wait for the image to load, and return it immediately.
501 fork_exec_wait (cmd);
503 *name_ret = get_name (dpy, window);
505 get_geometry (dpy, window, geom_ret);
512 #else /* HAVE_COCOA */
514 /* Gets the name of an image file to load by running xscreensaver-getimage-file
515 at the end of a pipe. This can be very slow!
518 open_image_name_pipe (const char *dir)
520 char *cmd = malloc (strlen(dir) * 2 + 100);
522 strcpy (cmd, "xscreensaver-getimage-file --name ");
523 s = cmd + strlen (cmd);
526 /* put a backslash in front of any character that might confuse sh. */
527 if (! ((c >= 'a' && c <= 'z') ||
528 (c >= 'A' && c <= 'Z') ||
529 (c >= '0' && c <= '9') ||
530 c == '.' || c == '_' || c == '-' || c == '+' || c == '/'))
536 FILE *pipe = popen (cmd, "r");
542 struct pipe_closure {
549 void (*callback) (Screen *, Window, Drawable,
550 const char *name, XRectangle *geom,
557 pipe_cb (XtPointer closure, int *source, XtInputId *id)
559 /* This is not called from a signal handler, so doing stuff here is fine.
561 struct pipe_closure *clo2 = (struct pipe_closure *) closure;
563 const char *dir = clo2->directory;
565 fgets (buf, sizeof(buf)-1, clo2->pipe);
568 XtRemoveInput (clo2->id);
571 /* strip trailing newline */
573 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
576 Display *dpy = DisplayOfScreen (clo2->screen);
579 if (*buf && *buf != '/') /* pathname is relative to dir. */
581 absfile = malloc (strlen(dir) + strlen(buf) + 10);
582 strcpy (absfile, dir);
583 if (dir[strlen(dir)-1] != '/')
584 strcat (absfile, "/");
585 strcat (absfile, buf);
588 if (! osx_load_image_file (clo2->screen, clo2->xwindow, clo2->drawable,
589 (absfile ? absfile : buf), &geom)) {
590 /* unable to load image - draw colorbars
592 XWindowAttributes xgwa;
593 XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
596 unsigned int w, h, bbw, d;
599 /* Log something to syslog so we can tell the difference between
600 corrupted images and broken symlinks. */
602 fprintf (stderr, "%s: no image filename found\n", progname);
603 else if (! stat (buf, &st))
604 fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
608 sprintf (buf2, "%.255s: %.1024s", progname, buf);
612 XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
613 draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
620 if (absfile) free (absfile);
621 clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable, buf, &geom,
624 free (clo2->directory);
630 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
632 void (*callback) (Screen *, Window, Drawable,
638 #if 0 /* do it synchronously */
640 FILE *pipe = open_image_name_pipe (dir);
643 fgets (buf, sizeof(buf)-1, pipe);
646 /* strip trailing newline */
648 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
652 if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
656 callback (screen, xwindow, drawable, buf, &geom, closure);
658 #else /* do it asynchronously */
660 Display *dpy = DisplayOfScreen (screen);
661 struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
662 clo2->directory = strdup (dir);
663 clo2->pipe = open_image_name_pipe (dir);
664 clo2->id = XtAppAddInput (XtDisplayToApplicationContext (dpy),
666 (XtPointer) (XtInputReadMask | XtInputExceptMask),
667 pipe_cb, (XtPointer) clo2);
668 clo2->screen = screen;
669 clo2->xwindow = xwindow;
670 clo2->drawable = drawable;
671 clo2->callback = callback;
672 clo2->closure = closure;
677 /* Loads an image into the Drawable, returning once the image is loaded.
680 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
681 void (*callback) (Screen *, Window, Drawable,
682 const char *name, XRectangle *geom,
686 XRectangle *geom_ret)
688 Display *dpy = DisplayOfScreen (screen);
689 XWindowAttributes xgwa;
690 Bool deskp = get_boolean_resource (dpy, "grabDesktopImages", "Boolean");
691 Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
694 XRectangle geom_ret_2;
695 char *name_ret_2 = 0;
697 if (!drawable) abort();
700 geom_ret = &geom_ret_2;
701 name_ret = &name_ret_2;
704 XGetWindowAttributes (dpy, window, &xgwa);
708 unsigned int w, h, bbw, d;
709 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
720 geom_ret->width = xgwa.width;
721 geom_ret->height = xgwa.height;
725 dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
730 if (deskp && filep) {
731 deskp = !(random() & 5); /* if both, desktop 1/5th of the time */
735 if (filep && !done) {
736 osx_load_image_file_async (screen, window, drawable, dir,
741 if (deskp && !done) {
742 osx_grab_desktop_image (screen, window, drawable);
744 *name_ret = strdup ("desktop");
749 draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
750 0, 0, xgwa.width, xgwa.height);
753 /* If we got here, we loaded synchronously even though they wanted async.
755 callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
759 #endif /* HAVE_COCOA */
762 /* Writes the string "Loading..." in the middle of the screen.
763 This will presumably get blown away when the image finally loads,
764 minutes or hours later...
766 This is called by load_image_async_simple() but not by load_image_async(),
767 since it is assumed that hacks that are loading more than one image
768 *at one time* will be doing something more clever than just blocking
772 print_loading_msg (Screen *screen, Window window)
774 Display *dpy = DisplayOfScreen (screen);
775 XWindowAttributes xgwa;
779 char *fn = get_string_resource (dpy, "labelFont", "Font");
780 const char *text = "Loading...";
783 if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
784 if (!fn) fn = get_string_resource (dpy, "font", "Font");
785 if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
786 f = XLoadQueryFont (dpy, fn);
787 if (!f) f = XLoadQueryFont (dpy, "fixed");
792 XGetWindowAttributes (dpy, window, &xgwa);
793 w = XTextWidth (f, text, strlen(text));
795 gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
796 "foreground", "Foreground");
797 gcv.background = get_pixel_resource (dpy, xgwa.colormap,
798 "background", "Background");
800 gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
801 XDrawImageString (dpy, window, gc,
802 (xgwa.width - w) / 2,
803 (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
811 /* Loads an image into the Drawable in the background;
812 when the image is fully loaded, runs the callback.
813 When grabbing desktop images, the Window will be unmapped first.
816 load_image_async (Screen *screen, Window window, Drawable drawable,
817 void (*callback) (Screen *, Window, Drawable,
818 const char *name, XRectangle *geom,
822 load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
825 struct async_load_state {
832 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
833 const char *name, XRectangle *geom, void *closure)
835 async_load_state *state = (async_load_state *) closure;
836 state->done_p = True;
837 state->filename = (name ? strdup (name) : 0);
842 load_image_async_simple (async_load_state *state,
847 XRectangle *geometry_ret)
849 if (state && state->done_p) /* done! */
852 *filename_ret = state->filename;
853 else if (state->filename)
854 free (state->filename);
857 *geometry_ret = state->geom;
862 else if (! state) /* first time */
864 state = (async_load_state *) calloc (1, sizeof(*state));
865 state->done_p = False;
866 print_loading_msg (screen, window);
867 load_image_async (screen, window, drawable,
868 load_image_async_simple_cb,
872 else /* still waiting */