ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / hacks / screenhack.c
index 8a882fcd7b4ddd41426d673f79d387822241abc2..8ea025d0da6068ba5e3b8795718a14d61c960d27 100644 (file)
@@ -1,5 +1,4 @@
-/* xscreensaver, Copyright (c) 1992, 1995, 1997, 1998
- *  Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 1992-2006 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
    The idea being that, when writing a graphics hack, you can just link
    with this .o to get all of the uninteresting junk out of the way.
 
-   -  create a procedure `screenhack(dpy, window)'
+   Create a few static global procedures and variables:
 
-   -  create a variable `char *progclass' which names this program's
-      resource class.
+      static void *YOURNAME_init (Display *, Window);
 
-   -  create a variable `char defaults []' for the default resources, and
-      null-terminate it.
+          Return an opaque structure representing your drawing state.
 
-   -  create a variable `XrmOptionDescRec options[]' for the command-line,
-      and null-terminate it.
+      static unsigned long YOURNAME_draw (Display *, Window, void *closure);
 
-   And that's it...
+          Draw one frame.
+          The `closure' arg is your drawing state, that you created in `init'.
+          Return the number of microseconds to wait until the next frame.
+
+          This should return in some small fraction of a second. 
+          Do not call `usleep' or loop excessively.  For long loops, use a
+          finite state machine.
+
+      static void YOURNAME_reshape (Display *, Window, void *closure,
+                                   unsigned int width, unsigned int height);
+
+          Called when the size of the window changes with the new size.
+
+      static Bool YOURNAME_event (Display *, Window, void *closure,
+                                 XEvent *event);
+
+          Called when a keyboard or mouse event arrives.
+          Return True if you handle it in some way, False otherwise.
+
+      static void YOURNAME_event (Display *, Window, void *closure);
+
+           Called when you are done: free everything you've allocated,
+           including your private `state' structure.
+
+      static char YOURNAME_defaults [] = { "...", "...", ... , 0 };
+
+           This variable is an array of strings, your default resources.
+           Null-terminate the list.
+
+      static XrmOptionDescRec YOURNAME_options[] = { { ... }, ... { 0,0,0,0 } }
+
+           This variable describes your command-line options.
+           Null-terminate the list.
+
+      Finally , invoke the XSCREENSAVER_MODULE() macro to tie it all together.
+
+   Additional caveats:
+
+      - Make sure that all functions in your module are static (check this
+        by running "nm -g" on the .o file).
+
+      - Do not use global variables: all such info must be stored in the
+        private `state' structure.
+
+      - Do not static function-local variables, either.  Put it in `state'.
+
+        Assume that there are N independent runs of this code going in the
+        same address space at the same time: they must not affect each other.
+
+      - Don't forget to write an XML file to describe the user interface
+        of your screen saver module.  See .../hacks/config/README for details.
  */
 
+#define DEBUG_TIMING
+#define DEBUG_PAIR
+
 #include <stdio.h>
 #include <X11/Intrinsic.h>
 #include <X11/IntrinsicP.h>
 #include <X11/CoreP.h>
 #include <X11/Shell.h>
 #include <X11/StringDefs.h>
-#include <X11/Xutil.h>
 #include <X11/keysym.h>
 
 #ifdef __sgi
 #else
 # include "xmu.h"
 #endif
-#include "screenhack.h"
+
+#include "screenhackI.h"
 #include "version.h"
 #include "vroot.h"
 
-char *progname;
-XrmDatabase db;
-XtAppContext app;
-Bool mono_p;
+#ifndef _XSCREENSAVER_VROOT_H_
+# error Error!  You have an old version of vroot.h!  Check -I args.
+#endif /* _XSCREENSAVER_VROOT_H_ */
+
+#ifndef isupper
+# define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
+#endif
+#ifndef _tolower
+# define _tolower(c)  ((c) - 'A' + 'a')
+#endif
+
+
+/* This is defined by the SCREENHACK_MAIN() macro via screenhack.h.
+ */
+extern struct xscreensaver_function_table *xscreensaver_function_table;
+
+
+const char *progname;   /* used by hacks in error messages */
+const char *progclass;  /* used by ../utils/resources.c */
+Bool mono_p;           /* used by hacks */
+
 
 static XrmOptionDescRec default_options [] = {
   { "-root",   ".root",                XrmoptionNoArg, "True" },
@@ -70,6 +136,10 @@ static XrmOptionDescRec default_options [] = {
   { "-noinstall",".installColormap",   XrmoptionNoArg, "False" },
   { "-visual", ".visualID",            XrmoptionSepArg, 0 },
   { "-window-id", ".windowID",         XrmoptionSepArg, 0 },
+
+# ifdef DEBUG_PAIR
+  { "-pair",   ".pair",                XrmoptionNoArg, "True" },
+# endif /* DEBUG_PAIR */
   { 0, 0, 0, 0 }
 };
 
@@ -80,6 +150,7 @@ static char *default_defaults[] = {
   "*installColormap:   false",
   "*visualID:          default",
   "*windowID:          ",
+  "*desktopGrabber:    xscreensaver-getimage %s",
   0
 };
 
@@ -90,6 +161,12 @@ static char **merged_defaults;
 static void
 merge_options (void)
 {
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  const XrmOptionDescRec *options = ft->options;
+  const char * const *defaults    = ft->defaults;
+  const char *progclass           = ft->progclass;
+
   int def_opts_size, opts_size;
   int def_defaults_size, defaults_size;
 
@@ -161,18 +238,14 @@ MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
 }
 
 
-#ifdef XLOCKMORE
-extern void pre_merge_options (void);
-#endif
-
-
 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
 
 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
+   Returns False if the screen saver should now terminate.
  */
-void
-screenhack_handle_event (Display *dpy, XEvent *event)
+static Bool
+screenhack_handle_event_1 (Display *dpy, XEvent *event)
 {
   switch (event->xany.type)
     {
@@ -185,7 +258,7 @@ screenhack_handle_event (Display *dpy, XEvent *event)
             c == 'Q' ||
             c == 3 ||  /* ^C */
             c == 27)   /* ESC */
-          exit (0);
+          return False;  /* exit */
         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
           XBell (dpy, 0);  /* beep for non-chord keys */
       }
@@ -213,72 +286,440 @@ screenhack_handle_event (Display *dpy, XEvent *event)
           }
         else
           {
-            exit (0);
+            return False;  /* exit */
           }
       }
       break;
     }
+  return True;
 }
 
 
-void
-screenhack_handle_events (Display *dpy)
+static Visual *
+pick_visual (Screen *screen)
 {
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  if (ft->pick_visual_hook)
+    {
+      Visual *v = ft->pick_visual_hook (screen);
+      if (v) return v;
+    }
+
+  return get_visual_resource (screen, "visualID", "VisualID", False);
+}
+
+
+/* Notice when the user has requested a different visual or colormap
+   on a pre-existing window (e.g., "-root -visual truecolor" or
+   "-window-id 0x2c00001 -install") and complain, since when drawing
+   on an existing window, we have no choice about these things.
+ */
+static void
+visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
+                Bool window_p)
+{
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  char *visual_string = get_string_resource (DisplayOfScreen (screen),
+                                             "visualID", "VisualID");
+  Visual *desired_visual = pick_visual (screen);
+  char win[100];
+  char why[100];
+
+  if (window == RootWindowOfScreen (screen))
+    strcpy (win, "root window");
+  else
+    sprintf (win, "window 0x%lx", (unsigned long) window);
+
+  if (window_p)
+    sprintf (why, "-window-id 0x%lx", (unsigned long) window);
+  else
+    strcpy (why, "-root");
+
+  if (visual_string && *visual_string)
+    {
+      char *s;
+      for (s = visual_string; *s; s++)
+        if (isupper (*s)) *s = _tolower (*s);
+
+      if (!strcmp (visual_string, "default") ||
+          !strcmp (visual_string, "default") ||
+          !strcmp (visual_string, "best"))
+        /* don't warn about these, just silently DWIM. */
+        ;
+      else if (visual != desired_visual)
+        {
+          fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
+                   progname, visual_string, why);
+          fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
+                   progname, win, XVisualIDFromVisual (visual));
+        }
+      free (visual_string);
+    }
+
+  if (visual == DefaultVisualOfScreen (screen) &&
+      has_writable_cells (screen, visual) &&
+      get_boolean_resource (DisplayOfScreen (screen),
+                            "installColormap", "InstallColormap"))
+    {
+      fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
+               progname, why);
+      fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
+               progname, win, (unsigned long) cmap);
+    }
+
+  if (ft->validate_visual_hook)
+    {
+      if (! ft->validate_visual_hook (screen, win, visual))
+        exit (1);
+    }
+}
+
+
+static void
+fix_fds (void)
+{
+  /* Bad Things Happen if stdin, stdout, and stderr have been closed
+     (as by the `sh incantation "attraction >&- 2>&-").  When you do
+     that, the X connection gets allocated to one of these fds, and
+     then some random library writes to stderr, and random bits get
+     stuffed down the X pipe, causing "Xlib: sequence lost" errors.
+     So, we cause the first three file descriptors to be open to
+     /dev/null if they aren't open to something else already.  This
+     must be done before any other files are opened (or the closing
+     of that other file will again free up one of the "magic" first
+     three FDs.)
+
+     We do this by opening /dev/null three times, and then closing
+     those fds, *unless* any of them got allocated as #0, #1, or #2,
+     in which case we leave them open.  Gag.
+
+     Really, this crap is technically required of *every* X program,
+     if you want it to be robust in the face of "2>&-".
+   */
+  int fd0 = open ("/dev/null", O_RDWR);
+  int fd1 = open ("/dev/null", O_RDWR);
+  int fd2 = open ("/dev/null", O_RDWR);
+  if (fd0 > 2) close (fd0);
+  if (fd1 > 2) close (fd1);
+  if (fd2 > 2) close (fd2);
+}
+
+
+#ifdef DEBUG_TIMING
+
+static void
+check_timing (unsigned long delay)
+{
+  static unsigned long frame_count = 0;
+  static unsigned long delay_sum = 0;
+  static struct timeval prev1 = { 0, };
+  static struct timeval prev2 = { 0, };
+  struct timeval now;
+  double uprev1, uprev2, unow;
+
+# ifdef GETTIMEOFDAY_TWO_ARGS
+  gettimeofday (&now, 0);
+# else
+  gettimeofday (&now);
+# endif
+
+  if (prev1.tv_sec == 0)
+    prev1 = prev2 = now;
+
+  frame_count++;
+
+  uprev1 = prev1.tv_sec + ((double) prev1.tv_usec * 0.000001);
+  uprev2 = prev2.tv_sec + ((double) prev2.tv_usec * 0.000001);
+  unow   =  now.tv_sec  + ((double)   now.tv_usec * 0.000001);
+
+  if (unow >= uprev1 + 1.5)
+    fprintf (stderr,
+             "%s: warning: blocked event processing for %.1f secs!\n",
+             progname, unow - uprev1);
+  prev1 = now;
+
+  if (unow >= uprev2 + 10.0)
+    {
+      double fps   = frame_count / (unow - uprev2);
+      double elapsed = unow - uprev2;
+      double slept = (delay_sum * 0.000001);
+      double sleep_ratio = slept / elapsed;
+
+      if (sleep_ratio < 0.10) {
+        fprintf (stderr, 
+                 "%s: warning: only %.0f%% idle over the"
+                 " last %2.0f secs (at %.1f FPS)\n",
+                 progname, 100 * sleep_ratio, elapsed, fps);
+      }
+
+      prev2 = now;
+      frame_count = 0;
+      delay_sum = 0;
+    }
+
+  delay_sum += delay;
+}
+
+#else  /* !DEBUG_TIMING */
+# define check_timing(delay) /**/
+#endif /* !DEBUG_TIMING */
+
+static Boolean
+screenhack_table_handle_events (Display *dpy,
+                                const struct xscreensaver_function_table *ft,
+                                Window window, void *closure
+#ifdef DEBUG_PAIR
+                                , Window window2, void *closure2
+#endif
+                                )
+{
+  XtAppContext app = XtDisplayToApplicationContext (dpy);
+
+  if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
+    XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
+
   while (XPending (dpy))
     {
       XEvent event;
       XNextEvent (dpy, &event);
-      screenhack_handle_event (dpy, &event);
+
+      if (event.xany.type == ConfigureNotify)
+        {
+          if (event.xany.window == window)
+            ft->reshape_cb (dpy, window, closure,
+                            event.xconfigure.width, event.xconfigure.height);
+#ifdef DEBUG_PAIR
+          if (event.xany.window == window2)
+            ft->reshape_cb (dpy, window2, closure2,
+                            event.xconfigure.width, event.xconfigure.height);
+#endif
+        }
+      else if (event.xany.type == ClientMessage ||
+               (! (event.xany.window == window
+                   ? ft->event_cb (dpy, window, closure, &event)
+#ifdef DEBUG_PAIR
+                   : event.xany.window == window2
+                   ? ft->event_cb (dpy, window2, closure2, &event)
+#endif
+                   : 0)))
+        if (! screenhack_handle_event_1 (dpy, &event))
+          return False;
+
+      if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
+        XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
     }
+  return True;
 }
 
 
-static Visual *
-pick_visual (Screen *screen)
+static Boolean
+usleep_and_process_events (Display *dpy,
+                           const struct xscreensaver_function_table *ft,
+                           Window window, void *closure, unsigned long delay
+#ifdef DEBUG_PAIR
+                         , Window window2, void *closure2, unsigned long delay2
+#endif
+                           )
+{
+  do {
+    unsigned long quantum = 100000;  /* 1/10th second */
+    if (quantum > delay) 
+      quantum = delay;
+    delay -= quantum;
+
+    XSync (dpy, False);
+    if (quantum > 0)
+      usleep (quantum);
+
+    check_timing (quantum);
+
+    /* The above isn't quite right in pair-mode: we always run both windows
+       with the timing of window 2.  But, it's just a debugging hack, so
+       that doesn't really matter that much... */
+
+    if (! screenhack_table_handle_events (dpy, ft, window, closure
+#ifdef DEBUG_PAIR
+                                          , window2, closure2
+#endif
+                                          ))
+      return False;
+  } while (delay > 0);
+
+  return True;
+}
+
+
+static void
+run_screenhack_table (Display *dpy, 
+                      Window window,
+# ifdef DEBUG_PAIR
+                      Window window2,
+# endif
+                      const struct xscreensaver_function_table *ft)
 {
-#ifdef USE_GL
-  /* If we're linking against GL (that is, this is the version of screenhack.o
-     that the GL hacks will use, which is different from the one that the
-     non-GL hacks will use) then try to pick the "best" visual by interrogating
-     the GL library instead of by asking Xlib.  GL knows better.
+
+  /* Kludge: even though the init_cb functions are declared to take 2 args,
+     actually call them with 3, for the benefit of xlockmore_init() and
+     xlockmore_setup().
    */
-  Visual *v = 0;
-  char *string = get_string_resource ("visualID", "VisualID");
-
-  if (!string || !*string ||
-      !strcmp (string, "gl") ||
-      !strcmp (string, "best") ||
-      !strcmp (string, "color") ||
-      !strcmp (string, "default"))
-    v = get_gl_visual (screen);                /* from ../utils/visual-gl.c */
-
-  if (string)
-    free (string);
-  if (v)
-    return v;
-#endif /* USE_GL */
+  void *(*init_cb) (Display *, Window, void *) = 
+    (void *(*) (Display *, Window, void *)) ft->init_cb;
 
-  return get_visual_resource (screen, "visualID", "VisualID", False);
+  void *closure = init_cb (dpy, window, ft->setup_arg);
+
+#ifdef DEBUG_PAIR
+  void *closure2 = 0;
+  if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
+#endif
+
+  if (! closure)  /* if it returns nothing, it can't possibly be re-entrant. */
+    abort();
+
+  while (1)
+    {
+      unsigned long delay = ft->draw_cb (dpy, window, closure);
+#ifdef DEBUG_PAIR
+      unsigned long delay2;
+      if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
+#endif
+
+      if (! usleep_and_process_events (dpy, ft,
+                                       window, closure, delay
+#ifdef DEBUG_PAIR
+                                       , window2, closure2, delay2
+#endif
+                                       ))
+        break;
+    }
+
+  ft->free_cb (dpy, window, closure);
+
+#ifdef DEBUG_PAIR
+  if (window2)
+    ft->free_cb (dpy, window2, closure2);
+#endif
+}
+
+
+static Widget
+make_shell (Screen *screen, Widget toplevel, int width, int height)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Visual *visual = pick_visual (screen);
+  Boolean def_visual_p = (toplevel && 
+                          visual == DefaultVisualOfScreen (screen));
+
+  if (width  <= 0) width  = 600;
+  if (height <= 0) height = 480;
+
+# ifdef USE_GL
+  if (!validate_gl_visual (stderr, screen, "window", visual))
+    exit (1);
+# endif /* USE_GL */
+
+  if (def_visual_p)
+    {
+      Window window;
+      XtVaSetValues (toplevel,
+                     XtNmappedWhenManaged, False,
+                     XtNwidth, width,
+                     XtNheight, height,
+                     XtNinput, True,  /* for WM_HINTS */
+                     NULL);
+      XtRealizeWidget (toplevel);
+      window = XtWindow (toplevel);
+
+      if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
+        {
+          Colormap cmap = 
+            XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
+                             AllocNone);
+          XSetWindowColormap (dpy, window, cmap);
+        }
+    }
+  else
+    {
+      unsigned int bg, bd;
+      Widget new;
+      Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
+                                       visual, AllocNone);
+      bg = get_pixel_resource (dpy, cmap, "background", "Background");
+      bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
+
+      new = XtVaAppCreateShell (progname, progclass,
+                                topLevelShellWidgetClass, dpy,
+                                XtNmappedWhenManaged, False,
+                                XtNvisual, visual,
+                                XtNdepth, visual_depth (screen, visual),
+                                XtNwidth, width,
+                                XtNheight, height,
+                                XtNcolormap, cmap,
+                                XtNbackground, (Pixel) bg,
+                                XtNborderColor, (Pixel) bd,
+                                XtNinput, True,  /* for WM_HINTS */
+                                NULL);
+
+      if (!toplevel)  /* kludge for the second window in -pair mode... */
+        XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
+
+      XtRealizeWidget (new);
+      toplevel = new;
+    }
+
+  return toplevel;
+}
+
+static void
+init_window (Display *dpy, Widget toplevel, const char *title)
+{
+  Window window;
+  XWindowAttributes xgwa;
+  XtPopup (toplevel, XtGrabNone);
+  XtVaSetValues (toplevel, XtNtitle, title, NULL);
+
+  /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
+   */
+  window = XtWindow (toplevel);
+  XGetWindowAttributes (dpy, window, &xgwa);
+  XSelectInput (dpy, window,
+                (xgwa.your_event_mask | KeyPressMask |
+                 ButtonPressMask | ButtonReleaseMask));
+  XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
+                   PropModeReplace,
+                   (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
 }
 
 
 int
 main (int argc, char **argv)
 {
+  struct xscreensaver_function_table *ft = xscreensaver_function_table;
+
+  XWindowAttributes xgwa;
   Widget toplevel;
   Display *dpy;
   Window window;
-  Visual *visual;
-  Colormap cmap;
+# ifdef DEBUG_PAIR
+  Window window2 = 0;
+  Widget toplevel2 = 0;
+# endif
+  XtAppContext app;
   Bool root_p;
   Window on_window = 0;
   XEvent event;
-  Boolean dont_clear /*, dont_map */;
+  Boolean dont_clear;
   char version[255];
 
-#ifdef XLOCKMORE
-  pre_merge_options ();
-#endif
+  fix_fds();
+
+  progname = argv[0];   /* reset later */
+  progclass = ft->progclass;
+
+  if (ft->setup_cb)
+    ft->setup_cb (ft, ft->setup_arg);
+
   merge_options ();
 
 #ifdef __sgi
@@ -296,9 +737,16 @@ main (int argc, char **argv)
   toplevel = XtAppInitialize (&app, progclass, merged_options,
                              merged_options_size, &argc, argv,
                              merged_defaults, 0, 0);
+
   dpy = XtDisplay (toplevel);
-  db = XtDatabase (dpy);
-  XtGetApplicationNameAndClass (dpy, &progname, &progclass);
+
+  XtGetApplicationNameAndClass (dpy,
+                                (char **) &progname,
+                                (char **) &progclass);
+
+  /* half-assed way of avoiding buffer-overrun attacks. */
+  if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
+
   XSetErrorHandler (screenhack_ehandler);
 
   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
@@ -324,7 +772,8 @@ main (int argc, char **argv)
       int i;
       int x = 18;
       int end = 78;
-      Bool help_p = !strcmp(argv[1], "-help");
+      Bool help_p = (!strcmp(argv[1], "-help") ||
+                     !strcmp(argv[1], "--help"));
       fprintf (stderr, "%s\n", version);
       for (s = progclass; *s; s++) fprintf(stderr, " ");
       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
@@ -347,134 +796,134 @@ main (int argc, char **argv)
          if (argp) fprintf (stderr, " <arg>");
          if (i != merged_options_size - 1) fprintf (stderr, ", ");
        }
+
       fprintf (stderr, ".\n");
+
+#if 0
+      if (help_p)
+        {
+          fprintf (stderr, "\nResources:\n\n");
+          for (i = 0; i < merged_options_size; i++)
+            {
+              const char *opt = merged_options [i].option;
+              const char *res = merged_options [i].specifier + 1;
+              const char *val = merged_options [i].value;
+              char *s = get_string_resource (dpy, (char *) res, (char *) res);
+
+              if (s)
+                {
+                  int L = strlen(s);
+                while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
+                  s[--L] = 0;
+                }
+
+              fprintf (stderr, "    %-16s %-18s ", opt, res);
+              if (merged_options [i].argKind == XrmoptionSepArg)
+                {
+                  fprintf (stderr, "[%s]", (s ? s : "?"));
+                }
+              else
+                {
+                  fprintf (stderr, "%s", (val ? val : "(null)"));
+                  if (val && s && !strcasecmp (val, s))
+                    fprintf (stderr, " [default]");
+                }
+              fprintf (stderr, "\n");
+            }
+          fprintf (stderr, "\n");
+        }
+#endif
+
       exit (help_p ? 0 : 1);
     }
 
-  dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
-/*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
-  mono_p = get_boolean_resource ("mono", "Boolean");
+  free (merged_options);
+  free (merged_defaults);
+  merged_options = 0;
+  merged_defaults = 0;
+
+  dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
+  mono_p = get_boolean_resource (dpy, "mono", "Boolean");
   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
     mono_p = True;
 
-  root_p = get_boolean_resource ("root", "Boolean");
+  root_p = get_boolean_resource (dpy, "root", "Boolean");
 
   {
-    char *s = get_string_resource ("windowID", "WindowID");
+    char *s = get_string_resource (dpy, "windowID", "WindowID");
     if (s && *s)
-      on_window = get_integer_resource ("windowID", "WindowID");
+      on_window = get_integer_resource (dpy, "windowID", "WindowID");
     if (s) free (s);
   }
 
   if (on_window)
     {
-      XWindowAttributes xgwa;
       window = (Window) on_window;
       XtDestroyWidget (toplevel);
       XGetWindowAttributes (dpy, window, &xgwa);
-      cmap = xgwa.colormap;
-      visual = xgwa.visual;
+      visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
+
+      /* Select KeyPress and resize events on the external window.
+       */
+      xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
+      XSelectInput (dpy, window, xgwa.your_event_mask);
+
+      /* Select ButtonPress and ButtonRelease events on the external window,
+         if no other app has already selected them (only one app can select
+         ButtonPress at a time: BadAccess results.)
+       */
+      if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
+        XSelectInput (dpy, window,
+                      (xgwa.your_event_mask |
+                       ButtonPressMask | ButtonReleaseMask));
     }
   else if (root_p)
     {
-      XWindowAttributes xgwa;
-      window = RootWindowOfScreen (XtScreen (toplevel));
+      window = VirtualRootWindowOfScreen (XtScreen (toplevel));
       XtDestroyWidget (toplevel);
       XGetWindowAttributes (dpy, window, &xgwa);
-      cmap = xgwa.colormap;
-      visual = xgwa.visual;
+      visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
     }
   else
     {
-      Boolean def_visual_p;
-      Screen *screen = XtScreen (toplevel);
-      visual = pick_visual (screen);
-
-      if (toplevel->core.width <= 0)
-       toplevel->core.width = 600;
-      if (toplevel->core.height <= 0)
-       toplevel->core.height = 480;
-
-      def_visual_p = (visual == DefaultVisualOfScreen (screen));
-
-      if (!def_visual_p)
-       {
-         unsigned int bg, bd;
-         Widget new;
-
-         cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
-                                 visual, AllocNone);
-         bg = get_pixel_resource ("background", "Background", dpy, cmap);
-         bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
-
-         new = XtVaAppCreateShell (progname, progclass,
-                                   topLevelShellWidgetClass, dpy,
-                                   XtNmappedWhenManaged, False,
-                                   XtNvisual, visual,
-                                   XtNdepth, visual_depth (screen, visual),
-                                   XtNwidth, toplevel->core.width,
-                                   XtNheight, toplevel->core.height,
-                                   XtNcolormap, cmap,
-                                   XtNbackground, (Pixel) bg,
-                                   XtNborderColor, (Pixel) bd,
-                                   0);
-         XtDestroyWidget (toplevel);
-         toplevel = new;
-         XtRealizeWidget (toplevel);
-         window = XtWindow (toplevel);
-       }
-      else
-       {
-         XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
-         XtRealizeWidget (toplevel);
-         window = XtWindow (toplevel);
-
-         if (get_boolean_resource ("installColormap", "InstallColormap"))
-           {
-             cmap = XCreateColormap (dpy, window,
-                                  DefaultVisualOfScreen (XtScreen (toplevel)),
-                                     AllocNone);
-             XSetWindowColormap (dpy, window, cmap);
-           }
-         else
-           {
-             cmap = DefaultColormap (dpy, DefaultScreen (dpy));
-           }
-       }
-
-/*
-      if (dont_map)
-       {
-         XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
-         XtRealizeWidget (toplevel);
-       }
-      else
-*/
-       {
-         XtPopup (toplevel, XtGrabNone);
-       }
-
-      XtVaSetValues(toplevel, XtNtitle, version, 0);
+      Widget new = make_shell (XtScreen (toplevel), toplevel,
+                               toplevel->core.width,
+                               toplevel->core.height);
+      if (new != toplevel)
+        {
+          XtDestroyWidget (toplevel);
+          toplevel = new;
+        }
+
+      init_window (dpy, toplevel, version);
+      window = XtWindow (toplevel);
+      XGetWindowAttributes (dpy, window, &xgwa);
 
-      /* For screenhack_handle_events(): select KeyPress, and
-         announce that we accept WM_DELETE_WINDOW. */
-      {
-        XWindowAttributes xgwa;
-        XGetWindowAttributes (dpy, window, &xgwa);
-        XSelectInput (dpy, window,
-                      xgwa.your_event_mask | KeyPressMask | ButtonPressMask);
-        XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
-                         PropModeReplace,
-                         (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
-      }
+# ifdef DEBUG_PAIR
+      if (get_boolean_resource (dpy, "pair", "Boolean"))
+        {
+          toplevel2 = make_shell (xgwa.screen, 0,
+                                  toplevel->core.width,
+                                  toplevel->core.height);
+          init_window (dpy, toplevel2, version);
+          window2 = XtWindow (toplevel2);
+        }
+# endif /* DEBUG_PAIR */
     }
 
   if (!dont_clear)
     {
-      XSetWindowBackground (dpy, window,
-                           get_pixel_resource ("background", "Background",
-                                               dpy, cmap));
+      unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
+                                            "background", "Background");
+      XSetWindowBackground (dpy, window, bg);
       XClearWindow (dpy, window);
+# ifdef DEBUG_PAIR
+      if (window2)
+        {
+          XSetWindowBackground (dpy, window2, bg);
+          XClearWindow (dpy, window2);
+        }
+# endif
     }
 
   if (!root_p && !on_window)
@@ -482,7 +931,21 @@ main (int argc, char **argv)
     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
 
   XSync (dpy, False);
-  srandom ((int) time ((time_t *) 0));
-  screenhack (dpy, window); /* doesn't return */
+
+  /* This is the one and only place that the random-number generator is
+     seeded in any screenhack.  You do not need to seed the RNG again,
+     it is done for you before your code is invoked. */
+# undef ya_rand_init
+  ya_rand_init (0);
+
+  run_screenhack_table (dpy, window, 
+# ifdef DEBUG_PAIR
+                        window2,
+# endif
+                        ft);
+
+  XtDestroyWidget (toplevel);
+  XtDestroyApplicationContext (app);
+
   return 0;
 }