http://packetstorm.tacticalflex.com/UNIX/admin/xscreensaver-3.27.tar.gz
[xscreensaver] / driver / demo-Gtk.c
index 2b07ab0ce892fa2905e0b3c2b43d820d111b6e4b..0ff66e9382ae0a7bcbb721edff8ad66b1cb439c4 100644 (file)
@@ -1,5 +1,5 @@
 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
- * xscreensaver, Copyright (c) 1993-1998 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 1993-1999 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 "xmu.h"
 #endif
 
-
-
 #include <gtk/gtk.h>
 
+#ifdef HAVE_CRAPPLET
+# include <gnome.h>
+# include <capplet-widget.h>
+#endif
+
 extern Display *gdk_display;
 
 #include "version.h"
@@ -81,6 +84,10 @@ char *progname = 0;
 char *progclass = "XScreenSaver";
 XrmDatabase db;
 
+static Bool crapplet_p = False;
+static Bool initializing_p;
+static GtkWidget *toplevel_widget;
+
 typedef struct {
   saver_preferences *a, *b;
 } prefs_pair;
@@ -101,7 +108,8 @@ static void populate_demo_window (GtkWidget *toplevel,
                                   int which, prefs_pair *pair);
 static void populate_prefs_page (GtkWidget *top, prefs_pair *pair);
 static int apply_changes_and_save (GtkWidget *widget);
-
+static int maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair);
+static void await_xscreensaver (GtkWidget *widget);
 
 \f
 /* Some random utility functions
@@ -110,21 +118,10 @@ static int apply_changes_and_save (GtkWidget *widget);
 static GtkWidget *
 name_to_widget (GtkWidget *widget, const char *name)
 {
-  while (1)
-    {
-      GtkWidget *parent = (GTK_IS_MENU (widget)
-                           ? gtk_menu_get_attach_widget (GTK_MENU (widget))
-                           : widget->parent);
-      if (parent)
-        widget = parent;
-      else
-        break;
-    }
-  return (GtkWidget *) gtk_object_get_data (GTK_OBJECT (widget), name);
+  return (GtkWidget *) gtk_object_get_data (GTK_OBJECT(toplevel_widget), name);
 }
 
 
-
 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
    Takes a scroller, viewport, or list as an argument.
  */
@@ -175,7 +172,7 @@ ensure_selected_item_visible (GtkWidget *widget)
        kids; kids = kids->next)
     nkids++;
 
-  adj = gtk_scrolled_window_get_vadjustment (scroller);                        
+  adj = gtk_scrolled_window_get_vadjustment (scroller);
 
   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
                            &ignore, &ignore, &ignore, &parent_h, &ignore);
@@ -186,6 +183,9 @@ ensure_selected_item_visible (GtkWidget *widget)
   ratio_t = ((double) child_y) / ((double) children_h);
   ratio_b = ((double) child_y + child_h) / ((double) children_h);
 
+  if (adj->upper == 0.0)  /* no items in list */
+    return;
+
   if (ratio_t < (adj->value / adj->upper) ||
       ratio_b > ((adj->value + adj->page_size) / adj->upper))
     {
@@ -214,9 +214,8 @@ ensure_selected_item_visible (GtkWidget *widget)
     }
 }
 
-
 static void
-warning_dialog_dismiss_cb (GtkButton *button, gpointer user_data)
+warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
 {
   GtkWidget *shell = GTK_WIDGET (user_data);
   while (shell->parent)
@@ -225,8 +224,17 @@ warning_dialog_dismiss_cb (GtkButton *button, gpointer user_data)
 }
 
 
+void restart_menu_cb (GtkWidget *widget, gpointer user_data);
+
+static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
+{
+  restart_menu_cb (widget, user_data);
+  warning_dialog_dismiss_cb (widget, user_data);
+}
+
 static void
-warning_dialog (GtkWidget *parent, const char *message, int center)
+warning_dialog (GtkWidget *parent, const char *message,
+                Boolean restart_button_p, int center)
 {
   char *msg = strdup (message);
   char *head;
@@ -234,6 +242,7 @@ warning_dialog (GtkWidget *parent, const char *message, int center)
   GtkWidget *dialog = gtk_dialog_new ();
   GtkWidget *label = 0;
   GtkWidget *ok = 0;
+  GtkWidget *cancel = 0;
   int i = 0;
 
   while (parent->parent)
@@ -249,12 +258,16 @@ warning_dialog (GtkWidget *parent, const char *message, int center)
       sprintf (name, "label%d", i++);
 
       {
+#if 0
         char buf[255];
+#endif
         label = gtk_label_new (head);
+#if 0
         sprintf (buf, "warning_dialog.%s.font", name);
         GTK_WIDGET (label)->style = gtk_style_copy (GTK_WIDGET (label)->style);
         GTK_WIDGET (label)->style->font =
           gdk_font_load (get_string_resource (buf, "Dialog.Label.Font"));
+#endif
         if (center <= 0)
           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
@@ -279,22 +292,40 @@ warning_dialog (GtkWidget *parent, const char *message, int center)
   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
                       label, TRUE, TRUE, 0);
 
-  ok = gtk_button_new_with_label (
-                          get_string_resource ("warning_dialog.ok.label",
-                                               "warning_dialog.Button.Label"));
+  ok = gtk_button_new_with_label ("OK");
   gtk_container_add (GTK_CONTAINER (label), ok);
 
+  if (restart_button_p)
+    {
+      cancel = gtk_button_new_with_label ("Cancel");
+      gtk_container_add (GTK_CONTAINER (label), cancel);
+    }
+
   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_show (ok);
+  if (cancel)
+    gtk_widget_show (cancel);
   gtk_widget_show (label);
   gtk_widget_show (dialog);
 /*  gtk_window_set_default (GTK_WINDOW (dialog), ok);*/
 
-  gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
-                             GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
-                             (gpointer) dialog);
+  if (restart_button_p)
+    {
+      gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
+                                 GTK_SIGNAL_FUNC (warning_dialog_restart_cb),
+                                 (gpointer) dialog);
+      gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
+                                 GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
+                                 (gpointer) dialog);
+    }
+  else
+    {
+      gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
+                                 GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
+                                 (gpointer) dialog);
+    }
   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
                                 GTK_WIDGET (parent)->window);
 
@@ -320,7 +351,7 @@ run_cmd (GtkWidget *widget, Atom command, int arg)
         sprintf (buf, "Error:\n\n%s", err);
       else
         strcpy (buf, "Unknown error!");
-      warning_dialog (widget, buf, 100);
+      warning_dialog (widget, buf, False, 100);
     }
   if (err) free (err);
 }
@@ -332,7 +363,7 @@ run_hack (GtkWidget *widget, int which, Bool report_errors_p)
   if (which < 0) return;
   apply_changes_and_save (widget);
   if (report_errors_p)
-    run_cmd (widget, XA_ACTIVATE, 0);
+    run_cmd (widget, XA_DEMO, which + 1);
   else
     {
       char *s = 0;
@@ -367,7 +398,7 @@ cut_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
   /* #### */
   warning_dialog (GTK_WIDGET (menuitem),
                   "Error:\n\n"
-                  "cut unimplemented\n", 1);
+                  "cut unimplemented\n", False, 1);
 }
 
 
@@ -377,7 +408,7 @@ copy_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
   /* #### */
   warning_dialog (GTK_WIDGET (menuitem),
                   "Error:\n\n"
-                  "copy unimplemented\n", 1);
+                  "copy unimplemented\n", False, 1);
 }
 
 
@@ -387,7 +418,7 @@ paste_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
   /* #### */
   warning_dialog (GTK_WIDGET (menuitem),
                   "Error:\n\n"
-                  "paste unimplemented\n", 1);
+                  "paste unimplemented\n", False, 1);
 }
 
 
@@ -407,7 +438,7 @@ about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
            s, s2);
   free (s);
 
-  warning_dialog (GTK_WIDGET (menuitem), buf, 100);
+  warning_dialog (GTK_WIDGET (menuitem), buf, False, 100);
 }
 
 
@@ -424,7 +455,7 @@ doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
     {
       warning_dialog (GTK_WIDGET (menuitem),
                       "Error:\n\n"
-                      "No Help URL has been specified.\n", 100);
+                      "No Help URL has been specified.\n", False, 100);
       return;
     }
 
@@ -461,16 +492,79 @@ kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
 
 
 void
-restart_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
+restart_menu_cb (GtkWidget *widget, gpointer user_data)
 {
 #if 0
-  run_cmd (GTK_WIDGET (menuitem), XA_RESTART, 0);
+  run_cmd (GTK_WIDGET (widget), XA_RESTART, 0);
 #else
-  apply_changes_and_save (GTK_WIDGET (menuitem));
+  apply_changes_and_save (GTK_WIDGET (widget));
   xscreensaver_command (gdk_display, XA_EXIT, 0, False, NULL);
   sleep (1);
   system ("xscreensaver -nosplash &");
 #endif
+
+  await_xscreensaver (GTK_WIDGET (widget));
+}
+
+static void
+await_xscreensaver (GtkWidget *widget)
+{
+  int countdown = 5;
+
+  Display *dpy = gdk_display;
+  /*  GtkWidget *dialog = 0;*/
+  char *rversion = 0;
+
+  while (!rversion && (--countdown > 0))
+    {
+      /* Check for the version of the running xscreensaver... */
+      server_xscreensaver_version (dpy, &rversion, 0, 0);
+
+      /* If it's not there yet, wait a second... */
+      sleep (1);
+    }
+
+/*  if (dialog) gtk_widget_destroy (dialog);*/
+
+  if (rversion)
+    {
+      /* Got it. */
+      free (rversion);
+    }
+  else
+    {
+      /* Timed out, no screensaver running. */
+
+      char buf [1024];
+      Bool root_p = (geteuid () == 0);
+      
+      strcpy (buf, 
+              "Error:\n\n"
+              "The xscreensaver daemon did not start up properly.\n"
+              "\n");
+
+      if (root_p)
+        strcat (buf,
+            "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"
+            "                        xhost +localhost\n"
+            "\n"
+            "and then selecting `File / Restart Daemon'.\n"
+            "\n"
+            "Note that turning off access control will allow anyone logged\n"
+            "on to this machine to access your screen, which might be\n"
+            "considered a security problem.  Please read the xscreensaver\n"
+            "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.");
+      else
+        strcat (buf, "Please check your $PATH and permissions.");
+
+      warning_dialog (widget, buf, False, 1);
+    }
 }
 
 
@@ -505,12 +599,12 @@ demo_write_init_file (GtkWidget *widget, saver_preferences *p)
       if (!f || !*f)
         warning_dialog (widget,
                         "Error:\n\nCouldn't determine init file name!\n",
-                        100);
+                        False, 100);
       else
         {
           char *b = (char *) malloc (strlen(f) + 1024);
           sprintf (b, "Error:\n\nCouldn't write %s\n", f);
-          warning_dialog (widget, b, 100);
+          warning_dialog (widget, b, False, 100);
           free (b);
         }
       return -1;
@@ -519,7 +613,7 @@ demo_write_init_file (GtkWidget *widget, saver_preferences *p)
 
 
 static int
-apply_changes_and_save (GtkWidget *widget)
+apply_changes_and_save_1 (GtkWidget *widget)
 {
   /* prefs_pair *pair = (prefs_pair *) client_data; */
   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
@@ -542,6 +636,9 @@ apply_changes_and_save (GtkWidget *widget)
 
   if (which < 0) return -1;
 
+  if (maybe_reload_init_file (widget, pair) != 0)
+    return 1;
+
   /* Sanity-check and canonicalize whatever the user typed into the combo box.
    */
   if      (!strcasecmp (visual, ""))                   visual = "";
@@ -599,6 +696,16 @@ apply_changes_and_save (GtkWidget *widget)
   return 0;
 }
 
+void prefs_ok_cb (GtkButton *button, gpointer user_data);
+
+static int
+apply_changes_and_save (GtkWidget *widget)
+{
+  prefs_ok_cb ((GtkButton *) widget, 0);
+  return apply_changes_and_save_1 (widget);
+}
+
+
 void
 run_this_cb (GtkButton *button, gpointer user_data)
 {
@@ -648,7 +755,7 @@ manual_cb (GtkButton *button, gpointer user_data)
     {
       warning_dialog (GTK_WIDGET (button),
                       "Error:\n\nno `manualCommand' resource set.",
-                      100);
+                      False, 100);
     }
 
   free (name);
@@ -713,7 +820,7 @@ run_prev_cb (GtkButton *button, gpointer user_data)
    this parses the text, and does error checking.
  */
 static void 
-hack_time_text (const char *line, Time *store, Bool sec_p)
+hack_time_text (GtkWidget *widget, const char *line, Time *store, Bool sec_p)
 {
   if (*line)
     {
@@ -721,7 +828,14 @@ hack_time_text (const char *line, Time *store, Bool sec_p)
       value = parse_time ((char *) line, sec_p, True);
       value *= 1000;   /* Time measures in microseconds */
       if (value < 0)
-        /* gdk_beep () */;
+       {
+         char b[255];
+         sprintf (b,
+                  "Error:\n\n"
+                  "Unparsable time format: \"%s\"\n",
+                  line);
+         warning_dialog (widget, b, False, 100);
+       }
       else
        *store = value;
     }
@@ -739,13 +853,13 @@ prefs_ok_cb (GtkButton *button, gpointer user_data)
   Bool changed = False;
 
 # define SECONDS(field, name) \
-  hack_time_text (gtk_entry_get_text (\
+  hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
                   (field), \
                   True)
 
 # define MINUTES(field, name) \
-  hack_time_text (gtk_entry_get_text (\
+  hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
                   (field), \
                   False)
@@ -758,7 +872,11 @@ prefs_ok_cb (GtkButton *button, gpointer user_data)
     if (! *line) \
       ; \
     else if (sscanf (line, "%u%c", &value, &c) != 1) \
-     gdk_beep(); \
+      { \
+       char b[255]; \
+       sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", line); \
+       warning_dialog (GTK_WIDGET (button), b, False, 100); \
+      } \
    else \
      *(field) = value; \
   } while(0)
@@ -819,6 +937,14 @@ prefs_cancel_cb (GtkButton *button, gpointer user_data)
 }
 
 
+void
+pref_changed_cb (GtkButton *button, gpointer user_data)
+{
+  if (! initializing_p)
+    apply_changes_and_save (GTK_WIDGET (button));
+}
+
+
 static gint
 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
                      gpointer client_data)
@@ -857,6 +983,80 @@ list_unselect_cb (GtkList *list, GtkWidget *child)
   populate_demo_window (GTK_WIDGET (list), -1, pair);
 }
 
+
+static int updating_enabled_cb = 0;  /* kludge to make sure that enabled_cb
+                                        is only run by user action, not by
+                                        program action. */
+
+/* Called when the checkboxes that are in the left column of the
+   scrolling list are clicked.  This both populates the right pane
+   (just as clicking on the label (really, listitem) does) and
+   also syncs this checkbox with  the right pane Enabled checkbox.
+ */
+static void
+list_checkbox_cb (GtkWidget *cb, gpointer client_data)
+{
+  prefs_pair *pair = (prefs_pair *) client_data;
+
+  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);
+  GtkAdjustment *adj;
+  double scroll_top;
+
+  GtkToggleButton *enabled =
+    GTK_TOGGLE_BUTTON (name_to_widget (cb, "enabled"));
+
+  int which = gtk_list_child_position (list, line);
+
+  /* remember previous scroll position of the top of the list */
+  adj = gtk_scrolled_window_get_vadjustment (scroller);
+  scroll_top = adj->value;
+
+  apply_changes_and_save (GTK_WIDGET (list));
+  gtk_list_select_item (list, which);
+  /* ensure_selected_item_visible (GTK_WIDGET (list)); */
+  populate_demo_window (GTK_WIDGET (list), which, pair);
+  
+  updating_enabled_cb++;
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (enabled),
+                                GTK_TOGGLE_BUTTON (cb)->active);
+  updating_enabled_cb--;
+
+  /* restore the previous scroll position of the top of the list.
+     this is weak, but I don't really know why it's moving... */
+  gtk_adjustment_set_value (adj, scroll_top);
+}
+
+
+/* Called when the right pane Enabled checkbox is clicked.  This syncs
+   the corresponding checkbox inside the scrolling list to the state
+   of this checkbox.
+ */
+void
+enabled_cb (GtkWidget *cb, gpointer client_data)
+{
+  int which = selected_hack_number (cb);
+  
+  if (updating_enabled_cb) return;
+
+  if (which != -1)
+    {
+      GtkList *list = GTK_LIST (name_to_widget (cb, "list"));
+      GList *kids = GTK_LIST (list)->children;
+      GtkWidget *line = GTK_WIDGET (g_list_nth_data (kids, which));
+      GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
+      GtkWidget *line_check =
+        GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
+
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
+                                    GTK_TOGGLE_BUTTON (cb)->active);
+    }
+}
+
 \f
 /* Populating the various widgets
  */
@@ -931,6 +1131,7 @@ make_pretty_name (const char *shell_command)
 static void
 scroll_to_current_hack (GtkWidget *toplevel, prefs_pair *pair)
 {
+  saver_preferences *p =  pair->a;
   Atom type;
   int format;
   unsigned long nitems, bytesafter;
@@ -957,9 +1158,12 @@ scroll_to_current_hack (GtkWidget *toplevel, prefs_pair *pair)
 
   list = GTK_LIST (name_to_widget (toplevel, "list"));
   apply_changes_and_save (toplevel);
-  gtk_list_select_item (list, which);
-  ensure_selected_item_visible (GTK_WIDGET (list));
-  populate_demo_window (toplevel, which, pair);
+  if (which < p->screenhacks_count)
+    {
+      gtk_list_select_item (list, which);
+      ensure_selected_item_visible (GTK_WIDGET (list));
+      populate_demo_window (toplevel, which, pair);
+    }
 }
 
 
@@ -972,20 +1176,52 @@ populate_hack_list (GtkWidget *toplevel, prefs_pair *pair)
   screenhack **hacks = p->screenhacks;
   screenhack **h;
 
-  for (h = hacks; *h; h++)
+  for (h = hacks; h && *h; h++)
     {
+      /* A GtkList must contain only GtkListItems, but those can contain
+         an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
+         and a Label.  We handle single and double click events on the
+         line itself, for clicking on the text, but the interior checkbox
+         also handles its own events.
+       */
       GtkWidget *line;
+      GtkWidget *line_hbox;
+      GtkWidget *line_check;
+      GtkWidget *line_label;
+
       char *pretty_name = (h[0]->name
                            ? strdup (h[0]->name)
                            : make_pretty_name (h[0]->command));
 
-      line = gtk_list_item_new_with_label (pretty_name);
+      line = gtk_list_item_new ();
+      line_hbox = gtk_hbox_new (FALSE, 0);
+      line_check = gtk_check_button_new ();
+      line_label = gtk_label_new (pretty_name);
+
+      gtk_container_add (GTK_CONTAINER (line), line_hbox);
+      gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
+      gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
+
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
+                                    h[0]->enabled_p);
+      gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
+
+      gtk_widget_show (line_check);
+      gtk_widget_show (line_label);
+      gtk_widget_show (line_hbox);
+      gtk_widget_show (line);
+
       free (pretty_name);
 
       gtk_container_add (GTK_CONTAINER (list), line);
       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
                           (gpointer) pair);
+
+      gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
+                          GTK_SIGNAL_FUNC (list_checkbox_cb),
+                          (gpointer) pair);
+
 #if 0 /* #### */
       GTK_WIDGET (GTK_BIN(line)->child)->style =
         gtk_style_copy (GTK_WIDGET (text_line)->style);
@@ -1183,6 +1419,7 @@ static char *down_arrow_xpm[] = {
   "+   c #D6D6D6",
   "@   c #000000",
 
+  "               ",
   " ------------- ",
   " -+++++++++++@ ",
   "  -+++++++++@  ",
@@ -1205,7 +1442,7 @@ static char *down_arrow_xpm[] = {
 };
 
 static void
-pixmapify_buttons (GtkWidget *toplevel)
+pixmapify_button (GtkWidget *toplevel, int down_p)
 {
   GdkPixmap *pixmap;
   GdkBitmap *mask;
@@ -1213,27 +1450,31 @@ pixmapify_buttons (GtkWidget *toplevel)
   GtkStyle *style;
   GtkWidget *w;
 
-  w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "next"));
+  w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel),
+                                  (down_p ? "next" : "prev")));
   style = gtk_widget_get_style (w);
   mask = 0;
   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
                                          &style->bg[GTK_STATE_NORMAL],
-                                         (gchar **) down_arrow_xpm);
+                                         (down_p
+                                          ? (gchar **) down_arrow_xpm
+                                          : (gchar **) up_arrow_xpm));
   pixmapwid = gtk_pixmap_new (pixmap, mask);
   gtk_widget_show (pixmapwid);
   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
+}
 
-  w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "prev"));
-  style = gtk_widget_get_style (w);
-  mask = 0;
-  pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
-                                         &style->bg[GTK_STATE_NORMAL],
-                                         (gchar **) up_arrow_xpm);
-  pixmapwid = gtk_pixmap_new (pixmap, mask);
-  gtk_widget_show (pixmapwid);
-  gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
-  gtk_container_add (GTK_CONTAINER (w), pixmapwid);
+static void
+map_next_button_cb (GtkWidget *w, gpointer user_data)
+{
+  pixmapify_button (w, 1);
+}
+
+static void
+map_prev_button_cb (GtkWidget *w, gpointer user_data)
+{
+  pixmapify_button (w, 0);
 }
 
 
@@ -1383,7 +1624,8 @@ static void
 populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
 {
   saver_preferences *p = pair->a;
-  screenhack *hack = (which >= 0 ? p->screenhacks[which] : 0);
+  screenhack *hack = (which >= 0 && which < p->screenhacks_count
+                     ? p->screenhacks[which] : 0);
   GtkFrame *frame = GTK_FRAME (name_to_widget (toplevel, "frame"));
   GtkLabel *doc = GTK_LABEL (name_to_widget (toplevel, "doc"));
   GtkEntry *cmd = GTK_ENTRY (name_to_widget (toplevel, "cmd_text"));
@@ -1402,7 +1644,11 @@ populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
   gtk_label_set_text (doc, (doc_string ? doc_string : ""));
   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
   gtk_entry_set_position (cmd, 0);
+
+  updating_enabled_cb++;
   gtk_toggle_button_set_active (enabled, (hack ? hack->enabled_p : False));
+  updating_enabled_cb--;
+
   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
                       (hack
                        ? (hack->visual && *hack->visual
@@ -1421,6 +1667,69 @@ populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
 }
 
 
+static void
+widget_deleter (GtkWidget *widget, gpointer data)
+{
+  /* #### Well, I want to destroy these widgets, but if I do that, they get
+     referenced again, and eventually I get a SEGV.  So instead of
+     destroying them, I'll just hide them, and leak a bunch of memory
+     every time the disk file changes.  Go go go Gtk!
+
+     #### Ok, that's a lie, I get a crash even if I just hide the widget
+     and don't ever delete it.  Fuck!
+   */
+#if 0
+  gtk_widget_destroy (widget);
+#else
+  gtk_widget_hide (widget);
+#endif
+}
+
+
+static int
+maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair)
+{
+  int status = 0;
+  saver_preferences *p =  pair->a;
+
+  static Bool reentrant_lock = False;
+  if (reentrant_lock) return 0;
+  reentrant_lock = True;
+
+  if (init_file_changed_p (p))
+    {
+      const char *f = init_file_name();
+      char *b;
+      int which;
+      GtkList *list;
+
+      if (!f || !*f) return 0;
+      b = (char *) malloc (strlen(f) + 1024);
+      sprintf (b,
+               "Warning:\n\n"
+               "file \"%s\" has changed, reloading.\n",
+               f);
+      warning_dialog (widget, b, False, 100);
+      free (b);
+
+      load_init_file (p);
+
+      which = selected_hack_number (widget);
+      list = GTK_LIST (name_to_widget (widget, "list"));
+      gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
+      populate_hack_list (widget, pair);
+      gtk_list_select_item (list, which);
+      populate_prefs_page (widget, pair);
+      populate_demo_window (widget, which, pair);
+      ensure_selected_item_visible (GTK_WIDGET (list));
+
+      status = 1;
+    }
+
+  reentrant_lock = False;
+  return status;
+}
+
 
 \f
 /* The main demo-mode command loop.
@@ -1495,9 +1804,7 @@ the_network_is_not_the_computer (GtkWidget *parent)
       sprintf (msg,
               "Warning:\n\n"
                "The XScreenSaver daemon doesn't seem to be running\n"
-               "on display \"%s\".  You can launch it by selecting\n"
-               "`Restart Daemon' from the File menu, or by typing\n"
-               "\"xscreensaver &\" in a shell.",
+               "on display \"%s\".  Launch it now?",
               d);
     }
   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
@@ -1514,9 +1821,10 @@ the_network_is_not_the_computer (GtkWidget *parent)
              "the same ~/.xscreensaver file, so %s isn't\n"
              "going to work right.\n"
              "\n"
-             "Either re-run %s as \"%s\", or re-run\n"
-             "xscreensaver as \"%s\" (which you can do by\n"
-              "selecting `Restart Daemon' from the File menu.)\n",
+             "You should either re-run %s as \"%s\", or re-run\n"
+             "xscreensaver as \"%s\".\n"
+              "\n"
+              "Restart the xscreensaver daemon now?\n",
              progname, luser, lhost,
              d,
              (ruser ? ruser : "???"), (rhost ? rhost : "???"),
@@ -1538,8 +1846,7 @@ the_network_is_not_the_computer (GtkWidget *parent)
               "if they don't see the same ~%s/.xscreensaver file) then\n"
               "%s won't work right.\n"
                "\n"
-               "You can restart the daemon on \"%s\" as \"%s\" by\n"
-               "selecting `Restart Daemon' from the File menu.)",
+               "Restart the daemon on \"%s\" as \"%s\" now?\n",
               progname, luser, lhost,
               d,
               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
@@ -1555,7 +1862,9 @@ the_network_is_not_the_computer (GtkWidget *parent)
               "Warning:\n\n"
               "This is %s version %s.\n"
               "But the xscreensaver managing display \"%s\"\n"
-              "is version %s.  This could cause problems.",
+              "is version %s.  This could cause problems.\n"
+              "\n"
+              "Restart the xscreensaver daemon now?\n",
               progname, short_version,
               d,
               rversion);
@@ -1563,7 +1872,7 @@ the_network_is_not_the_computer (GtkWidget *parent)
 
 
   if (*msg)
-    warning_dialog (parent, msg, 1);
+    warning_dialog (parent, msg, True, 1);
 
   free (msg);
 }
@@ -1619,6 +1928,30 @@ static char *defaults[] = {
  0
 };
 
+#if 0
+#ifdef HAVE_CRAPPLET
+static struct poptOption crapplet_options[] = {
+  {NULL, '\0', 0, NULL, 0}
+};
+#endif /* HAVE_CRAPPLET */
+#endif /* 0 */
+
+#define USAGE() \
+  fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n", \
+           real_progname)
+
+
+static void
+map_window_cb (GtkWidget *w, gpointer user_data)
+{
+  Boolean oi = initializing_p;
+  initializing_p = True;
+  eschew_gtk_lossage (w);
+  ensure_selected_item_visible (GTK_WIDGET(name_to_widget(w, "list")));
+  initializing_p = oi;
+}
+
+
 int
 main (int argc, char **argv)
 {
@@ -1633,6 +1966,8 @@ main (int argc, char **argv)
   char *real_progname = argv[0];
   char *s;
 
+  initializing_p = True;
+
   s = strrchr (real_progname, '/');
   if (s) real_progname = s+1;
 
@@ -1671,9 +2006,104 @@ main (int argc, char **argv)
         !strncmp(argv[i], "-display", strlen(argv[i])))
       argv[i] = "--display";
 
+
+  /* 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[i]);
+        USAGE ();
+        exit (1);
+# endif /* !HAVE_CRAPPLET */
+      }
+
   /* Let Gtk open the X connection, then initialize Xt to use that
-     same connection.  Doctor Frankenstein would be proud. */   
-  gtk_init (&argc, &argv);
+     same connection.  Doctor Frankenstein would be proud.
+   */
+# ifdef HAVE_CRAPPLET
+  if (crapplet_p)
+    {
+      GnomeClient *client;
+      GnomeClientFlags flags = 0;
+
+      int init_results = gnome_capplet_init ("screensaver-properties",
+                                             short_version,
+                                             argc, argv, NULL, 0, NULL);
+      /* init_results is:
+         0 upon successful initialization;
+         1 if --init-session-settings was passed on the cmdline;
+         2 if --ignore was passed on the cmdline;
+        -1 on error.
+
+         So the 1 signifies just to init the settings, and quit, basically.
+         (Meaning launch the xscreensaver daemon.)
+       */
+
+      if (init_results < 0)
+        {
+#  if 0
+          g_error ("An initialization error occurred while "
+                   "starting xscreensaver-capplet.\n");
+#  else  /* !0 */
+          fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
+                   real_progname, init_results);
+          exit (1);
+#  endif /* !0 */
+        }
+
+      client = gnome_master_client ();
+
+      if (client)
+        flags = gnome_client_get_flags (client);
+
+      if (flags & GNOME_CLIENT_IS_CONNECTED)
+        {
+          int token =
+            gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
+                                         gnome_client_get_id (client));
+          if (token)
+            {
+              char *session_args[20];
+              int i = 0;
+              session_args[i++] = real_progname;
+              session_args[i++] = "--capplet";
+              session_args[i++] = "--init-session-settings";
+              session_args[i] = 0;
+              gnome_client_set_priority (client, 20);
+              gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
+              gnome_client_set_restart_command (client, i, session_args);
+            }
+          else
+            {
+              gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
+            }
+
+          gnome_client_flush (client);
+        }
+
+      if (init_results == 1)
+       {
+         system ("xscreensaver -nosplash &");
+         return 0;
+       }
+
+    }
+  else
+# endif /* HAVE_CRAPPLET */
+    {
+      gtk_init (&argc, &argv);
+    }
 
 
   /* We must read exactly the same resources as xscreensaver.
@@ -1712,11 +2142,15 @@ main (int argc, char **argv)
        s++;
       if (!strcmp (s, "-prefs"))
        prefs = True;
+      else if (crapplet_p)
+        /* There are lots of random args that we don't care about when we're
+           started as a crapplet, so just ignore unknown args in that case. */
+        ;
       else
        {
-         fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n",
-                  real_progname);
-         exit (1);
+         fprintf (stderr, "%s: unknown option: %s\n", real_progname, argv[i]);
+          USAGE ();
+          exit (1);
        }
     }
 
@@ -1768,6 +2202,7 @@ main (int argc, char **argv)
   /* Create the window and all its widgets.
    */
   gtk_window = create_xscreensaver_demo ();
+  toplevel_widget = gtk_window;
 
   /* Set the window's title. */
   {
@@ -1795,12 +2230,17 @@ main (int argc, char **argv)
   sensitize_demo_widgets (gtk_window, False);
   fix_text_entry_sizes (gtk_window);
   scroll_to_current_hack (gtk_window, pair);
-  gtk_widget_show (gtk_window);
 
-  /* The next three calls must come after gtk_widget_show(). */
-  pixmapify_buttons (gtk_window);
-  eschew_gtk_lossage (gtk_window);
-  ensure_selected_item_visible (GTK_WIDGET(name_to_widget(gtk_window,"list")));
+  gtk_signal_connect (
+              GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "list")),
+              "map", GTK_SIGNAL_FUNC(map_window_cb), 0);
+  gtk_signal_connect (
+              GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "prev")),
+              "map", GTK_SIGNAL_FUNC(map_prev_button_cb), 0);
+  gtk_signal_connect (
+              GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "next")),
+              "map", GTK_SIGNAL_FUNC(map_next_button_cb), 0);
+
 
   /* Handle the -prefs command-line argument. */
   if (prefs)
@@ -1810,8 +2250,42 @@ main (int argc, char **argv)
       gtk_notebook_set_page (notebook, 1);
     }
 
-  /* Issue any warnings about the running xscreensaver daemon. */
-  the_network_is_not_the_computer (gtk_window);
+# ifdef HAVE_CRAPPLET
+  if (crapplet_p)
+    {
+      GtkWidget *capplet;
+      GtkWidget *top_vbox;
+
+      capplet = capplet_widget_new ();
+
+      top_vbox = GTK_BIN (gtk_window)->child;
+
+      gtk_widget_ref (top_vbox);
+      gtk_container_remove (GTK_CONTAINER (gtk_window), top_vbox);
+      GTK_OBJECT_SET_FLAGS (top_vbox, GTK_FLOATING);
+
+      /* In crapplet-mode, take off the menubar. */
+      gtk_widget_hide (name_to_widget (gtk_window, "menubar"));
+
+      gtk_container_add (GTK_CONTAINER (capplet), top_vbox);
+      gtk_widget_show (capplet);
+      gtk_widget_hide (gtk_window);
+
+      /* Hook up the Control Center's redundant Help button, too. */
+      gtk_signal_connect (GTK_OBJECT (capplet), "help",
+                          GTK_SIGNAL_FUNC (doc_menu_cb), 0);
+
+      /* Issue any warnings about the running xscreensaver daemon. */
+      the_network_is_not_the_computer (top_vbox);
+    }
+  else
+# endif /* HAVE_CRAPPLET */
+    {
+      gtk_widget_show (gtk_window);
+
+      /* Issue any warnings about the running xscreensaver daemon. */
+      the_network_is_not_the_computer (gtk_window);
+    }
 
   /* Run the Gtk event loop, and not the Xt event loop.  This means that
      if there were Xt timers or fds registered, they would never get serviced,
@@ -1820,7 +2294,15 @@ main (int argc, char **argv)
      Xt so that we could process the command line and use the X resource
      manager.
    */
-  gtk_main ();
+  initializing_p = False;
+
+# ifdef HAVE_CRAPPLET
+  if (crapplet_p)
+    capplet_gtk_main ();
+  else
+# endif /* HAVE_CRAPPLET */
+    gtk_main ();
+
   exit (0);
 }