http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.04.2.tar.gz
[xscreensaver] / driver / xscreensaver-getimage.c
1 /* xscreensaver, Copyright (c) 2001, 2002 by 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 /* xscreensaver-getimage -- helper program that puts an image
13    (e.g., a snapshot of the desktop) onto the given window.
14  */
15
16 #include "utils.h"
17
18 #include <X11/Intrinsic.h>
19 #include <ctype.h>
20 #include <errno.h>
21
22 #ifdef HAVE_SYS_WAIT_H
23 # include <sys/wait.h>          /* for waitpid() and associated macros */
24 #endif
25
26 #ifdef HAVE_XMU
27 # ifndef VMS
28 #  include <X11/Xmu/Error.h>
29 # else /* VMS */
30 #  include <Xmu/Error.h>
31 # endif
32 #else
33 # include "xmu.h"
34 #endif
35
36 #include "yarandom.h"
37 #include "grabscreen.h"
38 #include "resources.h"
39 #include "colorbars.h"
40 #include "visual.h"
41 #include "prefs.h"
42 #include "vroot.h"
43
44 #ifdef HAVE_GDK_PIXBUF
45
46 # ifdef HAVE_GTK2
47 #  include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
48 # else  /* !HAVE_GTK2 */
49 #  include <gdk-pixbuf/gdk-pixbuf-xlib.h>
50 # endif /* !HAVE_GTK2 */
51
52 # define HAVE_BUILTIN_IMAGE_LOADER
53 #endif /* HAVE_GDK_PIXBUF */
54
55
56 static char *defaults[] = {
57 #include "../driver/XScreenSaver_ad.h"
58  0
59 };
60
61
62
63 char *progname = 0;
64 char *progclass = "XScreenSaver";
65 XrmDatabase db;
66 XtAppContext app;
67
68 extern void grabscreen_verbose (void);
69
70
71 #define GETIMAGE_VIDEO_PROGRAM "xscreensaver-getimage-video"
72 #define GETIMAGE_FILE_PROGRAM  "xscreensaver-getimage-file"
73
74
75 const char *
76 blurb (void)
77 {
78   return progname;
79 }
80
81
82 static void
83 exec_error (char **av)
84 {
85   char buf [512];
86   char *token;
87
88   sprintf (buf, "%s: could not execute \"%s\"", progname, av[0]);
89   perror (buf);
90
91   if (errno == ENOENT &&
92       (token = getenv("PATH")))
93     {
94 # ifndef PATH_MAX
95 #  ifdef MAXPATHLEN
96 #   define PATH_MAX MAXPATHLEN
97 #  else
98 #   define PATH_MAX 2048
99 #  endif
100 # endif
101       char path[PATH_MAX];
102       fprintf (stderr, "\n");
103       *path = 0;
104 # if defined(HAVE_GETCWD)
105       getcwd (path, sizeof(path));
106 # elif defined(HAVE_GETWD)
107       getwd (path);
108 # endif
109       if (*path)
110         fprintf (stderr, "    Current directory is: %s\n", path);
111       fprintf (stderr, "    PATH is:\n");
112       token = strtok (strdup(token), ":");
113       while (token)
114         {
115           fprintf (stderr, "        %s\n", token);
116           token = strtok(0, ":");
117         }
118       fprintf (stderr, "\n");
119     }
120
121   exit (-1);
122 }
123
124 static int
125 x_ehandler (Display *dpy, XErrorEvent *error)
126 {
127   fprintf (stderr, "\nX error in %s:\n", progname);
128   XmuPrintDefaultErrorMessage (dpy, error, stderr);
129   exit (-1);
130   return 0;
131 }
132
133
134
135 #ifdef HAVE_BUILTIN_IMAGE_LOADER
136 static void load_image_internal (Screen *screen, Window window,
137                                  int win_width, int win_height,
138                                  Bool verbose_p,
139                                  int ac, char **av);
140 #endif /* HAVE_BUILTIN_IMAGE_LOADER */
141
142
143 static void
144 get_image (Screen *screen, Window window, Bool verbose_p)
145 {
146   Display *dpy = DisplayOfScreen (screen);
147   Bool desk_p  = get_boolean_resource ("grabDesktopImages",  "Boolean");
148   Bool video_p = get_boolean_resource ("grabVideoFrames",    "Boolean");
149   Bool image_p = get_boolean_resource ("chooseRandomImages", "Boolean");
150   char *dir    = get_string_resource ("imageDirectory", "ImageDirectory");
151
152   enum { do_desk, do_video, do_image, do_bars } which = do_bars;
153   int count = 0;
154
155   XWindowAttributes xgwa;
156   XGetWindowAttributes (dpy, window, &xgwa);
157   screen = xgwa.screen;
158
159   if (verbose_p)
160     {
161       fprintf (stderr, "%s: grabDesktopImages:  %s\n",
162                progname, desk_p ? "True" : "False");
163       fprintf (stderr, "%s: grabVideoFrames:    %s\n",
164                progname, video_p ? "True" : "False");
165       fprintf (stderr, "%s: chooseRandomImages: %s\n",
166                progname, image_p ? "True" : "False");
167       fprintf (stderr, "%s: imageDirectory:     %s\n",
168                progname, (dir ? dir : ""));
169     }
170
171   if (!dir || !*dir)
172     {
173       if (verbose_p && image_p)
174         fprintf (stderr,
175                  "%s: no imageDirectory: turning off chooseRandomImages.\n",
176                  progname);
177       image_p = False;
178     }
179
180 # ifndef _VROOT_H_
181 #  error Error!  This file definitely needs vroot.h!
182 # endif
183
184   /* If the window is not the root window (real or virtual!) then the hack
185      that called this program is running in "-window" mode instead of in
186      "-root" mode.
187
188      If the window is not the root window, then it's not possible to grab
189      video or images onto it (the contract with those programs is to draw on
190      the root.)  So turn off those options in that case, and turn on desktop
191      grabbing.  (Since we're running in a window on the desktop already, we
192      know it's not a security problem to expose desktop bits.)
193    */
194
195   if ((desk_p || video_p || image_p) &&
196       !top_level_window_p (screen, window))
197     {
198       Bool changed_p = False;
199       if (desk_p)  desk_p  = False, changed_p = True;
200       if (video_p) video_p = False, changed_p = True;
201 # ifndef HAVE_BUILTIN_IMAGE_LOADER
202       if (image_p) image_p = False, changed_p = True;
203       if (changed_p && verbose_p)
204         fprintf (stderr, "%s: not a top-level window: using colorbars.\n",
205                  progname);
206 # endif /* !HAVE_BUILTIN_IMAGE_LOADER */
207     }
208   else if (window != VirtualRootWindowOfScreen (screen))
209     {
210       Bool changed_p = False;
211       if (video_p) video_p = False, changed_p = True;
212 # ifndef HAVE_BUILTIN_IMAGE_LOADER
213       if (!desk_p) desk_p  = True,  changed_p = True;
214       if (image_p) image_p = False, changed_p = True;
215       if (changed_p && verbose_p)
216         fprintf (stderr,
217                  "%s: not running on root window: grabbing desktop.\n",
218                  progname);
219 # endif /* !HAVE_BUILTIN_IMAGE_LOADER */
220     }
221
222   count = 0;
223   if (desk_p)  count++;
224   if (video_p) count++;
225   if (image_p) count++;
226
227   if (count == 0)
228     which = do_bars;
229   else
230     {
231       int i = 0;
232       while (1)  /* loop until we get one that's permitted */
233         {
234           which = (random() % 3);
235           if (which == do_desk  && desk_p)  break;
236           if (which == do_video && video_p) break;
237           if (which == do_image && image_p) break;
238           if (++i > 200) abort();
239         }
240     }
241
242   if (which == do_desk)
243     {
244       if (verbose_p)
245         {
246           fprintf (stderr, "%s: grabbing desktop image\n", progname);
247           grabscreen_verbose();
248         }
249       grab_screen_image (screen, window);
250       XSync (dpy, False);
251     }
252   else if (which == do_bars)
253     {
254       if (verbose_p)
255         fprintf (stderr, "%s: drawing colorbars\n", progname);
256       draw_colorbars (dpy, window, 0, 0, xgwa.width, xgwa.height);
257       XSync (dpy, False);
258     }
259   else
260     {
261       char *av[10];
262       int ac = 0;
263       memset (av, 0, sizeof(av));
264       switch (which)
265         {
266         case do_video:
267           if (verbose_p)
268             fprintf (stderr, "%s: grabbing video\n", progname);
269           av[ac++] = GETIMAGE_VIDEO_PROGRAM;
270           break;
271         case do_image:
272           if (verbose_p)
273             fprintf (stderr, "%s: loading random image file\n", progname);
274           av[ac++] = GETIMAGE_FILE_PROGRAM;
275
276 # ifdef HAVE_BUILTIN_IMAGE_LOADER
277           av[ac++] = "--name";
278 # endif /* !HAVE_BUILTIN_IMAGE_LOADER */
279           av[ac++] = dir;
280           break;
281         default:
282           abort();
283           break;
284         }
285
286       if (verbose_p)
287         {
288           int i;
289           for (i = ac; i > 1; i--)
290             av[i] = av[i-1];
291           av[1] = strdup ("--verbose");
292           ac++;
293         }
294
295       if (verbose_p)
296         {
297           int i = 0;
298           fprintf (stderr, "%s: executing \"", progname);
299           while (av[i])
300             {
301               fprintf (stderr, "%s", av[i]);
302               if (av[++i]) fprintf (stderr, " ");
303             }
304           fprintf (stderr, "\"\n");
305         }
306
307 # ifdef HAVE_PUTENV
308       /* Store our "-display" argument into the $DISPLAY variable,
309          so that the subprocess gets the right display if the
310          prevailing $DISPLAY is different. */
311       {
312         const char *odpy = DisplayString (dpy);
313         char *ndpy = (char *) malloc(strlen(odpy) + 20);
314         char *s;
315         int screen_no = screen_number (screen);  /* might not be default now */
316
317         strcpy (ndpy, "DISPLAY=");
318         s = ndpy + strlen(ndpy);
319         strcpy (s, odpy);
320
321         while (*s && *s != ':') s++;            /* skip to colon */
322         while (*s == ':') s++;                  /* skip over colons */
323         while (isdigit(*s)) s++;                /* skip over dpy number */
324         while (*s == '.') s++;                  /* skip over dot */
325         if (s[-1] != '.') *s++ = '.';           /* put on a dot */
326         sprintf(s, "%d", screen_no);            /* put on screen number */
327
328         if (putenv (ndpy))
329           abort ();
330
331         /* don't free (ndpy) -- some implementations of putenv (BSD
332            4.4, glibc 2.0) copy the argument, but some (libc4,5, glibc
333            2.1.2) do not.  So we must leak it (and/or the previous
334            setting).  Yay.
335          */
336       }
337 # endif /* HAVE_PUTENV */
338
339 # ifdef HAVE_BUILTIN_IMAGE_LOADER
340       if (which == do_image)
341         {
342           load_image_internal (screen, window, xgwa.width, xgwa.height,
343                                verbose_p, ac, av);
344           return;
345         }
346 # endif /* HAVE_BUILTIN_IMAGE_LOADER */
347
348
349       close (ConnectionNumber (dpy));   /* close display fd */
350
351       execvp (av[0], av);               /* shouldn't return */
352       exec_error (av);
353     }
354 }
355
356
357 #ifdef HAVE_BUILTIN_IMAGE_LOADER
358
359 /* Reads a filename from "GETIMAGE_FILE_PROGRAM --name /DIR"
360  */
361 static char *
362 get_filename (Display *dpy, int ac, char **av)
363 {
364   pid_t forked;
365   int fds [2];
366   int in, out;
367   char buf[1024];
368
369   if (pipe (fds))
370     {
371       sprintf (buf, "%s: error creating pipe", progname);
372       perror (buf);
373       return 0;
374     }
375
376   in = fds [0];
377   out = fds [1];
378
379   switch ((int) (forked = fork ()))
380     {
381     case -1:
382       {
383         sprintf (buf, "%s: couldn't fork", progname);
384         perror (buf);
385         return 0;
386       }
387     case 0:
388       {
389         int stdout_fd = 1;
390
391         close (in);  /* don't need this one */
392         close (ConnectionNumber (dpy));         /* close display fd */
393
394         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
395           {
396             sprintf (buf, "%s: could not dup() a new stdout", progname);
397             exit (-1);                          /* exits fork */
398           }
399
400         execvp (av[0], av);                     /* shouldn't return. */
401         exit (-1);                              /* exits fork */
402         break;
403       }
404     default:
405       {
406         int wait_status = 0;
407         FILE *f = fdopen (in, "r");
408         int L;
409
410         close (out);  /* don't need this one */
411         *buf = 0;
412         fgets (buf, sizeof(buf)-1, f);
413         fclose (f);
414
415         /* Wait for the child to die. */
416         waitpid (-1, &wait_status, 0);
417
418         L = strlen (buf);
419         while (L && buf[L-1] == '\n')
420           buf[--L] = 0;
421           
422         return strdup (buf);
423       }
424     }
425
426   abort();
427 }
428
429
430
431 static void
432 load_image_internal (Screen *screen, Window window,
433                      int win_width, int win_height,
434                      Bool verbose_p,
435                      int ac, char **av)
436 {
437   GdkPixbuf *pb;
438   Display *dpy = DisplayOfScreen (screen);
439   char *filename = get_filename (dpy, ac, av);
440
441   if (!filename)
442     {
443       fprintf (stderr, "%s: no file name returned by %s\n",
444                progname, av[0]);
445       goto FAIL;
446     }
447   else if (verbose_p)
448     fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
449
450   gdk_pixbuf_xlib_init (dpy, screen_number (screen));
451   xlib_rgb_init (dpy, screen);
452
453   pb = gdk_pixbuf_new_from_file (filename
454 #ifdef HAVE_GTK2
455                                  , NULL
456 #endif /* HAVE_GTK2 */
457           );
458
459   if (pb)
460     {
461       int w = gdk_pixbuf_get_width (pb);
462       int h = gdk_pixbuf_get_height (pb);
463       int srcx, srcy, destx, desty;
464
465       Bool exact_fit_p = ((w == win_width  && h <= win_height) ||
466                           (h == win_height && w <= win_width));
467
468       if (!exact_fit_p)  /* scale the image up or down */
469         {
470           float rw = (float) win_width  / w;
471           float rh = (float) win_height / h;
472           float r = (rw < rh ? rw : rh);
473           int tw = w * r;
474           int th = h * r;
475           int pct = (r * 100);
476
477           if (pct < 95 || pct > 105)  /* don't scale if it's close */
478             {
479               GdkPixbuf *pb2;
480               if (verbose_p)
481                 fprintf (stderr,
482                          "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
483                          progname, pct, w, h, tw, th);
484
485               pb2 = gdk_pixbuf_scale_simple (pb, tw, th, GDK_INTERP_BILINEAR);
486               if (pb2)
487                 {
488                   gdk_pixbuf_unref (pb);
489                   pb = pb2;
490                   w = tw;
491                   h = th;
492                 }
493               else
494                 fprintf (stderr, "%s: out of memory when scaling?\n",
495                          progname);
496             }
497         }
498
499       /* Center the image on the window. */
500       srcx = 0;
501       srcy = 0;
502       destx = (win_width  - w) / 2;
503       desty = (win_height - h) / 2;
504       if (destx < 0) srcx = -destx, destx = 0;
505       if (desty < 0) srcy = -desty, desty = 0;
506
507       if (win_width  < w) w = win_width;
508       if (win_height < h) h = win_height;
509
510       /* #### Note that this always uses the default colormap!  Morons!
511               Owen says that in Gnome 2.0, I should try using
512               gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
513               But I don't have Gnome 2.0 yet.
514        */
515       XClearWindow (dpy, window);
516       gdk_pixbuf_xlib_render_to_drawable_alpha (pb, window,
517                                                 srcx, srcy, destx, desty, w, h,
518                                                 GDK_PIXBUF_ALPHA_FULL, 127,
519                                                 XLIB_RGB_DITHER_NORMAL, 0, 0);
520       XSync (dpy, False);
521
522       if (verbose_p)
523         fprintf (stderr, "%s: displayed %dx%d image at %d,%d.\n",
524                  progname, w, h, destx, desty);
525     }
526   else if (filename)
527     {
528       fprintf (stderr, "%s: unable to load %s\n", progname, filename);
529       goto FAIL;
530     }
531   else
532     {
533       fprintf (stderr, "%s: unable to initialize built-in images\n", progname);
534       goto FAIL;
535     }
536
537   return;
538
539  FAIL:
540   if (verbose_p)
541     fprintf (stderr, "%s: drawing colorbars\n", progname);
542   draw_colorbars (dpy, window, 0, 0, win_width, win_height);
543   XSync (dpy, False);
544 }
545
546 #endif /* HAVE_BUILTIN_IMAGE_LOADER */
547
548
549
550 #if 0
551 static Bool
552 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
553         XrmRepresentation *type, XrmValue *value, XPointer closure)
554 {
555   int i;
556   for (i = 0; quarks[i]; i++)
557     {
558       if (bindings[i] == XrmBindTightly)
559         fprintf (stderr, (i == 0 ? "" : "."));
560       else if (bindings[i] == XrmBindLoosely)
561         fprintf (stderr, "*");
562       else
563         fprintf (stderr, " ??? ");
564       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
565     }
566
567   fprintf (stderr, ": %s\n", (char *) value->addr);
568
569   return False;
570 }
571 #endif
572
573
574 int
575 main (int argc, char **argv)
576 {
577   saver_preferences P;
578   Widget toplevel;
579   Display *dpy;
580   Screen *screen;
581   Window window = (Window) 0;
582   Bool verbose_p = False;
583   char *s;
584   int i;
585
586   progname = argv[0];
587   s = strrchr (progname, '/');
588   if (s) progname = s+1;
589
590   /* We must read exactly the same resources as xscreensaver.
591      That means we must have both the same progclass *and* progname,
592      at least as far as the resource database is concerned.  So,
593      put "xscreensaver" in argv[0] while initializing Xt.
594    */
595   argv[0] = "xscreensaver";
596   toplevel = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
597                               defaults, 0, 0);
598   argv[0] = progname;
599   dpy = XtDisplay (toplevel);
600   screen = XtScreen (toplevel);
601   db = XtDatabase (dpy);
602
603   XtGetApplicationNameAndClass (dpy, &s, &progclass);
604   XSetErrorHandler (x_ehandler);
605   XSync (dpy, False);
606
607   /* half-assed way of avoiding buffer-overrun attacks. */
608   if (strlen (progname) >= 100) progname[100] = 0;
609
610   for (i = 1; i < argc; i++)
611     {
612       if (argv[i][0] == '-' && argv[i][1] == '-') argv[i]++;
613       if (!strcmp (argv[i], "-v") ||
614           !strcmp (argv[i], "-verbose"))
615         verbose_p = True;
616       else if (window == 0)
617         {
618           unsigned long w;
619           char dummy;
620
621           if (!strcmp (argv[i], "root") ||
622               !strcmp (argv[i], "-root") ||
623               !strcmp (argv[i], "--root"))
624             window = RootWindowOfScreen (screen);
625
626           else if ((1 == sscanf (argv[i], " 0x%x %c", &w, &dummy) ||
627                     1 == sscanf (argv[i], " %d %c",   &w, &dummy)) &&
628                    w != 0)
629             window = (Window) w;
630           else
631             goto LOSE;
632         }
633       else
634         {
635          LOSE:
636           fprintf (stderr,
637             "usage: %s [ -display host:dpy.screen ] [ -v ] window-id\n",
638                    progname);
639           fprintf (stderr, "\n"
640         "\tThis program puts an image of the desktop on the given window.\n"
641         "\tIt is used by those xscreensaver demos that operate on images.\n"
642         "\n");
643           exit (1);
644         }
645     }
646
647   if (window == 0) goto LOSE;
648
649   /* Randomize -- only need to do this here because this program
650      doesn't use the `screenhack.h' or `lockmore.h' APIs. */
651 # undef ya_rand_init
652   ya_rand_init (0);
653
654   memset (&P, 0, sizeof(P));
655   P.db = db;
656   load_init_file (&P);
657
658   if (P.verbose_p)
659     verbose_p = True;
660
661 #if 0
662   /* Print out all the resources we read. */
663   {
664     XrmName name = { 0 };
665     XrmClass class = { 0 };
666     int count = 0;
667     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
668                           (XtPointer) &count);
669   }
670 #endif
671
672   get_image (screen, window, verbose_p);
673   exit (0);
674 }