ftp://ftp.linux.ncsu.edu/mirror/ftp.redhat.com/pub/redhat/linux/enterprise/4/en/os...
[xscreensaver] / driver / demo-Gtk.c
index 26c69db6791e52d827c1d40065c7ea31076d177a..298860a7e130a07dfb0cd4f4c0a9cd5435e8b27c 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-2004 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
 
 #ifdef HAVE_GTK /* whole file */
 
+#include <xscreensaver-intl.h>
+
 #include <stdlib.h>
 
 #ifdef HAVE_UNISTD_H
 # 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 */
+
 #ifndef VMS
 # include <pwd.h>              /* for getpwuid() */
 #else /* VMS */
 
 #include <gdk/gdkx.h>
 
+#ifdef HAVE_GTK2
+#include <glade/glade-xml.h>
+#endif /* HAVE_GTK2 */
+
+#if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
+# define GLADE_DIR DEFAULT_ICONDIR
+#endif
+#if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR)
+# 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 "resources.h"         /* for parse_time() */
 #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"
 #include <string.h>
 #include <ctype.h>
 
+#ifdef HAVE_GTK2
+enum {
+  COL_ENABLED,
+  COL_NAME,
+  COL_LAST
+};
+#endif /* HAVE_GTK2 */
 
 /* from exec.c */
 extern void exec_command (const char *shell, const char *command, int nice);
 
+static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
+
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
@@ -118,6 +162,10 @@ typedef struct {
   GtkWidget *popup_widget;     /* the "Settings" dialog */
   conf_data *cdata;            /* private data for per-hack configuration */
 
+#ifdef HAVE_GTK2
+  GladeXML *glade_ui;           /* Glade UI file */
+#endif /* HAVE_GTK2 */
+
   Bool debug_p;                        /* whether to print diagnostics */
   Bool initializing_p;         /* flag for breaking recursion loops */
   Bool saving_p;               /* flag for breaking recursion loops */
@@ -134,6 +182,10 @@ 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 list_count;              /* how many items are in the list: this may be
+                                   less than p->screenhacks_count, if some are
+                                   suppressed. */
 
   int _selected_list_element;  /* don't use this: call
                                    selected_list_element() instead */
@@ -164,7 +216,7 @@ static int maybe_reload_init_file (state *);
 static void await_xscreensaver (state *);
 
 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 *);
 
 
@@ -197,11 +249,48 @@ name_to_widget (state *s, const char *name)
   if (!name) abort();
   if (!*name) abort();
 
+#ifdef HAVE_GTK2
+  if (!s->glade_ui)
+    {
+      /* First try to load the Glade file from the current directory;
+         if there isn't one there, check the installed directory.
+       */
+# define GLADE_FILE_NAME "xscreensaver-demo.glade2"
+      const char * const files[] = { GLADE_FILE_NAME,
+                                     GLADE_DIR "/" GLADE_FILE_NAME };
+      int i;
+      for (i = 0; i < countof (files); i++)
+        {
+          struct stat st;
+          if (!stat (files[i], &st))
+            {
+              s->glade_ui = glade_xml_new (files[i], NULL, NULL);
+              break;
+            }
+        }
+      if (!s->glade_ui)
+       {
+         fprintf (stderr,
+                   "%s: could not load \"" GLADE_FILE_NAME "\"\n"
+                   "\tfrom " GLADE_DIR "/ or current directory.\n",
+                   blurb());
+          exit (-1);
+       }
+# undef GLADE_FILE_NAME
+
+      glade_xml_signal_autoconnect (s->glade_ui);
+    }
+
+  w = glade_xml_get_widget (s->glade_ui, name);
+
+#else /* !HAVE_GTK2 */
+
   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget),
                                          name);
   if (w) return w;
   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget),
                                          name);
+#endif /* HAVE_GTK2 */
   if (w) return w;
 
   fprintf (stderr, "%s: no widget \"%s\"\n", blurb(), name);
@@ -215,6 +304,24 @@ name_to_widget (state *s, const char *name)
 static void
 ensure_selected_item_visible (GtkWidget *widget)
 {
+#ifdef HAVE_GTK2
+  GtkTreePath *path;
+  GtkTreeSelection *selection;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+       path = gtk_tree_path_new_first ();
+  else
+       path = gtk_tree_model_get_path (model, &iter);
+  
+  gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE);
+
+  gtk_tree_path_free (path);
+
+#else /* !HAVE_GTK2 */
+
   GtkScrolledWindow *scroller = 0;
   GtkViewport *vp = 0;
   GtkList *list_widget = 0;
@@ -299,6 +406,7 @@ ensure_selected_item_visible (GtkWidget *widget)
 
       gtk_adjustment_set_value (adj, target);
     }
+#endif /* !HAVE_GTK2 */
 }
 
 static void
@@ -335,7 +443,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;
@@ -352,7 +461,11 @@ warning_dialog (GtkWidget *parent, const char *message,
 
       {
         label = gtk_label_new (head);
+#ifdef HAVE_GTK2
+       gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+#endif /* HAVE_GTK2 */
 
+#ifndef HAVE_GTK2
         if (i == 1)
           {
             GTK_WIDGET (label)->style =
@@ -363,7 +476,7 @@ warning_dialog (GtkWidget *parent, const char *message,
             gtk_widget_set_style (GTK_WIDGET (label),
                                   GTK_WIDGET (label)->style);
           }
-
+#endif /* !HAVE_GTK2 */
         if (center <= 0)
           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
@@ -388,6 +501,18 @@ warning_dialog (GtkWidget *parent, const char *message,
   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
                       label, TRUE, TRUE, 0);
 
+#ifdef HAVE_GTK2
+  if (restart_button_p)
+    {
+      cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+      gtk_container_add (GTK_CONTAINER (label), cancel);
+    }
+
+  ok = gtk_button_new_from_stock (GTK_STOCK_OK);
+  gtk_container_add (GTK_CONTAINER (label), ok);
+
+#else /* !HAVE_GTK2 */
+
   ok = gtk_button_new_with_label ("OK");
   gtk_container_add (GTK_CONTAINER (label), ok);
 
@@ -397,15 +522,22 @@ warning_dialog (GtkWidget *parent, const char *message,
       gtk_container_add (GTK_CONTAINER (label), cancel);
     }
 
+#endif /* !HAVE_GTK2 */
+
   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);
+  STFU GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
   gtk_widget_show (ok);
+  gtk_widget_grab_focus (ok);
+
   if (cancel)
-    gtk_widget_show (cancel);
+    {
+      STFU GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); 
+      gtk_widget_show (cancel);
+    }
   gtk_widget_show (label);
   gtk_widget_show (dialog);
-/*  gtk_window_set_default (GTK_WINDOW (dialog), ok);*/
 
   if (restart_button_p)
     {
@@ -426,8 +558,12 @@ warning_dialog (GtkWidget *parent, const char *message,
   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
                                 GTK_WIDGET (parent)->window);
 
+#ifdef HAVE_GTK2
+  gtk_window_present (GTK_WINDOW (dialog));
+#else  /* !HAVE_GTK2 */
   gdk_window_show (GTK_WIDGET (dialog)->window);
   gdk_window_raise (GTK_WIDGET (dialog)->window);
+#endif /* !HAVE_GTK2 */
 
   free (msg);
 }
@@ -441,6 +577,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];
@@ -484,16 +625,17 @@ 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 ();
 }
 
-static void
+static gboolean
 wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
 {
   state *s = (state *) data;
   flush_dialog_changes_and_save (s);
   gtk_main_quit ();
+  return TRUE;
 }
 
 
@@ -504,13 +646,22 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
   char *vers = strdup (screensaver_id + 4);
   char *s;
   char copy[1024];
-  char *desc = "For updates, check http://www.jwz.org/xscreensaver/";
+  char *desc = _("For updates, check http://www.jwz.org/xscreensaver/");
 
   s = strchr (vers, ',');
   *s = 0;
   s += 2;
 
-  sprintf(copy, "Copyright \251 1991-2002 %s", s);
+  /* Ole Laursen <olau@hardworking.dk> says "don't use _() here because
+     non-ASCII characters aren't allowed in localizable string keys."
+     (I don't want to just use (c) instead of © because that doesn't
+     look as good in the plain-old default Latin1 "C" locale.)
+   */
+#ifdef HAVE_GTK2
+  sprintf(copy, ("Copyright \xC2\xA9 1991-2004 %s"), s);
+#else  /* !HAVE_GTK2 */
+  sprintf(copy, ("Copyright \251 1991-2004 %s"), s);
+#endif /* !HAVE_GTK2 */
 
   sprintf (msg, "%s\n\n%s", copy, desc);
 
@@ -560,27 +711,35 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
     gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
     gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
 
+#ifndef HAVE_GTK2
     GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
     GTK_WIDGET (label1)->style->font =
       gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
     gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
+#endif /* HAVE_GTK2 */
 
     label2 = gtk_label_new (msg);
     gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
     gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
     gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
 
+#ifndef HAVE_GTK2
     GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
     GTK_WIDGET (label2)->style->font =
       gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
     gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
+#endif /* HAVE_GTK2 */
 
     hb = gtk_hbutton_box_new ();
 
     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
                         hb, TRUE, TRUE, 0);
 
-    ok = gtk_button_new_with_label ("OK");
+#ifdef HAVE_GTK2
+    ok = gtk_button_new_from_stock (GTK_STOCK_OK);
+#else /* !HAVE_GTK2 */
+    ok = gtk_button_new_with_label (_("OK"));
+#endif /* !HAVE_GTK2 */
     gtk_container_add (GTK_CONTAINER (hb), ok);
 
     gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
@@ -617,8 +776,8 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
   if (!p->help_url || !*p->help_url)
     {
       warning_dialog (s->toplevel_widget,
-                      "Error:\n\n"
-                      "No Help URL has been specified.\n", False, 100);
+                      _("Error:\n\n"
+                       "No Help URL has been specified.\n"), False, 100);
       return;
     }
 
@@ -703,13 +862,19 @@ await_xscreensaver (state *s)
       Bool root_p = (geteuid () == 0);
       
       strcpy (buf, 
-              "Error:\n\n"
-              "The xscreensaver daemon did not start up properly.\n"
-              "\n");
+              _("Error:\n\n"
+               "The xscreensaver daemon did not start up properly.\n"
+               "\n"));
 
       if (root_p)
+
+# 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,
-            "You are running as root.  This usually means that xscreensaver\n"
+         _("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"
             "\n"
@@ -723,9 +888,9 @@ await_xscreensaver (state *s)
             "manual and FAQ for more information.\n"
             "\n"
             "You shouldn't run X as root. Instead, you should log in as a\n"
-            "normal user, and `su' as necessary.");
+            "normal user, and `su' as necessary."));
       else
-        strcat (buf, "Please check your $PATH and permissions.");
+        strcat (buf, _("Please check your $PATH and permissions."));
 
       warning_dialog (s->toplevel_widget, buf, False, 1);
     }
@@ -760,12 +925,12 @@ demo_write_init_file (state *s, saver_preferences *p)
       const char *f = init_file_name();
       if (!f || !*f)
         warning_dialog (s->toplevel_widget,
-                        "Error:\n\nCouldn't determine init file name!\n",
+                        _("Error:\n\nCouldn't determine init file name!\n"),
                         False, 100);
       else
         {
           char *b = (char *) malloc (strlen(f) + 1024);
-          sprintf (b, "Error:\n\nCouldn't write %s\n", f);
+          sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
           warning_dialog (s->toplevel_widget, b, False, 100);
           free (b);
         }
@@ -790,7 +955,7 @@ manual_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
   saver_preferences *p = &s->prefs;
-  GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
+  GtkWidget *list_widget = name_to_widget (s, "list");
   int list_elt = selected_list_element (s);
   int hack_number;
   char *name, *name2, *cmd, *str;
@@ -798,7 +963,7 @@ manual_cb (GtkButton *button, gpointer user_data)
   hack_number = s->list_elt_to_hack_number[list_elt];
 
   flush_dialog_changes_and_save (s);
-  ensure_selected_item_visible (GTK_WIDGET (list_widget));
+  ensure_selected_item_visible (list_widget);
 
   name = strdup (p->screenhacks[hack_number]->command);
   name2 = name;
@@ -824,7 +989,7 @@ manual_cb (GtkButton *button, gpointer user_data)
   else
     {
       warning_dialog (GTK_WIDGET (button),
-                      "Error:\n\nno `manualCommand' resource set.",
+                      _("Error:\n\nno `manualCommand' resource set."),
                       False, 100);
     }
 
@@ -833,13 +998,26 @@ manual_cb (GtkButton *button, gpointer user_data)
 
 
 static void
-force_list_select_item (state *s, GtkList *list, int list_elt, Bool scroll_p)
+force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
 {
   GtkWidget *parent = name_to_widget (s, "scroller");
   Bool was = GTK_WIDGET_IS_SENSITIVE (parent);
+#ifdef HAVE_GTK2
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  GtkTreeSelection *selection;
+#endif /* HAVE_GTK2 */
 
   if (!was) gtk_widget_set_sensitive (parent, True);
+#ifdef HAVE_GTK2
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+  STFU 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);
+#else  /* !HAVE_GTK2 */
   gtk_list_select_item (GTK_LIST (list), list_elt);
+#endif /* !HAVE_GTK2 */
   if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
   if (!was) gtk_widget_set_sensitive (parent, False);
 }
@@ -849,11 +1027,10 @@ void
 run_next_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
-  saver_preferences *p = &s->prefs;
+  /* saver_preferences *p = &s->prefs; */
   Bool ops = s->preview_suppressed_p;
 
-  GtkList *list_widget =
-    GTK_LIST (name_to_widget (s, "list"));
+  GtkWidget *list_widget = name_to_widget (s, "list");
   int list_elt = selected_list_element (s);
 
   if (list_elt < 0)
@@ -861,13 +1038,13 @@ run_next_cb (GtkButton *button, gpointer user_data)
   else
     list_elt++;
 
-  if (list_elt >= p->screenhacks_count)
+  if (list_elt >= s->list_count)
     list_elt = 0;
 
   s->preview_suppressed_p = True;
 
   flush_dialog_changes_and_save (s);
-  force_list_select_item (s, GTK_LIST (list_widget), list_elt, True);
+  force_list_select_item (s, list_widget, list_elt, True);
   populate_demo_window (s, list_elt);
   run_hack (s, list_elt, False);
 
@@ -879,25 +1056,24 @@ void
 run_prev_cb (GtkButton *button, gpointer user_data)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
-  saver_preferences *p = &s->prefs;
+  /* saver_preferences *p = &s->prefs; */
   Bool ops = s->preview_suppressed_p;
 
-  GtkList *list_widget =
-    GTK_LIST (name_to_widget (s, "list"));
+  GtkWidget *list_widget = name_to_widget (s, "list");
   int list_elt = selected_list_element (s);
 
   if (list_elt < 0)
-    list_elt = p->screenhacks_count - 1;
+    list_elt = s->list_count - 1;
   else
     list_elt--;
 
   if (list_elt < 0)
-    list_elt = p->screenhacks_count - 1;
+    list_elt = s->list_count - 1;
 
   s->preview_suppressed_p = True;
 
   flush_dialog_changes_and_save (s);
-  force_list_select_item (s, GTK_LIST (list_widget), list_elt, True);
+  force_list_select_item (s, list_widget, list_elt, True);
   populate_demo_window (s, list_elt);
   run_hack (s, list_elt, False);
 
@@ -920,7 +1096,7 @@ flush_changes (state *s,
   Bool changed = False;
   screenhack *hack;
   int hack_number;
-  if (list_elt < 0 || list_elt >= p->screenhacks_count)
+  if (list_elt < 0 || list_elt >= s->list_count)
     abort();
 
   hack_number = s->list_elt_to_hack_number[list_elt];
@@ -983,7 +1159,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;
@@ -994,8 +1170,8 @@ hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
        {
          char b[255];
          sprintf (b,
-                  "Error:\n\n"
-                  "Unparsable time format: \"%s\"\n",
+                  _("Error:\n\n"
+                    "Unparsable time format: \"%s\"\n"),
                   line);
          warning_dialog (s->toplevel_widget, b, False, 100);
        }
@@ -1024,8 +1200,8 @@ normalize_directory (const char *path)
 {
   int L;
   char *p2, *s;
-  if (!path) return 0;
-  L = strlen (path);;
+  if (!path || !*path) return 0;
+  L = strlen (path);
   p2 = (char *) malloc (L + 2);
   strcpy (p2, path);
   if (p2[L-1] == '/')  /* remove trailing slash */
@@ -1067,6 +1243,39 @@ normalize_directory (const char *path)
 }
 
 
+#ifdef HAVE_GTK2
+
+typedef struct {
+  state *s;
+  int i;
+  Bool *changed;
+} FlushForeachClosure;
+
+static gboolean
+flush_checkbox  (GtkTreeModel *model,
+                GtkTreePath *path,
+                GtkTreeIter *iter,
+                gpointer data)
+{
+  FlushForeachClosure *closure = data;
+  gboolean checked;
+
+  gtk_tree_model_get (model, iter,
+                     COL_ENABLED, &checked,
+                     -1);
+
+  if (flush_changes (closure->s, closure->i,
+                    checked, 0, 0))
+    *closure->changed = True;
+  
+  closure->i++;
+
+  /* don't remove row */
+  return FALSE;
+}
+
+#endif /* HAVE_GTK2 */
+
 /* Flush out any changes made in the main dialog window (where changes
    take place immediately: clicking on a checkbox causes the init file
    to be written right away.)
@@ -1076,11 +1285,18 @@ flush_dialog_changes_and_save (state *s)
 {
   saver_preferences *p = &s->prefs;
   saver_preferences P2, *p2 = &P2;
+#ifdef HAVE_GTK2
+  GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
+  GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
+  FlushForeachClosure closure;
+#else /* !HAVE_GTK2 */
   GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
   GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
+  int i;
+#endif /* !HAVE_GTK2 */
+
   Bool changed = False;
   GtkWidget *w;
-  int i;
 
   if (s->saving_p) return False;
   s->saving_p = True;
@@ -1089,6 +1305,14 @@ flush_dialog_changes_and_save (state *s)
 
   /* Flush any checkbox changes in the list down into the prefs struct.
    */
+#ifdef HAVE_GTK2
+  closure.s = s;
+  closure.changed = &changed;
+  closure.i = 0;
+  gtk_tree_model_foreach (model, flush_checkbox, &closure);
+
+#else /* !HAVE_GTK2 */
+
   for (i = 0; kids; kids = kids->next, i++)
     {
       GtkWidget *line = GTK_WIDGET (kids->data);
@@ -1101,7 +1325,7 @@ flush_dialog_changes_and_save (state *s)
       if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
         changed = True;
     }
-
+#endif /* ~HAVE_GTK2 */
 
   /* Flush the non-hack-specific settings down into the prefs struct.
    */
@@ -1187,7 +1411,7 @@ flush_dialog_changes_and_save (state *s)
   if (p->field != p2->field) { \
     changed = True; \
     if (s->debug_p) \
-      fprintf (stderr, "%s: %s => %d\n", blurb(), name, p2->field); \
+      fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \
   } \
   p->field = p2->field
 
@@ -1305,13 +1529,13 @@ 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
     {
       gdk_beep ();                               /* unparsable */
       visual = "";
-      gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");
+      gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
     }
 
   changed = flush_changes (s, list_elt, -1, command, visual);
@@ -1330,8 +1554,6 @@ flush_popup_changes_and_save (state *s)
 }
 
 
-
-
 void
 pref_changed_cb (GtkWidget *widget, gpointer user_data)
 {
@@ -1344,6 +1566,12 @@ pref_changed_cb (GtkWidget *widget, gpointer user_data)
     }
 }
 
+gboolean
+pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+  pref_changed_cb (widget, user_data);
+  return FALSE;
+}
 
 /* Callback on menu items in the "mode" options menu.
  */
@@ -1352,13 +1580,12 @@ mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
 {
   state *s = (state *) user_data;
   saver_preferences *p = &s->prefs;
-  GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+  GtkWidget *list = name_to_widget (s, "list");
   int list_elt;
 
   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)
     {
@@ -1372,17 +1599,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;
@@ -1393,9 +1615,6 @@ 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... */
 }
 
 
@@ -1414,6 +1633,52 @@ switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
     schedule_preview (s, 0);
 }
 
+#ifdef HAVE_GTK2
+static void
+list_activated_cb (GtkTreeView       *list,
+                  GtkTreePath       *path,
+                  GtkTreeViewColumn *column,
+                  gpointer           data)
+{
+  state *s = data;
+  char *str;
+  int list_elt;
+
+  STFU g_return_if_fail (!gdk_pointer_is_grabbed ());
+
+  str = gtk_tree_path_to_string (path);
+  list_elt = strtol (str, NULL, 10);
+  g_free (str);
+
+  if (list_elt >= 0)
+    run_hack (s, list_elt, True);
+}
+
+static void
+list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
+{
+  state *s = (state *)data;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GtkTreePath *path;
+  char *str;
+  int list_elt;
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+    return;
+
+  path = gtk_tree_model_get_path (model, &iter);
+  str = gtk_tree_path_to_string (path);
+  list_elt = strtol (str, NULL, 10);
+
+  gtk_tree_path_free (path);
+  g_free (str);
+
+  populate_demo_window (s, list_elt);
+  flush_dialog_changes_and_save (s);
+}
+
+#else /* !HAVE_GTK2 */
 
 static time_t last_doubleclick_time = 0;   /* FMH!  This is to suppress the
                                               list_select_cb that comes in
@@ -1462,6 +1727,8 @@ list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
   flush_dialog_changes_and_save (s);
 }
 
+#endif /* !HAVE_GTK2 */
+
 
 /* Called when the checkboxes that are in the left column of the
    scrolling list are clicked.  This both populates the right pane
@@ -1469,27 +1736,65 @@ list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
    also syncs this checkbox with  the right pane Enabled checkbox.
  */
 static void
-list_checkbox_cb (GtkWidget *cb, gpointer data)
+list_checkbox_cb (
+#ifdef HAVE_GTK2
+                 GtkCellRendererToggle *toggle,
+                 gchar                 *path_string,
+#else  /* !HAVE_GTK2 */
+                 GtkWidget *cb,
+#endif /* !HAVE_GTK2 */
+                 gpointer               data)
 {
   state *s = (state *) data;
 
+#ifdef HAVE_GTK2
+  GtkScrolledWindow *scroller =
+    GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
+  GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+  GtkTreeModel *model = gtk_tree_view_get_model (list);
+  GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+  GtkTreeIter iter;
+  gboolean active;
+#else /* !HAVE_GTK2 */
   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
 
   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
+#endif /* !HAVE_GTK2 */
   GtkAdjustment *adj;
   double scroll_top;
 
-  int list_elt = gtk_list_child_position (list, line);
+  int list_elt;
+
+#ifdef HAVE_GTK2
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    {
+      g_warning ("bad path: %s", path_string);
+      return;
+    }
+  gtk_tree_path_free (path);
+
+  gtk_tree_model_get (model, &iter,
+                     COL_ENABLED, &active,
+                     -1);
+
+  gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+                     COL_ENABLED, !active,
+                     -1);
+
+  list_elt = strtol (path_string, NULL, 10);  
+#else  /* !HAVE_GTK2 */
+  list_elt = gtk_list_child_position (list, line);
+#endif /* !HAVE_GTK2 */
 
   /* remember previous scroll position of the top of the list */
   adj = gtk_scrolled_window_get_vadjustment (scroller);
   scroll_top = adj->value;
 
   flush_dialog_changes_and_save (s);
-  force_list_select_item (s, list, list_elt, False);
+  force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
   populate_demo_window (s, list_elt);
   
   /* restore the previous scroll position of the top of the list.
@@ -1513,7 +1818,7 @@ store_image_directory (GtkWidget *button, gpointer user_data)
   GtkFileSelection *selector = fsd->widget;
   GtkWidget *top = s->toplevel_widget;
   saver_preferences *p = &s->prefs;
-  char *path = gtk_file_selection_get_filename (selector);
+  const char *path = gtk_file_selection_get_filename (selector);
 
   if (p->image_directory && !strcmp(p->image_directory, path))
     return;  /* no change */
@@ -1521,7 +1826,7 @@ store_image_directory (GtkWidget *button, gpointer user_data)
   if (!directory_p (path))
     {
       char b[255];
-      sprintf (b, "Error:\n\n" "Directory does not exist: \"%s\"\n", path);
+      sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
       warning_dialog (GTK_WIDGET (top), b, False, 100);
       return;
     }
@@ -1687,11 +1992,12 @@ settings_ok_cb (GtkButton *button, gpointer user_data)
   gtk_widget_hide (s->popup_widget);
 }
 
-static void
+static gboolean
 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
 {
   state *s = (state *) data;
   settings_cancel_cb (0, (gpointer) s);
+  return TRUE;
 }
 
 
@@ -1708,7 +2014,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;
 
@@ -1716,14 +2022,17 @@ 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)
+    {
+      CARD32 *data = (CARD32 *) dataP;
+      hack_number = (int) data[2] - 1;
+    }
 
-  if (data) free (data);
+  if (dataP) XFree (dataP);
 
   return hack_number;
 }
@@ -1746,22 +2055,162 @@ scroll_to_current_hack (state *s)
   if (hack_number >= 0 && hack_number < p->screenhacks_count)
     {
       int list_elt = s->hack_number_to_list_elt[hack_number];
-      GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+      GtkWidget *list = name_to_widget (s, "list");
       force_list_select_item (s, list, list_elt, True);
       populate_demo_window (s, list_elt);
     }
 }
 
 
+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)
 {
+#ifdef HAVE_GTK2
+  saver_preferences *p = &s->prefs;
+  GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+  GtkListStore *model;
+  GtkTreeSelection *selection;
+  GtkCellRenderer *ren;
+  GtkTreeIter iter;
+  int i;
+
+  g_object_get (G_OBJECT (list),
+               "model", &model,
+               NULL);
+  if (!model)
+    {
+      model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
+      g_object_set (G_OBJECT (list), "model", model, NULL);
+      g_object_unref (model);
+
+      ren = gtk_cell_renderer_toggle_new ();
+      gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
+                                                  _("Use"), ren,
+                                                  "active", COL_ENABLED,
+                                                  NULL);
+
+      g_signal_connect (ren, "toggled",
+                       G_CALLBACK (list_checkbox_cb),
+                       s);
+
+      ren = gtk_cell_renderer_text_new ();
+      gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
+                                                  _("Screen Saver"), ren,
+                                                  "markup", COL_NAME,
+                                                  NULL);
+
+      g_signal_connect_after (list, "row_activated",
+                             G_CALLBACK (list_activated_cb),
+                             s);
+
+      selection = gtk_tree_view_get_selection (list);
+      g_signal_connect (selection, "changed",
+                       G_CALLBACK (list_select_changed_cb),
+                       s);
+
+    }
+
+  for (i = 0; i < s->list_count; i++)
+    {
+      int hack_number = s->list_elt_to_hack_number[i];
+      screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
+      char *pretty_name;
+      Bool available_p = (hack && s->hacks_available_p [hack_number]);
+
+      if (!hack) continue;
+
+      /* If we're to suppress uninstalled hacks, check $PATH now. */
+      if (p->ignore_uninstalled_p && !available_p)
+        continue;
+
+      pretty_name = (hack->name
+                     ? strdup (hack->name)
+                     : make_hack_name (hack->command));
+
+      if (!available_p)
+        {
+          /* Make the text foreground be the color of insensitive widgets
+             (but don't actually make it be insensitive, since we still
+             want to be able to click on it.)
+           */
+          GtkStyle *style = GTK_WIDGET (list)->style;
+          GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
+       /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
+          char *buf = (char *) malloc (strlen (pretty_name) + 100);
+
+          sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
+                      /*     " background=\"#%02X%02X%02X\""  */
+                        ">%s</span>",
+                   fg->red >> 8, fg->green >> 8, fg->blue >> 8,
+                /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
+                   pretty_name);
+          free (pretty_name);
+          pretty_name = buf;
+        }
+
+      gtk_list_store_append (model, &iter);
+      gtk_list_store_set (model, &iter,
+                         COL_ENABLED, hack->enabled_p,
+                         COL_NAME, pretty_name,
+                         -1);
+      free (pretty_name);
+    }
+
+#else /* !HAVE_GTK2 */
+
   saver_preferences *p = &s->prefs;
   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
   int i;
-  for (i = 0; i < p->screenhacks_count; i++)
+  for (i = 0; i < s->list_count; i++)
     {
-      screenhack *hack = p->screenhacks[s->list_elt_to_hack_number[i]];
+      int hack_number = s->list_elt_to_hack_number[i];
+      screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
 
       /* A GtkList must contain only GtkListItems, but those can contain
          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
@@ -1773,10 +2222,18 @@ populate_hack_list (state *s)
       GtkWidget *line_hbox;
       GtkWidget *line_check;
       GtkWidget *line_label;
+      char *pretty_name;
+      Bool available_p = (hack && s->hacks_available_p [hack_number]);
+
+      if (!hack) continue;
+
+      /* If we're to suppress uninstalled hacks, check $PATH now. */
+      if (p->ignore_uninstalled_p && !available_p)
+        continue;
 
-      char *pretty_name = (hack->name
-                           ? strdup (hack->name)
-                           : make_hack_name (hack->command));
+      pretty_name = (hack->name
+                     ? strdup (hack->name)
+                     : make_hack_name (hack->command));
 
       line = gtk_list_item_new ();
       line_hbox = gtk_hbox_new (FALSE, 0);
@@ -1807,11 +2264,30 @@ populate_hack_list (state *s)
                           GTK_SIGNAL_FUNC (list_checkbox_cb),
                           (gpointer) s);
 
-#if 0 /* #### */
-      GTK_WIDGET (GTK_BIN(line)->child)->style =
-        gtk_style_copy (GTK_WIDGET (text_line)->style);
-#endif
       gtk_widget_show (line);
+
+      if (!available_p)
+        {
+          /* Make the widget be colored like insensitive widgets
+             (but don't actually make it be insensitive, since we
+             still want to be able to click on it.)
+           */
+          GtkRcStyle *rc_style;
+          GdkColor fg, bg;
+
+          gtk_widget_realize (GTK_WIDGET (line_label));
+
+          fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
+          bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
+
+          rc_style = gtk_rc_style_new ();
+          rc_style->fg[GTK_STATE_NORMAL] = fg;
+          rc_style->bg[GTK_STATE_NORMAL] = bg;
+          rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
+
+          gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
+          gtk_rc_style_unref (rc_style);
+        }
     }
 
   gtk_signal_connect (GTK_OBJECT (list), "select_child",
@@ -1820,9 +2296,9 @@ populate_hack_list (state *s)
   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
                       GTK_SIGNAL_FUNC (list_unselect_cb),
                       (gpointer) s);
+#endif /* !HAVE_GTK2 */
 }
 
-
 static void
 update_list_sensitivity (state *s)
 {
@@ -1831,21 +2307,31 @@ update_list_sensitivity (state *s)
   Bool checkable = (p->mode == RANDOM_HACKS);
   Bool blankable = (p->mode != DONT_BLANK);
 
+#ifndef HAVE_GTK2
   GtkWidget *head     = name_to_widget (s, "col_head_hbox");
   GtkWidget *use      = name_to_widget (s, "use_col_frame");
+#endif /* HAVE_GTK2 */
   GtkWidget *scroller = name_to_widget (s, "scroller");
   GtkWidget *buttons  = name_to_widget (s, "next_prev_hbox");
   GtkWidget *blanker  = name_to_widget (s, "blanking_table");
 
+#ifdef HAVE_GTK2
+  GtkTreeView *list      = GTK_TREE_VIEW (name_to_widget (s, "list"));
+  GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
+#else /* !HAVE_GTK2 */
   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
   GList *kids   = gtk_container_children (GTK_CONTAINER (list));
 
   gtk_widget_set_sensitive (GTK_WIDGET (head),     sensitive);
+#endif /* !HAVE_GTK2 */
   gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
   gtk_widget_set_sensitive (GTK_WIDGET (buttons),  sensitive);
 
   gtk_widget_set_sensitive (GTK_WIDGET (blanker),  blankable);
 
+#ifdef HAVE_GTK2
+  gtk_tree_view_column_set_visible (use, checkable);
+#else  /* !HAVE_GTK2 */
   if (checkable)
     gtk_widget_show (use);   /* the "Use" column header */
   else
@@ -1865,6 +2351,7 @@ update_list_sensitivity (state *s)
 
       kids = kids->next;
     }
+#endif /* !HAVE_GTK2 */
 }
 
 
@@ -1872,15 +2359,31 @@ static void
 populate_prefs_page (state *s)
 {
   saver_preferences *p = &s->prefs;
-  char str[100];
+
+  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
+
+
+  /* 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...)
+   */
+# define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
+  THROTTLE (timeout);
+  THROTTLE (cycle);
+  /* THROTTLE (passwd_timeout); */  /* GUI doesn't set this; leave it alone */
+# undef THROTTLE
 
 # define FMT_MINUTES(NAME,N) \
-    sprintf (str, "%d", ((N) + 59) / (60 * 1000)); \
-    gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, (NAME))), str)
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
 
 # define FMT_SECONDS(NAME,N) \
-    sprintf (str, "%d", ((N) / 1000)); \
-    gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, (NAME))), str)
+    gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
 
   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
@@ -1965,8 +2468,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
      */
@@ -2002,16 +2506,13 @@ 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 < p->screenhacks_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
+  gtk_label_set_selectable (doc);
+   */
+
 # ifdef HAVE_XML
   if (s->cdata)
     {
@@ -2019,25 +2520,35 @@ 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
                 : 0);
 # else  /* !HAVE_XML */
-  doc_string = "Descriptions not available: no XML support compiled in.";
+  doc_string = _("Descriptions not available: no XML support compiled in.");
 # endif /* !HAVE_XML */
 
   gtk_label_set_text (doc, (doc_string
-                            ? doc_string
-                            : "No description available."));
+                            ? _(doc_string)
+                            : _("No description available.")));
 }
 
 
@@ -2068,6 +2579,9 @@ sensitize_demo_widgets (state *s, Bool sensitive_p)
 static void
 fix_text_entry_sizes (state *s)
 {
+  GtkWidget *w;
+
+# if 0   /* appears no longer necessary with Gtk 1.2.10 */
   const char * const spinbuttons[] = {
     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
@@ -2075,7 +2589,6 @@ fix_text_entry_sizes (state *s)
     "-fade_spinbutton" };
   int i;
   int width = 0;
-  GtkWidget *w;
 
   for (i = 0; i < countof(spinbuttons); i++)
     {
@@ -2106,15 +2619,29 @@ fix_text_entry_sizes (state *s)
   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
   gtk_widget_set_usize (w, width, -2);
 
-  /* Now fix the height of the list.
+# endif /* 0 */
+
+  /* Now fix the height of the list widget:
+     make it default to being around 10 text-lines high instead of 4.
    */
+  w = GTK_WIDGET (name_to_widget (s, "list"));
   {
     int lines = 10;
     int height;
     int leading = 3;  /* approximate is ok... */
     int border = 2;
-    w = GTK_WIDGET (name_to_widget (s, "list"));
+
+#ifdef HAVE_GTK2
+    PangoFontMetrics *pain =
+      pango_context_get_metrics (gtk_widget_get_pango_context (w),
+                                 w->style->font_desc,
+                                 gtk_get_default_language ());
+    height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
+                           pango_font_metrics_get_descent (pain));
+#else  /* !HAVE_GTK2 */
     height = w->style->font->ascent + w->style->font->descent;
+#endif /* !HAVE_GTK2 */
+
     height += leading;
     height *= lines;
     height += border * 2;
@@ -2124,7 +2651,7 @@ fix_text_entry_sizes (state *s)
 }
 
 
-
+#ifndef HAVE_GTK2
 \f
 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
  */
@@ -2223,7 +2750,7 @@ map_prev_button_cb (GtkWidget *w, gpointer user_data)
   state *s = (state *) user_data;
   pixmapify_button (s, 0);
 }
-
+#endif /* !HAVE_GTK2 */
 
 \f
 /* Work around a Gtk bug that causes label widgets to wrap text too early.
@@ -2262,7 +2789,7 @@ eschew_gtk_lossage (GtkLabel *label)
   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
 
   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
-                      you_are_not_a_unique_or_beautiful_snowflake,
+                      GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
                       0);
 
   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
@@ -2286,18 +2813,18 @@ populate_demo_window (state *s, int list_elt)
   if (p->mode == BLANK_ONLY)
     {
       hack = 0;
-      pretty_name = strdup ("Blank Screen");
+      pretty_name = strdup (_("Blank Screen"));
       schedule_preview (s, 0);
     }
   else if (p->mode == DONT_BLANK)
     {
       hack = 0;
-      pretty_name = strdup ("Screen Saver Disabled");
+      pretty_name = strdup (_("Screen Saver Disabled"));
       schedule_preview (s, 0);
     }
   else
     {
-      int hack_number = (list_elt >= 0 && list_elt < p->screenhacks_count
+      int hack_number = (list_elt >= 0 && list_elt < s->list_count
                          ? s->list_elt_to_hack_number[list_elt]
                          : -1);
       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
@@ -2315,17 +2842,17 @@ populate_demo_window (state *s, int list_elt)
     }
 
   if (!pretty_name)
-    pretty_name = strdup ("Preview");
+    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);
   }
@@ -2334,7 +2861,7 @@ populate_demo_window (state *s, int list_elt)
                       (hack
                        ? (hack->visual && *hack->visual
                           ? hack->visual
-                          : "Any")
+                          : _("Any"))
                        : ""));
 
   sensitize_demo_widgets (s, (hack ? True : False));
@@ -2373,8 +2900,13 @@ sort_hack_cmp (const void *a, const void *b)
   if (a == b)
     return 0;
   else
-    return strcmp (sort_hack_cmp_names_kludge[*(int *) a],
-                   sort_hack_cmp_names_kludge[*(int *) b]);
+    {
+      int aa = *(int *) a;
+      int bb = *(int *) b;
+      const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
+      return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
+                     (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
+    }
 }
 
 
@@ -2382,21 +2914,44 @@ static void
 initialize_sort_map (state *s)
 {
   saver_preferences *p = &s->prefs;
-  int i;
+  int i, j;
 
   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
+  if (s->hacks_available_p) free (s->hacks_available_p);
 
   s->list_elt_to_hack_number = (int *)
     calloc (sizeof(int), p->screenhacks_count + 1);
   s->hack_number_to_list_elt = (int *)
     calloc (sizeof(int), p->screenhacks_count + 1);
+  s->hacks_available_p = (Bool *)
+    calloc (sizeof(Bool), p->screenhacks_count + 1);
 
-  /* Initialize table to 1:1 mapping */
+  /* Check which hacks actually exist on $PATH
+   */
   for (i = 0; i < p->screenhacks_count; i++)
-    s->list_elt_to_hack_number[i] = i;
+    {
+      screenhack *hack = p->screenhacks[i];
+      s->hacks_available_p[i] = on_path_p (hack->command);
+    }
 
-  /* Generate list of names (once)
+  /* Initialize list->hack table to unsorted mapping, omitting nonexistent
+     hacks, if desired.
+   */
+  j = 0;
+  for (i = 0; i < p->screenhacks_count; i++)
+    {
+      if (!p->ignore_uninstalled_p ||
+          s->hacks_available_p[i])
+        s->list_elt_to_hack_number[j++] = i;
+    }
+  s->list_count = j;
+
+  for (; j < p->screenhacks_count; j++)
+    s->list_elt_to_hack_number[j] = -1;
+
+
+  /* Generate list of sortable names (once)
    */
   sort_hack_cmp_names_kludge = (char **)
     calloc (sizeof(char *), p->screenhacks_count);
@@ -2412,7 +2967,7 @@ initialize_sort_map (state *s)
       sort_hack_cmp_names_kludge[i] = name;
     }
 
-  /* Sort alphabetically
+  /* Sort list->hack map alphabetically
    */
   qsort (s->list_elt_to_hack_number,
          p->screenhacks_count,
@@ -2447,13 +3002,13 @@ maybe_reload_init_file (state *s)
       const char *f = init_file_name();
       char *b;
       int list_elt;
-      GtkList *list;
+      GtkWidget *list;
 
       if (!f || !*f) return 0;
       b = (char *) malloc (strlen(f) + 1024);
       sprintf (b,
-               "Warning:\n\n"
-               "file \"%s\" has changed, reloading.\n",
+               _("Warning:\n\n"
+                "file \"%s\" has changed, reloading.\n"),
                f);
       warning_dialog (s->toplevel_widget, b, False, 100);
       free (b);
@@ -2462,13 +3017,13 @@ maybe_reload_init_file (state *s)
       initialize_sort_map (s);
 
       list_elt = selected_list_element (s);
-      list = GTK_LIST (name_to_widget (s, "list"));
+      list = name_to_widget (s, "list");
       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
       populate_hack_list (s);
       force_list_select_item (s, list, list_elt, True);
       populate_prefs_page (s);
       populate_demo_window (s, list_elt);
-      ensure_selected_item_visible (GTK_WIDGET (list));
+      ensure_selected_item_visible (list);
 
       status = 1;
     }
@@ -2517,30 +3072,75 @@ clear_preview_window (state *s)
   gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]);
   gdk_window_clear (window);
 
-  if (s->running_preview_error_p)
-    {
-      const char * const lines[] = { "No Preview", "Available" };
-      int lh = p->style->font->ascent + p->style->font->descent;
-      int y, i;
-      gint w, h;
-      gdk_window_get_size (window, &w, &h);
-      y = (h - (lh * countof(lines))) / 2;
-      y += p->style->font->ascent;
-      for (i = 0; i < countof(lines); i++)
-        {
-          int sw = gdk_string_width (p->style->font, lines[i]);
-          int x = (w - sw) / 2;
-          gdk_draw_string (window, p->style->font,
-                           p->style->fg_gc[GTK_STATE_NORMAL],
-                           x, y, lines[i]);
-          y += lh;
-        }
-    }
+  {
+    int list_elt = selected_list_element (s);
+    int hack_number = (list_elt >= 0
+                       ? s->list_elt_to_hack_number[list_elt]
+                       : -1);
+    Bool available_p = (hack_number >= 0
+                        ? s->hacks_available_p [hack_number]
+                        : True);
+#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)
+                            : 0));
+#else /* !HAVE_GTK2 */
+    if (s->running_preview_error_p)
+      {
+        const char * const lines1[] = { N_("No Preview"), N_("Available") };
+        const char * const lines2[] = { N_("Not"), N_("Installed") };
+        int nlines = countof(lines1);
+        int lh = p->style->font->ascent + p->style->font->descent;
+        int y, i;
+        gint w, h;
+
+        const char * const *lines = (available_p ? lines1 : lines2);
+
+        gdk_window_get_size (window, &w, &h);
+        y = (h - (lh * nlines)) / 2;
+        y += p->style->font->ascent;
+        for (i = 0; i < nlines; i++)
+          {
+            int sw = gdk_string_width (p->style->font, _(lines[i]));
+            int x = (w - sw) / 2;
+            gdk_draw_string (window, p->style->font,
+                             p->style->fg_gc[GTK_STATE_NORMAL],
+                             x, y, _(lines[i]));
+            y += lh;
+          }
+      }
+#endif /* !HAVE_GTK2 */
+  }
 
   gdk_flush ();
+}
 
-  /* Is there a GDK way of doing this? */
-  XSync (GDK_DISPLAY(), False);
+
+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);
+    }
 }
 
 
@@ -2587,6 +3187,11 @@ fix_preview_visual (state *s)
     style->fg_gc[GTK_STATE_NORMAL] = fgc;
     style->bg_gc[GTK_STATE_NORMAL] = fgc;
     gtk_widget_set_style (widget, style);
+
+    /* For debugging purposes, put a title on the window (so that
+       it can be easily found in the output of "xwininfo -tree".)
+     */
+    gdk_window_set_title (window, "Preview");
   }
 
   gtk_widget_show (widget);
@@ -2605,8 +3210,13 @@ subproc_pretty_name (state *s)
       char *ss = strchr (ps, ' ');
       if (ss) *ss = 0;
       ss = strrchr (ps, '/');
-      if (ss) *ss = 0;
-      else ss = ps;
+      if (!ss)
+        ss = ps;
+      else
+        {
+          ss = strdup (ss+1);
+          free (ps);
+        }
       return ss;
     }
   else
@@ -2626,11 +3236,13 @@ reap_zombies (state *s)
           if (pid == s->running_preview_pid)
             {
               char *ss = subproc_pretty_name (s);
-              fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(), pid, ss);
+              fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
+                       (unsigned long) pid, ss);
               free (ss);
             }
           else
-            fprintf (stderr, "%s: pid %lu died\n", blurb(), pid);
+            fprintf (stderr, "%s: pid %lu died\n", blurb(),
+                     (unsigned long) pid);
         }
     }
 }
@@ -2691,7 +3303,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:
@@ -2700,7 +3319,7 @@ get_best_gl_visual (state *s)
         int wait_status = 0;
 
         FILE *f = fdopen (in, "r");
-        unsigned long v = 0;
+        unsigned int v = 0;
         char c;
 
         close (out);  /* don't need this one */
@@ -2739,7 +3358,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;
 
@@ -2764,19 +3383,19 @@ kill_preview_subproc (state *s)
             {
               if (s->debug_p)
                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
-                         blurb(), s->running_preview_pid, ss);
+                         blurb(), (unsigned long) s->running_preview_pid, ss);
             }
           else
             {
               char buf [1024];
               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
-                       blurb(), s->running_preview_pid, ss);
+                       blurb(), (unsigned long) s->running_preview_pid, ss);
               perror (buf);
             }
         }
       else if (s->debug_p)
         fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
-                 s->running_preview_pid, ss);
+                 (unsigned long) s->running_preview_pid, ss);
 
       free (ss);
       s->running_preview_pid = 0;
@@ -2785,6 +3404,12 @@ kill_preview_subproc (state *s)
     }
 
   reap_zombies (s);
+
+  if (reset_p)
+    {
+      reset_preview_window (s);
+      clear_preview_window (s);
+    }
 }
 
 
@@ -2796,19 +3421,23 @@ launch_preview_subproc (state *s)
 {
   saver_preferences *p = &s->prefs;
   Window id;
-  char *new_cmd;
+  char *new_cmd = 0;
   pid_t forked;
   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);
-      return;
+      kill_preview_subproc (s, False);
+      goto DONE;
     }
 
   new_cmd = malloc (strlen (cmd) + 40);
@@ -2823,15 +3452,16 @@ launch_preview_subproc (state *s)
   else
     {
       strcpy (new_cmd, cmd);
-      sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", id);
+      sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
+               (unsigned int) id);
     }
 
-  kill_preview_subproc (s);
+  kill_preview_subproc (s, False);
   if (! new_cmd)
     {
       s->running_preview_error_p = True;
       clear_preview_window (s);
-      return;
+      goto DONE;
     }
 
   switch ((int) (forked = fork ()))
@@ -2842,12 +3472,15 @@ launch_preview_subproc (state *s)
         sprintf (buf, "%s: couldn't fork", blurb());
         perror (buf);
         s->running_preview_error_p = True;
-        return;
+        goto DONE;
+        break;
       }
     case 0:
       {
         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
@@ -2855,8 +3488,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:
@@ -2868,7 +3508,8 @@ launch_preview_subproc (state *s)
         if (s->debug_p)
           {
             char *ss = subproc_pretty_name (s);
-            fprintf (stderr, "%s: forked %lu (%s)\n", blurb(), forked, ss);
+            fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
+                     (unsigned long) forked, ss);
             free (ss);
           }
         break;
@@ -2876,6 +3517,10 @@ launch_preview_subproc (state *s)
     }
 
   schedule_preview_check (s);
+
+ DONE:
+  if (new_cmd) free (new_cmd);
+  new_cmd = 0;
 }
 
 
@@ -2902,6 +3547,11 @@ hack_environment (state *s)
   if (s->debug_p)
     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
 
+  /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
+     2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
+     So we must leak it (and/or the previous setting).  Yay.
+   */
+
   if (def_path && *def_path)
     {
       const char *opath = getenv("PATH");
@@ -2913,6 +3563,7 @@ hack_environment (state *s)
 
       if (putenv (npath))
        abort ();
+      /* do not free(npath) -- see above */
 
       if (s->debug_p)
         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
@@ -2920,6 +3571,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.
@@ -2929,7 +3603,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);
@@ -2992,7 +3666,7 @@ check_subproc_timer (gpointer data)
         {
           char *ss = subproc_pretty_name (s);
           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
-                   s->running_preview_pid, ss,
+                   (unsigned long) s->running_preview_pid, ss,
                    (s->running_preview_error_p ? "dead" : "alive"));
           free (ss);
         }
@@ -3052,7 +3726,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;
 
@@ -3060,14 +3734,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;
 }
@@ -3085,7 +3762,7 @@ 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 */
@@ -3139,7 +3816,7 @@ static void
 the_network_is_not_the_computer (state *s)
 {
   Display *dpy = GDK_DISPLAY();
-  char *rversion, *ruser, *rhost;
+  char *rversion = 0, *ruser = 0, *rhost = 0;
   char *luser, *lhost;
   char *msg = 0;
   struct passwd *p = getpwuid (getuid ());
@@ -3178,9 +3855,9 @@ the_network_is_not_the_computer (state *s)
   if (!rversion || !*rversion)
     {
       sprintf (msg,
-              "Warning:\n\n"
-               "The XScreenSaver daemon doesn't seem to be running\n"
-               "on display \"%s\".  Launch it now?",
+              _("Warning:\n\n"
+                "The XScreenSaver daemon doesn't seem to be running\n"
+                "on display \"%s\".  Launch it now?"),
               d);
     }
   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
@@ -3188,7 +3865,7 @@ the_network_is_not_the_computer (state *s)
       /* Warn that the two processes are running as different users.
        */
       sprintf(msg,
-              "Warning:\n\n"
+           _("Warning:\n\n"
              "%s is running as user \"%s\" on host \"%s\".\n"
              "But the xscreensaver managing display \"%s\"\n"
              "is running as user \"%s\" on host \"%s\".\n"
@@ -3200,12 +3877,12 @@ the_network_is_not_the_computer (state *s)
              "You should either re-run %s as \"%s\", or re-run\n"
              "xscreensaver as \"%s\".\n"
               "\n"
-              "Restart the xscreensaver daemon now?\n",
-             blurb(), luser, lhost,
+              "Restart the xscreensaver daemon now?\n"),
+             progname, luser, lhost,
              d,
              (ruser ? ruser : "???"), (rhost ? rhost : "???"),
-             blurb(),
-             blurb(), (ruser ? ruser : "???"),
+             progname,
+             progname, (ruser ? ruser : "???"),
              luser);
     }
   else if (rhost && *rhost && !!strcmp (rhost, lhost))
@@ -3213,7 +3890,7 @@ the_network_is_not_the_computer (state *s)
       /* Warn that the two processes are running on different hosts.
        */
       sprintf (msg,
-              "Warning:\n\n"
+             _("Warning:\n\n"
               "%s is running as user \"%s\" on host \"%s\".\n"
               "But the xscreensaver managing display \"%s\"\n"
               "is running as user \"%s\" on host \"%s\".\n"
@@ -3222,12 +3899,12 @@ the_network_is_not_the_computer (state *s)
               "if they don't see the same ~%s/.xscreensaver file) then\n"
               "%s won't work right.\n"
                "\n"
-               "Restart the daemon on \"%s\" as \"%s\" now?\n",
-              blurb(), luser, lhost,
+               "Restart the daemon on \"%s\" as \"%s\" now?\n"),
+              progname, luser, lhost,
               d,
               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
               luser,
-              blurb(),
+              progname,
                lhost, luser);
     }
   else if (!!strcmp (rversion, s->short_version))
@@ -3235,13 +3912,13 @@ the_network_is_not_the_computer (state *s)
       /* Warn that the version numbers don't match.
        */
       sprintf (msg,
-              "Warning:\n\n"
+            _("Warning:\n\n"
               "This is %s version %s.\n"
               "But the xscreensaver managing display \"%s\"\n"
               "is version %s.  This could cause problems.\n"
-              "\n"
-              "Restart the xscreensaver daemon now?\n",
-              blurb(), s->short_version,
+              "\n"
+              "Restart the xscreensaver daemon now?\n"),
+              progname, s->short_version,
               d,
               rversion);
     }
@@ -3250,6 +3927,9 @@ the_network_is_not_the_computer (state *s)
   if (*msg)
     warning_dialog (s->toplevel_widget, msg, True, 1);
 
+  if (rversion) free (rversion);
+  if (ruser) free (ruser);
+  if (rhost) free (rhost);
   free (msg);
 }
 
@@ -3262,13 +3942,9 @@ demo_ehandler (Display *dpy, XErrorEvent *error)
 {
   state *s = global_state_kludge;  /* I hate C so much... */
   fprintf (stderr, "\nX error in %s:\n", blurb());
-  if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
-    {
-      kill_preview_subproc (s);
-      exit (-1);
-    }
-  else
-    fprintf (stderr, " (nonfatal.)\n");
+  XmuPrintDefaultErrorMessage (dpy, error, stderr);
+  kill_preview_subproc (s, False);
+  exit (-1);
   return 0;
 }
 
@@ -3304,6 +3980,12 @@ 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
+
 static char *defaults[] = {
 #include "XScreenSaver_ad.h"
  0
@@ -3321,8 +4003,7 @@ const char *usage = "[--display dpy] [--prefs]"
 # ifdef HAVE_CRAPPLET
                     " [--crapplet]"
 # endif
-                    " [--debug]";
-
+            "\n\t\t   [--debug] [--sync] [--no-xshm] [--configdir dir]";
 
 static void
 map_popup_window_cb (GtkWidget *w, gpointer user_data)
@@ -3378,6 +4059,36 @@ delayed_scroll_kludge (gpointer data)
   return FALSE;  /* do not re-execute timer */
 }
 
+#ifdef HAVE_GTK2
+
+GtkWidget *
+create_xscreensaver_demo (void)
+{
+  GtkWidget *nb;
+
+  nb = name_to_widget (global_state_kludge, "preview_notebook");
+  gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
+
+  return name_to_widget (global_state_kludge, "xscreensaver_demo");
+}
+
+GtkWidget *
+create_xscreensaver_settings_dialog (void)
+{
+  GtkWidget *w, *box;
+
+  box = name_to_widget (global_state_kludge, "dialog_action_area");
+
+  w = name_to_widget (global_state_kludge, "adv_button");
+  gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
+
+  w = name_to_widget (global_state_kludge, "std_button");
+  gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
+
+  return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
+}
+
+#endif /* HAVE_GTK2 */
 
 int
 main (int argc, char **argv)
@@ -3390,10 +4101,24 @@ main (int argc, char **argv)
   Display *dpy;
   Widget toplevel_shell;
   char *real_progname = argv[0];
-  char window_title[255];
+  char *window_title;
+  char *geom = 0;
   Bool crapplet_p = False;
   char *str;
 
+#ifdef ENABLE_NLS
+  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+  textdomain (GETTEXT_PACKAGE);
+
+# ifdef HAVE_GTK2
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+# else  /* !HAVE_GTK2 */
+  if (!setlocale (LC_ALL, ""))
+    fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
+# endif /* !HAVE_GTK2 */
+
+#endif /* ENABLE_NLS */
+
   str = strrchr (real_progname, '/');
   if (str) real_progname = str+1;
 
@@ -3424,8 +4149,13 @@ main (int argc, char **argv)
   }
 
 #ifdef DEFAULT_ICONDIR  /* from -D on compile line */
-  add_pixmap_directory (DEFAULT_ICONDIR);
-#endif
+# ifndef HAVE_GTK2
+  {
+    const char *dir = DEFAULT_ICONDIR;
+    if (*dir) add_pixmap_directory (dir);
+  }
+# endif /* !HAVE_GTK2 */
+#endif /* DEFAULT_ICONDIR */
 
   /* This is gross, but Gtk understands --display and not -display...
    */
@@ -3437,36 +4167,86 @@ main (int argc, char **argv)
 
   /* We need to parse this arg really early... Sigh. */
   for (i = 1; i < argc; i++)
-    if (argv[i] &&
-        (!strcmp(argv[i], "--crapplet") ||
-         !strcmp(argv[i], "--capplet")))
-      {
-# ifdef HAVE_CRAPPLET
-        int j;
-        crapplet_p = True;
-        for (j = i; j < argc; j++)  /* remove it from the list */
-          argv[j] = argv[j+1];
-        argc--;
-
-# else  /* !HAVE_CRAPPLET */
-        fprintf (stderr, "%s: not compiled with --crapplet support\n",
-                 real_progname);
-        fprintf (stderr, "%s: %s\n", real_progname, usage);
-        exit (1);
-# endif /* !HAVE_CRAPPLET */
-      }
-  else if (argv[i] &&
-           (!strcmp(argv[i], "--debug") ||
-            !strcmp(argv[i], "-debug") ||
-            !strcmp(argv[i], "-d")))
     {
-      int j;
-      s->debug_p = True;
-      for (j = i; j < argc; j++)  /* remove it from the list */
-        argv[j] = argv[j+1];
-      argc--;
+      if (argv[i] &&
+          (!strcmp(argv[i], "--crapplet") ||
+           !strcmp(argv[i], "--capplet")))
+        {
+# if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
+          int j;
+          crapplet_p = True;
+          for (j = i; j < argc; j++)  /* remove it from the list */
+            argv[j] = argv[j+1];
+          argc--;
+# else  /* !HAVE_CRAPPLET && !HAVE_GTK2 */
+          fprintf (stderr, "%s: not compiled with --crapplet support\n",
+                   real_progname);
+          fprintf (stderr, "%s: %s\n", real_progname, usage);
+          exit (1);
+# endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
+        }
+      else if (argv[i] &&
+               (!strcmp(argv[i], "--debug") ||
+                !strcmp(argv[i], "-debug") ||
+                !strcmp(argv[i], "-d")))
+        {
+          int j;
+          s->debug_p = True;
+          for (j = i; j < argc; j++)  /* remove it from the list */
+            argv[j] = argv[j+1];
+          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] &&
+               (!strcmp(argv[i], "--configdir")))
+        {
+          int j;
+          struct stat st;
+          hack_configuration_path = argv[i+1];
+          for (j = i; j < argc; j++)  /* remove them from the list */
+            argv[j] = argv[j+2];
+          argc -= 2;
+          i -= 2;
+
+          if (0 != stat (hack_configuration_path, &st))
+            {
+              char buf[255];
+              sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
+              perror (buf);
+              exit (1);
+            }
+          else if (!S_ISDIR (st.st_mode))
+            {
+              fprintf (stderr, "%s: not a directory: %s\n",
+                       blurb(), hack_configuration_path);
+              exit (1);
+            }
+        }
     }
 
+
+  if (s->debug_p)
+    fprintf (stderr, "%s: using config directory \"%s\"\n",
+             progname, hack_configuration_path);
+
+
   /* Let Gtk open the X connection, then initialize Xt to use that
      same connection.  Doctor Frankenstein would be proud.
    */
@@ -3589,7 +4369,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);
        }
@@ -3601,6 +4382,9 @@ main (int argc, char **argv)
      was in argv[0].
    */
   p->db = db;
+
+  hack_environment (s);  /* must be before initialize_sort_map() */
+
   load_init_file (p);
   initialize_sort_map (s);
 
@@ -3649,6 +4433,7 @@ main (int argc, char **argv)
 
   /* Set the main window's title. */
   {
+    char *base_title = _("Screensaver Preferences");
     char *v = (char *) strdup(strchr(screensaver_id, ' '));
     char *s1, *s2, *s3, *s4;
     s1 = (char *) strchr(v,  ' '); s1++;
@@ -3657,7 +4442,12 @@ main (int argc, char **argv)
     s4 = (char *) strchr(s3, ')');
     *s2 = 0;
     *s4 = 0;
-    sprintf (window_title, "%.50s %.50s, %.50s", progclass, s1, s3);
+
+    window_title = (char *) malloc (strlen (base_title) +
+                                    strlen (progclass) +
+                                    strlen (s1) + strlen (s3) +
+                                    100);
+    sprintf (window_title, "%s  (%s %s, %s)", base_title, progclass, s1, s3);
     gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
     gtk_window_set_title (GTK_WINDOW (s->popup_widget),    window_title);
     free (v);
@@ -3703,13 +4493,14 @@ main (int argc, char **argv)
                       "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
                       (gpointer) s);
 
+#ifndef HAVE_GTK2
   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
                       "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
                       (gpointer) s);
   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
                       "map", GTK_SIGNAL_FUNC(map_next_button_cb),
                       (gpointer) s);
-
+#endif /* !HAVE_GTK2 */
 
   /* Hook up callbacks to the items on the mode menu. */
   {
@@ -3745,7 +4536,6 @@ main (int argc, char **argv)
 # ifdef HAVE_CRAPPLET_IMMEDIATE
       capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
 # endif /* HAVE_CRAPPLET_IMMEDIATE */
-
       /* In crapplet-mode, take off the menubar. */
       gtk_widget_hide (name_to_widget (s, "menubar"));
 
@@ -3756,7 +4546,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. */
@@ -3778,9 +4568,27 @@ main (int argc, char **argv)
 # endif /* HAVE_CRAPPLET */
 
 
+  /* The Gnome folks hate the menubar.  I think it's important to have access
+     to the commands on the File menu (Restart Daemon, etc.) and to the
+     About and Documentation commands on the Help menu.
+   */
+#if 0
+#ifdef HAVE_GTK2
+  gtk_widget_hide (name_to_widget (s, "menubar"));
+#endif
+#endif
+
+  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' */
-  hack_environment (s);
   fix_preview_visual (s);
 
   /* Realize page zero, so that we can diddle the scrollbar when the
@@ -3822,7 +4630,7 @@ main (int argc, char **argv)
     int i;
     for (i = 0; i < p->screenhacks_count; i++)
       {
-        screenhack *hack = p->screenhacks[s->hack_number_to_list_elt[i]];
+        screenhack *hack = p->screenhacks[i];
         conf_data *d = load_configurator (hack->command, False);
         if (d) free_conf_data (d);
       }
@@ -3837,7 +4645,7 @@ main (int argc, char **argv)
 # endif /* HAVE_CRAPPLET */
     gtk_main ();
 
-  kill_preview_subproc (s);
+  kill_preview_subproc (s, False);
   exit (0);
 }