ftp://ftp.linux.ncsu.edu/mirror/ftp.redhat.com/pub/redhat/linux/enterprise/4/en/os...
[xscreensaver] / utils / grabclient.c
1 /* xscreensaver, Copyright (c) 1992, 1993, 1994, 1997, 1998, 2001, 2003, 2004
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 #include <X11/Intrinsic.h>   /* for XtInputId, etc */
30
31
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif
35 #ifdef HAVE_SYS_WAIT_H
36 # include <sys/wait.h>          /* for waitpid() and associated macros */
37 #endif
38
39
40 extern char *progname;
41 extern XtAppContext app;
42
43
44 static Bool error_handler_hit_p = False;
45
46 static int
47 ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error)
48 {
49   error_handler_hit_p = True;
50   return 0;
51 }
52
53
54 /* Returns True if the given Drawable is a Window; False if it's a Pixmap.
55  */
56 static Bool
57 drawable_window_p (Display *dpy, Drawable d)
58 {
59   XErrorHandler old_handler;
60   XWindowAttributes xgwa;
61
62   XSync (dpy, False);
63   old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
64   error_handler_hit_p = False;
65   XGetWindowAttributes (dpy, d, &xgwa);
66   XSync (dpy, False);
67   XSetErrorHandler (old_handler);
68   XSync (dpy, False);
69
70   if (!error_handler_hit_p)
71     return True;   /* It's a Window. */
72   else
73     return False;  /* It's a Pixmap, or an invalid ID. */
74 }
75
76
77 static Bool
78 xscreensaver_window_p (Display *dpy, Window window)
79 {
80   Atom type;
81   int format;
82   unsigned long nitems, bytesafter;
83   unsigned char *version;
84   if (XGetWindowProperty (dpy, window,
85                           XInternAtom (dpy, "_SCREENSAVER_VERSION", False),
86                           0, 1, False, XA_STRING,
87                           &type, &format, &nitems, &bytesafter,
88                           &version)
89       == Success
90       && type != None)
91     return True;
92   return False;
93 }
94
95
96 /* XCopyArea seems not to work right on SGI O2s if you draw in SubwindowMode
97    on a window whose depth is not the maximal depth of the screen?  Or
98    something.  Anyway, things don't work unless we: use SubwindowMode for
99    the real root window (or a legitimate virtual root window); but do not
100    use SubwindowMode for the xscreensaver window.  I make no attempt to
101    explain.
102  */
103 Bool
104 use_subwindow_mode_p(Screen *screen, Window window)
105 {
106   if (window != VirtualRootWindowOfScreen(screen))
107     return False;
108   else if (xscreensaver_window_p(DisplayOfScreen(screen), window))
109     return False;
110   else
111     return True;
112 }
113
114
115 static void
116 checkerboard (Screen *screen, Drawable drawable)
117 {
118   Display *dpy = DisplayOfScreen (screen);
119   unsigned int x, y;
120   int size = 24;
121   XColor fg, bg;
122   XGCValues gcv;
123   GC gc = XCreateGC (dpy, drawable, 0, &gcv);
124   Colormap cmap;
125   unsigned int win_width, win_height;
126
127   fg.flags = bg.flags = DoRed|DoGreen|DoBlue;
128   fg.red = fg.green = fg.blue = 0x0000;
129   bg.red = bg.green = bg.blue = 0x4444;
130   fg.pixel = 0;
131   bg.pixel = 1;
132
133   if (drawable_window_p (dpy, drawable))
134     {
135       XWindowAttributes xgwa;
136       XGetWindowAttributes (dpy, drawable, &xgwa);
137       win_width = xgwa.width;
138       win_height = xgwa.height;
139       cmap = xgwa.colormap;
140       screen = xgwa.screen;
141     }
142   else  /* it's a pixmap */
143     {
144       XWindowAttributes xgwa;
145       Window root;
146       int x, y;
147       unsigned int bw, d;
148       XGetWindowAttributes (dpy, RootWindowOfScreen (screen), &xgwa);
149       cmap = xgwa.colormap;
150       XGetGeometry (dpy, drawable,
151                     &root, &x, &y, &win_width, &win_height, &bw, &d);
152     }
153
154   /* Allocate black and gray, but don't hold them locked. */
155   if (XAllocColor (dpy, cmap, &fg))
156     XFreeColors (dpy, cmap, &fg.pixel, 1, 0);
157   if (XAllocColor (dpy, cmap, &bg))
158     XFreeColors (dpy, cmap, &bg.pixel, 1, 0);
159
160   XSetForeground (dpy, gc, bg.pixel);
161   XFillRectangle (dpy, drawable, gc, 0, 0, win_width, win_height);
162   XSetForeground (dpy, gc, fg.pixel);
163   for (y = 0; y < win_height; y += size+size)
164     for (x = 0; x < win_width; x += size+size)
165       {
166         XFillRectangle (dpy, drawable, gc, x,      y,      size, size);
167         XFillRectangle (dpy, drawable, gc, x+size, y+size, size, size);
168       }
169 }
170
171
172 static char *
173 get_name (Display *dpy, Window window)
174 {
175   Atom type;
176   int format;
177   unsigned long nitems, bytesafter;
178   unsigned char *name = 0;
179   Atom atom = XInternAtom (dpy, XA_XSCREENSAVER_IMAGE_FILENAME, False);
180   if (XGetWindowProperty (dpy, window, atom,
181                           0, 1024, False, XA_STRING,
182                           &type, &format, &nitems, &bytesafter,
183                           &name)
184       == Success
185       && type != None)
186     return strdup((char *) name);
187   else
188     return 0;
189 }
190
191
192 static void
193 hack_subproc_environment (Display *dpy)
194 {
195   /* Store $DISPLAY into the environment, so that the $DISPLAY variable that
196      the spawned processes inherit is what we are actually using.
197    */
198   const char *odpy = DisplayString (dpy);
199   char *ndpy = (char *) malloc(strlen(odpy) + 20);
200   strcpy (ndpy, "DISPLAY=");
201   strcat (ndpy, odpy);
202
203   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
204      any more, right?  It's not Posix, but everyone seems to have it. */
205 #ifdef HAVE_PUTENV
206   if (putenv (ndpy))
207     abort ();
208 #endif /* HAVE_PUTENV */
209 }
210
211
212 /* Spawn a program, and wait for it to finish.
213    If we just use system() for this, then sometimes the subprocess
214    doesn't die when *this* process is sent a TERM signal.  Perhaps
215    this is due to the intermediate /bin/sh that system() uses to
216    parse arguments?  I'm not sure.  But using fork() and execvp()
217    here seems to close the race.
218  */
219
220 static void
221 exec_simple_command (const char *command)
222 {
223   char *av[1024];
224   int ac = 0;
225   char *token = strtok (strdup(command), " \t");
226   while (token)
227     {
228       av[ac++] = token;
229       token = strtok(0, " \t");
230     }
231   av[ac] = 0;
232
233   execvp (av[0], av);   /* shouldn't return. */
234 }
235
236 static void
237 fork_exec_wait (const char *command)
238 {
239   char buf [255];
240   pid_t forked;
241   int status;
242
243   switch ((int) (forked = fork ()))
244     {
245     case -1:
246       sprintf (buf, "%s: couldn't fork", progname);
247       perror (buf);
248       return;
249
250     case 0:
251       exec_simple_command (command);
252       exit (1);  /* exits child fork */
253       break;
254
255     default:
256       waitpid (forked, &status, 0);
257       break;
258     }
259 }
260
261
262 typedef struct {
263   void (*callback) (Screen *, Window, Drawable,
264                     const char *name, void *closure);
265   Screen *screen;
266   Window window;
267   Drawable drawable;
268   void *closure;
269   FILE *read_pipe;
270   FILE *write_pipe;
271   XtInputId pipe_id;
272 } grabclient_data;
273
274
275 static void finalize_cb (XtPointer closure, int *fd, XtIntervalId *id);
276
277 static void
278 fork_exec_cb (const char *command,
279               Screen *screen, Window window, Drawable drawable,
280               void (*callback) (Screen *, Window, Drawable,
281                                 const char *name, void *closure),
282               void *closure)
283 {
284   grabclient_data *data;
285   char buf [255];
286   pid_t forked;
287
288   int fds [2];
289
290   if (pipe (fds))
291     {
292       sprintf (buf, "%s: creating pipe", progname);
293       perror (buf);
294       exit (1);
295     }
296
297   data = (grabclient_data *) calloc (1, sizeof(*data));
298   data->callback   = callback;
299   data->closure    = closure;
300   data->screen     = screen;
301   data->window     = window;
302   data->drawable   = drawable;
303   data->read_pipe  = fdopen (fds[0], "r");
304   data->write_pipe = fdopen (fds[1], "w");
305
306   if (!data->read_pipe || !data->write_pipe)
307     {
308       sprintf (buf, "%s: fdopen", progname);
309       perror (buf);
310       exit (1);
311     }
312
313   data->pipe_id =
314     XtAppAddInput (app, fileno (data->read_pipe),
315                    (XtPointer) (XtInputReadMask | XtInputExceptMask),
316                    finalize_cb, (XtPointer) data);
317
318   switch ((int) (forked = fork ()))
319     {
320     case -1:
321       sprintf (buf, "%s: couldn't fork", progname);
322       perror (buf);
323       return;
324
325     case 0:                                     /* child */
326
327       fclose (data->read_pipe);
328       data->read_pipe = 0;
329
330       /* clone the write pipe onto stdout so that it gets closed
331          when the fork exits.  This will shut down the pipe and
332          signal the parent.
333        */
334       close (fileno (stdout));
335       dup2 (fds[1], fileno (stdout));
336       close (fds[1]);
337
338       close (fileno (stdin)); /* for kicks */
339
340       exec_simple_command (command);
341       exit (1);  /* exits child fork */
342       break;
343
344     default:                                    /* parent */
345       fclose (data->write_pipe);
346       data->write_pipe = 0;
347       break;
348     }
349 }
350
351
352 /* Called in the parent when the forked process dies.
353    Runs the caller's callback, and cleans up.
354  */
355 static void
356 finalize_cb (XtPointer closure, int *fd, XtIntervalId *id)
357 {
358   grabclient_data *data = (grabclient_data *) closure;
359   char *name;
360
361   XtRemoveInput (*id);
362
363   name = get_name (DisplayOfScreen (data->screen), data->window);
364   data->callback (data->screen, data->window, data->drawable,
365                   name, data->closure);
366   free (name);
367
368   fclose (data->read_pipe);
369   memset (data, 0, sizeof (*data));
370   free (data);
371 }
372
373
374 /* Loads an image into the Drawable.
375    When grabbing desktop images, the Window will be unmapped first.
376  */
377 static void
378 load_random_image_1 (Screen *screen, Window window, Drawable drawable,
379                      void (*callback) (Screen *, Window, Drawable,
380                                        const char *name, void *closure),
381                      void *closure,
382                      char **name_ret)
383 {
384   Display *dpy = DisplayOfScreen (screen);
385   char *grabber = get_string_resource ("desktopGrabber", "DesktopGrabber");
386   char *cmd;
387   char id[200];
388
389   if (!grabber || !*grabber)
390     {
391       fprintf (stderr,
392          "%s: resources installed incorrectly: \"desktopGrabber\" is unset!\n",
393                progname);
394       exit (1);
395     }
396
397   sprintf (id, "0x%lx 0x%lx",
398            (unsigned long) window,
399            (unsigned long) drawable);
400   cmd = (char *) malloc (strlen(grabber) + strlen(id) + 1);
401
402   /* Needn't worry about buffer overflows here, because the buffer is
403      longer than the length of the format string, and the length of what
404      we're putting into it.  So the only way to crash would be if the
405      format string itself was corrupted, but that comes from the
406      resource database, and if hostile forces have access to that,
407      then the game is already over.
408    */
409   sprintf (cmd, grabber, id);
410
411   /* In case "cmd" fails, leave some random image on the screen, not just
412      black or white, so that it's more obvious what went wrong. */
413   checkerboard (screen, drawable);
414
415   XSync (dpy, True);
416   hack_subproc_environment (dpy);
417
418   if (callback)
419     {
420       /* Start the image loading in another fork and return immediately.
421          Invoke the callback function when done.
422        */
423       if (name_ret) abort();
424       fork_exec_cb (cmd, screen, window, drawable, callback, closure);
425     }
426   else
427     {
428       /* Wait for the image to load, and return it immediately.
429        */
430       fork_exec_wait (cmd);
431       if (name_ret)
432         *name_ret = get_name (dpy, window);
433     }
434
435   free (cmd);
436   XSync (dpy, True);
437 }
438
439
440 /* Loads an image into the Drawable in the background;
441    when the image is fully loaded, runs the callback.
442    When grabbing desktop images, the Window will be unmapped first.
443  */
444 void
445 fork_load_random_image (Screen *screen, Window window, Drawable drawable,
446                         void (*callback) (Screen *, Window, Drawable,
447                                           const char *name, void *closure),
448                         void *closure)
449 {
450   load_random_image_1 (screen, window, drawable, callback, closure, 0);
451 }
452
453
454 #ifndef DEBUG
455
456 /* Loads an image into the Drawable, returning once the image is loaded.
457    When grabbing desktop images, the Window will be unmapped first.
458  */
459 void
460 load_random_image (Screen *screen, Window window, Drawable drawable,
461                    char **name_ret)
462 {
463   load_random_image_1 (screen, window, drawable, 0, 0, name_ret);
464 }
465
466 #else  /* DEBUG */
467
468 typedef struct {
469   char **name_ret;
470   Bool done;
471 } debug_closure;
472
473 static void
474 debug_cb (Screen *screen, Window window, Drawable drawable,
475           const char *name, void *closure)
476 {
477   debug_closure *data = (debug_closure *) closure;
478   fprintf (stderr, "%s: GRAB DEBUG: callback\n", progname);
479   if (data->name_ret)
480     *data->name_ret = (name ? strdup (name) : 0);
481   data->done = True;
482 }
483
484 void
485 load_random_image (Screen *screen, Window window, Drawable drawable,
486                    char **name_ret)
487 {
488   debug_closure data;
489   data.name_ret = name_ret;
490   data.done = False;
491   fprintf (stderr, "%s: GRAB DEBUG: forking\n", progname);
492   fork_load_random_image (screen, window, drawable, debug_cb, &data);
493   while (! data.done)
494     {
495       fprintf (stderr, "%s: GRAB DEBUG: waiting\n", progname);
496       if (XtAppPending (app) & XtIMAlternateInput)
497         XtAppProcessEvent (app, XtIMAlternateInput);
498       usleep (50000);
499     }
500   fprintf (stderr, "%s: GRAB DEBUG: done\n", progname);
501 }
502
503 #endif /* DEBUG */