ftp://netsw.org/x11/tools/desktop/xscreensaver-4.07.tar.gz
[xscreensaver] / driver / xscreensaver-getimage.c
index c5c302996315fd5e1352692292a9ac145603587c..2fe88afc0fde6bf2a3604f520ae7567677e5457c 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2001 by Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2001, 2002, 2003 by Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
 #include "utils.h"
 
 #include <X11/Intrinsic.h>
+#include <ctype.h>
 #include <errno.h>
 
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>         /* for waitpid() and associated macros */
+#endif
+
 #ifdef HAVE_XMU
 # ifndef VMS
 #  include <X11/Xmu/Error.h>
 #include "grabscreen.h"
 #include "resources.h"
 #include "colorbars.h"
+#include "visual.h"
 #include "prefs.h"
 #include "vroot.h"
 
+#ifdef HAVE_GDK_PIXBUF
+
+# ifdef HAVE_GTK2
+#  include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
+# else  /* !HAVE_GTK2 */
+#  include <gdk-pixbuf/gdk-pixbuf-xlib.h>
+# endif /* !HAVE_GTK2 */
+
+# define HAVE_BUILTIN_IMAGE_LOADER
+#endif /* HAVE_GDK_PIXBUF */
+
 
 static char *defaults[] = {
 #include "../driver/XScreenSaver_ad.h"
@@ -101,34 +118,44 @@ exec_error (char **av)
       fprintf (stderr, "\n");
     }
 
-  exit (1);
+  exit (-1);
 }
 
 static int
 x_ehandler (Display *dpy, XErrorEvent *error)
 {
   fprintf (stderr, "\nX error in %s:\n", progname);
-  if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
-    exit (-1);
-  else
-    fprintf (stderr, " (nonfatal.)\n");
+  XmuPrintDefaultErrorMessage (dpy, error, stderr);
+  exit (-1);
   return 0;
 }
 
 
 
+#ifdef HAVE_BUILTIN_IMAGE_LOADER
+static void load_image_internal (Screen *screen, Window window,
+                                 int win_width, int win_height,
+                                 Bool verbose_p,
+                                 int ac, char **av);
+#endif /* HAVE_BUILTIN_IMAGE_LOADER */
+
+
 static void
-get_image (Screen *screen, Window window, Bool verbose_p)
+get_image (Screen *screen, Window window,
+           Bool verbose_p,
+           Bool desk_p,
+           Bool video_p,
+           Bool image_p,
+           char *dir)
 {
   Display *dpy = DisplayOfScreen (screen);
-  Bool desk_p  = get_boolean_resource ("grabDesktopImages",  "Boolean");
-  Bool video_p = get_boolean_resource ("grabVideoFrames",    "Boolean");
-  Bool image_p = get_boolean_resource ("chooseRandomImages", "Boolean");
-  char *dir    = get_string_resource ("imageDirectory", "ImageDirectory");
-
   enum { do_desk, do_video, do_image, do_bars } which = do_bars;
   int count = 0;
 
+  XWindowAttributes xgwa;
+  XGetWindowAttributes (dpy, window, &xgwa);
+  screen = xgwa.screen;
+
   if (verbose_p)
     {
       fprintf (stderr, "%s: grabDesktopImages:  %s\n",
@@ -168,19 +195,33 @@ get_image (Screen *screen, Window window, Bool verbose_p)
   if ((desk_p || video_p || image_p) &&
       !top_level_window_p (screen, window))
     {
-      desk_p  = False;
-      video_p = False;
-      image_p = False;
-      if (verbose_p)
+      Bool changed_p = False;
+      if (desk_p)  desk_p  = False, changed_p = True;
+      if (video_p) video_p = False, changed_p = True;
+# ifndef HAVE_BUILTIN_IMAGE_LOADER
+      /* We can display images on non-top-level windows with the builtin
+         loader, but not if we're using the external (chbg-based) loader. */
+      if (image_p) image_p = False, changed_p = True;
+# endif /* !HAVE_BUILTIN_IMAGE_LOADER */
+
+      if (changed_p && verbose_p)
         fprintf (stderr, "%s: not a top-level window: using colorbars.\n",
                  progname);
     }
   else if (window != VirtualRootWindowOfScreen (screen))
     {
+      /* We can display images on non-root windows with the builtin loader,
+         but not if we're using the external (chbg-based) loader.
+         We can never display video on non-root windows (since that always
+         uses the external image loader.)
+      */
       Bool changed_p = False;
-      if (!desk_p) desk_p  = True,  changed_p = True;
       if (video_p) video_p = False, changed_p = True;
+# ifndef HAVE_BUILTIN_IMAGE_LOADER
+      if (!desk_p) desk_p  = True,  changed_p = True;
       if (image_p) image_p = False, changed_p = True;
+# endif /* !HAVE_BUILTIN_IMAGE_LOADER */
+
       if (changed_p && verbose_p)
         fprintf (stderr,
                  "%s: not running on root window: grabbing desktop.\n",
@@ -195,13 +236,17 @@ get_image (Screen *screen, Window window, Bool verbose_p)
   if (count == 0)
     which = do_bars;
   else
-    while (1)  /* loop until we get one that's permitted */
-      {
-        which = (random() % 3);
-        if (which == do_desk  && desk_p)  break;
-        if (which == do_video && video_p) break;
-        if (which == do_image && image_p) break;
-      }
+    {
+      int i = 0;
+      while (1)  /* loop until we get one that's permitted */
+        {
+          which = (random() % 3);
+          if (which == do_desk  && desk_p)  break;
+          if (which == do_video && video_p) break;
+          if (which == do_image && image_p) break;
+          if (++i > 200) abort();
+        }
+    }
 
   if (which == do_desk)
     {
@@ -215,8 +260,6 @@ get_image (Screen *screen, Window window, Bool verbose_p)
     }
   else if (which == do_bars)
     {
-      XWindowAttributes xgwa;
-      XGetWindowAttributes (dpy, window, &xgwa);
       if (verbose_p)
         fprintf (stderr, "%s: drawing colorbars\n", progname);
       draw_colorbars (dpy, window, 0, 0, xgwa.width, xgwa.height);
@@ -225,19 +268,24 @@ get_image (Screen *screen, Window window, Bool verbose_p)
   else
     {
       char *av[10];
+      int ac = 0;
       memset (av, 0, sizeof(av));
       switch (which)
         {
         case do_video:
           if (verbose_p)
             fprintf (stderr, "%s: grabbing video\n", progname);
-          av[0] = GETIMAGE_VIDEO_PROGRAM;
+          av[ac++] = GETIMAGE_VIDEO_PROGRAM;
           break;
         case do_image:
           if (verbose_p)
             fprintf (stderr, "%s: loading random image file\n", progname);
-          av[0] = GETIMAGE_FILE_PROGRAM;
-          av[1] = dir;
+          av[ac++] = GETIMAGE_FILE_PROGRAM;
+
+# ifdef HAVE_BUILTIN_IMAGE_LOADER
+          av[ac++] = "--name";
+# endif /* !HAVE_BUILTIN_IMAGE_LOADER */
+          av[ac++] = dir;
           break;
         default:
           abort();
@@ -247,9 +295,10 @@ get_image (Screen *screen, Window window, Bool verbose_p)
       if (verbose_p)
         {
           int i;
-          for (i = (sizeof(av)/sizeof(*av))-1; i > 1; i--)
+          for (i = ac; i > 1; i--)
             av[i] = av[i-1];
           av[1] = strdup ("--verbose");
+          ac++;
         }
 
       if (verbose_p)
@@ -271,13 +320,41 @@ get_image (Screen *screen, Window window, Bool verbose_p)
       {
         const char *odpy = DisplayString (dpy);
         char *ndpy = (char *) malloc(strlen(odpy) + 20);
+        char *s;
+        int screen_no = screen_number (screen);  /* might not be default now */
+
         strcpy (ndpy, "DISPLAY=");
-        strcat (ndpy, odpy);
+        s = ndpy + strlen(ndpy);
+        strcpy (s, odpy);
+
+        while (*s && *s != ':') s++;           /* skip to colon */
+        while (*s == ':') s++;                 /* skip over colons */
+        while (isdigit(*s)) s++;               /* skip over dpy number */
+        while (*s == '.') s++;                 /* skip over dot */
+        if (s[-1] != '.') *s++ = '.';          /* put on a dot */
+        sprintf(s, "%d", screen_no);           /* put on screen number */
+
         if (putenv (ndpy))
           abort ();
+
+        /* don't free (ndpy) -- some implementations of putenv (BSD
+           4.4, glibc 2.0) copy the argument, but some (libc4,5, glibc
+           2.1.2) do not.  So we must leak it (and/or the previous
+           setting).  Yay.
+         */
       }
 # endif /* HAVE_PUTENV */
 
+# ifdef HAVE_BUILTIN_IMAGE_LOADER
+      if (which == do_image)
+        {
+          load_image_internal (screen, window, xgwa.width, xgwa.height,
+                               verbose_p, ac, av);
+          return;
+        }
+# endif /* HAVE_BUILTIN_IMAGE_LOADER */
+
+
       close (ConnectionNumber (dpy));  /* close display fd */
 
       execvp (av[0], av);              /* shouldn't return */
@@ -286,7 +363,226 @@ get_image (Screen *screen, Window window, Bool verbose_p)
 }
 
 
-#if 0
+#ifdef HAVE_BUILTIN_IMAGE_LOADER
+
+/* Reads a filename from "GETIMAGE_FILE_PROGRAM --name /DIR"
+ */
+static char *
+get_filename (Display *dpy, int ac, char **av)
+{
+  pid_t forked;
+  int fds [2];
+  int in, out;
+  char buf[1024];
+
+  if (pipe (fds))
+    {
+      sprintf (buf, "%s: error creating pipe", progname);
+      perror (buf);
+      return 0;
+    }
+
+  in = fds [0];
+  out = fds [1];
+
+  switch ((int) (forked = fork ()))
+    {
+    case -1:
+      {
+        sprintf (buf, "%s: couldn't fork", progname);
+        perror (buf);
+        return 0;
+      }
+    case 0:
+      {
+        int stdout_fd = 1;
+
+        close (in);  /* don't need this one */
+        close (ConnectionNumber (dpy));                /* close display fd */
+
+        if (dup2 (out, stdout_fd) < 0)         /* pipe stdout */
+          {
+            sprintf (buf, "%s: could not dup() a new stdout", progname);
+            exit (-1);                          /* exits fork */
+          }
+
+        execvp (av[0], av);                    /* shouldn't return. */
+        exit (-1);                              /* exits fork */
+        break;
+      }
+    default:
+      {
+        int wait_status = 0;
+        FILE *f = fdopen (in, "r");
+        int L;
+
+        close (out);  /* don't need this one */
+        *buf = 0;
+        fgets (buf, sizeof(buf)-1, f);
+        fclose (f);
+
+        /* Wait for the child to die. */
+        waitpid (-1, &wait_status, 0);
+
+        L = strlen (buf);
+        while (L && buf[L-1] == '\n')
+          buf[--L] = 0;
+          
+        return strdup (buf);
+      }
+    }
+
+  abort();
+}
+
+
+
+static void
+load_image_internal (Screen *screen, Window window,
+                     int win_width, int win_height,
+                     Bool verbose_p,
+                     int ac, char **av)
+{
+  GdkPixbuf *pb;
+  Display *dpy = DisplayOfScreen (screen);
+  char *filename = get_filename (dpy, ac, av);
+#ifdef HAVE_GTK2
+  GError *gerr = 0;
+#endif /* HAVE_GTK2 */
+
+  if (!filename)
+    {
+      fprintf (stderr, "%s: no file name returned by %s\n",
+               progname, av[0]);
+      goto FAIL;
+    }
+  else if (verbose_p)
+    fprintf (stderr, "%s: loading \"%s\"\n", progname, filename);
+
+  gdk_pixbuf_xlib_init (dpy, screen_number (screen));
+#ifdef HAVE_GTK2
+  g_type_init();
+#else  /* !HAVE_GTK2 */
+  xlib_rgb_init (dpy, screen);
+#endif /* !HAVE_GTK2 */
+
+  pb = gdk_pixbuf_new_from_file (filename
+#ifdef HAVE_GTK2
+                                 , &gerr
+#endif /* HAVE_GTK2 */
+         );
+
+  if (pb)
+    {
+      int w = gdk_pixbuf_get_width (pb);
+      int h = gdk_pixbuf_get_height (pb);
+      int srcx, srcy, destx, desty;
+
+      Bool exact_fit_p = ((w == win_width  && h <= win_height) ||
+                          (h == win_height && w <= win_width));
+
+      if (!exact_fit_p)  /* scale the image up or down */
+        {
+          float rw = (float) win_width  / w;
+          float rh = (float) win_height / h;
+          float r = (rw < rh ? rw : rh);
+          int tw = w * r;
+          int th = h * r;
+          int pct = (r * 100);
+
+          if (pct < 95 || pct > 105)  /* don't scale if it's close */
+            {
+              GdkPixbuf *pb2;
+              if (verbose_p)
+                fprintf (stderr,
+                         "%s: scaling image by %d%% (%dx%d -> %dx%d)\n",
+                         progname, pct, w, h, tw, th);
+
+              pb2 = gdk_pixbuf_scale_simple (pb, tw, th, GDK_INTERP_BILINEAR);
+              if (pb2)
+                {
+                  gdk_pixbuf_unref (pb);
+                  pb = pb2;
+                  w = tw;
+                  h = th;
+                }
+              else
+                fprintf (stderr, "%s: out of memory when scaling?\n",
+                         progname);
+            }
+        }
+
+      /* Center the image on the window. */
+      srcx = 0;
+      srcy = 0;
+      destx = (win_width  - w) / 2;
+      desty = (win_height - h) / 2;
+      if (destx < 0) srcx = -destx, destx = 0;
+      if (desty < 0) srcy = -desty, desty = 0;
+
+      if (win_width  < w) w = win_width;
+      if (win_height < h) h = win_height;
+
+      /* The window might have no-op background of None, so to clear it,
+         draw a black rectangle first, then do XClearWindow (in case the
+         actual background color is non-black...) */
+      {
+        XGCValues gcv;
+        GC gc;
+        /* #### really we should allocate "black" instead, but I'm lazy... */
+        gcv.foreground = BlackPixelOfScreen (screen);
+        gc = XCreateGC (dpy, window, GCForeground, &gcv);
+        XFillRectangle (dpy, window, gc, 0, 0, win_width, win_height);
+        XFreeGC (dpy, gc);
+        XClearWindow (dpy, window);
+        XFlush (dpy);
+      }
+
+      /* #### Note that this always uses the default colormap!  Morons!
+              Owen says that in Gnome 2.0, I should try using
+              gdk_pixbuf_render_pixmap_and_mask_for_colormap() instead.
+              But I don't have Gnome 2.0 yet.
+       */
+      gdk_pixbuf_xlib_render_to_drawable_alpha (pb, window,
+                                                srcx, srcy, destx, desty, w, h,
+                                                GDK_PIXBUF_ALPHA_FULL, 127,
+                                                XLIB_RGB_DITHER_NORMAL, 0, 0);
+      XSync (dpy, False);
+
+      if (verbose_p)
+        fprintf (stderr, "%s: displayed %dx%d image at %d,%d.\n",
+                 progname, w, h, destx, desty);
+    }
+  else if (filename)
+    {
+      fprintf (stderr, "%s: unable to load %s\n", progname, filename);
+#ifdef HAVE_GTK2
+      if (gerr && gerr->message && *gerr->message)
+        fprintf (stderr, "%s: reason %s\n", progname, gerr->message);
+#endif /* HAVE_GTK2 */
+
+      goto FAIL;
+    }
+  else
+    {
+      fprintf (stderr, "%s: unable to initialize built-in images\n", progname);
+      goto FAIL;
+    }
+
+  return;
+
+ FAIL:
+  if (verbose_p)
+    fprintf (stderr, "%s: drawing colorbars\n", progname);
+  draw_colorbars (dpy, window, 0, 0, win_width, win_height);
+  XSync (dpy, False);
+}
+
+#endif /* HAVE_BUILTIN_IMAGE_LOADER */
+
+
+
+#ifdef DEBUG
 static Bool
 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
        XrmRepresentation *type, XrmValue *value, XPointer closure)
@@ -307,8 +603,31 @@ mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
 
   return False;
 }
-#endif
-
+#endif /* DEBUG */
+
+
+#define USAGE "usage: %s [ -options... ] window-id\n"                        \
+   "\n"                                                                              \
+   "    This program puts an image on the given window.\n"                   \
+   "\n"                                                                              \
+   "    It is used by those xscreensaver demos that operate on images.\n"     \
+   "    The image may be a file loaded from disk, a frame grabbed from\n"     \
+   "    the system's video camera, or a screenshot of the desktop,\n"         \
+   "    depending on command-line options or the ~/.xscreensaver file.\n"     \
+   "\n"                                                                              \
+   "    Options include:\n"                                                  \
+   "\n"                                                                              \
+   "      -display host:dpy.screen    which display to use\n"                \
+   "      -root                       draw to the root window\n"             \
+   "      -verbose                    print diagnostics\n"                   \
+   "      -images  / -no-images       whether to allow image file loading\n"  \
+   "      -video   / -no-video        whether to allow video grabs\n"        \
+   "      -desktop / -no-desktop      whether to allow desktop screen grabs\n"\
+   "      -directory <path>           where to find image files to load\n"    \
+   "\n"                                                                              \
+   "    The XScreenSaver Control Panel (xscreensaver-demo) lets you set the\n"\
+   "    defaults for these options in your ~/.xscreensaver file.\n"           \
+   "\n"
 
 int
 main (int argc, char **argv)
@@ -317,97 +636,121 @@ main (int argc, char **argv)
   Widget toplevel;
   Display *dpy;
   Screen *screen;
+  char *oprogname = progname;
+
   Window window = (Window) 0;
-  Bool verbose_p = False;
   char *s;
   int i;
 
   progname = argv[0];
   s = strrchr (progname, '/');
   if (s) progname = s+1;
+  oprogname = progname;
+
+  /* half-assed way of avoiding buffer-overrun attacks. */
+  if (strlen (progname) >= 100) progname[100] = 0;
+
 
   /* We must read exactly the same resources as xscreensaver.
      That means we must have both the same progclass *and* progname,
      at least as far as the resource database is concerned.  So,
      put "xscreensaver" in argv[0] while initializing Xt.
    */
-  argv[0] = "xscreensaver";
+  progname = argv[0] = "xscreensaver";
+
+  /* allow one dash or two. */
+  for (i = 1; i < argc; i++)
+    if (argv[i][0] == '-' && argv[i][1] == '-') argv[i]++;
+
   toplevel = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
-                             defaults, 0, 0);
-  argv[0] = progname;
+                              defaults, 0, 0);
   dpy = XtDisplay (toplevel);
   screen = XtScreen (toplevel);
   db = XtDatabase (dpy);
-
   XtGetApplicationNameAndClass (dpy, &s, &progclass);
   XSetErrorHandler (x_ehandler);
   XSync (dpy, False);
 
-  /* half-assed way of avoiding buffer-overrun attacks. */
-  if (strlen (progname) >= 100) progname[100] = 0;
+  /* Randomize -- only need to do this here because this program
+     doesn't use the `screenhack.h' or `lockmore.h' APIs. */
+# undef ya_rand_init
+  ya_rand_init (0);
+
+  memset (&P, 0, sizeof(P));
+  P.db = db;
+  load_init_file (&P);
+
+  progname = argv[0] = oprogname;
 
   for (i = 1; i < argc; i++)
     {
-      if (argv[i][0] == '-' && argv[i][1] == '-') argv[i]++;
-      if (!strcmp (argv[i], "-v") ||
-          !strcmp (argv[i], "-verbose"))
-        verbose_p = True;
+      /* Have to re-process these, or else the .xscreensaver file
+         has priority over the command line...
+       */
+      if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose"))
+        P.verbose_p = True;
+      else if (!strcmp (argv[i], "-desktop"))    P.grab_desktop_p = True;
+      else if (!strcmp (argv[i], "-no-desktop")) P.grab_desktop_p = False;
+      else if (!strcmp (argv[i], "-video"))      P.grab_video_p = True;
+      else if (!strcmp (argv[i], "-no-video"))   P.grab_video_p = False;
+      else if (!strcmp (argv[i], "-images"))     P.random_image_p = True;
+      else if (!strcmp (argv[i], "-no-images"))  P.random_image_p = False;
+      else if (!strcmp (argv[i], "-directory") || !strcmp (argv[i], "-dir"))
+        P.image_directory = argv[++i];
       else if (window == 0)
         {
           unsigned long w;
           char dummy;
 
-          if (!strcmp (argv[i], "root") ||
-              !strcmp (argv[i], "-root") ||
-              !strcmp (argv[i], "--root"))
+          if (!strcmp (argv[i], "-root") ||
+              !strcmp (argv[i], "root"))
             window = RootWindowOfScreen (screen);
 
-          else if ((1 == sscanf (argv[i], " 0x%x %c", &w, &dummy) ||
-                    1 == sscanf (argv[i], " %d %c",   &w, &dummy)) &&
+          else if ((1 == sscanf (argv[i], " 0x%lx %c", &w, &dummy) ||
+                    1 == sscanf (argv[i], " %ld %c",   &w, &dummy)) &&
                    w != 0)
             window = (Window) w;
           else
-            goto LOSE;
+            {
+              if (argv[i][0] == '-')
+                fprintf (stderr, "\n%s: unknown option \"%s\"\n",
+                         progname, argv[i]);
+              else
+                fprintf (stderr, "\n%s: unparsable window ID: \"%s\"\n",
+                         progname, argv[i]);
+              goto LOSE;
+            }
         }
       else
         {
+          fprintf (stderr, "\n%s: unknown option \"%s\"\n",
+                   progname, argv[i]);
          LOSE:
-          fprintf (stderr,
-            "usage: %s [ -display host:dpy.screen ] [ -v ] window-id\n",
-                   progname);
-          fprintf (stderr, "\n"
-       "\tThis program puts an image of the desktop on the given window.\n"
-       "\tIt is used by those xscreensaver demos that operate on images.\n"
-       "\n");
+          fprintf (stderr, USAGE, progname);
           exit (1);
         }
     }
 
-  if (window == 0) goto LOSE;
-
-  /* Randomize -- only need to do this here because this program
-     doesn't use the `screenhack.h' or `lockmore.h' APIs. */
-# undef ya_rand_init
-  ya_rand_init (0);
+  if (window == 0)
+    {
+      fprintf (stderr, "\n%s: no window specified!\n", progname);
+      goto LOSE;
+    }
 
-  memset (&P, 0, sizeof(P));
-  P.db = db;
-  load_init_file (&P);
 
-  if (P.verbose_p)
-    verbose_p = True;
-
-#if 0
-  /* Print out all the resources we read. */
-  {
-    XrmName name = { 0 };
-    XrmClass class = { 0 };
-    int count = 0;
-    XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
-                         (XtPointer) &count);
-  }
-#endif
+#ifdef DEBUG
+  if (P.verbose_p)       /* Print out all the resources we can see. */
+    {
+      XrmName name = { 0 };
+      XrmClass class = { 0 };
+      int count = 0;
+      XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
+                            (XtPointer) &count);
+    }
+#endif /* DEBUG */
 
-  get_image (screen, window, verbose_p);
+  get_image (screen, window, P.verbose_p,
+             P.grab_desktop_p, P.grab_video_p, P.random_image_p,
+             P.image_directory);
   exit (0);
 }