1 /* xscreensaver, Copyright (c) 1992-2010 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 {
548 void (*callback) (Screen *, Window, Drawable,
549 const char *name, XRectangle *geom,
556 pipe_cb (XtPointer closure, int *source, XtInputId *id)
558 /* This is not called from a signal handler, so doing stuff here is fine.
560 struct pipe_closure *clo2 = (struct pipe_closure *) closure;
562 fgets (buf, sizeof(buf)-1, clo2->pipe);
565 XtRemoveInput (clo2->id);
568 /* strip trailing newline */
570 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
573 Display *dpy = DisplayOfScreen (clo2->screen);
576 if (! osx_load_image_file (clo2->screen, clo2->xwindow, clo2->drawable,
578 /* unable to load image - draw colorbars
580 XWindowAttributes xgwa;
581 XGetWindowAttributes (dpy, clo2->xwindow, &xgwa);
584 unsigned int w, h, bbw, d;
587 /* Log something to syslog so we can tell the difference between
588 corrupted images and broken symlinks. */
590 fprintf (stderr, "%s: no image filename found\n", progname);
591 else if (! stat (buf, &st))
592 fprintf (stderr, "%s: %s: unparsable\n", progname, buf);
596 sprintf (buf2, "%.255s: %.1024s", progname, buf);
600 XGetGeometry (dpy, clo2->drawable, &r, &x, &y, &w, &h, &bbw, &d);
601 draw_colorbars (clo2->screen, xgwa.visual, clo2->drawable, xgwa.colormap,
608 clo2->callback (clo2->screen, clo2->xwindow, clo2->drawable, buf, &geom,
616 osx_load_image_file_async (Screen *screen, Window xwindow, Drawable drawable,
618 void (*callback) (Screen *, Window, Drawable,
624 #if 0 /* do it synchronously */
626 FILE *pipe = open_image_name_pipe (dir);
629 fgets (buf, sizeof(buf)-1, pipe);
632 /* strip trailing newline */
634 while (L > 0 && (buf[L-1] == '\r' || buf[L-1] == '\n'))
638 if (! osx_load_image_file (screen, xwindow, drawable, buf, &geom)) {
642 callback (screen, xwindow, drawable, buf, &geom, closure);
644 #else /* do it asynchronously */
646 Display *dpy = DisplayOfScreen (screen);
647 struct pipe_closure *clo2 = (struct pipe_closure *) calloc (1, sizeof(*clo2));
648 clo2->pipe = open_image_name_pipe (dir);
649 clo2->id = XtAppAddInput (XtDisplayToApplicationContext (dpy),
651 (XtPointer) (XtInputReadMask | XtInputExceptMask),
652 pipe_cb, (XtPointer) clo2);
653 clo2->screen = screen;
654 clo2->xwindow = xwindow;
655 clo2->drawable = drawable;
656 clo2->callback = callback;
657 clo2->closure = closure;
662 /* Loads an image into the Drawable, returning once the image is loaded.
665 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
666 void (*callback) (Screen *, Window, Drawable,
667 const char *name, XRectangle *geom,
671 XRectangle *geom_ret)
673 Display *dpy = DisplayOfScreen (screen);
674 XWindowAttributes xgwa;
675 Bool deskp = get_boolean_resource (dpy, "grabDesktopImages", "Boolean");
676 Bool filep = get_boolean_resource (dpy, "chooseRandomImages", "Boolean");
679 XRectangle geom_ret_2;
680 char *name_ret_2 = 0;
682 if (!drawable) abort();
685 geom_ret = &geom_ret_2;
686 name_ret = &name_ret_2;
689 XGetWindowAttributes (dpy, window, &xgwa);
693 unsigned int w, h, bbw, d;
694 XGetGeometry (dpy, drawable, &r, &x, &y, &w, &h, &bbw, &d);
705 geom_ret->width = xgwa.width;
706 geom_ret->height = xgwa.height;
710 dir = get_string_resource (dpy, "imageDirectory", "ImageDirectory");
715 if (deskp && filep) {
716 deskp = !(random() & 5); /* if both, desktop 1/5th of the time */
720 if (filep && !done) {
721 osx_load_image_file_async (screen, window, drawable, dir,
726 if (deskp && !done) {
727 osx_grab_desktop_image (screen, window, drawable);
729 *name_ret = strdup ("desktop");
734 draw_colorbars (screen, xgwa.visual, drawable, xgwa.colormap,
735 0, 0, xgwa.width, xgwa.height);
738 /* If we got here, we loaded synchronously even though they wanted async.
740 callback (screen, window, drawable, name_ret_2, &geom_ret_2, closure);
744 #endif /* HAVE_COCOA */
747 /* Writes the string "Loading..." in the middle of the screen.
748 This will presumably get blown away when the image finally loads,
749 minutes or hours later...
751 This is called by load_image_async_simple() but not by load_image_async(),
752 since it is assumed that hacks that are loading more than one image
753 *at one time* will be doing something more clever than just blocking
757 print_loading_msg (Screen *screen, Window window)
759 Display *dpy = DisplayOfScreen (screen);
760 XWindowAttributes xgwa;
764 char *fn = get_string_resource (dpy, "labelFont", "Font");
765 const char *text = "Loading...";
768 if (!fn) fn = get_string_resource (dpy, "titleFont", "Font");
769 if (!fn) fn = get_string_resource (dpy, "font", "Font");
770 if (!fn) fn = strdup ("-*-times-bold-r-normal-*-180-*");
771 f = XLoadQueryFont (dpy, fn);
772 if (!f) f = XLoadQueryFont (dpy, "fixed");
777 XGetWindowAttributes (dpy, window, &xgwa);
778 w = XTextWidth (f, text, strlen(text));
780 gcv.foreground = get_pixel_resource (dpy, xgwa.colormap,
781 "foreground", "Foreground");
782 gcv.background = get_pixel_resource (dpy, xgwa.colormap,
783 "background", "Background");
785 gc = XCreateGC (dpy, window, GCFont | GCForeground | GCBackground, &gcv);
786 XDrawImageString (dpy, window, gc,
787 (xgwa.width - w) / 2,
788 (xgwa.height - (f->ascent + f->descent)) / 2 + f->ascent,
796 /* Loads an image into the Drawable in the background;
797 when the image is fully loaded, runs the callback.
798 When grabbing desktop images, the Window will be unmapped first.
801 load_image_async (Screen *screen, Window window, Drawable drawable,
802 void (*callback) (Screen *, Window, Drawable,
803 const char *name, XRectangle *geom,
807 load_random_image_1 (screen, window, drawable, callback, closure, 0, 0);
810 struct async_load_state {
817 load_image_async_simple_cb (Screen *screen, Window window, Drawable drawable,
818 const char *name, XRectangle *geom, void *closure)
820 async_load_state *state = (async_load_state *) closure;
821 state->done_p = True;
822 state->filename = (name ? strdup (name) : 0);
827 load_image_async_simple (async_load_state *state,
832 XRectangle *geometry_ret)
834 if (state && state->done_p) /* done! */
837 *filename_ret = state->filename;
838 else if (state->filename)
839 free (state->filename);
842 *geometry_ret = state->geom;
847 else if (! state) /* first time */
849 state = (async_load_state *) calloc (1, sizeof(*state));
850 state->done_p = False;
851 print_loading_msg (screen, window);
852 load_image_async (screen, window, drawable,
853 load_image_async_simple_cb,
857 else /* still waiting */