7b5298dffd156f653653a550fae454f332de4dab
[xscreensaver] / utils / grabclient.c
1 /* xscreensaver, Copyright (c) 1992, 1993, 1994, 1997, 1998, 2001, 2003
2  *  Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* This file contains code for running an external program to grab an image
14    onto the given window.  The external program in question must take a
15    window ID as its argument, e.g., the "xscreensaver-getimage" program
16    in the hacks/ directory.
17
18    That program links against utils/grabimage.c, which happens to export the
19    same API as this program (utils/grabclient.c).
20  */
21
22 #include "utils.h"
23 #include "grabscreen.h"
24 #include "resources.h"
25
26 #include "vroot.h"
27 #include <X11/Xatom.h>
28
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #ifdef HAVE_SYS_WAIT_H
33 # include <sys/wait.h>          /* for waitpid() and associated macros */
34 #endif
35
36
37 extern char *progname;
38
39
40 static Bool error_handler_hit_p = False;
41
42 static int
43 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
44 {
45   error_handler_hit_p = True;
46   return 0;
47 }
48
49
50 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
51  */
52 static Bool
53 drawable_window_p (Display *dpy, Drawable d)
54 {
55   XErrorHandler old_handler;
56   XWindowAttributes xgwa;
57
58   XSync (dpy, False);
59   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
60   error_handler_hit_p = False;
61   XGetWindowAttributes (dpy, d, &xgwa);
62   XSync (dpy, False);
63   XSetErrorHandler (old_handler);
64   XSync (dpy, False);
65
66   if (!error_handler_hit_p)
67     return True;   /* It's a Window. */
68   else
69     return False;  /* It's a Pixmap, or an invalid ID. */
70 }
71
72
73 static Bool
74 xscreensaver_window_p (Display *dpy, Window window)
75 {
76   Atom type;
77   int format;
78   unsigned long nitems, bytesafter;
79   char *version;
80   if (XGetWindowProperty (dpy, window,
81                           XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
82                           0, 1, False, XA_STRING,
83                           &type, &format, &nitems, &bytesafter,
84                           (unsigned char **) &version)
85       == Success
86       && type != None)
87     return True;
88   return False;
89 }
90
91
92 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
93    on a window whose depth is not the maximal depth of the screen?  Or
94    something.  Anyway, things don't work unless we: use SubwindowMode for
95    the real root window (or a legitimate virtual root window); but do not
96    use SubwindowMode for the xscreensaver window.  I make no attempt to
97    explain.
98  */
99 Bool
100 use_subwindow_mode_p(Screen *screen, Window window)
101 {
102   if (window != VirtualRootWindowOfScreen(screen))
103     return False;
104   else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
105     return False;
106   else
107     return True;
108 }
109
110
111 static void
112 checkerboard (Screen *screen, Drawable drawable)
113 {
114   Display *dpy = DisplayOfScreen (screen);
115   unsigned int x, y;
116   int size = 24;
117   XColor fg, bg;
118   XGCValues gcv;
119   GC gc = XCreateGC (dpy, drawable, 0, &gcv);
120   Colormap cmap;
121   unsigned int win_width, win_height;
122
123   fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
124   fg.red = fg.green = fg.blue = 0x0000;
125   bg.red = bg.green = bg.blue = 0x4444;
126   fg.pixel = 0;
127   bg.pixel = 1;
128
129   if (drawable_window_p (dpy, drawable))
130     {
131       XWindowAttributes xgwa;
132       XGetWindowAttributes (dpy, drawable, &xgwa);
133       win_width = xgwa.width;
134       win_height = xgwa.height;
135       cmap = xgwa.colormap;
136       screen = xgwa.screen;
137     }
138   else  /* it's a pixmap */
139     {
140       XWindowAttributes xgwa;
141       Window root;
142       int x, y;
143       unsigned int bw, d;
144       XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
145       cmap = xgwa.colormap;
146       XGetGeometry (dpy, drawable,
147                     &root, &x, &y, &win_width, &win_height, &bw, &d);
148     }
149
150   /* Allocate black and gray, but don't hold them locked. */
151   if (XAllocColor (dpy, cmap, &fg))
152     XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
153   if (XAllocColor (dpy, cmap, &bg))
154     XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
155
156   XSetForeground (dpy, gc, bg.pixel);
157   XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
158   XSetForeground (dpy, gc, fg.pixel);
159   for (y = 0; y < win_height; y += size+size)
160     for (x = 0; x < win_width; x += size+size)
161       {
162         XFillRectangle (dpy, drawable, gc, x,      y,      size, size);
163         XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
164       }
165 }
166
167 static void
168 hack_subproc_environment (Display *dpy)
169 {
170   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
171      the spawned processes inherit is what we are actually using.
172    */
173   const char *odpy = DisplayString (dpy);
174   char *ndpy = (char *) malloc(strlen(odpy) + 20);
175   strcpy (ndpy, "DISPLAY=");
176   strcat (ndpy, odpy);
177
178   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
179      any more, right?  It's not Posix, but everyone seems to have it. */
180 #ifdef HAVE_PUTENV
181   if (putenv (ndpy))
182     abort ();
183 #endif /* HAVE_PUTENV */
184 }
185
186
187 /* Spawn a program, and wait for it to finish.
188    If we just use system() for this, then sometimes the subprocess
189    doesn't die when *this* process is sent a TERM signal.  Perhaps
190    this is due to the intermediate /bin/sh that system() uses to
191    parse arguments?  I'm not sure.  But using fork() and execvp()
192    here seems to close the race.
193  */
194
195 static void
196 exec_simple_command (const char *command)
197 {
198   char *av[1024];
199   int ac = 0;
200   char *token = strtok (strdup(command), " \t");
201   while (token)
202     {
203       av[ac++] = token;
204       token = strtok(0, " \t");
205     }
206   av[ac] = 0;
207
208   execvp (av[0], av);   /* shouldn't return. */
209 }
210
211 static void
212 fork_exec_wait (const char *command)
213 {
214   char buf [255];
215   pid_t forked;
216   int status;
217
218   switch ((int) (forked = fork ()))
219     {
220     case -1:
221       sprintf (buf, "%s: couldn't fork", progname);
222       perror (buf);
223       return;
224
225     case 0:
226       exec_simple_command (command);
227       exit (1);  /* exits child fork */
228       break;
229
230     default:
231       waitpid (forked, &status, 0);
232       break;
233     }
234 }
235
236
237 /* Loads an image into the Drawable.
238    When grabbing desktop images, the Window will be unmapped first.
239  */
240 void
241 load_random_image (Screen *screen, Window window, Drawable drawable,
242                    char **name_ret)
243 {
244   Display *dpy = DisplayOfScreen (screen);
245   char *grabber = get_string_resource ("desktopGrabber", "DesktopGrabber");
246   char *cmd;
247   char id[200];
248
249   if (!grabber || !*grabber)
250     {
251       fprintf (stderr,
252          "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
253                progname);
254       exit (1);
255     }
256
257   sprintf (id, "0x%lx 0x%lx",
258            (unsigned long) window,
259            (unsigned long) drawable);
260   cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
261
262   /* Needn't worry about buffer overflows here, because the buffer is
263      longer than the length of the format string, and the length of what
264      we're putting into it.  So the only way to crash would be if the
265      format string itself was corrupted, but that comes from the
266      resource database, and if hostile forces have access to that,
267      then the game is already over.
268    */
269   sprintf (cmd, grabber, id);
270
271   /* In case "cmd" fails, leave some random image on the screen, not just
272      black or white, so that it's more obvious what went wrong. */
273   checkerboard (screen, drawable);
274
275   XSync (dpy, True);
276   hack_subproc_environment (dpy);
277   fork_exec_wait (cmd);
278   free (cmd);
279   XSync (dpy, True);
280
281   if (name_ret) 
282     {
283       Atom type;
284       int format;
285       unsigned long nitems, bytesafter;
286       char *name=NULL;
287
288       *name_ret = NULL;
289
290       if (XGetWindowProperty (dpy, window,
291                               XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME,
292                                            False),
293                               0, 1024, False, XA_STRING,
294                               &type, &format, &nitems, &bytesafter,
295                               (unsigned char **) &name)
296           == Success
297           && type != None)
298         *name_ret = strdup(name);
299     }
300 }