d1cd8515262ed5557a7d5508a8e7ed5dea3b8ae6
[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 #ifdef HAVE_GTK2
441   GError *gerr = 0;
442 #endif /* HAVE_GTK2 */
443
444   if (!filename)
445     {
446       fprintf (stderr, "%s: no file name returned by %s\n",
447                progname, av[0]);
448       goto FAIL;
449     }
450   else if (verbose_p)
451     fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
452
453   gdk_pixbuf_xlib_init (dpy, screen_number (screen));
454 #ifdef HAVE_GTK2
455   g_type_init();
456 #else  /* !HAVE_GTK2 */
457   xlib_rgb_init (dpy, screen);
458 #endif /* !HAVE_GTK2 */
459
460   pb = gdk_pixbuf_new_from_file (filename
461 #ifdef HAVE_GTK2
462                                  , &gerr
463 #endif /* HAVE_GTK2 */
464           );
465
466   if (pb)
467     {
468       int w = gdk_pixbuf_get_width (pb);
469       int h = gdk_pixbuf_get_height (pb);
470       int srcx, srcy, destx, desty;
471
472       Bool exact_fit_p = ((w == win_width  && h <= win_height) ||
473                           (h == win_height && w <= win_width));
474
475       if (!exact_fit_p)  /* scale the image up or down */
476         {
477           float rw = (float) win_width  / w;
478           float rh = (float) win_height / h;
479           float r = (rw < rh ? rw : rh);
480           int tw = w * r;
481           int th = h * r;
482           int pct = (r * 100);
483
484           if (pct < 95 || pct > 105)  /* don't scale if it's close */
485             {
486               GdkPixbuf *pb2;
487               if (verbose_p)
488                 fprintf (stderr,
489                          "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
490                          progname, pct, w, h, tw, th);
491
492               pb2 = gdk_pixbuf_scale_simple (pb, tw, th, GDK_INTERP_BILINEAR);
493               if (pb2)
494                 {
495                   gdk_pixbuf_unref (pb);
496                   pb = pb2;
497                   w = tw;
498                   h = th;
499                 }
500               else
501                 fprintf (stderr, "%s: out of memory when scaling?\n",
502                          progname);
503             }
504         }
505
506       /* Center the image on the window. */
507       srcx = 0;
508       srcy = 0;
509       destx = (win_width  - w) / 2;
510       desty = (win_height - h) / 2;
511       if (destx < 0) srcx = -destx, destx = 0;
512       if (desty < 0) srcy = -desty, desty = 0;
513
514       if (win_width  < w) w = win_width;
515       if (win_height < h) h = win_height;
516
517       /* The window might have no-op background of None, so to clear it,
518          draw a black rectangle first, then do XClearWindow (in case the
519          actual background color is non-black...) */
520       {
521         XGCValues gcv;
522         GC gc;
523         /* #### really we should allocate "black" instead, but I'm lazy... */
524         gcv.foreground = BlackPixelOfScreen (screen);
525         gc = XCreateGC (dpy, window, GCForeground, &gcv);
526         XFillRectangle (dpy, window, gc, 0, 0, win_width, win_height);
527         XFreeGC (dpy, gc);
528         XClearWindow (dpy, window);
529         XFlush (dpy);
530       }
531
532       /* #### Note that this always uses the default colormap!  Morons!
533               Owen says that in Gnome 2.0, I should try using
534               gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
535               But I don't have Gnome 2.0 yet.
536        */
537       gdk_pixbuf_xlib_render_to_drawable_alpha (pb, window,
538                                                 srcx, srcy, destx, desty, w, h,
539                                                 GDK_PIXBUF_ALPHA_FULL, 127,
540                                                 XLIB_RGB_DITHER_NORMAL, 0, 0);
541       XSync (dpy, False);
542
543       if (verbose_p)
544         fprintf (stderr, "%s: displayed %dx%d image at %d,%d.\n",
545                  progname, w, h, destx, desty);
546     }
547   else if (filename)
548     {
549       fprintf (stderr, "%s: unable to load %s\n", progname, filename);
550 #ifdef HAVE_GTK2
551       if (gerr && gerr->message && *gerr->message)
552         fprintf (stderr, "%s: reason %s\n", progname, gerr->message);
553 #endif /* HAVE_GTK2 */
554
555       goto FAIL;
556     }
557   else
558     {
559       fprintf (stderr, "%s: unable to initialize built-in images\n", progname);
560       goto FAIL;
561     }
562
563   return;
564
565  FAIL:
566   if (verbose_p)
567     fprintf (stderr, "%s: drawing colorbars\n", progname);
568   draw_colorbars (dpy, window, 0, 0, win_width, win_height);
569   XSync (dpy, False);
570 }
571
572 #endif /* HAVE_BUILTIN_IMAGE_LOADER */
573
574
575
576 #if 0
577 static Bool
578 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
579         XrmRepresentation *type, XrmValue *value, XPointer closure)
580 {
581   int i;
582   for (i = 0; quarks[i]; i++)
583     {
584       if (bindings[i] == XrmBindTightly)
585         fprintf (stderr, (i == 0 ? "" : "."));
586       else if (bindings[i] == XrmBindLoosely)
587         fprintf (stderr, "*");
588       else
589         fprintf (stderr, " ??? ");
590       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
591     }
592
593   fprintf (stderr, ": %s\n", (char *) value->addr);
594
595   return False;
596 }
597 #endif
598
599
600 int
601 main (int argc, char **argv)
602 {
603   saver_preferences P;
604   Widget toplevel;
605   Display *dpy;
606   Screen *screen;
607   Window window = (Window) 0;
608   Bool verbose_p = False;
609   char *s;
610   int i;
611
612   progname = argv[0];
613   s = strrchr (progname, '/');
614   if (s) progname = s+1;
615
616   /* We must read exactly the same resources as xscreensaver.
617      That means we must have both the same progclass *and* progname,
618      at least as far as the resource database is concerned.  So,
619      put "xscreensaver" in argv[0] while initializing Xt.
620    */
621   argv[0] = "xscreensaver";
622   toplevel = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
623                               defaults, 0, 0);
624   argv[0] = progname;
625   dpy = XtDisplay (toplevel);
626   screen = XtScreen (toplevel);
627   db = XtDatabase (dpy);
628
629   XtGetApplicationNameAndClass (dpy, &s, &progclass);
630   XSetErrorHandler (x_ehandler);
631   XSync (dpy, False);
632
633   /* half-assed way of avoiding buffer-overrun attacks. */
634   if (strlen (progname) >= 100) progname[100] = 0;
635
636   for (i = 1; i < argc; i++)
637     {
638       if (argv[i][0] == '-' && argv[i][1] == '-') argv[i]++;
639       if (!strcmp (argv[i], "-v") ||
640           !strcmp (argv[i], "-verbose"))
641         verbose_p = True;
642       else if (window == 0)
643         {
644           unsigned long w;
645           char dummy;
646
647           if (!strcmp (argv[i], "root") ||
648               !strcmp (argv[i], "-root") ||
649               !strcmp (argv[i], "--root"))
650             window = RootWindowOfScreen (screen);
651
652           else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
653                     1 == sscanf (argv[i], " %ld %c",   &w, &dummy)) &&
654                    w != 0)
655             window = (Window) w;
656           else
657             goto LOSE;
658         }
659       else
660         {
661          LOSE:
662           fprintf (stderr,
663             "usage: %s [ -display host:dpy.screen ] [ -v ] window-id\n",
664                    progname);
665           fprintf (stderr, "\n"
666         "\tThis program puts an image of the desktop on the given window.\n"
667         "\tIt is used by those xscreensaver demos that operate on images.\n"
668         "\n");
669           exit (1);
670         }
671     }
672
673   if (window == 0) goto LOSE;
674
675   /* Randomize -- only need to do this here because this program
676      doesn't use the `screenhack.h' or `lockmore.h' APIs. */
677 # undef ya_rand_init
678   ya_rand_init (0);
679
680   memset (&P, 0, sizeof(P));
681   P.db = db;
682   load_init_file (&P);
683
684   if (P.verbose_p)
685     verbose_p = True;
686
687 #if 0
688   /* Print out all the resources we read. */
689   {
690     XrmName name = { 0 };
691     XrmClass class = { 0 };
692     int count = 0;
693     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
694                           (XtPointer) &count);
695   }
696 #endif
697
698   get_image (screen, window, verbose_p);
699   exit (0);
700 }