http://www.tienza.es/crux/src/www.jwz.org/xscreensaver/xscreensaver-5.05.tar.gz
[xscreensaver] / driver / demo-Gtk.c
index f0ca8e4aa96a784d6d0dd6bbf49acfffd4b8b9cc..d1eb3c85089901c9bc840c201580c7dbc711fb85 100644 (file)
@@ -1,5 +1,5 @@
 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
- * xscreensaver, Copyright (c) 1993-2002 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-2008 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 <unistd.h>
 #endif
 
+# ifdef __GNUC__
+#  define STFU __extension__  /* ignore gcc -pendantic warnings in next sexp */
+# else
+#  define STFU /* */
+# endif
+
+
 #ifdef ENABLE_NLS
 # include <locale.h>
 #endif /* ENABLE_NLS */
 # include "xmu.h"
 #endif
 
+#ifdef HAVE_XINERAMA
+# include <X11/extensions/Xinerama.h>
+#endif /* HAVE_XINERAMA */
+
 #include <gtk/gtk.h>
 
 #ifdef HAVE_CRAPPLET
 #include <gdk/gdkx.h>
 
 #ifdef HAVE_GTK2
-#include <glade/glade-xml.h>
-#endif /* HAVE_GTK2 */
+# include <glade/glade-xml.h>
+# include <gmodule.h>
+#else  /* !HAVE_GTK2 */
+# define G_MODULE_EXPORT /**/
+#endif /* !HAVE_GTK2 */
 
 #if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
 # define GLADE_DIR DEFAULT_ICONDIR
 # define DEFAULT_ICONDIR GLADE_DIR
 #endif
 
+#ifndef HAVE_XML
+ /* Kludge: this is defined in demo-Gtk-conf.c when HAVE_XML.
+    It is unused otherwise, so in that case, stub it out. */
+ static const char *hack_configuration_path = 0;
+#endif
+
+
 
 #include "version.h"
 #include "prefs.h"
 #include "logo-50.xpm"
 #include "logo-180.xpm"
 
+#undef dgettext  /* else these are defined twice... */
+#undef dcgettext
+
 #include "demo-Gtk-widgets.h"
 #include "demo-Gtk-support.h"
 #include "demo-Gtk-conf.h"
@@ -120,18 +144,28 @@ enum {
 
 /* from exec.c */
 extern void exec_command (const char *shell, const char *command, int nice);
+extern int on_path_p (const char *program);
+
+static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
 
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
 
+/* You might think that to read an array of 32-bit quantities out of a
+   server-side property, you would pass an array of 32-bit data quantities
+   into XGetWindowProperty().  You would be wrong.  You have to use an array
+   of longs, even if long is 64 bits (using 32 of each 64.)
+ */
+typedef long PROP32;
+
 char *progname = 0;
 char *progclass = "XScreenSaver";
 XrmDatabase db;
 
 /* The order of the items in the mode menu. */
 static int mode_menu_order[] = {
-  DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS };
+  DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME };
 
 
 typedef struct {
@@ -164,6 +198,7 @@ typedef struct {
   int *list_elt_to_hack_number;        /* table for sorting the hack list */
   int *hack_number_to_list_elt;        /* the inverse table */
   Bool *hacks_available_p;     /* whether hacks are on $PATH */
+  int total_available;         /* how many are on $PATH */
   int list_count;              /* how many items are in the list: this may be
                                    less than p->screenhacks_count, if some are
                                    suppressed. */
@@ -171,6 +206,8 @@ typedef struct {
   int _selected_list_element;  /* don't use this: call
                                    selected_list_element() instead */
 
+  int nscreens;                        /* How many X or Xinerama screens there are */
+
   saver_preferences prefs;
 
 } state;
@@ -195,16 +232,52 @@ static Bool flush_popup_changes_and_save (state *);
 
 static int maybe_reload_init_file (state *);
 static void await_xscreensaver (state *);
+static Bool xscreensaver_running_p (state *);
+static void sensitize_menu_items (state *s, Bool force_p);
+static void force_dialog_repaint (state *s);
 
 static void schedule_preview (state *, const char *cmd);
-static void kill_preview_subproc (state *);
+static void kill_preview_subproc (state *, Bool reset_p);
 static void schedule_preview_check (state *);
 
+\f
+/* Prototypes of functions used by the Glade-generated code,
+   to avoid warnings.
+ */
+void exit_menu_cb (GtkMenuItem *, gpointer user_data);
+void about_menu_cb (GtkMenuItem *, gpointer user_data);
+void doc_menu_cb (GtkMenuItem *, gpointer user_data);
+void file_menu_cb (GtkMenuItem *, gpointer user_data);
+void activate_menu_cb (GtkMenuItem *, gpointer user_data);
+void lock_menu_cb (GtkMenuItem *, gpointer user_data);
+void kill_menu_cb (GtkMenuItem *, gpointer user_data);
+void restart_menu_cb (GtkWidget *, gpointer user_data);
+void run_this_cb (GtkButton *, gpointer user_data);
+void manual_cb (GtkButton *, gpointer user_data);
+void run_next_cb (GtkButton *, gpointer user_data);
+void run_prev_cb (GtkButton *, gpointer user_data);
+void pref_changed_cb (GtkWidget *, gpointer user_data);
+gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer user_data);
+void mode_menu_item_cb (GtkWidget *, gpointer user_data);
+void switch_page_cb (GtkNotebook *, GtkNotebookPage *, 
+                     gint page_num, gpointer user_data);
+void browse_image_dir_cb (GtkButton *, gpointer user_data);
+void browse_text_file_cb (GtkButton *, gpointer user_data);
+void browse_text_program_cb (GtkButton *, gpointer user_data);
+void settings_cb (GtkButton *, gpointer user_data);
+void settings_adv_cb (GtkButton *, gpointer user_data);
+void settings_std_cb (GtkButton *, gpointer user_data);
+void settings_switch_page_cb (GtkNotebook *, GtkNotebookPage *,
+                              gint page_num, gpointer user_data);
+void settings_cancel_cb (GtkButton *, gpointer user_data);
+void settings_ok_cb (GtkButton *, gpointer user_data);
 
 \f
 /* Some random utility functions
  */
 
+const char *blurb (void);
+
 const char *
 blurb (void)
 {
@@ -274,7 +347,8 @@ name_to_widget (state *s, const char *name)
 #endif /* HAVE_GTK2 */
   if (w) return w;
 
-  fprintf (stderr, "%s: no widget \"%s\"\n", blurb(), name);
+  fprintf (stderr, "%s: no widget \"%s\" (wrong Glade file?)\n",
+           blurb(), name);
   abort();
 }
 
@@ -293,12 +367,14 @@ ensure_selected_item_visible (GtkWidget *widget)
   
   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
-    return;
-
-  path = gtk_tree_model_get_path (model, &iter);
+       path = gtk_tree_path_new_first ();
+  else
+       path = gtk_tree_model_get_path (model, &iter);
   
-  gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget),
-                               path, NULL, FALSE, 0.0, 0.0);
+  gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE);
+
+  gtk_tree_path_free (path);
+
 #else /* !HAVE_GTK2 */
 
   GtkScrolledWindow *scroller = 0;
@@ -422,7 +498,8 @@ warning_dialog (GtkWidget *parent, const char *message,
   while (parent && !parent->window)
     parent = parent->parent;
 
-  if (!GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */
+  if (!parent ||
+      !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */
     {
       fprintf (stderr, "%s: too early for dialog?\n", progname);
       return;
@@ -505,13 +582,13 @@ warning_dialog (GtkWidget *parent, const char *message,
   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
-  GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
+  STFU GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
   gtk_widget_show (ok);
   gtk_widget_grab_focus (ok);
 
   if (cancel)
     {
-      GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); 
+      STFU GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); 
       gtk_widget_show (cancel);
     }
   gtk_widget_show (label);
@@ -555,6 +632,11 @@ run_cmd (state *s, Atom command, int arg)
 
   flush_dialog_changes_and_save (s);
   status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
+
+  /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */
+  if (status < 0 && err && strstr (err, "unexpectedly deleted"))
+    status = 0;
+
   if (status < 0)
     {
       char buf [255];
@@ -565,6 +647,9 @@ run_cmd (state *s, Atom command, int arg)
       warning_dialog (s->toplevel_widget, buf, False, 100);
     }
   if (err) free (err);
+
+  sensitize_menu_items (s, True);
+  force_dialog_repaint (s);
 }
 
 
@@ -572,33 +657,73 @@ static void
 run_hack (state *s, int list_elt, Bool report_errors_p)
 {
   int hack_number;
+  char *err = 0;
+  int status;
+
   if (list_elt < 0) return;
   hack_number = s->list_elt_to_hack_number[list_elt];
 
   flush_dialog_changes_and_save (s);
   schedule_preview (s, 0);
-  if (report_errors_p)
-    run_cmd (s, XA_DEMO, hack_number + 1);
-  else
+
+  status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
+                                 False, &err);
+
+  if (status < 0 && report_errors_p)
     {
-      char *s = 0;
-      xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
-                            False, &s);
-      if (s) free (s);
+      if (xscreensaver_running_p (s))
+        {
+          /* Kludge: ignore the spurious "window unexpectedly deleted"
+             errors... */
+          if (err && strstr (err, "unexpectedly deleted"))
+            status = 0;
+
+          if (status < 0)
+            {
+              char buf [255];
+              if (err)
+                sprintf (buf, "Error:\n\n%s", err);
+              else
+                strcpy (buf, "Unknown error!");
+              warning_dialog (s->toplevel_widget, buf, False, 100);
+            }
+        }
+      else
+        {
+          /* The error is that the daemon isn't running;
+             offer to restart it.
+           */
+          const char *d = DisplayString (GDK_DISPLAY());
+          char msg [1024];
+          sprintf (msg,
+                   _("Warning:\n\n"
+                     "The XScreenSaver daemon doesn't seem to be running\n"
+                     "on display \"%s\".  Launch it now?"),
+                   d);
+          warning_dialog (s->toplevel_widget, msg, True, 1);
+        }
     }
+
+  if (err) free (err);
+
+  sensitize_menu_items (s, False);
 }
 
 
 \f
 /* Button callbacks
+
+   According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make
+   libglade work on Cygwin; apparently all Glade callbacks need this magic
+   extra declaration.  I do not pretend to understand.
  */
 
-void
+G_MODULE_EXPORT void
 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
   flush_dialog_changes_and_save (s);
-  kill_preview_subproc (s);
+  kill_preview_subproc (s, False);
   gtk_main_quit ();
 }
 
@@ -612,7 +737,7 @@ wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
 }
 
 
-void
+G_MODULE_EXPORT void
 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 {
   char msg [2048];
@@ -631,9 +756,9 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
      look as good in the plain-old default Latin1 "C" locale.)
    */
 #ifdef HAVE_GTK2
-  sprintf(copy, ("Copyright \xC2\xA9 1991-2002 %s"), s);
+  sprintf(copy, ("Copyright \xC2\xA9 1991-2008 %s"), s);
 #else  /* !HAVE_GTK2 */
-  sprintf(copy, ("Copyright \251 1991-2002 %s"), s);
+  sprintf(copy, ("Copyright \251 1991-2008 %s"), s);
 #endif /* !HAVE_GTK2 */
 
   sprintf (msg, "%s\n\n%s", copy, desc);
@@ -739,7 +864,7 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -755,18 +880,27 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
     }
 
   help_command = (char *) malloc (strlen (p->load_url_command) +
-                                 (strlen (p->help_url) * 2) + 20);
+                                 (strlen (p->help_url) * 4) + 20);
   strcpy (help_command, "( ");
   sprintf (help_command + strlen(help_command),
-           p->load_url_command, p->help_url, p->help_url);
+           p->load_url_command,
+           p->help_url, p->help_url, p->help_url, p->help_url);
   strcat (help_command, " ) &");
-fprintf(stderr, "## %s\n", help_command);
-  system (help_command);
+  if (system (help_command) < 0)
+    fprintf (stderr, "%s: fork error\n", blurb());
   free (help_command);
 }
 
 
-void
+G_MODULE_EXPORT void
+file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+{
+  state *s = global_state_kludge;  /* I hate C so much... */
+  sensitize_menu_items (s, False);
+}
+
+
+G_MODULE_EXPORT void
 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -774,7 +908,7 @@ activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -782,7 +916,7 @@ lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -790,45 +924,46 @@ kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 restart_menu_cb (GtkWidget *widget, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
   flush_dialog_changes_and_save (s);
   xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
   sleep (1);
-  system ("xscreensaver -nosplash &");
+  if (system ("xscreensaver -nosplash &") < 0)
+    fprintf (stderr, "%s: fork error\n", blurb());
 
   await_xscreensaver (s);
 }
 
-static void
-await_xscreensaver (state *s)
+static Bool
+xscreensaver_running_p (state *s)
 {
-  int countdown = 5;
-
   Display *dpy = GDK_DISPLAY();
-  /*  GtkWidget *dialog = 0;*/
   char *rversion = 0;
+  server_xscreensaver_version (dpy, &rversion, 0, 0);
+  if (!rversion)
+    return False;
+  free (rversion);
+  return True;
+}
 
-  while (!rversion && (--countdown > 0))
-    {
-      /* Check for the version of the running xscreensaver... */
-      server_xscreensaver_version (dpy, &rversion, 0, 0);
+static void
+await_xscreensaver (state *s)
+{
+  int countdown = 5;
+  Bool ok = False;
 
-      /* If it's not there yet, wait a second... */
-      if (!rversion)
-        sleep (1);
-    }
+  while (!ok && (--countdown > 0))
+    if (xscreensaver_running_p (s))
+      ok = True;
+    else
+      sleep (1);    /* If it's not there yet, wait a second... */
 
-/*  if (dialog) gtk_widget_destroy (dialog);*/
+  sensitize_menu_items (s, True);
 
-  if (rversion)
-    {
-      /* Got it. */
-      free (rversion);
-    }
-  else
+  if (! ok)
     {
       /* Timed out, no screensaver running. */
 
@@ -841,7 +976,13 @@ await_xscreensaver (state *s)
                "\n"));
 
       if (root_p)
-        strcat (buf,
+
+# ifdef __GNUC__
+        __extension__     /* don't warn about "string length is greater than
+                             the length ISO C89 compilers are required to
+                             support" in the following expression... */
+# endif
+        strcat (buf, STFU
          _("You are running as root.  This usually means that xscreensaver\n"
             "was unable to contact your X server because access control is\n"
             "turned on.  Try running this command:\n"
@@ -862,6 +1003,8 @@ await_xscreensaver (state *s)
 
       warning_dialog (s->toplevel_widget, buf, False, 1);
     }
+
+  force_dialog_repaint (s);
 }
 
 
@@ -875,6 +1018,7 @@ selected_list_element (state *s)
 static int
 demo_write_init_file (state *s, saver_preferences *p)
 {
+  Display *dpy = GDK_DISPLAY();
 
 #if 0
   /* #### try to figure out why shit keeps getting reordered... */
@@ -882,7 +1026,7 @@ demo_write_init_file (state *s, saver_preferences *p)
     abort();
 #endif
 
-  if (!write_init_file (p, s->short_version, False))
+  if (!write_init_file (dpy, p, s->short_version, False))
     {
       if (s->debug_p)
         fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
@@ -907,7 +1051,7 @@ demo_write_init_file (state *s, saver_preferences *p)
 }
 
 
-void
+G_MODULE_EXPORT void
 run_this_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -918,15 +1062,17 @@ run_this_cb (GtkButton *button, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 manual_cb (GtkButton *button, gpointer user_data)
 {
+  Display *dpy = GDK_DISPLAY();
   state *s = global_state_kludge;  /* I hate C so much... */
   saver_preferences *p = &s->prefs;
   GtkWidget *list_widget = name_to_widget (s, "list");
   int list_elt = selected_list_element (s);
   int hack_number;
   char *name, *name2, *cmd, *str;
+  char *oname = 0;
   if (list_elt < 0) return;
   hack_number = s->list_elt_to_hack_number[list_elt];
 
@@ -935,23 +1081,25 @@ manual_cb (GtkButton *button, gpointer user_data)
 
   name = strdup (p->screenhacks[hack_number]->command);
   name2 = name;
+  oname = name;
   while (isspace (*name2)) name2++;
   str = name2;
   while (*str && !isspace (*str)) str++;
   *str = 0;
   str = strrchr (name2, '/');
-  if (str) name = str+1;
+  if (str) name2 = str+1;
 
-  cmd = get_string_resource ("manualCommand", "ManualCommand");
+  cmd = get_string_resource (dpy, "manualCommand", "ManualCommand");
   if (cmd)
     {
-      char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100);
+      char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
       strcpy (cmd2, "( ");
       sprintf (cmd2 + strlen (cmd2),
                cmd,
                name2, name2, name2, name2);
       strcat (cmd2, " ) &");
-      system (cmd2);
+      if (system (cmd2) < 0)
+        fprintf (stderr, "%s: fork error\n", blurb());
       free (cmd2);
     }
   else
@@ -961,7 +1109,7 @@ manual_cb (GtkButton *button, gpointer user_data)
                       False, 100);
     }
 
-  free (name);
+  free (oname);
 }
 
 
@@ -980,9 +1128,11 @@ force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
 #ifdef HAVE_GTK2
   model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
   g_assert (model);
-  gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt);
-  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
-  gtk_tree_selection_select_iter (selection, &iter);
+  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
+    {
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+      gtk_tree_selection_select_iter (selection, &iter);
+    }
 #else  /* !HAVE_GTK2 */
   gtk_list_select_item (GTK_LIST (list), list_elt);
 #endif /* !HAVE_GTK2 */
@@ -991,7 +1141,7 @@ force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
 }
 
 
-void
+G_MODULE_EXPORT void
 run_next_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1020,7 +1170,7 @@ run_next_cb (GtkButton *button, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 run_prev_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1127,7 +1277,7 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
       else
         {
           char c;
-          if (sscanf (line, "%u%c", &value, &c) != 1)
+          if (sscanf (line, "%d%c", &value, &c) != 1)
             value = -1;
           if (!sec_p)
             value *= 60;
@@ -1163,6 +1313,20 @@ directory_p (const char *path)
     return True;
 }
 
+static Bool
+file_p (const char *path)
+{
+  struct stat st;
+  if (!path || !*path)
+    return False;
+  else if (stat (path, &st))
+    return False;
+  else if (S_ISDIR (st.st_mode))
+    return False;
+  else
+    return True;
+}
+
 static char *
 normalize_directory (const char *path)
 {
@@ -1314,6 +1478,10 @@ flush_dialog_changes_and_save (state *s)
     w = name_to_widget (s, (NAME)); \
     (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
 
+# define TEXT(FIELD,NAME) \
+    w = name_to_widget (s, (NAME)); \
+    (FIELD) = (char *) gtk_entry_get_text (GTK_ENTRY (w))
+
   MINUTES  (&p2->timeout,         "timeout_spinbutton");
   MINUTES  (&p2->cycle,           "cycle_spinbutton");
   CHECKBOX (p2->lock_p,           "lock_button");
@@ -1329,9 +1497,25 @@ flush_dialog_changes_and_save (state *s)
   CHECKBOX (p2->random_image_p,   "grab_image_button");
   PATHNAME (p2->image_directory,  "image_text");
 
+#if 0
   CHECKBOX (p2->verbose_p,        "verbose_button");
   CHECKBOX (p2->capture_stderr_p, "capture_button");
   CHECKBOX (p2->splash_p,         "splash_button");
+#endif
+
+  {
+    Bool v = False;
+    CHECKBOX (v, "text_host_radio");     if (v) p2->tmode = TEXT_DATE;
+    CHECKBOX (v, "text_radio");          if (v) p2->tmode = TEXT_LITERAL;
+    CHECKBOX (v, "text_file_radio");     if (v) p2->tmode = TEXT_FILE;
+    CHECKBOX (v, "text_program_radio");  if (v) p2->tmode = TEXT_PROGRAM;
+    CHECKBOX (v, "text_url_radio");      if (v) p2->tmode = TEXT_URL;
+    TEXT     (p2->text_literal, "text_entry");
+    PATHNAME (p2->text_file,    "text_file_entry");
+    PATHNAME (p2->text_program, "text_program_entry");
+    PATHNAME (p2->text_program, "text_program_entry");
+    TEXT     (p2->text_url,     "text_url_entry");
+  }
 
   CHECKBOX (p2->install_cmap_p,   "install_button");
   CHECKBOX (p2->fade_p,           "fade_button");
@@ -1342,6 +1526,7 @@ flush_dialog_changes_and_save (state *s)
 # undef MINUTES
 # undef CHECKBOX
 # undef PATHNAME
+# undef TEXT
 
   /* Warn if the image directory doesn't exist.
    */
@@ -1396,9 +1581,13 @@ flush_dialog_changes_and_save (state *s)
   COPY(dpms_suspend,   "dpms_suspend");
   COPY(dpms_off,       "dpms_off");
 
+#if 0
   COPY(verbose_p,        "verbose_p");
   COPY(capture_stderr_p, "capture_stderr_p");
   COPY(splash_p,         "splash_p");
+#endif
+
+  COPY(tmode,            "tmode");
 
   COPY(install_cmap_p,   "install_cmap_p");
   COPY(fade_p,           "fade_p");
@@ -1411,19 +1600,26 @@ flush_dialog_changes_and_save (state *s)
 
 # undef COPY
 
-  if (!p->image_directory ||
-      !p2->image_directory ||
-      strcmp(p->image_directory, p2->image_directory))
-    {
-      changed = True;
-      if (s->debug_p)
-        fprintf (stderr, "%s: image_directory => \"%s\"\n",
-                 blurb(), p2->image_directory);
-    }
-  if (p->image_directory && p->image_directory != p2->image_directory)
-    free (p->image_directory);
-  p->image_directory = p2->image_directory;
-  p2->image_directory = 0;
+# define COPYSTR(FIELD,NAME) \
+  if (!p->FIELD || \
+      !p2->FIELD || \
+      strcmp(p->FIELD, p2->FIELD)) \
+    { \
+      changed = True; \
+      if (s->debug_p) \
+        fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
+    } \
+  if (p->FIELD && p->FIELD != p2->FIELD) \
+    free (p->FIELD); \
+  p->FIELD = p2->FIELD; \
+  p2->FIELD = 0
+
+  COPYSTR(image_directory, "image_directory");
+  COPYSTR(text_literal,    "text_literal");
+  COPYSTR(text_file,       "text_file");
+  COPYSTR(text_program,    "text_program");
+  COPYSTR(text_url,        "text_url");
+# undef COPYSTR
 
   populate_prefs_page (s);
 
@@ -1497,7 +1693,7 @@ flush_popup_changes_and_save (state *s)
   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
-  else if (1 == sscanf (visual, " %ld %c", &id, &c))   ;
+  else if (1 == sscanf (visual, " %lu %c", &id, &c))   ;
   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
   else
     {
@@ -1522,7 +1718,7 @@ flush_popup_changes_and_save (state *s)
 }
 
 
-void
+G_MODULE_EXPORT void
 pref_changed_cb (GtkWidget *widget, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1534,7 +1730,7 @@ pref_changed_cb (GtkWidget *widget, gpointer user_data)
     }
 }
 
-gboolean
+G_MODULE_EXPORT gboolean
 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
 {
   pref_changed_cb (widget, user_data);
@@ -1543,7 +1739,7 @@ pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
 
 /* Callback on menu items in the "mode" options menu.
  */
-void
+G_MODULE_EXPORT void
 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
 {
   state *s = (state *) user_data;
@@ -1554,7 +1750,6 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
   GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent));
   int menu_index = 0;
   saver_mode new_mode;
-  int old_selected = p->selected_hack;
 
   while (menu_items)
     {
@@ -1568,17 +1763,12 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
   new_mode = mode_menu_order[menu_index];
 
   /* Keep the same list element displayed as before; except if we're
-     switching *to* "one screensaver" mode from any other mode, scroll
-     to and select "the one".
+     switching *to* "one screensaver" mode from any other mode, set
+     "the one" to be that which is currently selected.
    */
-  list_elt = -1;
+  list_elt = selected_list_element (s);
   if (new_mode == ONE_HACK)
-    list_elt = (p->selected_hack >= 0
-                ? s->hack_number_to_list_elt[p->selected_hack]
-                : -1);
-
-  if (list_elt < 0)
-    list_elt = selected_list_element (s);
+    p->selected_hack = s->list_elt_to_hack_number[list_elt];
 
   {
     saver_mode old_mode = p->mode;
@@ -1589,13 +1779,10 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
   }
 
   pref_changed_cb (widget, user_data);
-
-  if (old_selected != p->selected_hack)
-    abort();    /* dammit, not again... */
 }
 
 
-void
+G_MODULE_EXPORT void
 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
                 gint page_num, gpointer user_data)
 {
@@ -1621,7 +1808,7 @@ list_activated_cb (GtkTreeView       *list,
   char *str;
   int list_elt;
 
-  g_return_if_fail (!gdk_pointer_is_grabbed ());
+  STFU g_return_if_fail (!gdk_pointer_is_grabbed ());
 
   str = gtk_tree_path_to_string (path);
   list_elt = strtol (str, NULL, 10);
@@ -1653,6 +1840,11 @@ list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
 
   populate_demo_window (s, list_elt);
   flush_dialog_changes_and_save (s);
+
+  /* Re-populate the Settings window any time a new item is selected
+     in the list, in case both windows are currently visible.
+   */
+  populate_popup_window (s);
 }
 
 #else /* !HAVE_GTK2 */
@@ -1817,6 +2009,69 @@ store_image_directory (GtkWidget *button, gpointer user_data)
 }
 
 
+static void
+store_text_file (GtkWidget *button, gpointer user_data)
+{
+  file_selection_data *fsd = (file_selection_data *) user_data;
+  state *s = fsd->state;
+  GtkFileSelection *selector = fsd->widget;
+  GtkWidget *top = s->toplevel_widget;
+  saver_preferences *p = &s->prefs;
+  const char *path = gtk_file_selection_get_filename (selector);
+
+  if (p->text_file && !strcmp(p->text_file, path))
+    return;  /* no change */
+
+  if (!file_p (path))
+    {
+      char b[255];
+      sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
+      warning_dialog (GTK_WIDGET (top), b, False, 100);
+      return;
+    }
+
+  if (p->text_file) free (p->text_file);
+  p->text_file = normalize_directory (path);
+
+  gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
+                      (p->text_file ? p->text_file : ""));
+  demo_write_init_file (s, p);
+}
+
+
+static void
+store_text_program (GtkWidget *button, gpointer user_data)
+{
+  file_selection_data *fsd = (file_selection_data *) user_data;
+  state *s = fsd->state;
+  GtkFileSelection *selector = fsd->widget;
+  /*GtkWidget *top = s->toplevel_widget;*/
+  saver_preferences *p = &s->prefs;
+  const char *path = gtk_file_selection_get_filename (selector);
+
+  if (p->text_program && !strcmp(p->text_program, path))
+    return;  /* no change */
+
+# if 0
+  if (!file_p (path))
+    {
+      char b[255];
+      sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
+      warning_dialog (GTK_WIDGET (top), b, False, 100);
+      return;
+    }
+# endif
+
+  if (p->text_program) free (p->text_program);
+  p->text_program = normalize_directory (path);
+
+  gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
+                      (p->text_program ? p->text_program : ""));
+  demo_write_init_file (s, p);
+}
+
+
+
 static void
 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
 {
@@ -1831,6 +2086,20 @@ browse_image_dir_ok (GtkWidget *button, gpointer user_data)
   store_image_directory (button, user_data);
 }
 
+static void
+browse_text_file_ok (GtkWidget *button, gpointer user_data)
+{
+  browse_image_dir_cancel (button, user_data);
+  store_text_file (button, user_data);
+}
+
+static void
+browse_text_program_ok (GtkWidget *button, gpointer user_data)
+{
+  browse_image_dir_cancel (button, user_data);
+  store_text_program (button, user_data);
+}
+
 static void
 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
 {
@@ -1838,7 +2107,7 @@ browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
 browse_image_dir_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1874,7 +2143,78 @@ browse_image_dir_cb (GtkButton *button, gpointer user_data)
 }
 
 
-void
+G_MODULE_EXPORT void
+browse_text_file_cb (GtkButton *button, gpointer user_data)
+{
+  state *s = global_state_kludge;  /* I hate C so much... */
+  saver_preferences *p = &s->prefs;
+  static file_selection_data *fsd = 0;
+
+  GtkFileSelection *selector = GTK_FILE_SELECTION(
+    gtk_file_selection_new ("Please select a text file."));
+
+  if (!fsd)
+    fsd = (file_selection_data *) malloc (sizeof (*fsd));  
+
+  fsd->widget = selector;
+  fsd->state = s;
+
+  if (p->text_file && *p->text_file)
+    gtk_file_selection_set_filename (selector, p->text_file);
+
+  gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+                      "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
+                      (gpointer *) fsd);
+  gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+                      "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+                      (gpointer *) fsd);
+  gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+                      GTK_SIGNAL_FUNC (browse_image_dir_close),
+                      (gpointer *) fsd);
+
+  gtk_window_set_modal (GTK_WINDOW (selector), True);
+  gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+G_MODULE_EXPORT void
+browse_text_program_cb (GtkButton *button, gpointer user_data)
+{
+  state *s = global_state_kludge;  /* I hate C so much... */
+  saver_preferences *p = &s->prefs;
+  static file_selection_data *fsd = 0;
+
+  GtkFileSelection *selector = GTK_FILE_SELECTION(
+    gtk_file_selection_new ("Please select a text-generating program."));
+
+  if (!fsd)
+    fsd = (file_selection_data *) malloc (sizeof (*fsd));  
+
+  fsd->widget = selector;
+  fsd->state = s;
+
+  if (p->text_program && *p->text_program)
+    gtk_file_selection_set_filename (selector, p->text_program);
+
+  gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+                      "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
+                      (gpointer *) fsd);
+  gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+                      "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+                      (gpointer *) fsd);
+  gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+                      GTK_SIGNAL_FUNC (browse_image_dir_close),
+                      (gpointer *) fsd);
+
+  gtk_window_set_modal (GTK_WINDOW (selector), True);
+  gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+
+
+
+G_MODULE_EXPORT void
 settings_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1897,7 +2237,7 @@ settings_sync_cmd_text (state *s)
 # endif /* HAVE_XML */
 }
 
-void
+G_MODULE_EXPORT void
 settings_adv_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1908,7 +2248,7 @@ settings_adv_cb (GtkButton *button, gpointer user_data)
   gtk_notebook_set_page (notebook, 1);
 }
 
-void
+G_MODULE_EXPORT void
 settings_std_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1921,7 +2261,7 @@ settings_std_cb (GtkButton *button, gpointer user_data)
   gtk_notebook_set_page (notebook, 0);
 }
 
-void
+G_MODULE_EXPORT void
 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
                          gint page_num, gpointer user_data)
 {
@@ -1945,14 +2285,14 @@ settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
 
 
 
-void
+G_MODULE_EXPORT void
 settings_cancel_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
   gtk_widget_hide (s->popup_widget);
 }
 
-void
+G_MODULE_EXPORT void
 settings_ok_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
@@ -1991,7 +2331,7 @@ server_current_hack (void)
   Atom type;
   int format;
   unsigned long nitems, bytesafter;
-  CARD32 *data = 0;
+  unsigned char *dataP = 0;
   Display *dpy = GDK_DISPLAY();
   int hack_number = -1;
 
@@ -1999,32 +2339,55 @@ server_current_hack (void)
                           XA_SCREENSAVER_STATUS,
                           0, 3, False, XA_INTEGER,
                           &type, &format, &nitems, &bytesafter,
-                          (unsigned char **) &data)
+                          &dataP)
       == Success
       && type == XA_INTEGER
       && nitems >= 3
-      && data)
-    hack_number = (int) data[2] - 1;
+      && dataP)
+    {
+      PROP32 *data = (PROP32 *) dataP;
+      hack_number = (int) data[2] - 1;
+    }
 
-  if (data) free (data);
+  if (dataP) XFree (dataP);
 
   return hack_number;
 }
 
 
-/* Finds the number of the last hack to run, and makes that item be
+/* Finds the number of the last hack that was run, and makes that item be
    selected by default.
  */
 static void
 scroll_to_current_hack (state *s)
 {
   saver_preferences *p = &s->prefs;
-  int hack_number;
+  int hack_number = -1;
 
-  if (p->mode == ONE_HACK)
+  if (p->mode == ONE_HACK)                /* in "one" mode, use the one */
     hack_number = p->selected_hack;
-  else
+  if (hack_number < 0)                    /* otherwise, use the last-run */
     hack_number = server_current_hack ();
+  if (hack_number < 0)                    /* failing that, last "one mode" */
+    hack_number = p->selected_hack;
+  if (hack_number < 0)                    /* failing that, newest hack. */
+    {
+      /* We should only get here if the user does not have a .xscreensaver
+         file, and the screen has not been blanked with a hack since X
+         started up: in other words, this is probably a fresh install.
+
+         Instead of just defaulting to hack #0 (in either "programs" or
+         "alphabetical" order) let's try to default to the last runnable
+         hack in the "programs" list: this is probably the hack that was
+         most recently added to the xscreensaver distribution (and so
+         it's probably the currently-coolest one!)
+       */
+      hack_number = p->screenhacks_count-1;
+      while (hack_number > 0 &&
+             ! (s->hacks_available_p[hack_number] &&
+                p->screenhacks[hack_number]->enabled_p))
+        hack_number--;
+    }
 
   if (hack_number >= 0 && hack_number < p->screenhacks_count)
     {
@@ -2036,55 +2399,10 @@ scroll_to_current_hack (state *s)
 }
 
 
-static Bool
-on_path_p (const char *program)
-{
-  int result = False;
-  struct stat st;
-  char *cmd = strdup (program);
-  char *token = strchr (cmd, ' ');
-  char *path = 0;
-  int L;
-
-  if (token) *token = 0;
-  token = 0;
-
-  if (strchr (cmd, '/'))
-    {
-      result = (0 == stat (cmd, &st));
-      goto DONE;
-    }
-
-  path = getenv("PATH");
-  if (!path || !*path)
-    goto DONE;
-
-  L = strlen (cmd);
-  path = strdup (path);
-  token = strtok (path, ":");
-
-  while (token)
-    {
-      char *p2 = (char *) malloc (strlen (token) + L + 3);
-      strcpy (p2, token);
-      strcat (p2, "/");
-      strcat (p2, cmd);
-      result = (0 == stat (p2, &st));
-      if (result)
-        goto DONE;
-      token = strtok (0, ":");
-    }
-
- DONE:
-  free (cmd);
-  if (path) free (path);
-  return result;
-}
-
-
 static void
 populate_hack_list (state *s)
 {
+  Display *dpy = GDK_DISPLAY();
 #ifdef HAVE_GTK2
   saver_preferences *p = &s->prefs;
   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
@@ -2145,7 +2463,7 @@ populate_hack_list (state *s)
 
       pretty_name = (hack->name
                      ? strdup (hack->name)
-                     : make_hack_name (hack->command));
+                     : make_hack_name (dpy, hack->command));
 
       if (!available_p)
         {
@@ -2277,8 +2595,11 @@ static void
 update_list_sensitivity (state *s)
 {
   saver_preferences *p = &s->prefs;
-  Bool sensitive = (p->mode == RANDOM_HACKS || p->mode == ONE_HACK);
-  Bool checkable = (p->mode == RANDOM_HACKS);
+  Bool sensitive = (p->mode == RANDOM_HACKS ||
+                    p->mode == RANDOM_HACKS_SAME ||
+                    p->mode == ONE_HACK);
+  Bool checkable = (p->mode == RANDOM_HACKS ||
+                    p->mode == RANDOM_HACKS_SAME);
   Bool blankable = (p->mode != DONT_BLANK);
 
 #ifndef HAVE_GTK2
@@ -2334,6 +2655,22 @@ populate_prefs_page (state *s)
 {
   saver_preferences *p = &s->prefs;
 
+  Bool can_lock_p = True;
+
+  /* Disable all the "lock" controls if locking support was not provided
+     at compile-time, or if running on MacOS. */
+# if defined(NO_LOCKING) || defined(__APPLE__)
+  can_lock_p = False;
+# endif
+
+
+  /* If there is only one screen, the mode menu contains
+     "random" but not "random-same".
+   */
+  if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
+    p->mode = RANDOM_HACKS;
+
+
   /* The file supports timeouts of less than a minute, but the GUI does
      not, so throttle the values to be at least one minute (since "0" is
      a bad rounding choice...)
@@ -2341,7 +2678,7 @@ populate_prefs_page (state *s)
 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
   THROTTLE (timeout);
   THROTTLE (cycle);
-  THROTTLE (passwd_timeout);
+  /* THROTTLE (passwd_timeout); */  /* GUI doesn't set this; leave it alone */
 # undef THROTTLE
 
 # define FMT_MINUTES(NAME,N) \
@@ -2366,9 +2703,11 @@ populate_prefs_page (state *s)
                                 (ACTIVEP))
 
   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
+#if 0
   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
   TOGGLE_ACTIVE ("capture_button",    p->capture_stderr_p);
   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
+#endif
   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
@@ -2377,6 +2716,15 @@ populate_prefs_page (state *s)
   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
 
+  switch (p->tmode)
+    {
+    case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio",         True); break;
+    case TEXT_FILE:    TOGGLE_ACTIVE ("text_file_radio",    True); break;
+    case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
+    case TEXT_URL:     TOGGLE_ACTIVE ("text_url_radio",     True); break;
+    default:           TOGGLE_ACTIVE ("text_host_radio",    True); break;
+    }
+
 # undef TOGGLE_ACTIVE
 
   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
@@ -2386,6 +2734,29 @@ populate_prefs_page (state *s)
   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
                             p->random_image_p);
 
+  gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
+                      (p->text_literal ? p->text_literal : ""));
+  gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
+                      (p->text_file ? p->text_file : ""));
+  gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
+                      (p->text_program ? p->text_program : ""));
+  gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
+                      (p->text_url ? p->text_url : ""));
+
+  gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
+                            p->tmode == TEXT_LITERAL);
+  gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
+                            p->tmode == TEXT_FILE);
+  gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
+                            p->tmode == TEXT_FILE);
+  gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
+                            p->tmode == TEXT_PROGRAM);
+  gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
+                            p->tmode == TEXT_PROGRAM);
+  gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
+                            p->tmode == TEXT_URL);
+
+
   /* Map the `saver_mode' enum to mode menu to values. */
   {
     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
@@ -2400,10 +2771,11 @@ populate_prefs_page (state *s)
 
   {
     Bool found_any_writable_cells = False;
+    Bool fading_possible = False;
     Bool dpms_supported = False;
 
     Display *dpy = GDK_DISPLAY();
-    int nscreens = ScreenCount(dpy);
+    int nscreens = ScreenCount(dpy);  /* real screens, not Xinerama */
     int i;
     for (i = 0; i < nscreens; i++)
       {
@@ -2415,8 +2787,9 @@ populate_prefs_page (state *s)
          }
       }
 
+    fading_possible = found_any_writable_cells;
 #ifdef HAVE_XF86VMODE_GAMMA
-    found_any_writable_cells = True;  /* if we can gamma fade, go for it */
+    fading_possible = True;
 #endif
 
 #ifdef HAVE_DPMS_EXTENSION
@@ -2433,8 +2806,9 @@ populate_prefs_page (state *s)
 
     /* Blanking and Locking
      */
-    SENSITIZE ("lock_spinbutton", p->lock_p);
-    SENSITIZE ("lock_mlabel",     p->lock_p);
+    SENSITIZE ("lock_button",     can_lock_p);
+    SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
+    SENSITIZE ("lock_mlabel",     can_lock_p && p->lock_p);
 
     /* DPMS
      */
@@ -2452,14 +2826,14 @@ populate_prefs_page (state *s)
 
     /* Colormaps
      */
-    SENSITIZE ("cmap_frame",      found_any_writable_cells);
+    SENSITIZE ("cmap_frame",      found_any_writable_cells || fading_possible);
     SENSITIZE ("install_button",  found_any_writable_cells);
-    SENSITIZE ("fade_button",     found_any_writable_cells);
-    SENSITIZE ("unfade_button",   found_any_writable_cells);
+    SENSITIZE ("fade_button",     fading_possible);
+    SENSITIZE ("unfade_button",   fading_possible);
 
-    SENSITIZE ("fade_label",      (found_any_writable_cells &&
+    SENSITIZE ("fade_label",      (fading_possible &&
                                    (p->fade_p || p->unfade_p)));
-    SENSITIZE ("fade_spinbutton", (found_any_writable_cells &&
+    SENSITIZE ("fade_spinbutton", (fading_possible &&
                                    (p->fade_p || p->unfade_p)));
 
 # undef SENSITIZE
@@ -2470,14 +2844,7 @@ populate_prefs_page (state *s)
 static void
 populate_popup_window (state *s)
 {
-  saver_preferences *p = &s->prefs;
-  GtkWidget *parent = name_to_widget (s, "settings_vbox");
   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
-  int list_elt = selected_list_element (s);
-  int hack_number = (list_elt >= 0 && list_elt < s->list_count
-                     ? s->list_elt_to_hack_number[list_elt]
-                     : -1);
-  screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
   char *doc_string = 0;
 
   /* #### not in Gtk 1.2
@@ -2491,14 +2858,24 @@ populate_popup_window (state *s)
       s->cdata = 0;
     }
 
-  if (hack)
-    {
-      GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
-      const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
-      s->cdata = load_configurator (cmd_line, s->debug_p);
-      if (s->cdata && s->cdata->widget)
-        gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget, TRUE, TRUE, 0);
-    }
+  {
+    saver_preferences *p = &s->prefs;
+    int list_elt = selected_list_element (s);
+    int hack_number = (list_elt >= 0 && list_elt < s->list_count
+                       ? s->list_elt_to_hack_number[list_elt]
+                       : -1);
+    screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
+    if (hack)
+      {
+        GtkWidget *parent = name_to_widget (s, "settings_vbox");
+        GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+        const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
+        s->cdata = load_configurator (cmd_line, s->debug_p);
+        if (s->cdata && s->cdata->widget)
+          gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
+                              TRUE, TRUE, 0);
+      }
+  }
 
   doc_string = (s->cdata
                 ? s->cdata->description
@@ -2516,23 +2893,78 @@ populate_popup_window (state *s)
 static void
 sensitize_demo_widgets (state *s, Bool sensitive_p)
 {
-  const char *names1[] = { "demo", "settings" };
-  const char *names2[] = { "cmd_label", "cmd_text", "manual",
-                           "visual", "visual_combo" };
+  const char *names[] = { "demo", "settings",
+                          "cmd_label", "cmd_text", "manual",
+                          "visual", "visual_combo" };
   int i;
-  for (i = 0; i < countof(names1); i++)
+  for (i = 0; i < countof(names); i++)
     {
-      GtkWidget *w = name_to_widget (s, names1[i]);
+      GtkWidget *w = name_to_widget (s, names[i]);
       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
     }
-  for (i = 0; i < countof(names2); i++)
+}
+
+
+static void
+sensitize_menu_items (state *s, Bool force_p)
+{
+  static Bool running_p = False;
+  static time_t last_checked = 0;
+  time_t now = time ((time_t *) 0);
+  const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
+                          /* "demo" */ };
+  int i;
+
+  if (force_p || now > last_checked + 10)   /* check every 10 seconds */
+    {
+      running_p = xscreensaver_running_p (s);
+      last_checked = time ((time_t *) 0);
+    }
+
+  for (i = 0; i < countof(names); i++)
     {
-      GtkWidget *w = name_to_widget (s, names2[i]);
-      gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
+      GtkWidget *w = name_to_widget (s, names[i]);
+      gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
     }
 }
 
 
+/* When the File menu is de-posted after a "Restart Daemon" command,
+   the window underneath doesn't repaint for some reason.  I guess this
+   is a bug in exposure handling in GTK or GDK.  This works around it.
+ */
+static void
+force_dialog_repaint (state *s)
+{
+#if 1
+  /* Tell GDK to invalidate and repaint the whole window.
+   */
+  GdkWindow *w = s->toplevel_widget->window;
+  GdkRegion *region = gdk_region_new ();
+  GdkRectangle rect;
+  rect.x = rect.y = 0;
+  rect.width = rect.height = 32767;
+  gdk_region_union_with_rect (region, &rect);
+  gdk_window_invalidate_region (w, region, True);
+  gdk_region_destroy (region);
+  gdk_window_process_updates (w, True);
+#else
+  /* Force the server to send an exposure event by creating and then
+     destroying a window as a child of the top level shell.
+   */
+  Display *dpy = GDK_DISPLAY();
+  Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
+  Window w;
+  XWindowAttributes xgwa;
+  XGetWindowAttributes (dpy, parent, &xgwa);
+  w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
+  XMapRaised (dpy, w);
+  XDestroyWindow (dpy, w);
+  XSync (dpy, False);
+#endif
+}
+
+
 /* Even though we've given these text fields a maximum number of characters,
    their default size is still about 30 characters wide -- so measure out
    a string in their font, and resize them to just fit that.
@@ -2714,6 +3146,7 @@ map_prev_button_cb (GtkWidget *w, gpointer user_data)
 #endif /* !HAVE_GTK2 */
 
 \f
+#ifndef HAVE_GTK2
 /* Work around a Gtk bug that causes label widgets to wrap text too early.
  */
 
@@ -2735,7 +3168,6 @@ you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
   gtk_widget_size_request (label, &req);
 }
 
-
 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
  */
 static void
@@ -2757,11 +3189,13 @@ eschew_gtk_lossage (GtkLabel *label)
 
   gtk_widget_queue_resize (GTK_WIDGET (label));
 }
+#endif /* !HAVE_GTK2 */
 
 
 static void
 populate_demo_window (state *s, int list_elt)
 {
+  Display *dpy = GDK_DISPLAY();
   saver_preferences *p = &s->prefs;
   screenhack *hack;
   char *pretty_name;
@@ -2793,7 +3227,7 @@ populate_demo_window (state *s, int list_elt)
       pretty_name = (hack
                      ? (hack->name
                         ? strdup (hack->name)
-                        : make_hack_name (hack->command))
+                        : make_hack_name (dpy, hack->command))
                      : 0);
 
       if (hack)
@@ -2805,15 +3239,15 @@ populate_demo_window (state *s, int list_elt)
   if (!pretty_name)
     pretty_name = strdup (_("Preview"));
 
-  gtk_frame_set_label (frame1, pretty_name);
-  gtk_frame_set_label (frame2, pretty_name);
+  gtk_frame_set_label (frame1, _(pretty_name));
+  gtk_frame_set_label (frame2, _(pretty_name));
 
   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
   gtk_entry_set_position (cmd, 0);
 
   {
     char title[255];
-    sprintf (title, "%s: %.100s Settings",
+    sprintf (title, _("%s: %.100s Settings"),
              progclass, (pretty_name ? pretty_name : "???"));
     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
   }
@@ -2874,6 +3308,7 @@ sort_hack_cmp (const void *a, const void *b)
 static void
 initialize_sort_map (state *s)
 {
+  Display *dpy = GDK_DISPLAY();
   saver_preferences *p = &s->prefs;
   int i, j;
 
@@ -2887,13 +3322,16 @@ initialize_sort_map (state *s)
     calloc (sizeof(int), p->screenhacks_count + 1);
   s->hacks_available_p = (Bool *)
     calloc (sizeof(Bool), p->screenhacks_count + 1);
+  s->total_available = 0;
 
   /* Check which hacks actually exist on $PATH
    */
   for (i = 0; i < p->screenhacks_count; i++)
     {
       screenhack *hack = p->screenhacks[i];
-      s->hacks_available_p[i] = on_path_p (hack->command);
+      int on = on_path_p (hack->command) ? 1 : 0;
+      s->hacks_available_p[i] = on;
+      s->total_available += on;
     }
 
   /* Initialize list->hack table to unsorted mapping, omitting nonexistent
@@ -2921,7 +3359,7 @@ initialize_sort_map (state *s)
       screenhack *hack = p->screenhacks[i];
       char *name = (hack->name && *hack->name
                     ? strdup (hack->name)
-                    : make_hack_name (hack->command));
+                    : make_hack_name (dpy, hack->command));
       char *str;
       for (str = name; *str; str++)
         *str = tolower(*str);
@@ -2944,13 +3382,18 @@ initialize_sort_map (state *s)
 
   /* Build inverse table */
   for (i = 0; i < p->screenhacks_count; i++)
-    s->hack_number_to_list_elt[s->list_elt_to_hack_number[i]] = i;
+    {
+      int n = s->list_elt_to_hack_number[i];
+      if (n != -1)
+        s->hack_number_to_list_elt[n] = i;
+    }
 }
 
 
 static int
 maybe_reload_init_file (state *s)
 {
+  Display *dpy = GDK_DISPLAY();
   saver_preferences *p = &s->prefs;
   int status = 0;
 
@@ -2974,7 +3417,7 @@ maybe_reload_init_file (state *s)
       warning_dialog (s->toplevel_widget, b, False, 100);
       free (b);
 
-      load_init_file (p);
+      load_init_file (dpy, p);
       initialize_sort_map (s);
 
       list_elt = selected_list_element (s);
@@ -3041,11 +3484,14 @@ clear_preview_window (state *s)
     Bool available_p = (hack_number >= 0
                         ? s->hacks_available_p [hack_number]
                         : True);
+    Bool nothing_p = (s->total_available < 5);
+
 #ifdef HAVE_GTK2
     GtkWidget *notebook = name_to_widget (s, "preview_notebook");
     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
                           (s->running_preview_error_p
-                            ? (available_p ? 1 : 2)
+                            ? (available_p ? 1 :
+                               nothing_p ? 3 : 2)
                             : 0));
 #else /* !HAVE_GTK2 */
     if (s->running_preview_error_p)
@@ -3079,6 +3525,32 @@ clear_preview_window (state *s)
 }
 
 
+static void
+reset_preview_window (state *s)
+{
+  /* On some systems (most recently, MacOS X) OpenGL programs get confused
+     when you kill one and re-start another on the same window.  So maybe
+     it's best to just always destroy and recreate the preview window
+     when changing hacks, instead of always trying to reuse the same one?
+   */
+  GtkWidget *pr = name_to_widget (s, "preview");
+  if (GTK_WIDGET_REALIZED (pr))
+    {
+      Window oid = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0);
+      Window id;
+      gtk_widget_hide (pr);
+      gtk_widget_unrealize (pr);
+      gtk_widget_realize (pr);
+      gtk_widget_show (pr);
+      id = (pr->window ? GDK_WINDOW_XWINDOW (pr->window) : 0);
+      if (s->debug_p)
+        fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
+                 (unsigned int) oid,
+                 (unsigned int) id);
+    }
+}
+
+
 static void
 fix_preview_visual (state *s)
 {
@@ -3238,7 +3710,14 @@ get_best_gl_visual (state *s)
             sprintf (buf, "%s: running %s", blurb(), av[0]);
             perror (buf);
           }
-        exit (1);                               /* exits fork */
+
+        /* Note that one must use _exit() instead of exit() in procs forked
+           off of Gtk programs -- Gtk installs an atexit handler that has a
+           copy of the X connection (which we've already closed, for safety.)
+           If one uses exit() instead of _exit(), then one sometimes gets a
+           spurious "Gdk-ERROR: Fatal IO error on X server" error message.
+        */
+        _exit (1);                              /* exits fork */
         break;
       }
     default:
@@ -3253,7 +3732,8 @@ get_best_gl_visual (state *s)
         close (out);  /* don't need this one */
 
         *buf = 0;
-        fgets (buf, sizeof(buf)-1, f);
+        if (!fgets (buf, sizeof(buf)-1, f))
+          *buf = 0;
         fclose (f);
 
         /* Wait for the child to die. */
@@ -3286,7 +3766,7 @@ get_best_gl_visual (state *s)
 
 
 static void
-kill_preview_subproc (state *s)
+kill_preview_subproc (state *s, Bool reset_p)
 {
   s->running_preview_error_p = False;
 
@@ -3321,9 +3801,13 @@ kill_preview_subproc (state *s)
               perror (buf);
             }
         }
-      else if (s->debug_p)
-        fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
-                 (unsigned long) s->running_preview_pid, ss);
+      else {
+       int endstatus;
+       waitpid(s->running_preview_pid, &endstatus, 0);
+       if (s->debug_p)
+         fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
+                  (unsigned long) s->running_preview_pid, ss);
+      }
 
       free (ss);
       s->running_preview_pid = 0;
@@ -3332,6 +3816,12 @@ kill_preview_subproc (state *s)
     }
 
   reap_zombies (s);
+
+  if (reset_p)
+    {
+      reset_preview_window (s);
+      clear_preview_window (s);
+    }
 }
 
 
@@ -3348,13 +3838,17 @@ launch_preview_subproc (state *s)
   const char *cmd = s->desired_preview_cmd;
 
   GtkWidget *pr = name_to_widget (s, "preview");
-  GdkWindow *window = pr->window;
+  GdkWindow *window;
+
+  reset_preview_window (s);
+
+  window = pr->window;
 
   s->running_preview_error_p = False;
 
   if (s->preview_suppressed_p)
     {
-      kill_preview_subproc (s);
+      kill_preview_subproc (s, False);
       goto DONE;
     }
 
@@ -3374,7 +3868,7 @@ launch_preview_subproc (state *s)
                (unsigned int) id);
     }
 
-  kill_preview_subproc (s);
+  kill_preview_subproc (s, False);
   if (! new_cmd)
     {
       s->running_preview_error_p = True;
@@ -3397,6 +3891,8 @@ launch_preview_subproc (state *s)
       {
         close (ConnectionNumber (GDK_DISPLAY()));
 
+        hack_subproc_environment (id, s->debug_p);
+
         usleep (250000);  /* pause for 1/4th second before launching, to give
                              the previous program time to die and flush its X
                              buffer, so we don't get leftover turds on the
@@ -3404,8 +3900,15 @@ launch_preview_subproc (state *s)
 
         exec_command (p->shell, new_cmd, p->nice_inferior);
         /* Don't bother printing an error message when we are unable to
-           exec subprocesses; we handle that by polling the pid later. */
-        exit (1);  /* exits child fork */
+           exec subprocesses; we handle that by polling the pid later.
+
+           Note that one must use _exit() instead of exit() in procs forked
+           off of Gtk programs -- Gtk installs an atexit handler that has a
+           copy of the X connection (which we've already closed, for safety.)
+           If one uses exit() instead of _exit(), then one sometimes gets a
+           spurious "Gdk-ERROR: Fatal IO error on X server" error message.
+        */
+        _exit (1);  /* exits child fork */
         break;
 
       default:
@@ -3480,6 +3983,29 @@ hack_environment (state *s)
 }
 
 
+static void
+hack_subproc_environment (Window preview_window_id, Bool debug_p)
+{
+  /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
+     necessary yet, but it will make programs work if we had invoked
+     them with "-root" and not with "-window-id" -- which, of course,
+     doesn't happen.
+   */
+  char *nssw = (char *) malloc (40);
+  sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
+
+  /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
+     any more, right?  It's not Posix, but everyone seems to have it. */
+  if (putenv (nssw))
+    abort ();
+
+  if (debug_p)
+    fprintf (stderr, "%s: %s\n", blurb(), nssw);
+
+  /* do not free(nssw) -- see above */
+}
+
+
 /* Called from a timer:
    Launches the currently-chosen subprocess, if it's not already running.
    If there's a different process running, kills it.
@@ -3489,7 +4015,7 @@ update_subproc_timer (gpointer data)
 {
   state *s = (state *) data;
   if (! s->desired_preview_cmd)
-    kill_preview_subproc (s);
+    kill_preview_subproc (s, True);
   else if (!s->running_preview_cmd ||
            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
     launch_preview_subproc (s);
@@ -3612,7 +4138,7 @@ screen_blanked_p (void)
   Atom type;
   int format;
   unsigned long nitems, bytesafter;
-  CARD32 *data = 0;
+  unsigned char *dataP = 0;
   Display *dpy = GDK_DISPLAY();
   Bool blanked_p = False;
 
@@ -3620,14 +4146,17 @@ screen_blanked_p (void)
                           XA_SCREENSAVER_STATUS,
                           0, 3, False, XA_INTEGER,
                           &type, &format, &nitems, &bytesafter,
-                          (unsigned char **) &data)
+                          &dataP)
       == Success
       && type == XA_INTEGER
       && nitems >= 3
-      && data)
-    blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
+      && dataP)
+    {
+      Atom *data = (Atom *) dataP;
+      blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
+    }
 
-  if (data) free (data);
+  if (dataP) XFree (dataP);
 
   return blanked_p;
 }
@@ -3645,12 +4174,35 @@ check_blanked_timer (gpointer data)
     {
       if (s->debug_p)
         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
-      kill_preview_subproc (s);
+      kill_preview_subproc (s, True);
     }
 
   return True;  /* re-execute timer */
 }
 
+
+/* How many screens are there (including Xinerama.)
+ */
+static int
+screen_count (Display *dpy)
+{
+  int nscreens = ScreenCount(dpy);
+# ifdef HAVE_XINERAMA
+  if (nscreens <= 1)
+    {
+      int event_number, error_number;
+      if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
+          XineramaIsActive (dpy))
+        {
+          XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
+          if (xsi) XFree (xsi);
+        }
+    }
+# endif /* HAVE_XINERAMA */
+
+  return nscreens;
+}
+
 \f
 /* Setting window manager icon
  */
@@ -3826,7 +4378,7 @@ demo_ehandler (Display *dpy, XErrorEvent *error)
   state *s = global_state_kludge;  /* I hate C so much... */
   fprintf (stderr, "\nX error in %s:\n", blurb());
   XmuPrintDefaultErrorMessage (dpy, error, stderr);
-  kill_preview_subproc (s);
+  kill_preview_subproc (s, False);
   exit (-1);
   return 0;
 }
@@ -3863,6 +4415,13 @@ g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
 }
 
 
+#ifdef __GNUC__
+ __extension__     /* shut up about "string length is greater than the length
+                      ISO C89 compilers are required to support" when including
+                      the .ad file... */
+#endif
+
+STFU
 static char *defaults[] = {
 #include "XScreenSaver_ad.h"
  0
@@ -3887,9 +4446,13 @@ map_popup_window_cb (GtkWidget *w, gpointer user_data)
 {
   state *s = (state *) user_data;
   Boolean oi = s->initializing_p;
+#ifndef HAVE_GTK2
   GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
+#endif
   s->initializing_p = True;
+#ifndef HAVE_GTK2
   eschew_gtk_lossage (label);
+#endif
   s->initializing_p = oi;
 }
 
@@ -3979,6 +4542,7 @@ main (int argc, char **argv)
   Widget toplevel_shell;
   char *real_progname = argv[0];
   char *window_title;
+  char *geom = 0;
   Bool crapplet_p = False;
   char *str;
 
@@ -4073,6 +4637,21 @@ main (int argc, char **argv)
           argc--;
           i--;
         }
+      else if (argv[i] &&
+               argc > i+1 &&
+               *argv[i+1] &&
+               (!strcmp(argv[i], "-geometry") ||
+                !strcmp(argv[i], "-geom") ||
+                !strcmp(argv[i], "-geo") ||
+                !strcmp(argv[i], "-g")))
+        {
+          int j;
+          geom = argv[i+1];
+          for (j = i; j < argc; j++)  /* remove them from the list */
+            argv[j] = argv[j+2];
+          argc -= 2;
+          i -= 2;
+        }
       else if (argv[i] &&
                argc > i+1 &&
                *argv[i+1] &&
@@ -4230,7 +4809,8 @@ main (int argc, char **argv)
         ;
       else
        {
-         fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, argv[i]);
+         fprintf (stderr, _("%s: unknown option: %s\n"), real_progname,
+                   argv[i]);
           fprintf (stderr, "%s: %s\n", real_progname, usage);
           exit (1);
        }
@@ -4242,10 +4822,11 @@ main (int argc, char **argv)
      was in argv[0].
    */
   p->db = db;
+  s->nscreens = screen_count (dpy);
 
   hack_environment (s);  /* must be before initialize_sort_map() */
 
-  load_init_file (p);
+  load_init_file (dpy, p);
   initialize_sort_map (s);
 
   /* Now that Xt has been initialized, and the resources have been read,
@@ -4367,10 +4948,26 @@ main (int argc, char **argv)
     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
-    for (; kids; kids = kids->next)
-      gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
-                          GTK_SIGNAL_FUNC (mode_menu_item_cb),
-                          (gpointer) s);
+    int i;
+    for (i = 0; kids; kids = kids->next, i++)
+      {
+        gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
+                            GTK_SIGNAL_FUNC (mode_menu_item_cb),
+                            (gpointer) s);
+
+        /* The "random-same" mode menu item does not appear unless
+           there are multple screens.
+         */
+        if (s->nscreens <= 1 &&
+            mode_menu_order[i] == RANDOM_HACKS_SAME)
+          gtk_widget_hide (GTK_WIDGET (kids->data));
+      }
+
+    if (s->nscreens <= 1)   /* recompute option-menu size */
+      {
+        gtk_widget_unrealize (GTK_WIDGET (menu));
+        gtk_widget_realize (GTK_WIDGET (menu));
+      }
   }
 
 
@@ -4406,7 +5003,7 @@ main (int argc, char **argv)
       gtk_widget_ref (outer_vbox);
       gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
                             outer_vbox);
-      GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
+      STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
       gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
 
       /* Find the window above us, and set the title and close handler. */
@@ -4441,6 +5038,11 @@ main (int argc, char **argv)
   free (window_title);
   window_title = 0;
 
+#ifdef HAVE_GTK2
+  /* After picking the default size, allow -geometry to override it. */
+  if (geom)
+    gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom);
+#endif
 
   gtk_widget_show (s->toplevel_widget);
   init_icon (GTK_WIDGET (s->toplevel_widget)->window);  /* after `show' */
@@ -4461,7 +5063,8 @@ main (int argc, char **argv)
 
 
   /* Issue any warnings about the running xscreensaver daemon. */
-  the_network_is_not_the_computer (s);
+  if (! s->debug_p)
+    the_network_is_not_the_computer (s);
 
 
   /* Run the Gtk event loop, and not the Xt event loop.  This means that
@@ -4479,17 +5082,18 @@ main (int argc, char **argv)
   gtk_timeout_add (500, delayed_scroll_kludge, s);
 
 
-#if 0
+#if 1
   /* Load every configurator in turn, to scan them for errors all at once. */
-  {
-    int i;
-    for (i = 0; i < p->screenhacks_count; i++)
-      {
-        screenhack *hack = p->screenhacks[i];
-        conf_data *d = load_configurator (hack->command, False);
-        if (d) free_conf_data (d);
-      }
-  }
+  if (s->debug_p)
+    {
+      int i;
+      for (i = 0; i < p->screenhacks_count; i++)
+        {
+          screenhack *hack = p->screenhacks[i];
+          conf_data *d = load_configurator (hack->command, s->debug_p);
+          if (d) free_conf_data (d);
+        }
+    }
 #endif
 
 
@@ -4500,7 +5104,7 @@ main (int argc, char **argv)
 # endif /* HAVE_CRAPPLET */
     gtk_main ();
 
-  kill_preview_subproc (s);
+  kill_preview_subproc (s, False);
   exit (0);
 }