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