"Fossies" - the Fresh Open Source Software Archive

Member "xscreensaver-6.01/driver/demo-Gtk.c" (6 Jun 2021, 157611 Bytes) of package /linux/misc/xscreensaver-6.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "demo-Gtk.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.00_vs_6.01.

    1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
    2  * xscreensaver, Copyright © 1993-2021 Jamie Zawinski <jwz@jwz.org>
    3  *
    4  * Permission to use, copy, modify, distribute, and sell this software and its
    5  * documentation for any purpose is hereby granted without fee, provided that
    6  * the above copyright notice appear in all copies and that both that
    7  * copyright notice and this permission notice appear in supporting
    8  * documentation.  No representations are made about the suitability of this
    9  * software for any purpose.  It is provided "as is" without express or 
   10  * implied warranty.
   11  */
   12 
   13 #ifdef HAVE_CONFIG_H
   14 # include "config.h"
   15 #endif
   16 
   17 #ifdef HAVE_GTK /* whole file */
   18 
   19 #include "blurb.h"
   20 
   21 #include <xscreensaver-intl.h>
   22 
   23 #include <stdlib.h>
   24 
   25 #ifdef HAVE_UNISTD_H
   26 # include <unistd.h>
   27 #endif
   28 
   29 # ifdef __GNUC__
   30 #  define STFU __extension__  /* ignore gcc -pendantic warnings in next sexp */
   31 # else
   32 #  define STFU /* */
   33 # endif
   34 
   35 
   36 #ifdef ENABLE_NLS
   37 # include <locale.h>
   38 #endif /* ENABLE_NLS */
   39 
   40 #ifdef HAVE_UNAME
   41 # include <sys/utsname.h>   /* for uname() */
   42 #endif /* HAVE_UNAME */
   43 
   44 #include <stdio.h>
   45 #include <time.h>
   46 #include <pwd.h>        /* for getpwuid() */
   47 #include <sys/stat.h>
   48 #include <sys/time.h>
   49 
   50 
   51 #include <signal.h>
   52 #include <errno.h>
   53 #ifdef HAVE_SYS_WAIT_H
   54 # include <sys/wait.h>      /* for waitpid() and associated macros */
   55 #endif
   56 
   57 
   58 #include <X11/Xproto.h>     /* for CARD32 */
   59 #include <X11/Xatom.h>      /* for XA_INTEGER */
   60 #include <X11/Intrinsic.h>
   61 #include <X11/StringDefs.h>
   62 
   63 /* We don't actually use any widget internals, but these are included
   64    so that gdb will have debug info for the widgets... */
   65 #include <X11/IntrinsicP.h>
   66 #include <X11/ShellP.h>
   67 
   68 #ifdef HAVE_XINERAMA
   69 # include <X11/extensions/Xinerama.h>
   70 #endif /* HAVE_XINERAMA */
   71 
   72 #if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */
   73 # undef inline
   74 # pragma GCC diagnostic push
   75 # pragma GCC diagnostic ignored "-Wstrict-prototypes"
   76 # pragma GCC diagnostic ignored "-Wlong-long"
   77 # pragma GCC diagnostic ignored "-Wvariadic-macros"
   78 # pragma GCC diagnostic ignored "-Wpedantic"
   79 #endif
   80 
   81 #include <gtk/gtk.h>
   82 
   83 #ifdef HAVE_CRAPPLET
   84 # include <gnome.h>
   85 # include <capplet-widget.h>
   86 #endif
   87 
   88 #include <gdk/gdkx.h>
   89 
   90 #ifdef HAVE_GTK2
   91 # include <gmodule.h>
   92 #else  /* !HAVE_GTK2 */
   93 # define G_MODULE_EXPORT /**/
   94 #endif /* !HAVE_GTK2 */
   95 
   96 #ifndef HAVE_XML
   97  /* Kludge: this is defined in demo-Gtk-conf.c when HAVE_XML.
   98     It is unused otherwise, so in that case, stub it out. */
   99  static const char *hack_configuration_path = 0;
  100 #endif
  101 
  102 #if (__GNUC__ >= 4)
  103 # pragma GCC diagnostic pop
  104 #endif
  105 
  106 
  107 #include "version.h"
  108 #include "types.h"
  109 #include "resources.h"      /* for parse_time() */
  110 #include "remote.h"     /* for xscreensaver_command() */
  111 #include "visual.h"
  112 #include "atoms.h"
  113 #include "usleep.h"
  114 #include "xmu.h"
  115 
  116 #include "logo-50.xpm"
  117 #include "logo-180.xpm"
  118 
  119 #include "demo-Gtk-conf.h"
  120 #include "atoms.h"
  121 
  122 #include <stdio.h>
  123 #include <string.h>
  124 #include <ctype.h>
  125 
  126 #ifdef HAVE_GTK2
  127 enum {
  128   COL_ENABLED,
  129   COL_NAME,
  130   COL_LAST
  131 };
  132 #endif /* HAVE_GTK2 */
  133 
  134 /* Deal with deprecation of direct access to struct fields on the way to GTK3
  135    See http://live.gnome.org/GnomeGoals/UseGseal
  136  */
  137 #if GTK_CHECK_VERSION(2,14,0)
  138 # define GET_PARENT(w)          gtk_widget_get_parent (w)
  139 # define GET_WINDOW(w)          gtk_widget_get_window (w)
  140 # define GET_ACTION_AREA(d)     gtk_dialog_get_action_area (d)
  141 # define GET_CONTENT_AREA(d)    gtk_dialog_get_content_area (d)
  142 # define GET_ADJ_VALUE(a)       gtk_adjustment_get_value (a)
  143 # define SET_ADJ_VALUE(a,v)     gtk_adjustment_set_value (a, v)
  144 # define SET_ADJ_UPPER(a,v)     gtk_adjustment_set_upper (a, v)
  145 #else
  146 # define GET_PARENT(w)          ((w)->parent)
  147 # define GET_WINDOW(w)          ((w)->window)
  148 # define GET_ACTION_AREA(d)     ((d)->action_area)
  149 # define GET_CONTENT_AREA(d)    ((d)->vbox)
  150 # define GET_ADJ_VALUE(a)       ((a)->value)
  151 # define SET_ADJ_VALUE(a,v)     (a)->value = v
  152 # define SET_ADJ_UPPER(a,v)     (a)->upper = v
  153 #endif
  154 
  155 #if GTK_CHECK_VERSION(2,18,0)
  156 # define SET_CAN_DEFAULT(w)     gtk_widget_set_can_default ((w), TRUE)
  157 # define GET_SENSITIVE(w)       gtk_widget_get_sensitive (w)
  158 #else
  159 # define SET_CAN_DEFAULT(w)     GTK_WIDGET_SET_FLAGS ((w), GTK_CAN_DEFAULT)
  160 # define GET_SENSITIVE(w)       GTK_WIDGET_IS_SENSITIVE (w)
  161 #endif
  162 
  163 #if GTK_CHECK_VERSION(2,20,0)
  164 # define GET_REALIZED(w)        gtk_widget_get_realized (w)
  165 #else
  166 # define GET_REALIZED(w)        GTK_WIDGET_REALIZED (w)
  167 #endif
  168 
  169 /* from exec.c */
  170 extern void exec_command (const char *shell, const char *command, int nice);
  171 extern int on_path_p (const char *program);
  172 
  173 static void hack_subproc_environment (Window preview_window_id, Bool debug_p);
  174 
  175 #undef countof
  176 #define countof(x) (sizeof((x))/sizeof((*x)))
  177 
  178 
  179 char *progclass = "XScreenSaver";
  180 XrmDatabase db;
  181 
  182 /* The order of the items in the mode menu. */
  183 static int mode_menu_order[] = {
  184   DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS, RANDOM_HACKS_SAME };
  185 
  186 
  187 typedef struct {
  188 
  189   char *short_version;      /* version number of this xscreensaver build */
  190 
  191   GtkWidget *toplevel_widget;   /* the main window */
  192   GtkWidget *base_widget;   /* root of our hierarchy (for name lookups) */
  193   GtkWidget *popup_widget;  /* the "Settings" dialog */
  194   conf_data *cdata;     /* private data for per-hack configuration */
  195 
  196 #ifdef HAVE_GTK2
  197   GtkBuilder *gtk_ui;           /* UI file */
  198 #endif /* HAVE_GTK2 */
  199 
  200   Bool debug_p;         /* whether to print diagnostics */
  201   Bool initializing_p;      /* flag for breaking recursion loops */
  202   Bool saving_p;        /* flag for breaking recursion loops */
  203 
  204   char *desired_preview_cmd;    /* subprocess we intend to run */
  205   char *running_preview_cmd;    /* subprocess we are currently running */
  206   pid_t running_preview_pid;    /* pid of forked subproc (might be dead) */
  207   Bool running_preview_error_p; /* whether the pid died abnormally */
  208 
  209   Bool preview_suppressed_p;    /* flag meaning "don't launch subproc" */
  210   int subproc_timer_id;     /* timer to delay subproc launch */
  211   int subproc_check_timer_id;   /* timer to check whether it started up */
  212   int subproc_check_countdown;  /* how many more checks left */
  213 
  214   int *list_elt_to_hack_number; /* table for sorting the hack list */
  215   int *hack_number_to_list_elt; /* the inverse table */
  216   Bool *hacks_available_p;  /* whether hacks are on $PATH */
  217   int total_available;      /* how many are on $PATH */
  218   int list_count;       /* how many items are in the list: this may be
  219                                    less than p->screenhacks_count, if some are
  220                                    suppressed. */
  221 
  222   int _selected_list_element;   /* don't use this: call
  223                                    selected_list_element() instead */
  224 
  225   int nscreens;         /* How many X or Xinerama screens there are */
  226 
  227   saver_preferences prefs;
  228 
  229 } state;
  230 
  231 
  232 /* Total fucking evilness due to the fact that it's rocket science to get
  233    a closure object of our own down into the various widget callbacks. */
  234 static state *global_state_kludge;
  235 
  236 static void populate_demo_window (state *, int list_elt);
  237 static void populate_prefs_page (state *);
  238 static void populate_popup_window (state *);
  239 
  240 static Bool flush_dialog_changes_and_save (state *);
  241 static Bool flush_popup_changes_and_save (state *);
  242 
  243 static int maybe_reload_init_file (state *);
  244 static void await_xscreensaver (state *);
  245 static Bool xscreensaver_running_p (state *);
  246 static void sensitize_menu_items (state *s, Bool force_p);
  247 static void force_dialog_repaint (state *s);
  248 
  249 static void schedule_preview (state *, const char *cmd);
  250 static void kill_preview_subproc (state *, Bool reset_p);
  251 static void schedule_preview_check (state *);
  252 
  253 
  254 /* Prototypes of functions used by the Gtk-generated code, to avoid warnings.
  255  */
  256 void exit_menu_cb (GtkAction *, gpointer user_data);
  257 void about_menu_cb (GtkAction *, gpointer user_data);
  258 void doc_menu_cb (GtkAction *, gpointer user_data);
  259 void file_menu_cb (GtkAction *, gpointer user_data);
  260 void activate_menu_cb (GtkAction *, gpointer user_data);
  261 void lock_menu_cb (GtkAction *, gpointer user_data);
  262 void kill_menu_cb (GtkAction *, gpointer user_data);
  263 void restart_menu_cb (GtkWidget *, gpointer user_data);
  264 void run_this_cb (GtkButton *, gpointer user_data);
  265 void manual_cb (GtkButton *, gpointer user_data);
  266 void run_next_cb (GtkButton *, gpointer user_data);
  267 void run_prev_cb (GtkButton *, gpointer user_data);
  268 void pref_changed_cb (GtkWidget *, gpointer user_data);
  269 gboolean pref_changed_event_cb (GtkWidget *, GdkEvent *, gpointer user_data);
  270 void mode_menu_item_cb (GtkWidget *, gpointer user_data);
  271 void switch_page_cb (GtkNotebook *, GtkNotebookPage *, 
  272                      gint page_num, gpointer user_data);
  273 void browse_image_dir_cb (GtkButton *, gpointer user_data);
  274 void browse_text_file_cb (GtkButton *, gpointer user_data);
  275 void browse_text_program_cb (GtkButton *, gpointer user_data);
  276 void settings_cb (GtkButton *, gpointer user_data);
  277 void settings_adv_cb (GtkButton *, gpointer user_data);
  278 void settings_std_cb (GtkButton *, gpointer user_data);
  279 void settings_reset_cb (GtkButton *, gpointer user_data);
  280 void settings_switch_page_cb (GtkNotebook *, GtkNotebookPage *,
  281                               gint page_num, gpointer user_data);
  282 void settings_cancel_cb (GtkButton *, gpointer user_data);
  283 void settings_ok_cb (GtkButton *, gpointer user_data);
  284 void preview_theme_cb (GtkWidget *, gpointer user_data);
  285 
  286 static void kill_gnome_screensaver (void);
  287 static void kill_kde_screensaver (void);
  288 
  289 /* Some random utility functions
  290  */
  291 
  292 static GtkWidget *
  293 name_to_widget (state *s, const char *name)
  294 {
  295   GtkWidget *w;
  296   if (!s) abort();
  297   if (!name) abort();
  298   if (!*name) abort();
  299 
  300 #ifdef HAVE_GTK2
  301   if (!s->gtk_ui)
  302     {
  303       /* First try to load the UI file from the current directory;
  304          if there isn't one there, check the installed directory.
  305        */
  306 # define UI_FILE "xscreensaver.ui"
  307       const char * const files[] = { UI_FILE,
  308                                      DEFAULT_ICONDIR "/" UI_FILE };
  309       int i;
  310 
  311       s->gtk_ui = gtk_builder_new ();
  312 
  313       for (i = 0; i < countof (files); i++)
  314         {
  315           struct stat st;
  316           if (!stat (files[i], &st))
  317             {
  318               GError* error = NULL;
  319 
  320               if (gtk_builder_add_from_file (s->gtk_ui, files[i], &error))
  321                 break;
  322               else
  323                 {
  324                   g_warning ("Couldn't load builder file %s: %s",
  325                  files[i], error->message);
  326                   g_error_free (error);
  327                 }
  328             }
  329         }
  330       if (i >= countof (files))
  331     {
  332       fprintf (stderr,
  333                    "%s: could not load \"" UI_FILE "\"\n"
  334                    "\tfrom " DEFAULT_ICONDIR "/ or current directory.\n",
  335                    blurb());
  336           exit (-1);
  337     }
  338 # undef UI_FILE
  339 
  340       gtk_builder_connect_signals (s->gtk_ui, NULL);
  341     }
  342 
  343   w = GTK_WIDGET (gtk_builder_get_object (s->gtk_ui, name));
  344 
  345 #else /* !HAVE_GTK2 */
  346 
  347   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget),
  348                                          name);
  349   if (w) return w;
  350   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget),
  351                                          name);
  352 #endif /* HAVE_GTK2 */
  353   if (w) return w;
  354 
  355   fprintf (stderr, "%s: no widget \"%s\" (wrong UI file?)\n",
  356            blurb(), name);
  357   abort();
  358 }
  359 
  360 
  361 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
  362    Takes a scroller, viewport, or list as an argument.
  363  */
  364 static void
  365 ensure_selected_item_visible (GtkWidget *widget)
  366 {
  367 #ifdef HAVE_GTK2
  368   GtkTreePath *path;
  369   GtkTreeSelection *selection;
  370   GtkTreeIter iter;
  371   GtkTreeModel *model;
  372   
  373   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
  374   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
  375     path = gtk_tree_path_new_first ();
  376   else
  377     path = gtk_tree_model_get_path (model, &iter);
  378   
  379   gtk_tree_view_set_cursor (GTK_TREE_VIEW (widget), path, NULL, FALSE);
  380 
  381   gtk_tree_path_free (path);
  382 
  383 #else /* !HAVE_GTK2 */
  384 
  385   GtkScrolledWindow *scroller = 0;
  386   GtkViewport *vp = 0;
  387   GtkList *list_widget = 0;
  388   GList *slist;
  389   GList *kids;
  390   int nkids = 0;
  391   GtkWidget *selected = 0;
  392   int list_elt = -1;
  393   GtkAdjustment *adj;
  394   gint parent_h, child_y, child_h, children_h, ignore;
  395   double ratio_t, ratio_b;
  396 
  397   if (GTK_IS_SCROLLED_WINDOW (widget))
  398     {
  399       scroller = GTK_SCROLLED_WINDOW (widget);
  400       vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
  401       list_widget = GTK_LIST (GTK_BIN(vp)->child);
  402     }
  403   else if (GTK_IS_VIEWPORT (widget))
  404     {
  405       vp = GTK_VIEWPORT (widget);
  406       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
  407       list_widget = GTK_LIST (GTK_BIN(vp)->child);
  408     }
  409   else if (GTK_IS_LIST (widget))
  410     {
  411       list_widget = GTK_LIST (widget);
  412       vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
  413       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
  414     }
  415   else
  416     abort();
  417 
  418   slist = list_widget->selection;
  419   selected = (slist ? GTK_WIDGET (slist->data) : 0);
  420   if (!selected)
  421     return;
  422 
  423   list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
  424 
  425   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
  426        kids; kids = kids->next)
  427     nkids++;
  428 
  429   adj = gtk_scrolled_window_get_vadjustment (scroller);
  430 
  431   gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (vp)),
  432                            &ignore, &ignore, &ignore, &parent_h, &ignore);
  433   gdk_window_get_geometry (GET_WINDOW (GTK_WIDGET (selected)),
  434                            &ignore, &child_y, &ignore, &child_h, &ignore);
  435   children_h = nkids * child_h;
  436 
  437   ratio_t = ((double) child_y) / ((double) children_h);
  438   ratio_b = ((double) child_y + child_h) / ((double) children_h);
  439 
  440   if (adj->upper == 0.0)  /* no items in list */
  441     return;
  442 
  443   if (ratio_t < (adj->value / adj->upper) ||
  444       ratio_b > ((adj->value + adj->page_size) / adj->upper))
  445     {
  446       double target;
  447       int slop = parent_h * 0.75; /* how much to overshoot by */
  448 
  449       if (ratio_t < (adj->value / adj->upper))
  450         {
  451           double ratio_w = ((double) parent_h) / ((double) children_h);
  452           double ratio_l = (ratio_b - ratio_t);
  453           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
  454           target += slop;
  455         }
  456       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
  457         {
  458           target = ratio_t * adj->upper;
  459           target -= slop;
  460         }
  461 
  462       if (target > adj->upper - adj->page_size)
  463         target = adj->upper - adj->page_size;
  464       if (target < 0)
  465         target = 0;
  466 
  467       gtk_adjustment_set_value (adj, target);
  468     }
  469 #endif /* !HAVE_GTK2 */
  470 }
  471 
  472 static void
  473 warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
  474 {
  475   GtkWidget *shell = GTK_WIDGET (user_data);
  476   while (GET_PARENT (shell))
  477     shell = GET_PARENT (shell);
  478   gtk_widget_destroy (GTK_WIDGET (shell));
  479 }
  480 
  481 
  482 void restart_menu_cb (GtkWidget *widget, gpointer user_data);
  483 
  484 static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
  485 {
  486   restart_menu_cb (widget, user_data);
  487   warning_dialog_dismiss_cb (widget, user_data);
  488 }
  489 
  490 static void warning_dialog_killg_cb (GtkWidget *widget, gpointer user_data)
  491 {
  492   kill_gnome_screensaver ();
  493   warning_dialog_dismiss_cb (widget, user_data);
  494 }
  495 
  496 static void warning_dialog_killk_cb (GtkWidget *widget, gpointer user_data)
  497 {
  498   kill_kde_screensaver ();
  499   warning_dialog_dismiss_cb (widget, user_data);
  500 }
  501 
  502 typedef enum { D_NONE, D_LAUNCH, D_GNOME, D_KDE } dialog_button;
  503 
  504 static Bool
  505 warning_dialog (GtkWidget *parent, const char *message,
  506                 dialog_button button_type, int center)
  507 {
  508   char *msg = strdup (message);
  509   char *head;
  510 
  511   GtkWidget *dialog = gtk_dialog_new ();
  512   GtkWidget *label = 0;
  513   GtkWidget *ok = 0;
  514   GtkWidget *cancel = 0;
  515   int i = 0;
  516 
  517   while (parent && !GET_WINDOW (parent))
  518     parent = GET_PARENT (parent);
  519 
  520   if (!parent ||
  521       !GET_WINDOW (parent)) /* too early to pop up transient dialogs */
  522     {
  523       fprintf (stderr,
  524                "%s: too early for warning dialog?"
  525                "\n\n\t%s\n\n",
  526                progname, message);
  527       free(msg);
  528       return False;
  529     }
  530 
  531   head = msg;
  532   while (head)
  533     {
  534       char name[20];
  535       char *s = strchr (head, '\n');
  536       if (s) *s = 0;
  537 
  538       sprintf (name, "label%d", i++);
  539 
  540       {
  541         label = gtk_label_new (head);
  542 #ifdef HAVE_GTK2
  543     gtk_label_set_selectable (GTK_LABEL (label), TRUE);
  544 #endif /* HAVE_GTK2 */
  545 
  546 #ifndef HAVE_GTK2
  547         if (i == 1)
  548           {
  549             GTK_WIDGET (label)->style =
  550               gtk_style_copy (GTK_WIDGET (label)->style);
  551             GTK_WIDGET (label)->style->font =
  552               gdk_font_load (get_string_resource("warning_dialog.headingFont",
  553                                                  "Dialog.Font"));
  554             gtk_widget_set_style (GTK_WIDGET (label),
  555                                   GTK_WIDGET (label)->style);
  556           }
  557 #endif /* !HAVE_GTK2 */
  558         if (center <= 0)
  559           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  560         gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
  561                             label, TRUE, TRUE, 0);
  562         gtk_widget_show (label);
  563       }
  564 
  565       if (s)
  566     head = s+1;
  567       else
  568     head = 0;
  569 
  570       center--;
  571     }
  572 
  573   label = gtk_label_new ("");
  574   gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
  575                       label, TRUE, TRUE, 0);
  576   gtk_widget_show (label);
  577 
  578   label = gtk_hbutton_box_new ();
  579   gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))),
  580                       label, TRUE, TRUE, 0);
  581 
  582 #ifdef HAVE_GTK2
  583   if (button_type != D_NONE)
  584     {
  585       cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
  586       gtk_container_add (GTK_CONTAINER (label), cancel);
  587     }
  588 
  589   ok = gtk_button_new_from_stock (GTK_STOCK_OK);
  590   gtk_container_add (GTK_CONTAINER (label), ok);
  591 
  592 #else /* !HAVE_GTK2 */
  593 
  594   ok = gtk_button_new_with_label ("OK");
  595   gtk_container_add (GTK_CONTAINER (label), ok);
  596 
  597   if (button_type != D_NONE)
  598     {
  599       cancel = gtk_button_new_with_label ("Cancel");
  600       gtk_container_add (GTK_CONTAINER (label), cancel);
  601     }
  602 
  603 #endif /* !HAVE_GTK2 */
  604 
  605   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
  606   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
  607   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
  608   SET_CAN_DEFAULT (ok);
  609   gtk_widget_show (ok);
  610   gtk_widget_grab_focus (ok);
  611 
  612   if (cancel)
  613     {
  614       SET_CAN_DEFAULT (cancel);
  615       gtk_widget_show (cancel);
  616     }
  617   gtk_widget_show (label);
  618   gtk_widget_show (dialog);
  619 
  620   if (button_type != D_NONE)
  621     {
  622       GtkSignalFunc fn;
  623       switch (button_type) {
  624       case D_LAUNCH: fn = GTK_SIGNAL_FUNC (warning_dialog_restart_cb); break;
  625       case D_GNOME:  fn = GTK_SIGNAL_FUNC (warning_dialog_killg_cb);   break;
  626       case D_KDE:    fn = GTK_SIGNAL_FUNC (warning_dialog_killk_cb);   break;
  627       default: abort(); break;
  628       }
  629       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", fn, 
  630                                  (gpointer) dialog);
  631       gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
  632                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
  633                                  (gpointer) dialog);
  634     }
  635   else
  636     {
  637       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
  638                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
  639                                  (gpointer) dialog);
  640     }
  641 
  642   gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)),
  643                                 GET_WINDOW (GTK_WIDGET (parent)));
  644 
  645 #ifdef HAVE_GTK2
  646   gtk_window_present (GTK_WINDOW (dialog));
  647 #else  /* !HAVE_GTK2 */
  648   gdk_window_show (GTK_WIDGET (dialog)->window);
  649   gdk_window_raise (GTK_WIDGET (dialog)->window);
  650 #endif /* !HAVE_GTK2 */
  651 
  652   free (msg);
  653   return True;
  654 }
  655 
  656 
  657 static void
  658 run_cmd (state *s, Atom command, int arg)
  659 {
  660   char *err = 0;
  661   int status;
  662 
  663   flush_dialog_changes_and_save (s);
  664   status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
  665 
  666   /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */
  667   if (status < 0 && err && strstr (err, "unexpectedly deleted"))
  668     status = 0;
  669 
  670   if (status < 0)
  671     {
  672       char buf [255];
  673       if (err)
  674         sprintf (buf, "Error:\n\n%s", err);
  675       else
  676         strcpy (buf, "Unknown error!");
  677       warning_dialog (s->toplevel_widget, buf, D_NONE, 100);
  678     }
  679   if (err) free (err);
  680 
  681   sensitize_menu_items (s, True);
  682   force_dialog_repaint (s);
  683 }
  684 
  685 
  686 static void
  687 run_hack (state *s, int list_elt, Bool report_errors_p)
  688 {
  689   int hack_number;
  690   char *err = 0;
  691   int status;
  692 
  693   if (list_elt < 0) return;
  694   hack_number = s->list_elt_to_hack_number[list_elt];
  695 
  696   flush_dialog_changes_and_save (s);
  697   schedule_preview (s, 0);
  698 
  699   status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
  700                                  False, &err);
  701 
  702   if (status < 0 && report_errors_p)
  703     {
  704       if (xscreensaver_running_p (s))
  705         {
  706           /* Kludge: ignore the spurious "window unexpectedly deleted"
  707              errors... */
  708           if (err && strstr (err, "unexpectedly deleted"))
  709             status = 0;
  710 
  711           if (status < 0)
  712             {
  713               char buf [255];
  714               if (err)
  715                 sprintf (buf, "Error:\n\n%s", err);
  716               else
  717                 strcpy (buf, "Unknown error!");
  718               warning_dialog (s->toplevel_widget, buf, D_NONE, 100);
  719             }
  720         }
  721       else
  722         {
  723           /* The error is that the daemon isn't running;
  724              offer to restart it.
  725            */
  726           const char *d = DisplayString (GDK_DISPLAY());
  727           char msg [1024];
  728           sprintf (msg,
  729                    _("Warning:\n\n"
  730                      "The XScreenSaver daemon doesn't seem to be running\n"
  731                      "on display \"%s\".  Launch it now?"),
  732                    d);
  733           warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1);
  734         }
  735     }
  736 
  737   if (err) free (err);
  738 
  739   sensitize_menu_items (s, False);
  740 }
  741 
  742 
  743 
  744 /* Button callbacks
  745 
  746    According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make
  747    GTK work on Cygwin; apparently all GTK callbacks need this magic extra
  748    declaration.  I do not pretend to understand.
  749  */
  750 
  751 G_MODULE_EXPORT void
  752 exit_menu_cb (GtkAction *menu_action, gpointer user_data)
  753 {
  754   state *s = global_state_kludge;  /* I hate C so much... */
  755   flush_dialog_changes_and_save (s);
  756   kill_preview_subproc (s, False);
  757   gtk_main_quit ();
  758 }
  759 
  760 static gboolean
  761 wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
  762 {
  763   state *s = (state *) data;
  764   flush_dialog_changes_and_save (s);
  765   gtk_main_quit ();
  766   return TRUE;
  767 }
  768 
  769 
  770 G_MODULE_EXPORT void
  771 about_menu_cb (GtkAction *menu_action, gpointer user_data)
  772 {
  773 #if 1
  774   /* Let's just pop up the splash dialog instead. */
  775   preview_theme_cb (NULL, user_data);
  776 #else
  777   char msg [2048];
  778   char copy[1024];
  779   char *desc = _("For updates, check https://www.jwz.org/xscreensaver/");
  780 
  781   char *version = strdup (screensaver_id + 17);
  782   char *year = strchr (version, '-');
  783   char *s = strchr (version, ' ');
  784   *s = 0;
  785   year = strchr (year+1, '-') + 1;
  786   s = strchr (year, ')');
  787   *s = 0;
  788 
  789   /* Ole Laursen <olau@hardworking.dk> says "don't use _() here because
  790      non-ASCII characters aren't allowed in localizable string keys."
  791      (I don't want to just use (c) instead of © because that doesn't
  792      look as good in the plain-old default Latin1 "C" locale.)
  793    */
  794 #ifdef HAVE_GTK2
  795   sprintf(copy, ("Copyright \xC2\xA9 1991-%s %s"), year, s);
  796 #else  /* !HAVE_GTK2 */
  797   sprintf(copy, ("Copyright \251 1991-%s %s"), year, s);
  798 #endif /* !HAVE_GTK2 */
  799 
  800   sprintf (msg, "%s\n\n%s", copy, desc);
  801 
  802   /* I can't make gnome_about_new() work here -- it starts dying in
  803      gdk_imlib_get_visual() under gnome_about_new().  If this worked,
  804      then this might be the thing to do:
  805 
  806      #ifdef HAVE_CRAPPLET
  807      {
  808        const gchar *auth[] = { 0 };
  809        GtkWidget *about = gnome_about_new (progclass, version, "", auth, desc,
  810                                            "xscreensaver.xpm");
  811        gtk_widget_show (about);
  812      }
  813      #else / * GTK but not GNOME * /
  814       ...
  815    */
  816   {
  817     GdkColormap *colormap;
  818     GdkPixmap *gdkpixmap;
  819     GdkBitmap *mask;
  820 
  821     GtkWidget *dialog = gtk_dialog_new ();
  822     GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
  823     GSList *proxies = gtk_action_get_proxies (menu_action);
  824     GtkWidget *parent = GTK_WIDGET (proxies->data);
  825     while (GET_PARENT (parent))
  826       parent = GET_PARENT (parent);
  827 
  828     hbox = gtk_hbox_new (FALSE, 20);
  829     gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
  830                         hbox, TRUE, TRUE, 0);
  831 
  832     colormap = gtk_widget_get_colormap (parent);
  833     gdkpixmap =
  834       gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
  835                                              (gchar **) logo_180_xpm);
  836     icon = gtk_pixmap_new (gdkpixmap, mask);
  837     gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
  838 
  839     gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
  840 
  841     vbox = gtk_vbox_new (FALSE, 0);
  842     gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
  843 
  844     label1 = gtk_label_new (version);
  845     gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
  846     gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
  847     gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
  848 
  849 #ifndef HAVE_GTK2
  850     GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
  851     GTK_WIDGET (label1)->style->font =
  852       gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
  853     gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
  854 #endif /* HAVE_GTK2 */
  855 
  856     label2 = gtk_label_new (msg);
  857     gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
  858     gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
  859     gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
  860 
  861 #ifndef HAVE_GTK2
  862     GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
  863     GTK_WIDGET (label2)->style->font =
  864       gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
  865     gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
  866 #endif /* HAVE_GTK2 */
  867 
  868     hb = gtk_hbutton_box_new ();
  869 
  870     gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))),
  871                         hb, TRUE, TRUE, 0);
  872 
  873 #ifdef HAVE_GTK2
  874     ok = gtk_button_new_from_stock (GTK_STOCK_OK);
  875 #else /* !HAVE_GTK2 */
  876     ok = gtk_button_new_with_label (_("OK"));
  877 #endif /* !HAVE_GTK2 */
  878     gtk_container_add (GTK_CONTAINER (hb), ok);
  879 
  880     gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
  881     gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
  882     gtk_window_set_title (GTK_WINDOW (dialog), progclass);
  883 
  884     gtk_widget_show (hbox);
  885     gtk_widget_show (icon);
  886     gtk_widget_show (vbox);
  887     gtk_widget_show (label1);
  888     gtk_widget_show (label2);
  889     gtk_widget_show (hb);
  890     gtk_widget_show (ok);
  891     gtk_widget_show (dialog);
  892 
  893     gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
  894                                GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
  895                                (gpointer) dialog);
  896     gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)),
  897                                   GET_WINDOW (GTK_WIDGET (parent)));
  898     gdk_window_show (GET_WINDOW (GTK_WIDGET (dialog)));
  899     gdk_window_raise (GET_WINDOW (GTK_WIDGET (dialog)));
  900   }
  901 #endif /* 0 */
  902 }
  903 
  904 
  905 G_MODULE_EXPORT void
  906 doc_menu_cb (GtkAction *menu_action, gpointer user_data)
  907 {
  908   state *s = global_state_kludge;  /* I hate C so much... */
  909   saver_preferences *p = &s->prefs;
  910   char *help_command;
  911 
  912   if (!p->help_url || !*p->help_url)
  913     {
  914       warning_dialog (s->toplevel_widget,
  915                       _("Error:\n\n"
  916             "No Help URL has been specified.\n"), D_NONE, 100);
  917       return;
  918     }
  919 
  920   help_command = (char *) malloc (strlen (p->load_url_command) +
  921                   (strlen (p->help_url) * 5) + 20);
  922   strcpy (help_command, "( ");
  923   sprintf (help_command + strlen(help_command),
  924            p->load_url_command,
  925            p->help_url, p->help_url, p->help_url, p->help_url, p->help_url);
  926   strcat (help_command, " ) &");
  927   if (system (help_command) < 0)
  928     fprintf (stderr, "%s: fork error\n", blurb());
  929   free (help_command);
  930 }
  931 
  932 
  933 G_MODULE_EXPORT void
  934 file_menu_cb (GtkAction *menu_action, gpointer user_data)
  935 {
  936   state *s = global_state_kludge;  /* I hate C so much... */
  937   sensitize_menu_items (s, False);
  938 }
  939 
  940 
  941 G_MODULE_EXPORT void
  942 activate_menu_cb (GtkAction *menu_action, gpointer user_data)
  943 {
  944   state *s = global_state_kludge;  /* I hate C so much... */
  945   run_cmd (s, XA_ACTIVATE, 0);
  946 }
  947 
  948 
  949 G_MODULE_EXPORT void
  950 lock_menu_cb (GtkAction *menu_action, gpointer user_data)
  951 {
  952   state *s = global_state_kludge;  /* I hate C so much... */
  953   run_cmd (s, XA_LOCK, 0);
  954 }
  955 
  956 
  957 G_MODULE_EXPORT void
  958 kill_menu_cb (GtkAction *menu_action, gpointer user_data)
  959 {
  960   state *s = global_state_kludge;  /* I hate C so much... */
  961   run_cmd (s, XA_EXIT, 0);
  962 }
  963 
  964 
  965 G_MODULE_EXPORT void
  966 restart_menu_cb (GtkWidget *widget, gpointer user_data)
  967 {
  968   state *s = global_state_kludge;  /* I hate C so much... */
  969   flush_dialog_changes_and_save (s);
  970   xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
  971   sleep (1);
  972   if (system ("xscreensaver -splash &") < 0)
  973     fprintf (stderr, "%s: fork error\n", blurb());
  974 
  975   await_xscreensaver (s);
  976 }
  977 
  978 static Bool
  979 xscreensaver_running_p (state *s)
  980 {
  981   Display *dpy = GDK_DISPLAY();
  982   char *rversion = 0;
  983   server_xscreensaver_version (dpy, &rversion, 0, 0);
  984   if (!rversion)
  985     return False;
  986   free (rversion);
  987   return True;
  988 }
  989 
  990 static void
  991 await_xscreensaver (state *s)
  992 {
  993   int countdown = 5;
  994   Bool ok = False;
  995 
  996   while (!ok && (--countdown > 0))
  997     if (xscreensaver_running_p (s))
  998       ok = True;
  999     else
 1000       sleep (1);    /* If it's not there yet, wait a second... */
 1001 
 1002   sensitize_menu_items (s, True);
 1003 
 1004   if (! ok)
 1005     {
 1006       /* Timed out, no screensaver running. */
 1007 
 1008       char buf [1024];
 1009       Bool root_p = (geteuid () == 0);
 1010       
 1011       strcpy (buf, 
 1012               _("Error:\n\n"
 1013         "The xscreensaver daemon did not start up properly.\n"
 1014         "\n"));
 1015 
 1016       if (root_p)
 1017         strcat (buf, STFU
 1018       _("You are running as root.  This usually means that xscreensaver\n"
 1019             "was unable to contact your X server because access control is\n"
 1020             "turned on."
 1021 /*
 1022             "  Try running this command:\n"
 1023             "\n"
 1024             "                        xhost +localhost\n"
 1025             "\n"
 1026             "and then selecting `File / Restart Daemon'.\n"
 1027             "\n"
 1028             "Note that turning off access control will allow anyone logged\n"
 1029             "on to this machine to access your screen, which might be\n"
 1030             "considered a security problem.  Please read the xscreensaver\n"
 1031             "manual and FAQ for more information.\n"
 1032  */
 1033             "\n"
 1034             "You shouldn't run X as root. Instead, you should log in as a\n"
 1035             "normal user, and `sudo' as necessary."));
 1036       else
 1037         strcat (buf, _("Please check your $PATH and permissions."));
 1038 
 1039       warning_dialog (s->toplevel_widget, buf, D_NONE, 1);
 1040     }
 1041 
 1042   force_dialog_repaint (s);
 1043 }
 1044 
 1045 
 1046 static int
 1047 selected_list_element (state *s)
 1048 {
 1049   return s->_selected_list_element;
 1050 }
 1051 
 1052 
 1053 static int
 1054 demo_write_init_file (state *s, saver_preferences *p)
 1055 {
 1056   Display *dpy = GDK_DISPLAY();
 1057 
 1058   if (!write_init_file (dpy, p, s->short_version, False))
 1059     {
 1060       if (s->debug_p)
 1061         fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
 1062       return 0;
 1063     }
 1064   else
 1065     {
 1066       const char *f = init_file_name();
 1067       if (!f || !*f)
 1068         warning_dialog (s->toplevel_widget,
 1069                         _("Error:\n\nCouldn't determine init file name!\n"),
 1070                         D_NONE, 100);
 1071       else
 1072         {
 1073           char *b = (char *) malloc (strlen(f) + 1024);
 1074           sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
 1075           warning_dialog (s->toplevel_widget, b, D_NONE, 100);
 1076           free (b);
 1077         }
 1078       return -1;
 1079     }
 1080 }
 1081 
 1082 
 1083 G_MODULE_EXPORT void
 1084 run_this_cb (GtkButton *button, gpointer user_data)
 1085 {
 1086   state *s = global_state_kludge;  /* I hate C so much... */
 1087   int list_elt = selected_list_element (s);
 1088   if (list_elt < 0) return;
 1089   if (!flush_dialog_changes_and_save (s))
 1090     run_hack (s, list_elt, True);
 1091 }
 1092 
 1093 
 1094 G_MODULE_EXPORT void
 1095 manual_cb (GtkButton *button, gpointer user_data)
 1096 {
 1097   Display *dpy = GDK_DISPLAY();
 1098   state *s = global_state_kludge;  /* I hate C so much... */
 1099   saver_preferences *p = &s->prefs;
 1100   GtkWidget *list_widget = name_to_widget (s, "list");
 1101   int list_elt = selected_list_element (s);
 1102   int hack_number;
 1103   char *name, *name2, *cmd, *str;
 1104   char *oname = 0;
 1105   if (list_elt < 0) return;
 1106   hack_number = s->list_elt_to_hack_number[list_elt];
 1107 
 1108   flush_dialog_changes_and_save (s);
 1109   ensure_selected_item_visible (list_widget);
 1110 
 1111   name = strdup (p->screenhacks[hack_number]->command);
 1112   name2 = name;
 1113   oname = name;
 1114   while (isspace (*name2)) name2++;
 1115   str = name2;
 1116   while (*str && !isspace (*str)) str++;
 1117   *str = 0;
 1118   str = strrchr (name2, '/');
 1119   if (str) name2 = str+1;
 1120 
 1121   cmd = get_string_resource (dpy, "manualCommand", "ManualCommand");
 1122   if (cmd)
 1123     {
 1124       char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
 1125       strcpy (cmd2, "( ");
 1126       sprintf (cmd2 + strlen (cmd2),
 1127                cmd,
 1128                name2, name2, name2, name2);
 1129       strcat (cmd2, " ) &");
 1130       if (system (cmd2) < 0)
 1131         fprintf (stderr, "%s: fork error\n", blurb());
 1132       free (cmd2);
 1133     }
 1134   else
 1135     {
 1136       warning_dialog (GTK_WIDGET (button),
 1137                       _("Error:\n\nno `manualCommand' resource set."),
 1138                       D_NONE, 100);
 1139     }
 1140 
 1141   free (oname);
 1142 }
 1143 
 1144 
 1145 static void
 1146 force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
 1147 {
 1148   GtkWidget *parent = name_to_widget (s, "scroller");
 1149   gboolean was = GET_SENSITIVE (parent);
 1150 #ifdef HAVE_GTK2
 1151   GtkTreeIter iter;
 1152   GtkTreeModel *model;
 1153   GtkTreeSelection *selection;
 1154 #endif /* HAVE_GTK2 */
 1155 
 1156   if (!was) gtk_widget_set_sensitive (parent, True);
 1157 #ifdef HAVE_GTK2
 1158   model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
 1159   if (!model) abort();
 1160   if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
 1161     {
 1162       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
 1163       gtk_tree_selection_select_iter (selection, &iter);
 1164     }
 1165 #else  /* !HAVE_GTK2 */
 1166   gtk_list_select_item (GTK_LIST (list), list_elt);
 1167 #endif /* !HAVE_GTK2 */
 1168   if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
 1169   if (!was) gtk_widget_set_sensitive (parent, False);
 1170 }
 1171 
 1172 
 1173 G_MODULE_EXPORT void
 1174 run_next_cb (GtkButton *button, gpointer user_data)
 1175 {
 1176   state *s = global_state_kludge;  /* I hate C so much... */
 1177   /* saver_preferences *p = &s->prefs; */
 1178   Bool ops = s->preview_suppressed_p;
 1179 
 1180   GtkWidget *list_widget = name_to_widget (s, "list");
 1181   int list_elt = selected_list_element (s);
 1182 
 1183   if (list_elt < 0)
 1184     list_elt = 0;
 1185   else
 1186     list_elt++;
 1187 
 1188   if (list_elt >= s->list_count)
 1189     list_elt = 0;
 1190 
 1191   s->preview_suppressed_p = True;
 1192 
 1193   flush_dialog_changes_and_save (s);
 1194   force_list_select_item (s, list_widget, list_elt, True);
 1195   populate_demo_window (s, list_elt);
 1196   run_hack (s, list_elt, False);
 1197 
 1198   s->preview_suppressed_p = ops;
 1199 }
 1200 
 1201 
 1202 G_MODULE_EXPORT void
 1203 run_prev_cb (GtkButton *button, gpointer user_data)
 1204 {
 1205   state *s = global_state_kludge;  /* I hate C so much... */
 1206   /* saver_preferences *p = &s->prefs; */
 1207   Bool ops = s->preview_suppressed_p;
 1208 
 1209   GtkWidget *list_widget = name_to_widget (s, "list");
 1210   int list_elt = selected_list_element (s);
 1211 
 1212   if (list_elt < 0)
 1213     list_elt = s->list_count - 1;
 1214   else
 1215     list_elt--;
 1216 
 1217   if (list_elt < 0)
 1218     list_elt = s->list_count - 1;
 1219 
 1220   s->preview_suppressed_p = True;
 1221 
 1222   flush_dialog_changes_and_save (s);
 1223   force_list_select_item (s, list_widget, list_elt, True);
 1224   populate_demo_window (s, list_elt);
 1225   run_hack (s, list_elt, False);
 1226 
 1227   s->preview_suppressed_p = ops;
 1228 }
 1229 
 1230 
 1231 /* Writes the given settings into prefs.
 1232    Returns true if there was a change, False otherwise.
 1233    command and/or visual may be 0, or enabled_p may be -1, meaning "no change".
 1234  */
 1235 static Bool
 1236 flush_changes (state *s,
 1237                int list_elt,
 1238                int enabled_p,
 1239                const char *command,
 1240                const char *visual)
 1241 {
 1242   saver_preferences *p = &s->prefs;
 1243   Bool changed = False;
 1244   screenhack *hack;
 1245   int hack_number;
 1246   if (list_elt < 0 || list_elt >= s->list_count)
 1247     abort();
 1248 
 1249   hack_number = s->list_elt_to_hack_number[list_elt];
 1250   hack = p->screenhacks[hack_number];
 1251 
 1252   if (enabled_p != -1 &&
 1253       enabled_p != hack->enabled_p)
 1254     {
 1255       hack->enabled_p = enabled_p;
 1256       changed = True;
 1257       if (s->debug_p)
 1258         fprintf (stderr, "%s: \"%s\": enabled => %d\n",
 1259                  blurb(), hack->name, enabled_p);
 1260     }
 1261 
 1262   if (command)
 1263     {
 1264       if (!hack->command || !!strcmp (command, hack->command))
 1265         {
 1266           if (hack->command) free (hack->command);
 1267           hack->command = strdup (command);
 1268           changed = True;
 1269           if (s->debug_p)
 1270             fprintf (stderr, "%s: \"%s\": command => \"%s\"\n",
 1271                      blurb(), hack->name, command);
 1272         }
 1273     }
 1274 
 1275   if (visual)
 1276     {
 1277       const char *ov = hack->visual;
 1278       if (!ov || !*ov) ov = "any";
 1279       if (!*visual) visual = "any";
 1280       if (!!strcasecmp (visual, ov))
 1281         {
 1282           if (hack->visual) free (hack->visual);
 1283           hack->visual = strdup (visual);
 1284           changed = True;
 1285           if (s->debug_p)
 1286             fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n",
 1287                      blurb(), hack->name, visual);
 1288         }
 1289     }
 1290 
 1291   return changed;
 1292 }
 1293 
 1294 
 1295 /* Helper for the text fields that contain time specifications:
 1296    this parses the text, and does error checking.
 1297  */
 1298 static void 
 1299 hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
 1300 {
 1301   if (*line)
 1302     {
 1303       int value;
 1304       if (!sec_p || strchr (line, ':'))
 1305         value = parse_time ((char *) line, sec_p, True);
 1306       else
 1307         {
 1308           char c;
 1309           if (sscanf (line, "%d%c", &value, &c) != 1)
 1310             value = -1;
 1311           if (!sec_p)
 1312             value *= 60;
 1313         }
 1314 
 1315       value *= 1000;    /* Time measures in microseconds */
 1316       if (value < 0)
 1317     {
 1318       char b[255];
 1319       sprintf (b,
 1320            _("Error:\n\n"
 1321              "Unparsable time format: \"%s\"\n"),
 1322            line);
 1323       warning_dialog (s->toplevel_widget, b, D_NONE, 100);
 1324     }
 1325       else
 1326     *store = value;
 1327     }
 1328 }
 1329 
 1330 
 1331 static Bool
 1332 directory_p (const char *path)
 1333 {
 1334   struct stat st;
 1335   if (!path || !*path)
 1336     return False;
 1337   else if (stat (path, &st))
 1338     return False;
 1339   else if (!S_ISDIR (st.st_mode))
 1340     return False;
 1341   else
 1342     return True;
 1343 }
 1344 
 1345 static Bool
 1346 file_p (const char *path)
 1347 {
 1348   struct stat st;
 1349   if (!path || !*path)
 1350     return False;
 1351   else if (stat (path, &st))
 1352     return False;
 1353   else if (S_ISDIR (st.st_mode))
 1354     return False;
 1355   else
 1356     return True;
 1357 }
 1358 
 1359 static char *
 1360 normalize_directory (const char *path)
 1361 {
 1362   int L;
 1363   char *p2, *s;
 1364   if (!path || !*path) return 0;
 1365   L = strlen (path);
 1366   p2 = (char *) malloc (L + 2);
 1367   strcpy (p2, path);
 1368   if (p2[L-1] == '/')  /* remove trailing slash */
 1369     p2[--L] = 0;
 1370 
 1371   for (s = p2; s && *s; s++)
 1372     {
 1373       if (*s == '/' &&
 1374           (!strncmp (s, "/../", 4) ||           /* delete "XYZ/../" */
 1375            !strncmp (s, "/..\000", 4)))         /* delete "XYZ/..$" */
 1376         {
 1377           char *s0 = s;
 1378           while (s0 > p2 && s0[-1] != '/')
 1379             s0--;
 1380           if (s0 > p2)
 1381             {
 1382               s0--;
 1383               s += 3;
 1384               /* strcpy (s0, s); */
 1385               memmove(s0, s, strlen(s) + 1);
 1386               s = s0-1;
 1387             }
 1388         }
 1389       else if (*s == '/' && !strncmp (s, "/./", 3)) {   /* delete "/./" */
 1390         /* strcpy (s, s+2), s--; */
 1391         memmove(s, s+2, strlen(s+2) + 1);
 1392         s--;
 1393        }
 1394       else if (*s == '/' && !strncmp (s, "/.\000", 3))  /* delete "/.$" */
 1395         *s = 0, s--;
 1396     }
 1397 
 1398   /*
 1399     Normalize consecutive slashes.
 1400     Ignore doubled slashes after ":" to avoid mangling URLs.
 1401   */
 1402 
 1403   for (s = p2; s && *s; s++){
 1404     if (*s == ':') continue;
 1405     if (!s[1] || !s[2]) continue;
 1406     while (s[1] == '/' && s[2] == '/')
 1407       /* strcpy (s+1, s+2); */
 1408       memmove (s+1, s+2, strlen(s+2) + 1);
 1409   }
 1410 
 1411   /* and strip trailing whitespace for good measure. */
 1412   L = strlen(p2);
 1413   while (isspace(p2[L-1]))
 1414     p2[--L] = 0;
 1415 
 1416   return p2;
 1417 }
 1418 
 1419 
 1420 #ifdef HAVE_GTK2
 1421 
 1422 typedef struct {
 1423   state *s;
 1424   int i;
 1425   Bool *changed;
 1426 } FlushForeachClosure;
 1427 
 1428 static gboolean
 1429 flush_checkbox  (GtkTreeModel *model,
 1430          GtkTreePath *path,
 1431          GtkTreeIter *iter,
 1432          gpointer data)
 1433 {
 1434   FlushForeachClosure *closure = data;
 1435   gboolean checked;
 1436 
 1437   gtk_tree_model_get (model, iter,
 1438               COL_ENABLED, &checked,
 1439               -1);
 1440 
 1441   if (flush_changes (closure->s, closure->i,
 1442              checked, 0, 0))
 1443     *closure->changed = True;
 1444   
 1445   closure->i++;
 1446 
 1447   /* don't remove row */
 1448   return FALSE;
 1449 }
 1450 
 1451 #endif /* HAVE_GTK2 */
 1452 
 1453 
 1454 static char *
 1455 theme_name_strip (const char *s)
 1456 {
 1457   const char *in = s;
 1458   char *s2 = strdup(s);
 1459   char *out = s2;
 1460   for (; *in; in++)
 1461     if (*in >= 'A' && *in <= 'Z')
 1462       *out++ = *in + ('a'-'A');
 1463     else if (*in == ' ' || *in == '\t')
 1464       ;
 1465     else
 1466       *out++ = *in;
 1467   *out = 0;
 1468   return s2;
 1469 }
 1470 
 1471 
 1472 /* Flush out any changes made in the main dialog window (where changes
 1473    take place immediately: clicking on a checkbox causes the init file
 1474    to be written right away.)
 1475  */
 1476 static Bool
 1477 flush_dialog_changes_and_save (state *s)
 1478 {
 1479   Display *dpy = GDK_DISPLAY();
 1480   saver_preferences *p = &s->prefs;
 1481   saver_preferences P2, *p2 = &P2;
 1482 #ifdef HAVE_GTK2
 1483   GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
 1484   GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
 1485   FlushForeachClosure closure;
 1486 #else /* !HAVE_GTK2 */
 1487   GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
 1488   GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
 1489   int i;
 1490 #endif /* !HAVE_GTK2 */
 1491   static Bool already_warned_about_missing_image_directory = False; /* very long name... */
 1492 
 1493   Bool changed = False;
 1494   GtkWidget *w;
 1495 
 1496   if (s->saving_p) return False;
 1497   s->saving_p = True;
 1498 
 1499   *p2 = *p;
 1500 
 1501   /* Flush any checkbox changes in the list down into the prefs struct.
 1502    */
 1503 #ifdef HAVE_GTK2
 1504   closure.s = s;
 1505   closure.changed = &changed;
 1506   closure.i = 0;
 1507   gtk_tree_model_foreach (model, flush_checkbox, &closure);
 1508 
 1509 #else /* !HAVE_GTK2 */
 1510 
 1511   for (i = 0; kids; kids = kids->next, i++)
 1512     {
 1513       GtkWidget *line = GTK_WIDGET (kids->data);
 1514       GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
 1515       GtkWidget *line_check =
 1516         GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
 1517       Bool checked =
 1518         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check));
 1519 
 1520       if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
 1521         changed = True;
 1522     }
 1523 #endif /* ~HAVE_GTK2 */
 1524 
 1525   /* Flush the non-hack-specific settings down into the prefs struct.
 1526    */
 1527 
 1528 # define SECONDS(FIELD,NAME) \
 1529     w = name_to_widget (s, (NAME)); \
 1530     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True)
 1531 
 1532 # define MINUTES(FIELD,NAME) \
 1533     w = name_to_widget (s, (NAME)); \
 1534     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False)
 1535 
 1536 # define CHECKBOX(FIELD,NAME) \
 1537     w = name_to_widget (s, (NAME)); \
 1538     (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w))
 1539 
 1540 # define PATHNAME(FIELD,NAME) \
 1541     w = name_to_widget (s, (NAME)); \
 1542     (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
 1543 
 1544 # define TEXT(FIELD,NAME) \
 1545     w = name_to_widget (s, (NAME)); \
 1546     (FIELD) = (char *) g_strdup(gtk_entry_get_text (GTK_ENTRY (w)))
 1547 
 1548   MINUTES  (&p2->timeout,         "timeout_spinbutton");
 1549   MINUTES  (&p2->cycle,           "cycle_spinbutton");
 1550   CHECKBOX (p2->lock_p,           "lock_button");
 1551   MINUTES  (&p2->lock_timeout,    "lock_spinbutton");
 1552 
 1553   CHECKBOX (p2->dpms_enabled_p,   "dpms_button");
 1554   CHECKBOX (p2->dpms_quickoff_p,  "dpms_quickoff_button");
 1555   MINUTES  (&p2->dpms_standby,    "dpms_standby_spinbutton");
 1556   MINUTES  (&p2->dpms_suspend,    "dpms_suspend_spinbutton");
 1557   MINUTES  (&p2->dpms_off,        "dpms_off_spinbutton");
 1558 
 1559   CHECKBOX (p2->grab_desktop_p,   "grab_desk_button");
 1560   CHECKBOX (p2->grab_video_p,     "grab_video_button");
 1561   CHECKBOX (p2->random_image_p,   "grab_image_button");
 1562   PATHNAME (p2->image_directory,  "image_text");
 1563 
 1564 #if 0
 1565   CHECKBOX (p2->verbose_p,        "verbose_button");
 1566   CHECKBOX (p2->splash_p,         "splash_button");
 1567 #endif
 1568 
 1569   {
 1570     Bool v = False;
 1571     CHECKBOX (v, "text_host_radio");     if (v) p2->tmode = TEXT_DATE;
 1572     CHECKBOX (v, "text_radio");          if (v) p2->tmode = TEXT_LITERAL;
 1573     CHECKBOX (v, "text_file_radio");     if (v) p2->tmode = TEXT_FILE;
 1574     CHECKBOX (v, "text_program_radio");  if (v) p2->tmode = TEXT_PROGRAM;
 1575     CHECKBOX (v, "text_url_radio");      if (v) p2->tmode = TEXT_URL;
 1576     TEXT     (p2->text_literal, "text_entry");
 1577     PATHNAME (p2->text_file,    "text_file_entry");
 1578     PATHNAME (p2->text_program, "text_program_entry");
 1579     PATHNAME (p2->text_program, "text_program_entry");
 1580     TEXT     (p2->text_url,     "text_url_entry");
 1581   }
 1582 
 1583   CHECKBOX (p2->fade_p,           "fade_button");
 1584   CHECKBOX (p2->unfade_p,         "unfade_button");
 1585   SECONDS  (&p2->fade_seconds,    "fade_spinbutton");
 1586 
 1587 # undef SECONDS
 1588 # undef MINUTES
 1589 # undef CHECKBOX
 1590 # undef PATHNAME
 1591 # undef TEXT
 1592 
 1593   /* Warn if the image directory doesn't exist, when:
 1594      - not being warned before
 1595      - image directory is changed and the directory doesn't exist
 1596      - image directory is not a URL
 1597    */
 1598   if (p2->image_directory &&
 1599       *p2->image_directory &&
 1600       !directory_p (p2->image_directory) &&
 1601        strncmp(p2->image_directory, "http://", 7) &&
 1602        strncmp(p2->image_directory, "https://", 8) &&
 1603         ( !already_warned_about_missing_image_directory ||
 1604           ( p->image_directory &&
 1605             *p->image_directory &&
 1606             strcmp(p->image_directory, p2->image_directory)
 1607           )
 1608         )
 1609       )
 1610     {
 1611       char b[255];
 1612       sprintf (b, "Warning:\n\n" "Directory does not exist: \"%s\"\n",
 1613                p2->image_directory);
 1614       if (warning_dialog (s->toplevel_widget, b, D_NONE, 100))
 1615         already_warned_about_missing_image_directory = True;
 1616     }
 1617 
 1618 
 1619   /* Map the mode menu to `saver_mode' enum values. */
 1620   {
 1621     GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
 1622     int menu_elt = gtk_combo_box_get_active (opt);
 1623     if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
 1624     p2->mode = mode_menu_order[menu_elt];
 1625   }
 1626 
 1627   if (p2->mode == ONE_HACK)
 1628     {
 1629       int list_elt = selected_list_element (s);
 1630       p2->selected_hack = (list_elt >= 0
 1631                            ? s->list_elt_to_hack_number[list_elt]
 1632                            : -1);
 1633     }
 1634 
 1635   /* Theme menu. */
 1636   {
 1637     GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu"));
 1638     char *themes = get_string_resource (dpy, "themeNames", "ThemeNames");
 1639     int menu_index = gtk_combo_box_get_active (cbox);
 1640     char *token = themes;
 1641     char *name, *last;
 1642     int i = 0;
 1643     while ((name = strtok_r (token, ",", &last)))
 1644       {
 1645         token = 0;
 1646         if (i == menu_index)
 1647           {
 1648             char *name2 = theme_name_strip (name);
 1649             if (p->dialog_theme) free (p->dialog_theme);
 1650             p2->dialog_theme = name2;
 1651           }
 1652         i++;
 1653       }
 1654   }
 1655 
 1656 # define COPY(field, name) \
 1657   if (p->field != p2->field) { \
 1658     changed = True; \
 1659     if (s->debug_p) \
 1660       fprintf (stderr, "%s: %s => %ld\n", blurb(), \
 1661                name, (unsigned long) p2->field);   \
 1662   } \
 1663   p->field = p2->field
 1664 
 1665   COPY(mode,             "mode");
 1666   COPY(selected_hack,    "selected_hack");
 1667 
 1668   COPY(timeout,        "timeout");
 1669   COPY(cycle,          "cycle");
 1670   COPY(lock_p,         "lock_p");
 1671   COPY(lock_timeout,   "lock_timeout");
 1672 
 1673   COPY(dpms_enabled_p,  "dpms_enabled_p");
 1674   COPY(dpms_quickoff_p, "dpms_quickoff_enabled_p");
 1675   COPY(dpms_standby,    "dpms_standby");
 1676   COPY(dpms_suspend,    "dpms_suspend");
 1677   COPY(dpms_off,        "dpms_off");
 1678 
 1679 #if 0
 1680   COPY(verbose_p,        "verbose_p");
 1681   COPY(splash_p,         "splash_p");
 1682 #endif
 1683 
 1684   COPY(tmode,            "tmode");
 1685 
 1686   COPY(install_cmap_p,   "install_cmap_p");
 1687   COPY(fade_p,           "fade_p");
 1688   COPY(unfade_p,         "unfade_p");
 1689   COPY(fade_seconds,     "fade_seconds");
 1690 
 1691   COPY(grab_desktop_p, "grab_desktop_p");
 1692   COPY(grab_video_p,   "grab_video_p");
 1693   COPY(random_image_p, "random_image_p");
 1694 
 1695   COPY(dialog_theme,   "dialog_theme");
 1696 # undef COPY
 1697 
 1698 # define COPYSTR(FIELD,NAME) \
 1699   if (!p->FIELD || \
 1700       !p2->FIELD || \
 1701       strcmp(p->FIELD, p2->FIELD)) \
 1702     { \
 1703       changed = True; \
 1704       if (s->debug_p) \
 1705         fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
 1706     } \
 1707   if (p->FIELD && p->FIELD != p2->FIELD) \
 1708     free (p->FIELD); \
 1709   p->FIELD = p2->FIELD; \
 1710   p2->FIELD = 0
 1711 
 1712   COPYSTR(image_directory, "image_directory");
 1713   COPYSTR(text_literal,    "text_literal");
 1714   COPYSTR(text_file,       "text_file");
 1715   COPYSTR(text_program,    "text_program");
 1716   COPYSTR(text_url,        "text_url");
 1717 # undef COPYSTR
 1718 
 1719   populate_prefs_page (s);
 1720 
 1721   if (changed)
 1722     {
 1723       sync_server_dpms_settings (GDK_DISPLAY(), p);
 1724       changed = demo_write_init_file (s, p);
 1725     }
 1726 
 1727   s->saving_p = False;
 1728   return changed;
 1729 }
 1730 
 1731 
 1732 /* Flush out any changes made in the popup dialog box (where changes
 1733    take place only when the OK button is clicked.)
 1734  */
 1735 static Bool
 1736 flush_popup_changes_and_save (state *s)
 1737 {
 1738   Bool changed = False;
 1739   saver_preferences *p = &s->prefs;
 1740   int list_elt = selected_list_element (s);
 1741 
 1742   GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
 1743   GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo"));
 1744   GtkEntry *visent = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis)));
 1745 
 1746   const char *visual = gtk_entry_get_text (visent);
 1747   const char *command = gtk_entry_get_text (cmd);
 1748 
 1749   char c;
 1750   unsigned long id;
 1751 
 1752   if (s->saving_p) return False;
 1753   s->saving_p = True;
 1754 
 1755   if (list_elt < 0)
 1756     goto DONE;
 1757 
 1758   if (maybe_reload_init_file (s) != 0)
 1759     {
 1760       changed = True;
 1761       goto DONE;
 1762     }
 1763 
 1764   /* Sanity-check and canonicalize whatever the user typed into the combo box.
 1765    */
 1766   if      (!strcasecmp (visual, ""))                   visual = "";
 1767   else if (!strcasecmp (visual, "any"))                visual = "";
 1768   else if (!strcasecmp (visual, "default"))            visual = "Default";
 1769   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
 1770   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
 1771   else if (!strcasecmp (visual, "best"))               visual = "Best";
 1772   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
 1773   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
 1774   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
 1775   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
 1776   else if (!strcasecmp (visual, "color"))              visual = "Color";
 1777   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
 1778   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
 1779   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
 1780   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
 1781   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
 1782   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
 1783   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
 1784   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
 1785   else if (1 == sscanf (visual, " %lu %c", &id, &c))   ;
 1786   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
 1787   else
 1788     {
 1789       gdk_beep ();                /* unparsable */
 1790       visual = "";
 1791       gtk_entry_set_text (visent, _("Any"));
 1792     }
 1793 
 1794   changed = flush_changes (s, list_elt, -1, command, visual);
 1795   if (changed)
 1796     {
 1797       changed = demo_write_init_file (s, p);
 1798 
 1799       /* Do this to re-launch the hack if (and only if) the command line
 1800          has changed. */
 1801       populate_demo_window (s, selected_list_element (s));
 1802     }
 1803 
 1804  DONE:
 1805   s->saving_p = False;
 1806   return changed;
 1807 }
 1808 
 1809 
 1810 G_MODULE_EXPORT void
 1811 pref_changed_cb (GtkWidget *widget, gpointer user_data)
 1812 {
 1813   state *s = global_state_kludge;  /* I hate C so much... */
 1814   if (! s->initializing_p)
 1815     {
 1816       s->initializing_p = True;
 1817       flush_dialog_changes_and_save (s);
 1818       s->initializing_p = False;
 1819     }
 1820 }
 1821 
 1822 G_MODULE_EXPORT gboolean
 1823 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
 1824 {
 1825   pref_changed_cb (widget, user_data);
 1826   return FALSE;
 1827 }
 1828 
 1829 /* Callback on menu items in the "mode" options menu.
 1830  */
 1831 G_MODULE_EXPORT void
 1832 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
 1833 {
 1834   state *s = (state *) user_data;
 1835   saver_preferences *p = &s->prefs;
 1836   GtkWidget *list = name_to_widget (s, "list");
 1837   int list_elt;
 1838 
 1839   int menu_index = gtk_combo_box_get_active (GTK_COMBO_BOX (widget));
 1840   saver_mode new_mode = mode_menu_order[menu_index];
 1841 
 1842   /* Keep the same list element displayed as before; except if we're
 1843      switching *to* "one screensaver" mode from any other mode, set
 1844      "the one" to be that which is currently selected.
 1845    */
 1846   list_elt = selected_list_element (s);
 1847   if (new_mode == ONE_HACK)
 1848     p->selected_hack = s->list_elt_to_hack_number[list_elt];
 1849 
 1850   {
 1851     saver_mode old_mode = p->mode;
 1852     p->mode = new_mode;
 1853     populate_demo_window (s, list_elt);
 1854     force_list_select_item (s, list, list_elt, True);
 1855     p->mode = old_mode;  /* put it back, so the init file gets written */
 1856   }
 1857 
 1858   pref_changed_cb (widget, user_data);
 1859 }
 1860 
 1861 
 1862 G_MODULE_EXPORT void
 1863 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
 1864                 gint page_num, gpointer user_data)
 1865 {
 1866   state *s = global_state_kludge;  /* I hate C so much... */
 1867   pref_changed_cb (GTK_WIDGET (notebook), user_data);
 1868 
 1869   /* If we're switching to page 0, schedule the current hack to be run.
 1870      Otherwise, schedule it to stop. */
 1871   if (page_num == 0)
 1872     populate_demo_window (s, selected_list_element (s));
 1873   else
 1874     schedule_preview (s, 0);
 1875 }
 1876 
 1877 #ifdef HAVE_GTK2
 1878 static void
 1879 list_activated_cb (GtkTreeView       *list,
 1880            GtkTreePath       *path,
 1881            GtkTreeViewColumn *column,
 1882            gpointer           data)
 1883 {
 1884   state *s = data;
 1885   char *str;
 1886   int list_elt;
 1887 
 1888   if (gdk_pointer_is_grabbed()) return;
 1889 
 1890   str = gtk_tree_path_to_string (path);
 1891   list_elt = strtol (str, NULL, 10);
 1892   g_free (str);
 1893 
 1894   if (list_elt >= 0)
 1895     run_hack (s, list_elt, True);
 1896 }
 1897 
 1898 static void
 1899 list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
 1900 {
 1901   state *s = (state *)data;
 1902   GtkTreeModel *model;
 1903   GtkTreeIter iter;
 1904   GtkTreePath *path;
 1905   char *str;
 1906   int list_elt;
 1907  
 1908   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
 1909     return;
 1910 
 1911   path = gtk_tree_model_get_path (model, &iter);
 1912   str = gtk_tree_path_to_string (path);
 1913   list_elt = strtol (str, NULL, 10);
 1914 
 1915   gtk_tree_path_free (path);
 1916   g_free (str);
 1917 
 1918   populate_demo_window (s, list_elt);
 1919   flush_dialog_changes_and_save (s);
 1920 
 1921   /* Re-populate the Settings window any time a new item is selected
 1922      in the list, in case both windows are currently visible.
 1923    */
 1924   populate_popup_window (s);
 1925 }
 1926 
 1927 #else /* !HAVE_GTK2 */
 1928 
 1929 static time_t last_doubleclick_time = 0;   /* FMH!  This is to suppress the
 1930                                               list_select_cb that comes in
 1931                                               *after* we've double-clicked.
 1932                                             */
 1933 
 1934 static gint
 1935 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
 1936                      gpointer data)
 1937 {
 1938   state *s = (state *) data;
 1939   if (event->type == GDK_2BUTTON_PRESS)
 1940     {
 1941       GtkList *list = GTK_LIST (name_to_widget (s, "list"));
 1942       int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
 1943 
 1944       last_doubleclick_time = time ((time_t *) 0);
 1945 
 1946       if (list_elt >= 0)
 1947         run_hack (s, list_elt, True);
 1948     }
 1949 
 1950   return FALSE;
 1951 }
 1952 
 1953 
 1954 static void
 1955 list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
 1956 {
 1957   state *s = (state *) data;
 1958   time_t now = time ((time_t *) 0);
 1959 
 1960   if (now >= last_doubleclick_time + 2)
 1961     {
 1962       int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
 1963       populate_demo_window (s, list_elt);
 1964       flush_dialog_changes_and_save (s);
 1965     }
 1966 }
 1967 
 1968 static void
 1969 list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
 1970 {
 1971   state *s = (state *) data;
 1972   populate_demo_window (s, -1);
 1973   flush_dialog_changes_and_save (s);
 1974 }
 1975 
 1976 #endif /* !HAVE_GTK2 */
 1977 
 1978 
 1979 /* Called when the checkboxes that are in the left column of the
 1980    scrolling list are clicked.  This both populates the right pane
 1981    (just as clicking on the label (really, listitem) does) and
 1982    also syncs this checkbox with  the right pane Enabled checkbox.
 1983  */
 1984 static void
 1985 list_checkbox_cb (
 1986 #ifdef HAVE_GTK2
 1987           GtkCellRendererToggle *toggle,
 1988           gchar                 *path_string,
 1989 #else  /* !HAVE_GTK2 */
 1990           GtkWidget *cb,
 1991 #endif /* !HAVE_GTK2 */
 1992           gpointer               data)
 1993 {
 1994   state *s = (state *) data;
 1995 
 1996 #ifdef HAVE_GTK2
 1997   GtkScrolledWindow *scroller =
 1998     GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
 1999   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
 2000   GtkTreeModel *model = gtk_tree_view_get_model (list);
 2001   GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
 2002   GtkTreeIter iter;
 2003   gboolean active;
 2004 #else /* !HAVE_GTK2 */
 2005   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
 2006   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
 2007 
 2008   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
 2009   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
 2010   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
 2011 #endif /* !HAVE_GTK2 */
 2012   GtkAdjustment *adj;
 2013   double scroll_top;
 2014 
 2015   int list_elt;
 2016 
 2017 #ifdef HAVE_GTK2
 2018   if (!gtk_tree_model_get_iter (model, &iter, path))
 2019     {
 2020       g_warning ("bad path: %s", path_string);
 2021       return;
 2022     }
 2023   gtk_tree_path_free (path);
 2024 
 2025   gtk_tree_model_get (model, &iter,
 2026               COL_ENABLED, &active,
 2027               -1);
 2028 
 2029   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
 2030               COL_ENABLED, !active,
 2031               -1);
 2032 
 2033   list_elt = strtol (path_string, NULL, 10);  
 2034 #else  /* !HAVE_GTK2 */
 2035   list_elt = gtk_list_child_position (list, line);
 2036 #endif /* !HAVE_GTK2 */
 2037 
 2038   /* remember previous scroll position of the top of the list */
 2039   adj = gtk_scrolled_window_get_vadjustment (scroller);
 2040   scroll_top = GET_ADJ_VALUE (adj);
 2041 
 2042   flush_dialog_changes_and_save (s);
 2043   force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
 2044   populate_demo_window (s, list_elt);
 2045   
 2046   /* restore the previous scroll position of the top of the list.
 2047      this is weak, but I don't really know why it's moving... */
 2048   gtk_adjustment_set_value (adj, scroll_top);
 2049 }
 2050 
 2051 
 2052 typedef struct {
 2053   state *state;
 2054   GtkFileSelection *widget;
 2055 } file_selection_data;
 2056 
 2057 
 2058 
 2059 static void
 2060 store_image_directory (GtkWidget *button, gpointer user_data)
 2061 {
 2062   file_selection_data *fsd = (file_selection_data *) user_data;
 2063   state *s = fsd->state;
 2064   GtkFileSelection *selector = fsd->widget;
 2065   GtkWidget *top = s->toplevel_widget;
 2066   saver_preferences *p = &s->prefs;
 2067   const char *path = gtk_file_selection_get_filename (selector);
 2068 
 2069   if (p->image_directory && !strcmp(p->image_directory, path))
 2070     return;  /* no change */
 2071 
 2072   /* No warning for URLs. */
 2073   if ((!directory_p (path)) && strncmp(path, "http://", 6))
 2074     {
 2075       char b[255];
 2076       sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
 2077       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
 2078       return;
 2079     }
 2080 
 2081   if (p->image_directory) free (p->image_directory);
 2082   p->image_directory = normalize_directory (path);
 2083 
 2084   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
 2085                       (p->image_directory ? p->image_directory : ""));
 2086   demo_write_init_file (s, p);
 2087 }
 2088 
 2089 
 2090 static void
 2091 store_text_file (GtkWidget *button, gpointer user_data)
 2092 {
 2093   file_selection_data *fsd = (file_selection_data *) user_data;
 2094   state *s = fsd->state;
 2095   GtkFileSelection *selector = fsd->widget;
 2096   GtkWidget *top = s->toplevel_widget;
 2097   saver_preferences *p = &s->prefs;
 2098   const char *path = gtk_file_selection_get_filename (selector);
 2099 
 2100   if (p->text_file && !strcmp(p->text_file, path))
 2101     return;  /* no change */
 2102 
 2103   if (!file_p (path))
 2104     {
 2105       char b[255];
 2106       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
 2107       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
 2108       return;
 2109     }
 2110 
 2111   if (p->text_file) free (p->text_file);
 2112   p->text_file = normalize_directory (path);
 2113 
 2114   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
 2115                       (p->text_file ? p->text_file : ""));
 2116   demo_write_init_file (s, p);
 2117 }
 2118 
 2119 
 2120 static void
 2121 store_text_program (GtkWidget *button, gpointer user_data)
 2122 {
 2123   file_selection_data *fsd = (file_selection_data *) user_data;
 2124   state *s = fsd->state;
 2125   GtkFileSelection *selector = fsd->widget;
 2126   /*GtkWidget *top = s->toplevel_widget;*/
 2127   saver_preferences *p = &s->prefs;
 2128   const char *path = gtk_file_selection_get_filename (selector);
 2129 
 2130   if (p->text_program && !strcmp(p->text_program, path))
 2131     return;  /* no change */
 2132 
 2133 # if 0
 2134   if (!file_p (path))
 2135     {
 2136       char b[255];
 2137       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
 2138       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
 2139       return;
 2140     }
 2141 # endif
 2142 
 2143   if (p->text_program) free (p->text_program);
 2144   p->text_program = normalize_directory (path);
 2145 
 2146   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
 2147                       (p->text_program ? p->text_program : ""));
 2148   demo_write_init_file (s, p);
 2149 }
 2150 
 2151 
 2152 
 2153 static void
 2154 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
 2155 {
 2156   file_selection_data *fsd = (file_selection_data *) user_data;
 2157   gtk_widget_hide (GTK_WIDGET (fsd->widget));
 2158 }
 2159 
 2160 static void
 2161 browse_image_dir_ok (GtkWidget *button, gpointer user_data)
 2162 {
 2163   browse_image_dir_cancel (button, user_data);
 2164   store_image_directory (button, user_data);
 2165 }
 2166 
 2167 static void
 2168 browse_text_file_ok (GtkWidget *button, gpointer user_data)
 2169 {
 2170   browse_image_dir_cancel (button, user_data);
 2171   store_text_file (button, user_data);
 2172 }
 2173 
 2174 static void
 2175 browse_text_program_ok (GtkWidget *button, gpointer user_data)
 2176 {
 2177   browse_image_dir_cancel (button, user_data);
 2178   store_text_program (button, user_data);
 2179 }
 2180 
 2181 static void
 2182 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
 2183 {
 2184   browse_image_dir_cancel (widget, user_data);
 2185 }
 2186 
 2187 
 2188 G_MODULE_EXPORT void
 2189 browse_image_dir_cb (GtkButton *button, gpointer user_data)
 2190 {
 2191   state *s = global_state_kludge;  /* I hate C so much... */
 2192   saver_preferences *p = &s->prefs;
 2193   static file_selection_data *fsd = 0;
 2194 
 2195   GtkFileSelection *selector = GTK_FILE_SELECTION(
 2196     gtk_file_selection_new ("Please select the image directory."));
 2197 
 2198   if (!fsd)
 2199     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
 2200 
 2201   fsd->widget = selector;
 2202   fsd->state = s;
 2203 
 2204   if (p->image_directory && *p->image_directory)
 2205     gtk_file_selection_set_filename (selector, p->image_directory);
 2206 
 2207   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
 2208                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
 2209                       (gpointer *) fsd);
 2210   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
 2211                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
 2212                       (gpointer *) fsd);
 2213   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
 2214                       GTK_SIGNAL_FUNC (browse_image_dir_close),
 2215                       (gpointer *) fsd);
 2216 
 2217   gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
 2218 
 2219   gtk_window_set_modal (GTK_WINDOW (selector), True);
 2220   gtk_widget_show (GTK_WIDGET (selector));
 2221 }
 2222 
 2223 
 2224 G_MODULE_EXPORT void
 2225 browse_text_file_cb (GtkButton *button, gpointer user_data)
 2226 {
 2227   state *s = global_state_kludge;  /* I hate C so much... */
 2228   saver_preferences *p = &s->prefs;
 2229   static file_selection_data *fsd = 0;
 2230 
 2231   GtkFileSelection *selector = GTK_FILE_SELECTION(
 2232     gtk_file_selection_new ("Please select a text file."));
 2233 
 2234   if (!fsd)
 2235     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
 2236 
 2237   fsd->widget = selector;
 2238   fsd->state = s;
 2239 
 2240   if (p->text_file && *p->text_file)
 2241     gtk_file_selection_set_filename (selector, p->text_file);
 2242 
 2243   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
 2244                       "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
 2245                       (gpointer *) fsd);
 2246   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
 2247                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
 2248                       (gpointer *) fsd);
 2249   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
 2250                       GTK_SIGNAL_FUNC (browse_image_dir_close),
 2251                       (gpointer *) fsd);
 2252 
 2253   gtk_window_set_modal (GTK_WINDOW (selector), True);
 2254   gtk_widget_show (GTK_WIDGET (selector));
 2255 }
 2256 
 2257 
 2258 G_MODULE_EXPORT void
 2259 browse_text_program_cb (GtkButton *button, gpointer user_data)
 2260 {
 2261   state *s = global_state_kludge;  /* I hate C so much... */
 2262   saver_preferences *p = &s->prefs;
 2263   static file_selection_data *fsd = 0;
 2264 
 2265   GtkFileSelection *selector = GTK_FILE_SELECTION(
 2266     gtk_file_selection_new ("Please select a text-generating program."));
 2267 
 2268   if (!fsd)
 2269     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
 2270 
 2271   fsd->widget = selector;
 2272   fsd->state = s;
 2273 
 2274   if (p->text_program && *p->text_program)
 2275     gtk_file_selection_set_filename (selector, p->text_program);
 2276 
 2277   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
 2278                       "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
 2279                       (gpointer *) fsd);
 2280   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
 2281                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
 2282                       (gpointer *) fsd);
 2283   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
 2284                       GTK_SIGNAL_FUNC (browse_image_dir_close),
 2285                       (gpointer *) fsd);
 2286 
 2287   gtk_window_set_modal (GTK_WINDOW (selector), True);
 2288   gtk_widget_show (GTK_WIDGET (selector));
 2289 }
 2290 
 2291 
 2292 G_MODULE_EXPORT void
 2293 preview_theme_cb (GtkWidget *w, gpointer user_data)
 2294 {
 2295   if (system ("xscreensaver-auth --splash &") < 0)
 2296     fprintf (stderr, "%s: splash exec failed\n", blurb());
 2297 }
 2298 
 2299 
 2300 G_MODULE_EXPORT void
 2301 settings_cb (GtkButton *button, gpointer user_data)
 2302 {
 2303   state *s = global_state_kludge;  /* I hate C so much... */
 2304   int list_elt = selected_list_element (s);
 2305 
 2306   populate_demo_window (s, list_elt);   /* reset the widget */
 2307   populate_popup_window (s);        /* create UI on popup window */
 2308   gtk_widget_show (s->popup_widget);
 2309 }
 2310 
 2311 static void
 2312 settings_sync_cmd_text (state *s)
 2313 {
 2314 # ifdef HAVE_XML
 2315   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
 2316   char *cmd_line = get_configurator_command_line (s->cdata, False);
 2317   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
 2318   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
 2319   free (cmd_line);
 2320 # endif /* HAVE_XML */
 2321 }
 2322 
 2323 G_MODULE_EXPORT void
 2324 settings_adv_cb (GtkButton *button, gpointer user_data)
 2325 {
 2326   state *s = global_state_kludge;  /* I hate C so much... */
 2327   GtkNotebook *notebook =
 2328     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
 2329 
 2330   settings_sync_cmd_text (s);
 2331   gtk_notebook_set_page (notebook, 1);
 2332 }
 2333 
 2334 G_MODULE_EXPORT void
 2335 settings_std_cb (GtkButton *button, gpointer user_data)
 2336 {
 2337   state *s = global_state_kludge;  /* I hate C so much... */
 2338   GtkNotebook *notebook =
 2339     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
 2340 
 2341   /* Re-create UI to reflect the in-progress command-line settings. */
 2342   populate_popup_window (s);
 2343 
 2344   gtk_notebook_set_page (notebook, 0);
 2345 }
 2346 
 2347 G_MODULE_EXPORT void
 2348 settings_reset_cb (GtkButton *button, gpointer user_data)
 2349 {
 2350 # ifdef HAVE_XML
 2351   state *s = global_state_kludge;  /* I hate C so much... */
 2352   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
 2353   char *cmd_line = get_configurator_command_line (s->cdata, True);
 2354   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
 2355   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
 2356   free (cmd_line);
 2357   populate_popup_window (s);
 2358 # endif /* HAVE_XML */
 2359 }
 2360 
 2361 G_MODULE_EXPORT void
 2362 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
 2363                          gint page_num, gpointer user_data)
 2364 {
 2365   state *s = global_state_kludge;  /* I hate C so much... */
 2366   GtkWidget *adv = name_to_widget (s, "adv_button");
 2367   GtkWidget *std = name_to_widget (s, "std_button");
 2368 
 2369   if (page_num == 0)
 2370     {
 2371       gtk_widget_show (adv);
 2372       gtk_widget_hide (std);
 2373     }
 2374   else if (page_num == 1)
 2375     {
 2376       gtk_widget_hide (adv);
 2377       gtk_widget_show (std);
 2378     }
 2379   else
 2380     abort();
 2381 }
 2382 
 2383 
 2384 
 2385 G_MODULE_EXPORT void
 2386 settings_cancel_cb (GtkButton *button, gpointer user_data)
 2387 {
 2388   state *s = global_state_kludge;  /* I hate C so much... */
 2389   gtk_widget_hide (s->popup_widget);
 2390 }
 2391 
 2392 G_MODULE_EXPORT void
 2393 settings_ok_cb (GtkButton *button, gpointer user_data)
 2394 {
 2395   state *s = global_state_kludge;  /* I hate C so much... */
 2396   GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
 2397   int page = gtk_notebook_get_current_page (notebook);
 2398 
 2399   if (page == 0)
 2400     /* Regenerate the command-line from the widget contents before saving.
 2401        But don't do this if we're looking at the command-line page already,
 2402        or we will blow away what they typed... */
 2403     settings_sync_cmd_text (s);
 2404 
 2405   flush_popup_changes_and_save (s);
 2406   gtk_widget_hide (s->popup_widget);
 2407 }
 2408 
 2409 static gboolean
 2410 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
 2411 {
 2412   state *s = (state *) data;
 2413   settings_cancel_cb (0, (gpointer) s);
 2414   return TRUE;
 2415 }
 2416 
 2417 
 2418 
 2419 /* Populating the various widgets
 2420  */
 2421 
 2422 
 2423 /* Returns the number of the last hack run by the server.
 2424  */
 2425 static int
 2426 server_current_hack (void)
 2427 {
 2428   Atom type;
 2429   int format;
 2430   unsigned long nitems, bytesafter;
 2431   unsigned char *dataP = 0;
 2432   Display *dpy = GDK_DISPLAY();
 2433   int hack_number = -1;
 2434 
 2435   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
 2436                           XA_SCREENSAVER_STATUS,
 2437                           0, 3, False, XA_INTEGER,
 2438                           &type, &format, &nitems, &bytesafter,
 2439                           &dataP)
 2440       == Success
 2441       && type == XA_INTEGER
 2442       && nitems >= 3
 2443       && dataP)
 2444     {
 2445       PROP32 *data = (PROP32 *) dataP;
 2446       hack_number = (int) data[2] - 1;
 2447     }
 2448 
 2449   if (dataP) XFree (dataP);
 2450 
 2451   return hack_number;
 2452 }
 2453 
 2454 
 2455 /* Finds the number of the last hack that was run, and makes that item be
 2456    selected by default.
 2457  */
 2458 static void
 2459 scroll_to_current_hack (state *s)
 2460 {
 2461   saver_preferences *p = &s->prefs;
 2462   int hack_number = -1;
 2463 
 2464   if (p->mode == ONE_HACK)         /* in "one" mode, use the one */
 2465     hack_number = p->selected_hack;
 2466   if (hack_number < 0)             /* otherwise, use the last-run */
 2467     hack_number = server_current_hack ();
 2468   if (hack_number < 0)             /* failing that, last "one mode" */
 2469     hack_number = p->selected_hack;
 2470   if (hack_number < 0)             /* failing that, newest hack. */
 2471     {
 2472       /* We should only get here if the user does not have a .xscreensaver
 2473          file, and the screen has not been blanked with a hack since X
 2474          started up: in other words, this is probably a fresh install.
 2475 
 2476          Instead of just defaulting to hack #0 (in either "programs" or
 2477          "alphabetical" order) let's try to default to the last runnable
 2478          hack in the "programs" list: this is probably the hack that was
 2479          most recently added to the xscreensaver distribution (and so
 2480          it's probably the currently-coolest one!)
 2481        */
 2482       hack_number = p->screenhacks_count-1;
 2483       while (hack_number > 0 &&
 2484              ! (s->hacks_available_p[hack_number] &&
 2485                 p->screenhacks[hack_number]->enabled_p))
 2486         hack_number--;
 2487     }
 2488 
 2489   if (hack_number >= 0 && hack_number < p->screenhacks_count)
 2490     {
 2491       int list_elt = s->hack_number_to_list_elt[hack_number];
 2492       GtkWidget *list = name_to_widget (s, "list");
 2493       force_list_select_item (s, list, list_elt, True);
 2494       populate_demo_window (s, list_elt);
 2495     }
 2496 }
 2497 
 2498 
 2499 static void
 2500 populate_hack_list (state *s)
 2501 {
 2502   Display *dpy = GDK_DISPLAY();
 2503 #ifdef HAVE_GTK2
 2504   saver_preferences *p = &s->prefs;
 2505   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
 2506   GtkListStore *model;
 2507   GtkTreeSelection *selection;
 2508   GtkCellRenderer *ren;
 2509   GtkTreeIter iter;
 2510   int i;
 2511 
 2512   g_object_get (G_OBJECT (list),
 2513         "model", &model,
 2514         NULL);
 2515   if (!model)
 2516     {
 2517       model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
 2518       g_object_set (G_OBJECT (list), "model", model, NULL);
 2519       g_object_unref (model);
 2520 
 2521       ren = gtk_cell_renderer_toggle_new ();
 2522       gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
 2523                            _("Use"), ren,
 2524                            "active", COL_ENABLED,
 2525                            NULL);
 2526 
 2527       g_signal_connect (ren, "toggled",
 2528             G_CALLBACK (list_checkbox_cb),
 2529             s);
 2530 
 2531       ren = gtk_cell_renderer_text_new ();
 2532       gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
 2533                            _("Screen Saver"), ren,
 2534                            "markup", COL_NAME,
 2535                            NULL);
 2536 
 2537       g_signal_connect_after (list, "row_activated",
 2538                   G_CALLBACK (list_activated_cb),
 2539                   s);
 2540 
 2541       selection = gtk_tree_view_get_selection (list);
 2542       g_signal_connect (selection, "changed",
 2543             G_CALLBACK (list_select_changed_cb),
 2544             s);
 2545 
 2546     }
 2547 
 2548   for (i = 0; i < s->list_count; i++)
 2549     {
 2550       int hack_number = s->list_elt_to_hack_number[i];
 2551       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
 2552       char *pretty_name;
 2553       Bool available_p = (hack && s->hacks_available_p [hack_number]);
 2554 
 2555       if (!hack) continue;
 2556 
 2557       /* If we're to suppress uninstalled hacks, check $PATH now. */
 2558       if (p->ignore_uninstalled_p && !available_p)
 2559         continue;
 2560 
 2561       pretty_name = (hack->name
 2562                      ? strdup (hack->name)
 2563                      : make_hack_name (dpy, hack->command));
 2564 
 2565       if (!available_p)
 2566         {
 2567           /* Make the text foreground be the color of insensitive widgets
 2568              (but don't actually make it be insensitive, since we still
 2569              want to be able to click on it.)
 2570            */
 2571           GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (list));
 2572           GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
 2573        /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
 2574           char *buf = (char *) malloc (strlen (pretty_name) + 100);
 2575 
 2576           sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
 2577                       /*     " background=\"#%02X%02X%02X\""  */
 2578                         ">%s</span>",
 2579                    fg->red >> 8, fg->green >> 8, fg->blue >> 8,
 2580                 /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
 2581                    pretty_name);
 2582           free (pretty_name);
 2583           pretty_name = buf;
 2584         }
 2585 
 2586       gtk_list_store_append (model, &iter);
 2587       gtk_list_store_set (model, &iter,
 2588               COL_ENABLED, hack->enabled_p,
 2589               COL_NAME, pretty_name,
 2590               -1);
 2591       free (pretty_name);
 2592     }
 2593 
 2594 #else /* !HAVE_GTK2 */
 2595 
 2596   saver_preferences *p = &s->prefs;
 2597   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
 2598   int i;
 2599   for (i = 0; i < s->list_count; i++)
 2600     {
 2601       int hack_number = s->list_elt_to_hack_number[i];
 2602       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
 2603 
 2604       /* A GtkList must contain only GtkListItems, but those can contain
 2605          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
 2606          and a Label.  We handle single and double click events on the
 2607          line itself, for clicking on the text, but the interior checkbox
 2608          also handles its own events.
 2609        */
 2610       GtkWidget *line;
 2611       GtkWidget *line_hbox;
 2612       GtkWidget *line_check;
 2613       GtkWidget *line_label;
 2614       char *pretty_name;
 2615       Bool available_p = (hack && s->hacks_available_p [hack_number]);
 2616 
 2617       if (!hack) continue;
 2618 
 2619       /* If we're to suppress uninstalled hacks, check $PATH now. */
 2620       if (p->ignore_uninstalled_p && !available_p)
 2621         continue;
 2622 
 2623       pretty_name = (hack->name
 2624                      ? strdup (hack->name)
 2625                      : make_hack_name (hack->command));
 2626 
 2627       line = gtk_list_item_new ();
 2628       line_hbox = gtk_hbox_new (FALSE, 0);
 2629       line_check = gtk_check_button_new ();
 2630       line_label = gtk_label_new (pretty_name);
 2631 
 2632       gtk_container_add (GTK_CONTAINER (line), line_hbox);
 2633       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
 2634       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
 2635 
 2636       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
 2637                                     hack->enabled_p);
 2638       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
 2639 
 2640       gtk_widget_show (line_check);
 2641       gtk_widget_show (line_label);
 2642       gtk_widget_show (line_hbox);
 2643       gtk_widget_show (line);
 2644 
 2645       free (pretty_name);
 2646 
 2647       gtk_container_add (GTK_CONTAINER (list), line);
 2648       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
 2649                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
 2650                           (gpointer) s);
 2651 
 2652       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
 2653                           GTK_SIGNAL_FUNC (list_checkbox_cb),
 2654                           (gpointer) s);
 2655 
 2656       gtk_widget_show (line);
 2657 
 2658       if (!available_p)
 2659         {
 2660           /* Make the widget be colored like insensitive widgets
 2661              (but don't actually make it be insensitive, since we
 2662              still want to be able to click on it.)
 2663            */
 2664           GtkRcStyle *rc_style;
 2665           GdkColor fg, bg;
 2666 
 2667           gtk_widget_realize (GTK_WIDGET (line_label));
 2668 
 2669           fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
 2670           bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
 2671 
 2672           rc_style = gtk_rc_style_new ();
 2673           rc_style->fg[GTK_STATE_NORMAL] = fg;
 2674           rc_style->bg[GTK_STATE_NORMAL] = bg;
 2675           rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
 2676 
 2677           gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
 2678           gtk_rc_style_unref (rc_style);
 2679         }
 2680     }
 2681 
 2682   gtk_signal_connect (GTK_OBJECT (list), "select_child",
 2683                       GTK_SIGNAL_FUNC (list_select_cb),
 2684                       (gpointer) s);
 2685   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
 2686                       GTK_SIGNAL_FUNC (list_unselect_cb),
 2687                       (gpointer) s);
 2688 #endif /* !HAVE_GTK2 */
 2689 }
 2690 
 2691 static void
 2692 update_list_sensitivity (state *s)
 2693 {
 2694   saver_preferences *p = &s->prefs;
 2695   Bool sensitive = (p->mode == RANDOM_HACKS ||
 2696                     p->mode == RANDOM_HACKS_SAME ||
 2697                     p->mode == ONE_HACK);
 2698   Bool checkable = (p->mode == RANDOM_HACKS ||
 2699                     p->mode == RANDOM_HACKS_SAME);
 2700   Bool blankable = (p->mode != DONT_BLANK);
 2701 
 2702 #ifndef HAVE_GTK2
 2703   GtkWidget *head     = name_to_widget (s, "col_head_hbox");
 2704   GtkWidget *use      = name_to_widget (s, "use_col_frame");
 2705 #endif /* HAVE_GTK2 */
 2706   GtkWidget *scroller = name_to_widget (s, "scroller");
 2707   GtkWidget *buttons  = name_to_widget (s, "next_prev_hbox");
 2708   GtkWidget *blanker  = name_to_widget (s, "blanking_table");
 2709 
 2710 #ifdef HAVE_GTK2
 2711   GtkTreeView *list      = GTK_TREE_VIEW (name_to_widget (s, "list"));
 2712   GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
 2713 #else /* !HAVE_GTK2 */
 2714   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
 2715   GList *kids   = gtk_container_children (GTK_CONTAINER (list));
 2716 
 2717   gtk_widget_set_sensitive (GTK_WIDGET (head),     sensitive);
 2718 #endif /* !HAVE_GTK2 */
 2719   gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
 2720   gtk_widget_set_sensitive (GTK_WIDGET (buttons),  sensitive);
 2721 
 2722   gtk_widget_set_sensitive (GTK_WIDGET (blanker),  blankable);
 2723 
 2724 #ifdef HAVE_GTK2
 2725   gtk_tree_view_column_set_visible (use, checkable);
 2726 #else  /* !HAVE_GTK2 */
 2727   if (checkable)
 2728     gtk_widget_show (use);   /* the "Use" column header */
 2729   else
 2730     gtk_widget_hide (use);
 2731 
 2732   while (kids)
 2733     {
 2734       GtkBin *line = GTK_BIN (kids->data);
 2735       GtkContainer *line_hbox = GTK_CONTAINER (line->child);
 2736       GtkWidget *line_check =
 2737         GTK_WIDGET (gtk_container_children (line_hbox)->data);
 2738       
 2739       if (checkable)
 2740         gtk_widget_show (line_check);
 2741       else
 2742         gtk_widget_hide (line_check);
 2743 
 2744       kids = kids->next;
 2745     }
 2746 #endif /* !HAVE_GTK2 */
 2747 }
 2748 
 2749 
 2750 static void
 2751 populate_prefs_page (state *s)
 2752 {
 2753   Display *dpy = GDK_DISPLAY();
 2754   saver_preferences *p = &s->prefs;
 2755 
 2756   Bool can_lock_p = True;
 2757 
 2758   /* Disable all the "lock" controls if locking support was not provided
 2759      at compile-time, or if running on MacOS. */
 2760 # if defined(NO_LOCKING) || defined(__APPLE__)
 2761   can_lock_p = False;
 2762 # endif
 2763 
 2764 
 2765   /* If there is only one screen, the mode menu contains
 2766      "random" but not "random-same".
 2767    */
 2768   if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
 2769     p->mode = RANDOM_HACKS;
 2770 
 2771 
 2772   /* The file supports timeouts of less than a minute, but the GUI does
 2773      not, so throttle the values to be at least one minute (since "0" is
 2774      a bad rounding choice...)
 2775    */
 2776 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
 2777   THROTTLE (timeout);
 2778   THROTTLE (cycle);
 2779   /* THROTTLE (passwd_timeout); */  /* GUI doesn't set this; leave it alone */
 2780 # undef THROTTLE
 2781 
 2782 # define FMT_MINUTES(NAME,N) \
 2783     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
 2784 
 2785 # define FMT_SECONDS(NAME,N) \
 2786     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
 2787 
 2788   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
 2789   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
 2790   FMT_MINUTES ("lock_spinbutton",         p->lock_timeout);
 2791   FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
 2792   FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
 2793   FMT_MINUTES ("dpms_off_spinbutton",     p->dpms_off);
 2794   FMT_SECONDS ("fade_spinbutton",         p->fade_seconds);
 2795 
 2796 # undef FMT_MINUTES
 2797 # undef FMT_SECONDS
 2798 
 2799 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
 2800   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
 2801                                 (ACTIVEP))
 2802 
 2803   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
 2804 #if 0
 2805   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
 2806   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
 2807 #endif
 2808   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
 2809   TOGGLE_ACTIVE ("dpms_quickoff_button", p->dpms_quickoff_p);
 2810   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
 2811   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
 2812   TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
 2813   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
 2814   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
 2815 
 2816   switch (p->tmode)
 2817     {
 2818     case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio",         True); break;
 2819     case TEXT_FILE:    TOGGLE_ACTIVE ("text_file_radio",    True); break;
 2820     case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
 2821     case TEXT_URL:     TOGGLE_ACTIVE ("text_url_radio",     True); break;
 2822     default:           TOGGLE_ACTIVE ("text_host_radio",    True); break;
 2823     }
 2824 
 2825 # undef TOGGLE_ACTIVE
 2826 
 2827   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
 2828                       (p->image_directory ? p->image_directory : ""));
 2829   gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
 2830                             p->random_image_p);
 2831   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
 2832                             p->random_image_p);
 2833 
 2834   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
 2835                       (p->text_literal ? p->text_literal : ""));
 2836   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
 2837                       (p->text_file ? p->text_file : ""));
 2838   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
 2839                       (p->text_program ? p->text_program : ""));
 2840   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
 2841                       (p->text_url ? p->text_url : ""));
 2842 
 2843   gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
 2844                             p->tmode == TEXT_LITERAL);
 2845   gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
 2846                             p->tmode == TEXT_FILE);
 2847   gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
 2848                             p->tmode == TEXT_FILE);
 2849   gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
 2850                             p->tmode == TEXT_PROGRAM);
 2851   gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
 2852                             p->tmode == TEXT_PROGRAM);
 2853   gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
 2854                             p->tmode == TEXT_URL);
 2855 
 2856 
 2857   /* Theme menu */
 2858   {
 2859     GtkComboBox *cbox = GTK_COMBO_BOX (name_to_widget (s, "theme_menu"));
 2860 
 2861     /* Without this, pref_changed_cb gets called an exponentially-increasing
 2862        number of times on the themes menu, despite the call to
 2863        gtk_list_store_clear(). */
 2864     static Bool done_once = False;
 2865 
 2866     if (cbox && !done_once)
 2867       {
 2868         char *themes = get_string_resource (dpy, "themeNames", "ThemeNames");
 2869         char *token = themes;
 2870         char *name, *name2, *last;
 2871         GtkListStore *model;
 2872         GtkTreeIter iter;
 2873         int i = 0;
 2874         done_once = True;
 2875 
 2876         g_object_get (G_OBJECT (cbox), "model", &model, NULL);
 2877         if (!model) abort();
 2878         gtk_list_store_clear (model);
 2879 
 2880         gtk_signal_connect (GTK_OBJECT (cbox), "changed",
 2881                             GTK_SIGNAL_FUNC (pref_changed_cb), (gpointer) s);
 2882 
 2883         while ((name = strtok_r (token, ",", &last)))
 2884           {
 2885             int L;
 2886             token = 0;
 2887 
 2888             /* Strip leading and trailing whitespace */
 2889             while (*name == ' ' || *name == '\t' || *name == '\n')
 2890               name++;
 2891             L = strlen(name);
 2892             while (L && (name[L-1] == ' ' || name[L-1] == '\t' ||
 2893                          name[L-1] == '\n'))
 2894               name[--L] = 0;
 2895 
 2896             gtk_list_store_append (model, &iter);
 2897             gtk_list_store_set (model, &iter, 0, name, -1);
 2898 
 2899             name2 = theme_name_strip (name);
 2900             if (!strcmp (p->dialog_theme, name2))
 2901               gtk_combo_box_set_active (cbox, i);
 2902             free (name2);
 2903             i++;
 2904           }
 2905       }
 2906   }
 2907 
 2908 
 2909   /* Map the `saver_mode' enum to mode menu to values. */
 2910   {
 2911     GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
 2912 
 2913     int i;
 2914     for (i = 0; i < countof(mode_menu_order); i++)
 2915       if (mode_menu_order[i] == p->mode)
 2916         break;
 2917     gtk_combo_box_set_active (opt, i);
 2918     update_list_sensitivity (s);
 2919   }
 2920 
 2921   {
 2922     Bool dpms_supported = False;
 2923     Display *dpy = GDK_DISPLAY();
 2924 
 2925 #ifdef HAVE_DPMS_EXTENSION
 2926     {
 2927       int op = 0, event = 0, error = 0;
 2928       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
 2929         dpms_supported = True;
 2930     }
 2931 #endif /* HAVE_DPMS_EXTENSION */
 2932 
 2933 
 2934 # define SENSITIZE(NAME,SENSITIVEP) \
 2935     gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
 2936 
 2937     /* Blanking and Locking
 2938      */
 2939     SENSITIZE ("lock_button",     can_lock_p);
 2940     SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
 2941     SENSITIZE ("lock_mlabel",     can_lock_p && p->lock_p);
 2942 
 2943     /* DPMS
 2944      */
 2945     SENSITIZE ("dpms_frame",              dpms_supported);
 2946     SENSITIZE ("dpms_button",             dpms_supported);
 2947 
 2948     SENSITIZE ("dpms_standby_label",      dpms_supported && p->dpms_enabled_p);
 2949     SENSITIZE ("dpms_standby_mlabel",     dpms_supported && p->dpms_enabled_p);
 2950     SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
 2951     SENSITIZE ("dpms_suspend_label",      dpms_supported && p->dpms_enabled_p);
 2952     SENSITIZE ("dpms_suspend_mlabel",     dpms_supported && p->dpms_enabled_p);
 2953     SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
 2954     SENSITIZE ("dpms_off_label",          dpms_supported && p->dpms_enabled_p);
 2955     SENSITIZE ("dpms_off_mlabel",         dpms_supported && p->dpms_enabled_p);
 2956     SENSITIZE ("dpms_off_spinbutton",     dpms_supported && p->dpms_enabled_p);
 2957     SENSITIZE ("dpms_quickoff_button",    dpms_supported);
 2958 
 2959     SENSITIZE ("fade_label",      (p->fade_p || p->unfade_p));
 2960     SENSITIZE ("fade_spinbutton", (p->fade_p || p->unfade_p));
 2961 
 2962 # undef SENSITIZE
 2963   }
 2964 }
 2965 
 2966 
 2967 /* Allow the documentation label to re-flow when the text is changed.
 2968    http://blog.borovsak.si/2009/05/wrapping-adn-resizing-gtklabel.html
 2969  */
 2970 static void
 2971 cb_allocate (GtkWidget *label, GtkAllocation *allocation, gpointer data)
 2972 {
 2973   gtk_widget_set_size_request (label, allocation->width - 8, -1);
 2974 }
 2975 
 2976 
 2977 static void
 2978 populate_popup_window (state *s)
 2979 {
 2980   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
 2981   char *doc_string = 0;
 2982 
 2983   /* #### not in Gtk 1.2
 2984   gtk_label_set_selectable (doc);
 2985    */
 2986 
 2987   g_signal_connect (G_OBJECT (doc), "size-allocate", 
 2988                     G_CALLBACK (cb_allocate), NULL);
 2989 
 2990 # ifdef HAVE_XML
 2991   if (s->cdata)
 2992     {
 2993       free_conf_data (s->cdata);
 2994       s->cdata = 0;
 2995     }
 2996 
 2997   {
 2998     saver_preferences *p = &s->prefs;
 2999     int list_elt = selected_list_element (s);
 3000     int hack_number = (list_elt >= 0 && list_elt < s->list_count
 3001                        ? s->list_elt_to_hack_number[list_elt]
 3002                        : -1);
 3003     screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
 3004     if (hack)
 3005       {
 3006         GtkWidget *parent = name_to_widget (s, "settings_vbox");
 3007         GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
 3008         const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
 3009         s->cdata = load_configurator (cmd_line, s->debug_p);
 3010         if (s->cdata && s->cdata->widget)
 3011           gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
 3012                               TRUE, TRUE, 0);
 3013       }
 3014   }
 3015 
 3016   doc_string = (s->cdata
 3017                 ? s->cdata->description
 3018                 : 0);
 3019 # else  /* !HAVE_XML */
 3020   doc_string = _("Descriptions not available: no XML support compiled in.");
 3021 # endif /* !HAVE_XML */
 3022 
 3023   gtk_label_set_text (doc, (doc_string
 3024                             ? _(doc_string)
 3025                             : _("No description available.")));
 3026 
 3027   {
 3028     GtkWidget *w = name_to_widget (s, "dialog_vbox");
 3029     gtk_widget_hide (w);
 3030     gtk_widget_unrealize (w);
 3031     gtk_widget_realize (w);
 3032     gtk_widget_show (w);
 3033   }
 3034 }
 3035 
 3036 
 3037 static void
 3038 sensitize_demo_widgets (state *s, Bool sensitive_p)
 3039 {
 3040   const char *names[] = { "demo", "settings",
 3041                           "cmd_label", "cmd_text", "manual",
 3042                           "visual", "visual_combo" };
 3043   int i;
 3044   for (i = 0; i < countof(names); i++)
 3045     {
 3046       GtkWidget *w = name_to_widget (s, names[i]);
 3047       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
 3048     }
 3049 }
 3050 
 3051 
 3052 static void
 3053 sensitize_menu_items (state *s, Bool force_p)
 3054 {
 3055   static Bool running_p = False;
 3056   static time_t last_checked = 0;
 3057   time_t now = time ((time_t *) 0);
 3058   const char *names[] = { "activate_action", "lock_action", "kill_action",
 3059                           /* "demo" */ };
 3060   int i;
 3061 
 3062   if (force_p || now > last_checked + 10)   /* check every 10 seconds */
 3063     {
 3064       running_p = xscreensaver_running_p (s);
 3065       last_checked = time ((time_t *) 0);
 3066     }
 3067 
 3068   for (i = 0; i < countof(names); i++)
 3069     {
 3070       GtkAction *a = GTK_ACTION (gtk_builder_get_object (s->gtk_ui, names[i]));
 3071       gtk_action_set_sensitive (a, running_p);
 3072     }
 3073 }
 3074 
 3075 
 3076 /* When the File menu is de-posted after a "Restart Daemon" command,
 3077    the window underneath doesn't repaint for some reason.  I guess this
 3078    is a bug in exposure handling in GTK or GDK.  This works around it.
 3079  */
 3080 static void
 3081 force_dialog_repaint (state *s)
 3082 {
 3083 #if 1
 3084   /* Tell GDK to invalidate and repaint the whole window.
 3085    */
 3086   GdkWindow *w = GET_WINDOW (s->toplevel_widget);
 3087   GdkRegion *region = gdk_region_new ();
 3088   GdkRectangle rect;
 3089   rect.x = rect.y = 0;
 3090   rect.width = rect.height = 32767;
 3091   gdk_region_union_with_rect (region, &rect);
 3092   gdk_window_invalidate_region (w, region, True);
 3093   gdk_region_destroy (region);
 3094   gdk_window_process_updates (w, True);
 3095 #else
 3096   /* Force the server to send an exposure event by creating and then
 3097      destroying a window as a child of the top level shell.
 3098    */
 3099   Display *dpy = GDK_DISPLAY();
 3100   Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
 3101   Window w;
 3102   XWindowAttributes xgwa;
 3103   XGetWindowAttributes (dpy, parent, &xgwa);
 3104   w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
 3105   XMapRaised (dpy, w);
 3106   XDestroyWindow (dpy, w);
 3107   XSync (dpy, False);
 3108 #endif
 3109 }
 3110 
 3111 
 3112 /* Even though we've given these text fields a maximum number of characters,
 3113    their default size is still about 30 characters wide -- so measure out
 3114    a string in their font, and resize them to just fit that.
 3115  */
 3116 static void
 3117 fix_text_entry_sizes (state *s)
 3118 {
 3119   GtkWidget *w;
 3120 
 3121 # if 0   /* appears no longer necessary with Gtk 1.2.10 */
 3122   const char * const spinbuttons[] = {
 3123     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
 3124     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
 3125     "dpms_off_spinbutton",
 3126     "-fade_spinbutton" };
 3127   int i;
 3128   int width = 0;
 3129 
 3130   for (i = 0; i < countof(spinbuttons); i++)
 3131     {
 3132       const char *n = spinbuttons[i];
 3133       int cols = 4;
 3134       while (*n == '-') n++, cols--;
 3135       w = GTK_WIDGET (name_to_widget (s, n));
 3136       width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
 3137       gtk_widget_set_usize (w, width, -2);
 3138     }
 3139 
 3140   /* Now fix the width of the combo box.
 3141    */
 3142   w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
 3143   w = GTK_COMBO_BOX_ENTRY (w)->entry;
 3144   width = gdk_string_width (w->style->font, "PseudoColor___");
 3145   gtk_widget_set_usize (w, width, -2);
 3146 
 3147   /* Now fix the width of the file entry text.
 3148    */
 3149   w = GTK_WIDGET (name_to_widget (s, "image_text"));
 3150   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
 3151   gtk_widget_set_usize (w, width, -2);
 3152 
 3153   /* Now fix the width of the command line text.
 3154    */
 3155   w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
 3156   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
 3157   gtk_widget_set_usize (w, width, -2);
 3158 
 3159 # endif /* 0 */
 3160 
 3161   /* Now fix the height of the list widget:
 3162      make it default to being around 10 text-lines high instead of 4.
 3163    */
 3164   w = GTK_WIDGET (name_to_widget (s, "list"));
 3165   {
 3166     int lines = 10;
 3167     int height;
 3168     int leading = 3;  /* approximate is ok... */
 3169     int border = 2;
 3170 
 3171 #ifdef HAVE_GTK2
 3172     PangoFontMetrics *pain =
 3173       pango_context_get_metrics (gtk_widget_get_pango_context (w),
 3174                                  gtk_widget_get_style (w)->font_desc,
 3175                                  gtk_get_default_language ());
 3176     height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
 3177                            pango_font_metrics_get_descent (pain));
 3178 #else  /* !HAVE_GTK2 */
 3179     height = w->style->font->ascent + w->style->font->descent;
 3180 #endif /* !HAVE_GTK2 */
 3181 
 3182     height += leading;
 3183     height *= lines;
 3184     height += border * 2;
 3185     w = GTK_WIDGET (name_to_widget (s, "scroller"));
 3186     gtk_widget_set_usize (w, -2, height);
 3187   }
 3188 }
 3189 
 3190 
 3191 #ifndef HAVE_GTK2
 3192 
 3193 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
 3194  */
 3195 
 3196 static char *up_arrow_xpm[] = {
 3197   "15 15 4 1",
 3198   "     c None",
 3199   "-    c #FFFFFF",
 3200   "+    c #D6D6D6",
 3201   "@    c #000000",
 3202 
 3203   "       @       ",
 3204   "       @       ",
 3205   "      -+@      ",
 3206   "      -+@      ",
 3207   "     -+++@     ",
 3208   "     -+++@     ",
 3209   "    -+++++@    ",
 3210   "    -+++++@    ",
 3211   "   -+++++++@   ",
 3212   "   -+++++++@   ",
 3213   "  -+++++++++@  ",
 3214   "  -+++++++++@  ",
 3215   " -+++++++++++@ ",
 3216   " @@@@@@@@@@@@@ ",
 3217   "               ",
 3218 
 3219   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
 3220      the end of the array (Gtk 1.2.5.) */
 3221   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
 3222   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
 3223 };
 3224 
 3225 static char *down_arrow_xpm[] = {
 3226   "15 15 4 1",
 3227   "     c None",
 3228   "-    c #FFFFFF",
 3229   "+    c #D6D6D6",
 3230   "@    c #000000",
 3231 
 3232   "               ",
 3233   " ------------- ",
 3234   " -+++++++++++@ ",
 3235   "  -+++++++++@  ",
 3236   "  -+++++++++@  ",
 3237   "   -+++++++@   ",
 3238   "   -+++++++@   ",
 3239   "    -+++++@    ",
 3240   "    -+++++@    ",
 3241   "     -+++@     ",
 3242   "     -+++@     ",
 3243   "      -+@      ",
 3244   "      -+@      ",
 3245   "       @       ",
 3246   "       @       ",
 3247 
 3248   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
 3249      the end of the array (Gtk 1.2.5.) */
 3250   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
 3251   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
 3252 };
 3253 
 3254 static void
 3255 pixmapify_button (state *s, int down_p)
 3256 {
 3257   GdkPixmap *pixmap;
 3258   GdkBitmap *mask;
 3259   GtkWidget *pixmapwid;
 3260   GtkStyle *style;
 3261   GtkWidget *w;
 3262 
 3263   w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
 3264   style = gtk_widget_get_style (w);
 3265   mask = 0;
 3266   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
 3267                                          &style->bg[GTK_STATE_NORMAL],
 3268                                          (down_p
 3269                                           ? (gchar **) down_arrow_xpm
 3270                                           : (gchar **) up_arrow_xpm));
 3271   pixmapwid = gtk_pixmap_new (pixmap, mask);
 3272   gtk_widget_show (pixmapwid);
 3273   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
 3274   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
 3275 }
 3276 
 3277 static void
 3278 map_next_button_cb (GtkWidget *w, gpointer user_data)
 3279 {
 3280   state *s = (state *) user_data;
 3281   pixmapify_button (s, 1);
 3282 }
 3283 
 3284 static void
 3285 map_prev_button_cb (GtkWidget *w, gpointer user_data)
 3286 {
 3287   state *s = (state *) user_data;
 3288   pixmapify_button (s, 0);
 3289 }
 3290 #endif /* !HAVE_GTK2 */
 3291 
 3292 
 3293 #ifndef HAVE_GTK2
 3294 /* Work around a Gtk bug that causes label widgets to wrap text too early.
 3295  */
 3296 
 3297 static void
 3298 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
 3299                                              GtkAllocation *allocation,
 3300                          void *foo)
 3301 {
 3302   GtkRequisition req;
 3303   GtkWidgetAuxInfo *aux_info;
 3304 
 3305   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
 3306 
 3307   aux_info->width = allocation->width;
 3308   aux_info->height = -2;
 3309   aux_info->x = -1;
 3310   aux_info->y = -1;
 3311 
 3312   gtk_widget_size_request (label, &req);
 3313 }
 3314 
 3315 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
 3316  */
 3317 static void
 3318 eschew_gtk_lossage (GtkLabel *label)
 3319 {
 3320   GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
 3321   aux_info->width = GTK_WIDGET (label)->allocation.width;
 3322   aux_info->height = -2;
 3323   aux_info->x = -1;
 3324   aux_info->y = -1;
 3325 
 3326   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
 3327 
 3328   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
 3329                       GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
 3330                       0);
 3331 
 3332   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
 3333 
 3334   gtk_widget_queue_resize (GTK_WIDGET (label));
 3335 }
 3336 #endif /* !HAVE_GTK2 */
 3337 
 3338 
 3339 static void
 3340 populate_demo_window (state *s, int list_elt)
 3341 {
 3342   Display *dpy = GDK_DISPLAY();
 3343   saver_preferences *p = &s->prefs;
 3344   screenhack *hack;
 3345   char *pretty_name;
 3346   GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
 3347   GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "opt_frame"));
 3348   GtkEntry *cmd    = GTK_ENTRY (name_to_widget (s, "cmd_text"));
 3349   GtkComboBoxEntry *vis = GTK_COMBO_BOX_ENTRY (name_to_widget (s, "visual_combo"));
 3350   GtkWidget *list  = GTK_WIDGET (name_to_widget (s, "list"));
 3351 
 3352   if (p->mode == BLANK_ONLY)
 3353     {
 3354       hack = 0;
 3355       pretty_name = strdup (_("Blank Screen"));
 3356       schedule_preview (s, 0);
 3357     }
 3358   else if (p->mode == DONT_BLANK)
 3359     {
 3360       hack = 0;
 3361       pretty_name = strdup (_("Screen Saver Disabled"));
 3362       schedule_preview (s, 0);
 3363     }
 3364   else
 3365     {
 3366       int hack_number = (list_elt >= 0 && list_elt < s->list_count
 3367                          ? s->list_elt_to_hack_number[list_elt]
 3368                          : -1);
 3369       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
 3370 
 3371       pretty_name = (hack
 3372                      ? (hack->name
 3373                         ? strdup (hack->name)
 3374                         : make_hack_name (dpy, hack->command))
 3375                      : 0);
 3376 
 3377       if (hack)
 3378         schedule_preview (s, hack->command);
 3379       else
 3380         schedule_preview (s, 0);
 3381     }
 3382 
 3383   if (!pretty_name)
 3384     pretty_name = strdup (_("Preview"));
 3385 
 3386   gtk_frame_set_label (frame1, _(pretty_name));
 3387   gtk_frame_set_label (frame2, _(pretty_name));
 3388 
 3389   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
 3390   gtk_entry_set_position (cmd, 0);
 3391 
 3392   {
 3393     char title[255];
 3394     sprintf (title, _("%s: %.100s Settings"),
 3395              progclass, (pretty_name ? pretty_name : "???"));
 3396     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
 3397   }
 3398 
 3399   gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (vis))),
 3400                       (hack
 3401                        ? (hack->visual && *hack->visual
 3402                           ? hack->visual
 3403                           : _("Any"))
 3404                        : ""));
 3405 
 3406   sensitize_demo_widgets (s, (hack ? True : False));
 3407 
 3408   if (pretty_name) free (pretty_name);
 3409 
 3410   ensure_selected_item_visible (list);
 3411 
 3412   s->_selected_list_element = list_elt;
 3413 }
 3414 
 3415 
 3416 static void
 3417 widget_deleter (GtkWidget *widget, gpointer data)
 3418 {
 3419   /* #### Well, I want to destroy these widgets, but if I do that, they get
 3420      referenced again, and eventually I get a SEGV.  So instead of
 3421      destroying them, I'll just hide them, and leak a bunch of memory
 3422      every time the disk file changes.  Go go go Gtk!
 3423 
 3424      #### Ok, that's a lie, I get a crash even if I just hide the widget
 3425      and don't ever delete it.  Fuck!
 3426    */
 3427 #if 0
 3428   gtk_widget_destroy (widget);
 3429 #else
 3430   gtk_widget_hide (widget);
 3431 #endif
 3432 }
 3433 
 3434 
 3435 static char **sort_hack_cmp_names_kludge;
 3436 static int
 3437 sort_hack_cmp (const void *a, const void *b)
 3438 {
 3439   if (a == b)
 3440     return 0;
 3441   else
 3442     {
 3443       int aa = *(int *) a;
 3444       int bb = *(int *) b;
 3445       const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
 3446       return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
 3447                      (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
 3448     }
 3449 }
 3450 
 3451 
 3452 static void
 3453 initialize_sort_map (state *s)
 3454 {
 3455   Display *dpy = GDK_DISPLAY();
 3456   saver_preferences *p = &s->prefs;
 3457   int i, j;
 3458 
 3459   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
 3460   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
 3461   if (s->hacks_available_p) free (s->hacks_available_p);
 3462 
 3463   s->list_elt_to_hack_number = (int *)
 3464     calloc (sizeof(int), p->screenhacks_count + 1);
 3465   s->hack_number_to_list_elt = (int *)
 3466     calloc (sizeof(int), p->screenhacks_count + 1);
 3467   s->hacks_available_p = (Bool *)
 3468     calloc (sizeof(Bool), p->screenhacks_count + 1);
 3469   s->total_available = 0;
 3470 
 3471   /* Check which hacks actually exist on $PATH
 3472    */
 3473   for (i = 0; i < p->screenhacks_count; i++)
 3474     {
 3475       screenhack *hack = p->screenhacks[i];
 3476       int on = on_path_p (hack->command) ? 1 : 0;
 3477       s->hacks_available_p[i] = on;
 3478       s->total_available += on;
 3479     }
 3480 
 3481   /* Initialize list->hack table to unsorted mapping, omitting nonexistent
 3482      hacks, if desired.
 3483    */
 3484   j = 0;
 3485   for (i = 0; i < p->screenhacks_count; i++)
 3486     {
 3487       if (!p->ignore_uninstalled_p ||
 3488           s->hacks_available_p[i])
 3489         s->list_elt_to_hack_number[j++] = i;
 3490     }
 3491   s->list_count = j;
 3492 
 3493   for (; j < p->screenhacks_count; j++)
 3494     s->list_elt_to_hack_number[j] = -1;
 3495 
 3496 
 3497   /* Generate list of sortable names (once)
 3498    */
 3499   sort_hack_cmp_names_kludge = (char **)
 3500     calloc (sizeof(char *), p->screenhacks_count);
 3501   for (i = 0; i < p->screenhacks_count; i++)
 3502     {
 3503       screenhack *hack = p->screenhacks[i];
 3504       char *name = (hack->name && *hack->name
 3505                     ? strdup (hack->name)
 3506                     : make_hack_name (dpy, hack->command));
 3507       gchar *s2 = g_str_to_ascii (name, 0);  /* Sort "Möbius" properly */
 3508       gchar *s3 = g_ascii_strdown (s2, -1);
 3509       free (name);
 3510       free (s2);
 3511       sort_hack_cmp_names_kludge[i] = s3;
 3512     }
 3513 
 3514   /* Sort list->hack map alphabetically
 3515    */
 3516   qsort (s->list_elt_to_hack_number,
 3517          p->screenhacks_count,
 3518          sizeof(*s->list_elt_to_hack_number),
 3519          sort_hack_cmp);
 3520 
 3521   /* Free names
 3522    */
 3523   for (i = 0; i < p->screenhacks_count; i++)
 3524     free (sort_hack_cmp_names_kludge[i]);
 3525   free (sort_hack_cmp_names_kludge);
 3526   sort_hack_cmp_names_kludge = 0;
 3527 
 3528   /* Build inverse table */
 3529   for (i = 0; i < p->screenhacks_count; i++)
 3530     {
 3531       int n = s->list_elt_to_hack_number[i];
 3532       if (n != -1)
 3533         s->hack_number_to_list_elt[n] = i;
 3534     }
 3535 }
 3536 
 3537 
 3538 static int
 3539 maybe_reload_init_file (state *s)
 3540 {
 3541   Display *dpy = GDK_DISPLAY();
 3542   saver_preferences *p = &s->prefs;
 3543   int status = 0;
 3544 
 3545   static Bool reentrant_lock = False;
 3546   if (reentrant_lock) return 0;
 3547   reentrant_lock = True;
 3548 
 3549   if (init_file_changed_p (p))
 3550     {
 3551       const char *f = init_file_name();
 3552       char *b;
 3553       int list_elt;
 3554       GtkWidget *list;
 3555 
 3556       if (!f || !*f) return 0;
 3557       b = (char *) malloc (strlen(f) + 1024);
 3558       sprintf (b,
 3559                _("Warning:\n\n"
 3560          "file \"%s\" has changed, reloading.\n"),
 3561                f);
 3562       warning_dialog (s->toplevel_widget, b, D_NONE, 100);
 3563       free (b);
 3564 
 3565       load_init_file (dpy, p);
 3566       initialize_sort_map (s);
 3567 
 3568       list_elt = selected_list_element (s);
 3569       list = name_to_widget (s, "list");
 3570       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
 3571       populate_hack_list (s);
 3572       force_list_select_item (s, list, list_elt, True);
 3573       populate_prefs_page (s);
 3574       populate_demo_window (s, list_elt);
 3575       ensure_selected_item_visible (list);
 3576 
 3577       status = 1;
 3578     }
 3579 
 3580   reentrant_lock = False;
 3581   return status;
 3582 }
 3583 
 3584 
 3585 
 3586 /* Making the preview window have the right X visual (so that GL works.)
 3587  */
 3588 
 3589 static Visual *get_best_gl_visual (state *);
 3590 
 3591 static GdkVisual *
 3592 x_visual_to_gdk_visual (Visual *xv)
 3593 {
 3594   GList *gvs = gdk_list_visuals();
 3595   if (!xv) return gdk_visual_get_system();
 3596   for (; gvs; gvs = gvs->next)
 3597     {
 3598       GdkVisual *gv = (GdkVisual *) gvs->data;
 3599       if (xv == GDK_VISUAL_XVISUAL (gv))
 3600         return gv;
 3601     }
 3602   fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
 3603            blurb(), (unsigned long) xv->visualid);
 3604   abort();
 3605 }
 3606 
 3607 static void
 3608 clear_preview_window (state *s)
 3609 {
 3610   GtkWidget *p;
 3611   GdkWindow *window;
 3612   GtkStyle  *style;
 3613 
 3614   if (!s->toplevel_widget) return;  /* very early */
 3615   p = name_to_widget (s, "preview");
 3616   window = GET_WINDOW (p);
 3617 
 3618   if (!window) return;
 3619 
 3620   /* Flush the widget background down into the window, in case a subproc
 3621      has changed it. */
 3622   style = gtk_widget_get_style (p);
 3623   gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
 3624   gdk_window_clear (window);
 3625 
 3626   {
 3627     int list_elt = selected_list_element (s);
 3628     int hack_number = (list_elt >= 0
 3629                        ? s->list_elt_to_hack_number[list_elt]
 3630                        : -1);
 3631     Bool available_p = (hack_number >= 0
 3632                         ? s->hacks_available_p [hack_number]
 3633                         : True);
 3634     Bool nothing_p = (s->total_available < 5);
 3635 
 3636 #ifdef HAVE_GTK2
 3637     GtkWidget *notebook = name_to_widget (s, "preview_notebook");
 3638     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
 3639                (s->running_preview_error_p
 3640                             ? (available_p ? 1 :
 3641                                nothing_p ? 3 : 2)
 3642                             : 0));
 3643 #else /* !HAVE_GTK2 */
 3644     if (s->running_preview_error_p)
 3645       {
 3646         const char * const lines1[] = { N_("No Preview"), N_("Available") };
 3647         const char * const lines2[] = { N_("Not"), N_("Installed") };
 3648         int nlines = countof(lines1);
 3649         int lh = p->style->font->ascent + p->style->font->descent;
 3650         int y, i;
 3651         gint w, h;
 3652 
 3653         const char * const *lines = (available_p ? lines1 : lines2);
 3654 
 3655         gdk_window_get_size (window, &w, &h);
 3656         y = (h - (lh * nlines)) / 2;
 3657         y += p->style->font->ascent;
 3658         for (i = 0; i < nlines; i++)
 3659           {
 3660             int sw = gdk_string_width (p->style->font, _(lines[i]));
 3661             int x = (w - sw) / 2;
 3662             gdk_draw_string (window, p->style->font,
 3663                              p->style->fg_gc[GTK_STATE_NORMAL],
 3664                              x, y, _(lines[i]));
 3665             y += lh;
 3666           }
 3667       }
 3668 #endif /* !HAVE_GTK2 */
 3669   }
 3670 
 3671   gdk_flush ();
 3672 }
 3673 
 3674 
 3675 static void
 3676 reset_preview_window (state *s)
 3677 {
 3678   /* On some systems (most recently, MacOS X) OpenGL programs get confused
 3679      when you kill one and re-start another on the same window.  So maybe
 3680      it's best to just always destroy and recreate the preview window
 3681      when changing hacks, instead of always trying to reuse the same one?
 3682    */
 3683   GtkWidget *pr = name_to_widget (s, "preview");
 3684   if (GET_REALIZED (pr))
 3685     {
 3686       GdkWindow *window = GET_WINDOW (pr);
 3687       Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0);
 3688       Window id;
 3689       gtk_widget_hide (pr);
 3690       gtk_widget_unrealize (pr);
 3691       gtk_widget_realize (pr);
 3692       gtk_widget_show (pr);
 3693       id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
 3694       if (s->debug_p)
 3695         fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
 3696                  (unsigned int) oid,
 3697                  (unsigned int) id);
 3698     }
 3699 }
 3700 
 3701 
 3702 static void
 3703 fix_preview_visual (state *s)
 3704 {
 3705   GtkWidget *widget = name_to_widget (s, "preview");
 3706   Visual *xvisual = get_best_gl_visual (s);
 3707   GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
 3708   GdkVisual *dvisual = gdk_visual_get_system();
 3709   GdkColormap *cmap = (visual == dvisual
 3710                        ? gdk_colormap_get_system ()
 3711                        : gdk_colormap_new (visual, False));
 3712 
 3713   if (s->debug_p)
 3714     fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
 3715              (visual == dvisual ? "default" : "non-default"),
 3716              (xvisual ? (unsigned long) xvisual->visualid : 0L));
 3717 
 3718   if (!GET_REALIZED (widget) ||
 3719       gtk_widget_get_visual (widget) != visual)
 3720     {
 3721       gtk_widget_unrealize (widget);
 3722       gtk_widget_set_visual (widget, visual);
 3723       gtk_widget_set_colormap (widget, cmap);
 3724       gtk_widget_realize (widget);
 3725     }
 3726 
 3727   /* Set the Widget colors to be white-on-black. */
 3728   {
 3729     GdkWindow *window = GET_WINDOW (widget);
 3730     GtkStyle *style = gtk_style_copy (gtk_widget_get_style (widget));
 3731     GdkColormap *cmap = gtk_widget_get_colormap (widget);
 3732     GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
 3733     GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
 3734     GdkGC *fgc = gdk_gc_new(window);
 3735     GdkGC *bgc = gdk_gc_new(window);
 3736     if (!gdk_color_white (cmap, fg)) abort();
 3737     if (!gdk_color_black (cmap, bg)) abort();
 3738     gdk_gc_set_foreground (fgc, fg);
 3739     gdk_gc_set_background (fgc, bg);
 3740     gdk_gc_set_foreground (bgc, bg);
 3741     gdk_gc_set_background (bgc, fg);
 3742     style->fg_gc[GTK_STATE_NORMAL] = fgc;
 3743     style->bg_gc[GTK_STATE_NORMAL] = fgc;
 3744     gtk_widget_set_style (widget, style);
 3745 
 3746     /* For debugging purposes, put a title on the window (so that
 3747        it can be easily found in the output of "xwininfo -tree".)
 3748      */
 3749     gdk_window_set_title (window, "Preview");
 3750   }
 3751 
 3752   gtk_widget_show (widget);
 3753 }
 3754 
 3755 
 3756 /* Subprocesses
 3757  */
 3758 
 3759 static char *
 3760 subproc_pretty_name (state *s)
 3761 {
 3762   if (s->running_preview_cmd)
 3763     {
 3764       char *ps = strdup (s->running_preview_cmd);
 3765       char *ss = strchr (ps, ' ');
 3766       if (ss) *ss = 0;
 3767       ss = strrchr (ps, '/');
 3768       if (!ss)
 3769         ss = ps;
 3770       else
 3771         {
 3772           ss = strdup (ss+1);
 3773           free (ps);
 3774         }
 3775       return ss;
 3776     }
 3777   else
 3778     return strdup ("???");
 3779 }
 3780 
 3781 
 3782 static void
 3783 reap_zombies (state *s)
 3784 {
 3785   int wait_status = 0;
 3786   pid_t pid;
 3787   while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
 3788     {
 3789       if (s->debug_p)
 3790         {
 3791           if (pid == s->running_preview_pid)
 3792             {
 3793               char *ss = subproc_pretty_name (s);
 3794               fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
 3795                        (unsigned long) pid, ss);
 3796               free (ss);
 3797             }
 3798           else
 3799             fprintf (stderr, "%s: pid %lu died\n", blurb(),
 3800                      (unsigned long) pid);
 3801         }
 3802     }
 3803 }
 3804 
 3805 
 3806 /* Mostly lifted from driver/subprocs.c */
 3807 static Visual *
 3808 get_best_gl_visual (state *s)
 3809 {
 3810   Display *dpy = GDK_DISPLAY();
 3811   pid_t forked;
 3812   int fds [2];
 3813   int in, out;
 3814   char buf[1024];
 3815 
 3816   char *av[10];
 3817   int ac = 0;
 3818 
 3819   av[ac++] = "xscreensaver-gl-visual";
 3820   av[ac] = 0;
 3821 
 3822   if (pipe (fds))
 3823     {
 3824       perror ("error creating pipe:");
 3825       return 0;
 3826     }
 3827 
 3828   in = fds [0];
 3829   out = fds [1];
 3830 
 3831   switch ((int) (forked = fork ()))
 3832     {
 3833     case -1:
 3834       {
 3835         sprintf (buf, "%s: couldn't fork", blurb());
 3836         perror (buf);
 3837         exit (1);
 3838       }
 3839     case 0:
 3840       {
 3841         int stdout_fd = 1;
 3842 
 3843         close (in);  /* don't need this one */
 3844         close (ConnectionNumber (dpy));     /* close display fd */
 3845 
 3846         if (dup2 (out, stdout_fd) < 0)      /* pipe stdout */
 3847           {
 3848             perror ("could not dup() a new stdout:");
 3849             return 0;
 3850           }
 3851 
 3852         execvp (av[0], av);         /* shouldn't return. */
 3853 
 3854         if (errno != ENOENT)
 3855           {
 3856             /* Ignore "no such file or directory" errors, unless verbose.
 3857                Issue all other exec errors, though. */
 3858             sprintf (buf, "%s: running %s", blurb(), av[0]);
 3859             perror (buf);
 3860           }
 3861 
 3862         /* Note that one must use _exit() instead of exit() in procs forked
 3863            off of Gtk programs -- Gtk installs an atexit handler that has a
 3864            copy of the X connection (which we've already closed, for safety.)
 3865            If one uses exit() instead of _exit(), then one sometimes gets a
 3866            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
 3867         */
 3868         _exit (1);                              /* exits fork */
 3869         break;
 3870       }
 3871     default:
 3872       {
 3873         int result = 0;
 3874         int wait_status = 0;
 3875 
 3876         FILE *f = fdopen (in, "r");
 3877         unsigned int v = 0;
 3878         char c;
 3879 
 3880         close (out);  /* don't need this one */
 3881 
 3882         *buf = 0;
 3883         if (!fgets (buf, sizeof(buf)-1, f))
 3884           *buf = 0;
 3885         fclose (f);
 3886 
 3887         /* Wait for the child to die. */
 3888         waitpid (-1, &wait_status, 0);
 3889 
 3890         if (1 == sscanf (buf, "0x%x %c", &v, &c))
 3891           result = (int) v;
 3892 
 3893         if (result == 0)
 3894           {
 3895             if (s->debug_p)
 3896               fprintf (stderr, "%s: %s did not report a GL visual!\n",
 3897                        blurb(), av[0]);
 3898             return 0;
 3899           }
 3900         else
 3901           {
 3902             Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
 3903             if (s->debug_p)
 3904               fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
 3905                        blurb(), av[0], result);
 3906             if (!v) abort();
 3907             return v;
 3908           }
 3909       }
 3910     }
 3911 
 3912   abort();
 3913 }
 3914 
 3915 
 3916 static void
 3917 kill_preview_subproc (state *s, Bool reset_p)
 3918 {
 3919   s->running_preview_error_p = False;
 3920 
 3921   reap_zombies (s);
 3922   clear_preview_window (s);
 3923 
 3924   if (s->subproc_check_timer_id)
 3925     {
 3926       gtk_timeout_remove (s->subproc_check_timer_id);
 3927       s->subproc_check_timer_id = 0;
 3928       s->subproc_check_countdown = 0;
 3929     }
 3930 
 3931   if (s->running_preview_pid)
 3932     {
 3933       int status = kill (s->running_preview_pid, SIGTERM);
 3934       char *ss = subproc_pretty_name (s);
 3935 
 3936       if (status < 0)
 3937         {
 3938           if (errno == ESRCH)
 3939             {
 3940               if (s->debug_p)
 3941                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
 3942                          blurb(), (unsigned long) s->running_preview_pid, ss);
 3943             }
 3944           else
 3945             {
 3946               char buf [1024];
 3947               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
 3948                        blurb(), (unsigned long) s->running_preview_pid, ss);
 3949               perror (buf);
 3950             }
 3951         }
 3952       else {
 3953     int endstatus;
 3954     waitpid(s->running_preview_pid, &endstatus, 0);
 3955     if (s->debug_p)
 3956       fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
 3957            (unsigned long) s->running_preview_pid, ss);
 3958       }
 3959 
 3960       free (ss);
 3961       s->running_preview_pid = 0;
 3962       if (s->running_preview_cmd) free (s->running_preview_cmd);
 3963       s->running_preview_cmd = 0;
 3964     }
 3965 
 3966   reap_zombies (s);
 3967 
 3968   if (reset_p)
 3969     {
 3970       reset_preview_window (s);
 3971       clear_preview_window (s);
 3972     }
 3973 }
 3974 
 3975 
 3976 /* Immediately and unconditionally launches the given process,
 3977    after appending the -window-id option; sets running_preview_pid.
 3978  */
 3979 static void
 3980 launch_preview_subproc (state *s)
 3981 {
 3982   saver_preferences *p = &s->prefs;
 3983   Window id;
 3984   char *new_cmd = 0;
 3985   pid_t forked;
 3986   const char *cmd = s->desired_preview_cmd;
 3987 
 3988   GtkWidget *pr = name_to_widget (s, "preview");
 3989   GdkWindow *window;
 3990 
 3991   reset_preview_window (s);
 3992 
 3993   window = GET_WINDOW (pr);
 3994 
 3995   s->running_preview_error_p = False;
 3996 
 3997   if (s->preview_suppressed_p)
 3998     {
 3999       kill_preview_subproc (s, False);
 4000       goto DONE;
 4001     }
 4002 
 4003   new_cmd = malloc (strlen (cmd) + 40);
 4004 
 4005   id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
 4006   if (id == 0)
 4007     {
 4008       /* No window id?  No command to run. */
 4009       free (new_cmd);
 4010       new_cmd = 0;
 4011     }
 4012   else
 4013     {
 4014       /* We do this instead of relying on $XSCREENSAVER_WINDOW specifically
 4015          so that third-party savers that don't implement -window-id will fail:
 4016          otherwise we might have full-screen windows popping up when we were
 4017          just trying to get a preview thumbnail.
 4018        */
 4019       strcpy (new_cmd, cmd);
 4020       sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
 4021                (unsigned int) id);
 4022     }
 4023 
 4024   kill_preview_subproc (s, False);
 4025   if (! new_cmd)
 4026     {
 4027       s->running_preview_error_p = True;
 4028       clear_preview_window (s);
 4029       goto DONE;
 4030     }
 4031 
 4032   switch ((int) (forked = fork ()))
 4033     {
 4034     case -1:
 4035       {
 4036         char buf[255];
 4037         sprintf (buf, "%s: couldn't fork", blurb());
 4038         perror (buf);
 4039         s->running_preview_error_p = True;
 4040         goto DONE;
 4041         break;
 4042       }
 4043     case 0:
 4044       {
 4045         close (ConnectionNumber (GDK_DISPLAY()));
 4046 
 4047         hack_subproc_environment (id, s->debug_p);
 4048 
 4049         usleep (250000);  /* pause for 1/4th second before launching, to give
 4050                              the previous program time to die and flush its X
 4051                              buffer, so we don't get leftover turds on the
 4052                              window. */
 4053 
 4054         exec_command (p->shell, new_cmd, p->nice_inferior);
 4055         /* Don't bother printing an error message when we are unable to
 4056            exec subprocesses; we handle that by polling the pid later.
 4057 
 4058            Note that one must use _exit() instead of exit() in procs forked
 4059            off of Gtk programs -- Gtk installs an atexit handler that has a
 4060            copy of the X connection (which we've already closed, for safety.)
 4061            If one uses exit() instead of _exit(), then one sometimes gets a
 4062            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
 4063         */
 4064         _exit (1);  /* exits child fork */
 4065         break;
 4066 
 4067       default:
 4068 
 4069         if (s->running_preview_cmd) free (s->running_preview_cmd);
 4070         s->running_preview_cmd = strdup (s->desired_preview_cmd);
 4071         s->running_preview_pid = forked;
 4072 
 4073         if (s->debug_p)
 4074           {
 4075             char *ss = subproc_pretty_name (s);
 4076             fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
 4077                      (unsigned long) forked, ss);
 4078             free (ss);
 4079           }
 4080         break;
 4081       }
 4082     }
 4083 
 4084   schedule_preview_check (s);
 4085 
 4086  DONE:
 4087   if (new_cmd) free (new_cmd);
 4088   new_cmd = 0;
 4089 }
 4090 
 4091 
 4092 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
 4093  */
 4094 static void
 4095 hack_environment (state *s)
 4096 {
 4097   static const char *def_path =
 4098 # ifdef DEFAULT_PATH_PREFIX
 4099     DEFAULT_PATH_PREFIX;
 4100 # else
 4101     "";
 4102 # endif
 4103 
 4104   Display *dpy = GDK_DISPLAY();
 4105   const char *odpy = DisplayString (dpy);
 4106   char *ndpy = (char *) malloc(strlen(odpy) + 20);
 4107   strcpy (ndpy, "DISPLAY=");
 4108   strcat (ndpy, odpy);
 4109   if (putenv (ndpy))
 4110     abort ();
 4111 
 4112   if (s->debug_p)
 4113     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
 4114 
 4115   /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
 4116      2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
 4117      So we must leak it (and/or the previous setting).  Yay.
 4118    */
 4119 
 4120   if (def_path && *def_path)
 4121     {
 4122       const char *opath = getenv("PATH");
 4123       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
 4124       strcpy (npath, "PATH=");
 4125       strcat (npath, def_path);
 4126       strcat (npath, ":");
 4127       strcat (npath, opath);
 4128 
 4129       if (putenv (npath))
 4130     abort ();
 4131       /* do not free(npath) -- see above */
 4132 
 4133       if (s->debug_p)
 4134         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
 4135     }
 4136 }
 4137 
 4138 
 4139 static void
 4140 hack_subproc_environment (Window preview_window_id, Bool debug_p)
 4141 {
 4142   /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
 4143      necessary yet, but it will make programs work if we had invoked
 4144      them with "-root" and not with "-window-id" -- which, of course,
 4145      doesn't happen.
 4146    */
 4147   char *nssw = (char *) malloc (40);
 4148   sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
 4149 
 4150   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
 4151      any more, right?  It's not Posix, but everyone seems to have it. */
 4152   if (putenv (nssw))
 4153     abort ();
 4154 
 4155   if (debug_p)
 4156     fprintf (stderr, "%s: %s\n", blurb(), nssw);
 4157 
 4158   /* do not free(nssw) -- see above */
 4159 }
 4160 
 4161 
 4162 /* Called from a timer:
 4163    Launches the currently-chosen subprocess, if it's not already running.
 4164    If there's a different process running, kills it.
 4165  */
 4166 static int
 4167 update_subproc_timer (gpointer data)
 4168 {
 4169   state *s = (state *) data;
 4170   if (! s->desired_preview_cmd)
 4171     kill_preview_subproc (s, True);
 4172   else if (!s->running_preview_cmd ||
 4173            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
 4174     launch_preview_subproc (s);
 4175 
 4176   s->subproc_timer_id = 0;
 4177   return FALSE;  /* do not re-execute timer */
 4178 }
 4179 
 4180 static int
 4181 settings_timer (gpointer data)
 4182 {
 4183   settings_cb (0, 0);
 4184   return FALSE;
 4185 }
 4186 
 4187 
 4188 /* Call this when you think you might want a preview process running.
 4189    It will set a timer that will actually launch that program a second
 4190    from now, if you haven't changed your mind (to avoid double-click
 4191    spazzing, etc.)  `cmd' may be null meaning "no process".
 4192  */
 4193 static void
 4194 schedule_preview (state *s, const char *cmd)
 4195 {
 4196   int delay = 1000 * 0.5;   /* 1/2 second hysteresis */
 4197 
 4198   if (s->debug_p)
 4199     {
 4200       if (cmd)
 4201         fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
 4202       else
 4203         fprintf (stderr, "%s: scheduling preview death\n", blurb());
 4204     }
 4205 
 4206   if (s->desired_preview_cmd) free (s->desired_preview_cmd);
 4207   s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
 4208 
 4209   if (s->subproc_timer_id)
 4210     gtk_timeout_remove (s->subproc_timer_id);
 4211   s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
 4212 }
 4213 
 4214 
 4215 /* Called from a timer:
 4216    Checks to see if the subproc that should be running, actually is.
 4217  */
 4218 static int
 4219 check_subproc_timer (gpointer data)
 4220 {
 4221   state *s = (state *) data;
 4222   Bool again_p = True;
 4223 
 4224   if (s->running_preview_error_p ||   /* already dead */
 4225       s->running_preview_pid <= 0)
 4226     {
 4227       again_p = False;
 4228     }
 4229   else
 4230     {
 4231       int status;
 4232       reap_zombies (s);
 4233       status = kill (s->running_preview_pid, 0);
 4234       if (status < 0 && errno == ESRCH)
 4235         s->running_preview_error_p = True;
 4236 
 4237       if (s->debug_p)
 4238         {
 4239           char *ss = subproc_pretty_name (s);
 4240           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
 4241                    (unsigned long) s->running_preview_pid, ss,
 4242                    (s->running_preview_error_p ? "dead" : "alive"));
 4243           free (ss);
 4244         }
 4245 
 4246       if (s->running_preview_error_p)
 4247         {
 4248           clear_preview_window (s);
 4249           again_p = False;
 4250         }
 4251     }
 4252 
 4253   /* Otherwise, it's currently alive.  We might be checking again, or we
 4254      might be satisfied. */
 4255 
 4256   if (--s->subproc_check_countdown <= 0)
 4257     again_p = False;
 4258 
 4259   if (again_p)
 4260     return TRUE;     /* re-execute timer */
 4261   else
 4262     {
 4263       s->subproc_check_timer_id = 0;
 4264       s->subproc_check_countdown = 0;
 4265       return FALSE;  /* do not re-execute timer */
 4266     }
 4267 }
 4268 
 4269 
 4270 /* Call this just after launching a subprocess.
 4271    This sets a timer that will, five times a second for two seconds,
 4272    check whether the program is still running.  The assumption here
 4273    is that if the process didn't stay up for more than a couple of
 4274    seconds, then either the program doesn't exist, or it doesn't
 4275    take a -window-id argument.
 4276  */
 4277 static void
 4278 schedule_preview_check (state *s)
 4279 {
 4280   int seconds = 2;
 4281   int ticks = 5;
 4282 
 4283   if (s->debug_p)
 4284     fprintf (stderr, "%s: scheduling check\n", blurb());
 4285 
 4286   if (s->subproc_check_timer_id)
 4287     gtk_timeout_remove (s->subproc_check_timer_id);
 4288   s->subproc_check_timer_id =
 4289     gtk_timeout_add (1000 / ticks,
 4290                      check_subproc_timer, (gpointer) s);
 4291   s->subproc_check_countdown = ticks * seconds;
 4292 }
 4293 
 4294 
 4295 static Bool
 4296 screen_blanked_p (void)
 4297 {
 4298   Atom type;
 4299   int format;
 4300   unsigned long nitems, bytesafter;
 4301   unsigned char *dataP = 0;
 4302   Display *dpy = GDK_DISPLAY();
 4303   Bool blanked_p = False;
 4304 
 4305   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
 4306                           XA_SCREENSAVER_STATUS,
 4307                           0, 3, False, XA_INTEGER,
 4308                           &type, &format, &nitems, &bytesafter,
 4309                           &dataP)
 4310       == Success
 4311       && type == XA_INTEGER
 4312       && nitems >= 3
 4313       && dataP)
 4314     {
 4315       Atom *data = (Atom *) dataP;
 4316       blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
 4317     }
 4318 
 4319   if (dataP) XFree (dataP);
 4320 
 4321   return blanked_p;
 4322 }
 4323 
 4324 /* Wake up every now and then and see if the screen is blanked.
 4325    If it is, kill off the small-window demo -- no point in wasting
 4326    cycles by running two screensavers at once...
 4327  */
 4328 static int
 4329 check_blanked_timer (gpointer data)
 4330 {
 4331   state *s = (state *) data;
 4332   Bool blanked_p = screen_blanked_p ();
 4333   if (blanked_p && s->running_preview_pid)
 4334     {
 4335       if (s->debug_p)
 4336         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
 4337       kill_preview_subproc (s, True);
 4338     }
 4339 
 4340   return True;  /* re-execute timer */
 4341 }
 4342 
 4343 
 4344 /* How many screens are there (including Xinerama.)
 4345  */
 4346 static int
 4347 screen_count (Display *dpy)
 4348 {
 4349   int nscreens = ScreenCount(dpy);
 4350 # ifdef HAVE_XINERAMA
 4351   if (nscreens <= 1)
 4352     {
 4353       int event_number, error_number;
 4354       if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
 4355           XineramaIsActive (dpy))
 4356         {
 4357           XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
 4358           if (xsi) XFree (xsi);
 4359         }
 4360     }
 4361 # endif /* HAVE_XINERAMA */
 4362 
 4363   return nscreens;
 4364 }
 4365 
 4366 
 4367 /* Setting window manager icon
 4368  */
 4369 
 4370 static void
 4371 init_icon (GdkWindow *window)
 4372 {
 4373   GdkBitmap *mask = 0;
 4374   GdkPixmap *pixmap =
 4375     gdk_pixmap_create_from_xpm_d (window, &mask, 0,
 4376                                   (gchar **) logo_50_xpm);
 4377   if (pixmap)
 4378     gdk_window_set_icon (window, 0, pixmap, mask);
 4379 }
 4380 
 4381 
 4382 /* The main demo-mode command loop.
 4383  */
 4384 
 4385 #if 0
 4386 static Bool
 4387 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
 4388     XrmRepresentation *type, XrmValue *value, XPointer closure)
 4389 {
 4390   int i;
 4391   for (i = 0; quarks[i]; i++)
 4392     {
 4393       if (bindings[i] == XrmBindTightly)
 4394     fprintf (stderr, (i == 0 ? "" : "."));
 4395       else if (bindings[i] == XrmBindLoosely)
 4396     fprintf (stderr, "*");
 4397       else
 4398     fprintf (stderr, " ??? ");
 4399       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
 4400     }
 4401 
 4402   fprintf (stderr, ": %s\n", (char *) value->addr);
 4403 
 4404   return False;
 4405 }
 4406 #endif
 4407 
 4408 
 4409 static Window
 4410 gnome_screensaver_window (Screen *screen, char **name_ret)
 4411 {
 4412   Display *dpy = DisplayOfScreen (screen);
 4413   Window root = RootWindowOfScreen (screen);
 4414   Window parent, *kids;
 4415   unsigned int nkids;
 4416   Window gnome_window = 0;
 4417   int i;
 4418 
 4419   if (! XQueryTree (dpy, root, &root, &parent, &kids, &nkids))
 4420     abort ();
 4421   if (name_ret)
 4422     *name_ret = 0;
 4423   for (i = 0; i < nkids; i++)
 4424     {
 4425       Atom type;
 4426       int format;
 4427       unsigned long nitems, bytesafter;
 4428       unsigned char *name;
 4429       if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128,
 4430                               False, XA_STRING, &type, &format, &nitems,
 4431                               &bytesafter, &name)
 4432           == Success
 4433           && type != None
 4434           && (!strcmp ((char *) name, "gnome-screensaver") ||
 4435               !strcmp ((char *) name, "mate-screensaver") ||
 4436               !strcmp ((char *) name, "cinnamon-screensaver")))
 4437     {
 4438       gnome_window = kids[i];
 4439           if (name_ret)
 4440             *name_ret = strdup ((char *) name);
 4441           break;
 4442     }
 4443     }
 4444 
 4445   if (kids) XFree ((char *) kids);
 4446   return gnome_window;
 4447 }
 4448 
 4449 static Bool
 4450 gnome_screensaver_active_p (char **name_ret)
 4451 {
 4452   Display *dpy = GDK_DISPLAY();
 4453   Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy), name_ret);
 4454   return (w ? True : False);
 4455 }
 4456 
 4457 static void
 4458 kill_gnome_screensaver (void)
 4459 {
 4460   Display *dpy = GDK_DISPLAY();
 4461   Window w = gnome_screensaver_window (DefaultScreenOfDisplay (dpy), NULL);
 4462   if (w) XKillClient (dpy, (XID) w);
 4463 }
 4464 
 4465 static Bool
 4466 kde_screensaver_active_p (void)
 4467 {
 4468   /* Apparently this worked in KDE 3, but not 4 or 5.
 4469      Maybe parsing the output of this would work in KDE 5:
 4470      kreadconfig5 --file kscreenlockerrc --group Daemon --key Autolock
 4471      but there's probably no way to kill the KDE saver.
 4472      Fuck it. */
 4473   FILE *p = popen ("dcop kdesktop KScreensaverIface isEnabled 2>/dev/null",
 4474                    "r");
 4475   char buf[255];
 4476   if (!p) return False;
 4477   if (!fgets (buf, sizeof(buf)-1, p)) return False;
 4478   pclose (p);
 4479   if (!strcmp (buf, "true\n"))
 4480     return True;
 4481   else
 4482     return False;
 4483 }
 4484 
 4485 static void
 4486 kill_kde_screensaver (void)
 4487 {
 4488   /* Use empty body to kill warning from gcc -Wall with
 4489      "warning: ignoring return value of 'system',
 4490       declared with attribute warn_unused_result"
 4491   */
 4492   if (system ("dcop kdesktop KScreensaverIface enable false")) {}
 4493 }
 4494 
 4495 
 4496 static void
 4497 the_network_is_not_the_computer (state *s)
 4498 {
 4499   Display *dpy = GDK_DISPLAY();
 4500   char *rversion = 0, *ruser = 0, *rhost = 0;
 4501   char *luser, *lhost;
 4502   char *msg = 0;
 4503   char *oname = 0;
 4504   struct passwd *p = getpwuid (getuid ());
 4505   const char *d = DisplayString (dpy);
 4506 
 4507 # if defined(HAVE_UNAME)
 4508   struct utsname uts;
 4509   if (uname (&uts) < 0)
 4510     lhost = "<UNKNOWN>";
 4511   else
 4512     lhost = uts.nodename;
 4513 # else  /* !HAVE_UNAME */
 4514   strcat (lhost, "<UNKNOWN>");
 4515 # endif /* !HAVE_UNAME */
 4516 
 4517   if (p && p->pw_name)
 4518     luser = p->pw_name;
 4519   else
 4520     luser = "???";
 4521 
 4522   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
 4523 
 4524   /* Make a buffer that's big enough for a number of copies of all the
 4525      strings, plus some. */
 4526   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
 4527                    (ruser ? strlen(ruser) : 0) +
 4528                    (rhost ? strlen(rhost) : 0) +
 4529                    strlen(lhost) +
 4530                    strlen(luser) +
 4531                    strlen(d) +
 4532                    1024));
 4533   *msg = 0;
 4534 
 4535   if (!rversion || !*rversion)
 4536     {
 4537       sprintf (msg,
 4538            _("Warning:\n\n"
 4539          "The XScreenSaver daemon doesn't seem to be running\n"
 4540          "on display \"%s\".  Launch it now?"),
 4541            d);
 4542     }
 4543   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
 4544     {
 4545       /* Warn that the two processes are running as different users.
 4546        */
 4547       sprintf(msg,
 4548         _("Warning:\n\n"
 4549           "%s is running as user \"%s\" on host \"%s\".\n"
 4550           "But the xscreensaver managing display \"%s\"\n"
 4551           "is running as user \"%s\" on host \"%s\".\n"
 4552           "\n"
 4553           "Since they are different users, they won't be reading/writing\n"
 4554           "the same ~/.xscreensaver file, so %s isn't\n"
 4555           "going to work right.\n"
 4556           "\n"
 4557           "You should either re-run %s as \"%s\", or re-run\n"
 4558           "xscreensaver as \"%s\".\n"
 4559               "\n"
 4560               "Restart the xscreensaver daemon now?\n"),
 4561           progname, luser, lhost,
 4562           d,
 4563           (ruser ? ruser : "???"), (rhost ? rhost : "???"),
 4564           progname,
 4565           progname, (ruser ? ruser : "???"),
 4566           luser);
 4567     }
 4568   else if (rhost && *rhost && !!strcmp (rhost, lhost))
 4569     {
 4570       /* Warn that the two processes are running on different hosts.
 4571        */
 4572       sprintf (msg,
 4573           _("Warning:\n\n"
 4574            "%s is running as user \"%s\" on host \"%s\".\n"
 4575            "But the xscreensaver managing display \"%s\"\n"
 4576            "is running as user \"%s\" on host \"%s\".\n"
 4577            "\n"
 4578            "If those two machines don't share a file system (that is,\n"
 4579            "if they don't see the same ~%s/.xscreensaver file) then\n"
 4580            "%s won't work right.\n"
 4581                "\n"
 4582                "Restart the daemon on \"%s\" as \"%s\" now?\n"),
 4583            progname, luser, lhost,
 4584            d,
 4585            (ruser ? ruser : "???"), (rhost ? rhost : "???"),
 4586            luser,
 4587            progname,
 4588                lhost, luser);
 4589     }
 4590   else if (!!strcmp (rversion, s->short_version))
 4591     {
 4592       /* Warn that the version numbers don't match.
 4593        */
 4594       sprintf (msg,
 4595          _("Warning:\n\n"
 4596            "This is %s version %s.\n"
 4597            "But the xscreensaver managing display \"%s\"\n"
 4598            "is version %s.  This could cause problems.\n"
 4599            "\n"
 4600            "Restart the xscreensaver daemon now?\n"),
 4601            progname, s->short_version,
 4602            d,
 4603            rversion);
 4604     }
 4605 
 4606 
 4607   if (*msg)
 4608     warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1);
 4609 
 4610   if (rversion) free (rversion);
 4611   if (ruser) free (ruser);
 4612   if (rhost) free (rhost);
 4613   free (msg);
 4614   msg = 0;
 4615 
 4616   /* Note: since these dialogs are not modal, they will stack up.
 4617      So we do this check *after* popping up the "xscreensaver is not
 4618      running" dialog so that these are on top.  Good enough.
 4619    */
 4620 
 4621   if (gnome_screensaver_active_p (&oname))
 4622     {
 4623       char msg [1024];
 4624       sprintf (msg,
 4625                _("Warning:\n\n"
 4626                  "The GNOME screen saver daemon (%s) appears to be running.\n"
 4627                  "It must be stopped for XScreenSaver to work properly.\n"
 4628                  "\n"
 4629                  "Stop the \"%s\" daemon now?\n"),
 4630                oname, oname);
 4631       warning_dialog (s->toplevel_widget, msg, D_GNOME, 1);
 4632     }
 4633 
 4634   if (kde_screensaver_active_p ())
 4635     warning_dialog (s->toplevel_widget,
 4636                     _("Warning:\n\n"
 4637                       "The KDE screen saver daemon appears to be running.\n"
 4638                       "It must be stopped for XScreenSaver to work properly.\n"
 4639                       "\n"
 4640                       "Stop the KDE screen saver daemon now?\n"),
 4641                     D_KDE, 1);
 4642 
 4643   if (getenv ("WAYLAND_DISPLAY") || getenv ("WAYLAND_SOCKET"))
 4644     warning_dialog (s->toplevel_widget,
 4645                     _("Warning:\n\n"
 4646                    "You are running Wayland rather than the X Window System.\n"
 4647                    "\n"
 4648                    "Under Wayland, idle-detection fails when non-X11 programs\n"
 4649                    "are selected, meaning the screen may blank prematurely.\n"
 4650                    "Also, locking is impossible.\n"
 4651                    "\n"
 4652                    "See the XScreenSaver manual for instructions on\n"
 4653                    "configuring your system to use X11 instead of Wayland.\n"),
 4654                     D_NONE, 1);
 4655 }
 4656 
 4657 
 4658 /* We use this error handler so that X errors are preceeded by the name
 4659    of the program that generated them.
 4660  */
 4661 static int
 4662 demo_ehandler (Display *dpy, XErrorEvent *error)
 4663 {
 4664   state *s = global_state_kludge;  /* I hate C so much... */
 4665   fprintf (stderr, "\nX error in %s:\n", blurb());
 4666   XmuPrintDefaultErrorMessage (dpy, error, stderr);
 4667   kill_preview_subproc (s, False);
 4668   exit (-1);
 4669   return 0;
 4670 }
 4671 
 4672 
 4673 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
 4674    of the program that generated them; and also that we can ignore one
 4675    particular bogus error message that Gdk madly spews.
 4676  */
 4677 static void
 4678 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
 4679                const gchar *message, gpointer user_data)
 4680 {
 4681   /* Ignore the message "Got event for unknown window: 0x...".
 4682      Apparently some events are coming in for the xscreensaver window
 4683      (presumably reply events related to the ClientMessage) and Gdk
 4684      feels the need to complain about them.  So, just suppress any
 4685      messages that look like that one.
 4686    */
 4687   if (strstr (message, "unknown window"))
 4688     return;
 4689 
 4690   fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
 4691            (log_domain ? log_domain : progclass),
 4692            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
 4693             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
 4694             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
 4695             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
 4696             log_level == G_LOG_LEVEL_INFO     ? "info" :
 4697             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
 4698            message,
 4699            ((!*message || message[strlen(message)-1] != '\n')
 4700             ? "\n" : ""));
 4701 }
 4702 
 4703 
 4704 STFU
 4705 static char *defaults[] = {
 4706 #include "XScreenSaver_ad.h"
 4707  0
 4708 };
 4709 
 4710 #if 0
 4711 #ifdef HAVE_CRAPPLET
 4712 static struct poptOption crapplet_options[] = {
 4713   {NULL, '\0', 0, NULL, 0}
 4714 };
 4715 #endif /* HAVE_CRAPPLET */
 4716 #endif /* 0 */
 4717 
 4718 const char *usage = "[--display dpy] [--prefs | --settings]"
 4719 # ifdef HAVE_CRAPPLET
 4720                     " [--crapplet]"
 4721 # endif
 4722             "\n\t\t   [--debug] [--sync] [--no-xshm] [--configdir dir]";
 4723 
 4724 static void
 4725 map_popup_window_cb (GtkWidget *w, gpointer user_data)
 4726 {
 4727   state *s = (state *) user_data;
 4728   Boolean oi = s->initializing_p;
 4729 #ifndef HAVE_GTK2
 4730   GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
 4731 #endif
 4732   s->initializing_p = True;
 4733 #ifndef HAVE_GTK2
 4734   eschew_gtk_lossage (label);
 4735 #endif
 4736   s->initializing_p = oi;
 4737 }
 4738 
 4739 
 4740 #if 0
 4741 static void
 4742 print_widget_tree (GtkWidget *w, int depth)
 4743 {
 4744   int i;
 4745   for (i = 0; i < depth; i++)
 4746     fprintf (stderr, "  ");
 4747   fprintf (stderr, "%s\n", gtk_widget_get_name (w));
 4748 
 4749   if (GTK_IS_LIST (w))
 4750     {
 4751       for (i = 0; i < depth+1; i++)
 4752         fprintf (stderr, "  ");
 4753       fprintf (stderr, "...list kids...\n");
 4754     }
 4755   else if (GTK_IS_CONTAINER (w))
 4756     {
 4757       GList *kids = gtk_container_children (GTK_CONTAINER (w));
 4758       while (kids)
 4759         {
 4760           print_widget_tree (GTK_WIDGET (kids->data), depth+1);
 4761           kids = kids->next;
 4762         }
 4763     }
 4764 }
 4765 #endif /* 0 */
 4766 
 4767 static int
 4768 delayed_scroll_kludge (gpointer data)
 4769 {
 4770   state *s = (state *) data;
 4771   GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
 4772   ensure_selected_item_visible (w);
 4773 
 4774   /* Oh, this is just fucking lovely, too. */
 4775   w = GTK_WIDGET (name_to_widget (s, "preview"));
 4776   gtk_widget_hide (w);
 4777   gtk_widget_show (w);
 4778 
 4779   return FALSE;  /* do not re-execute timer */
 4780 }
 4781 
 4782 #ifdef HAVE_GTK2
 4783 
 4784 static GtkWidget *
 4785 create_xscreensaver_demo (void)
 4786 {
 4787   GtkWidget *nb;
 4788 
 4789   nb = name_to_widget (global_state_kludge, "preview_notebook");
 4790   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
 4791 
 4792   return name_to_widget (global_state_kludge, "xscreensaver_demo");
 4793 }
 4794 
 4795 static GtkWidget *
 4796 create_xscreensaver_settings_dialog (void)
 4797 {
 4798   GtkWidget *w, *box;
 4799 
 4800   box = name_to_widget (global_state_kludge, "dialog_action_area");
 4801 
 4802   w = name_to_widget (global_state_kludge, "adv_button");
 4803   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
 4804 
 4805   w = name_to_widget (global_state_kludge, "std_button");
 4806   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
 4807 
 4808   return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
 4809 }
 4810 
 4811 #endif /* HAVE_GTK2 */
 4812 
 4813 int
 4814 main (int argc, char **argv)
 4815 {
 4816   XtAppContext app;
 4817   state S, *s;
 4818   saver_preferences *p;
 4819   Bool prefs_p = False;
 4820   Bool settings_p = False;
 4821   int i;
 4822   Display *dpy;
 4823   Widget toplevel_shell;
 4824   char *real_progname = argv[0];
 4825   char *window_title;
 4826   char *geom = 0;
 4827   Bool crapplet_p = False;
 4828   char *str;
 4829 
 4830 #ifdef ENABLE_NLS
 4831   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
 4832   textdomain (GETTEXT_PACKAGE);
 4833 
 4834 # ifdef HAVE_GTK2
 4835   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
 4836 # else  /* !HAVE_GTK2 */
 4837   if (!setlocale (LC_ALL, ""))
 4838     fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
 4839 # endif /* !HAVE_GTK2 */
 4840 
 4841 #endif /* ENABLE_NLS */
 4842 
 4843   str = strrchr (real_progname, '/');
 4844   if (str) real_progname = str+1;
 4845 
 4846   s = &S;
 4847   memset (s, 0, sizeof(*s));
 4848   s->initializing_p = True;
 4849   p = &s->prefs;
 4850 
 4851   global_state_kludge = s;  /* I hate C so much... */
 4852 
 4853   progname = real_progname;
 4854 
 4855   s->short_version = XSCREENSAVER_VERSION;
 4856 
 4857   /* Register our error message logger for every ``log domain'' known.
 4858      There's no way to do this globally, so I grepped the Gtk/Gdk sources
 4859      for all of the domains that seem to be in use.
 4860   */
 4861   {
 4862     const char * const domains[] = { 0,
 4863                                      "Gtk", "Gdk", "GLib", "GModule",
 4864                                      "GThread", "Gnome", "GnomeUI" };
 4865     for (i = 0; i < countof(domains); i++)
 4866       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
 4867   }
 4868 
 4869 #ifdef DEFAULT_ICONDIR  /* from -D on compile line */
 4870 # ifndef HAVE_GTK2
 4871   {
 4872     const char *dir = DEFAULT_ICONDIR;
 4873     if (*dir) add_pixmap_directory (dir);
 4874   }
 4875 # endif /* !HAVE_GTK2 */
 4876 #endif /* DEFAULT_ICONDIR */
 4877 
 4878   /* This is gross, but Gtk understands --display and not -display...
 4879    */
 4880   for (i = 1; i < argc; i++)
 4881     if (argv[i][0] && argv[i][1] && 
 4882         !strncmp(argv[i], "-display", strlen(argv[i])))
 4883       argv[i] = "--display";
 4884 
 4885 
 4886   /* We need to parse this arg really early... Sigh. */
 4887   for (i = 1; i < argc; i++)
 4888     {
 4889       if (argv[i] &&
 4890           (!strcmp(argv[i], "--crapplet") ||
 4891            !strcmp(argv[i], "--capplet")))
 4892         {
 4893 # if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
 4894           int j;
 4895           crapplet_p = True;
 4896           for (j = i; j < argc; j++)  /* remove it from the list */
 4897             argv[j] = argv[j+1];
 4898           argc--;
 4899 # else  /* !HAVE_CRAPPLET && !HAVE_GTK2 */
 4900           fprintf (stderr, "%s: not compiled with --crapplet support\n",
 4901                    real_progname);
 4902           fprintf (stderr, "%s: %s\n", real_progname, usage);
 4903           exit (1);
 4904 # endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
 4905         }
 4906       else if (argv[i] &&
 4907                (!strcmp(argv[i], "--debug") ||
 4908                 !strcmp(argv[i], "-debug") ||
 4909                 !strcmp(argv[i], "-d")))
 4910         {
 4911           int j;
 4912           s->debug_p = True;
 4913           for (j = i; j < argc; j++)  /* remove it from the list */
 4914             argv[j] = argv[j+1];
 4915           argc--;
 4916           i--;
 4917         }
 4918       else if (argv[i] &&
 4919                argc > i+1 &&
 4920                *argv[i+1] &&
 4921                (!strcmp(argv[i], "-geometry") ||
 4922                 !strcmp(argv[i], "-geom") ||
 4923                 !strcmp(argv[i], "-geo") ||
 4924                 !strcmp(argv[i], "-g")))
 4925         {
 4926           int j;
 4927           geom = argv[i+1];
 4928           for (j = i; j < argc; j++)  /* remove them from the list */
 4929             argv[j] = argv[j+2];
 4930           argc -= 2;
 4931           i -= 2;
 4932         }
 4933       else if (argv[i] &&
 4934                argc > i+1 &&
 4935                *argv[i+1] &&
 4936                (!strcmp(argv[i], "--configdir")))
 4937         {
 4938           int j;
 4939           struct stat st;
 4940           hack_configuration_path = argv[i+1];
 4941           for (j = i; j < argc; j++)  /* remove them from the list */
 4942             argv[j] = argv[j+2];
 4943           argc -= 2;
 4944           i -= 2;
 4945 
 4946           if (0 != stat (hack_configuration_path, &st))
 4947             {
 4948               char buf[255];
 4949               sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
 4950               perror (buf);
 4951               exit (1);
 4952             }
 4953           else if (!S_ISDIR (st.st_mode))
 4954             {
 4955               fprintf (stderr, "%s: not a directory: %s\n",
 4956                        blurb(), hack_configuration_path);
 4957               exit (1);
 4958             }
 4959         }
 4960     }
 4961 
 4962 
 4963   if (s->debug_p)
 4964     fprintf (stderr, "%s: using config directory \"%s\"\n",
 4965              progname, hack_configuration_path);
 4966 
 4967 
 4968   /* Let Gtk open the X connection, then initialize Xt to use that
 4969      same connection.  Doctor Frankenstein would be proud.
 4970    */
 4971 # ifdef HAVE_CRAPPLET
 4972   if (crapplet_p)
 4973     {
 4974       GnomeClient *client;
 4975       GnomeClientFlags flags = 0;
 4976 
 4977       int init_results = gnome_capplet_init ("screensaver-properties",
 4978                                              s->short_version,
 4979                                              argc, argv, NULL, 0, NULL);
 4980       /* init_results is:
 4981          0 upon successful initialization;
 4982          1 if --init-session-settings was passed on the cmdline;
 4983          2 if --ignore was passed on the cmdline;
 4984         -1 on error.
 4985 
 4986          So the 1 signifies just to init the settings, and quit, basically.
 4987          (Meaning launch the xscreensaver daemon.)
 4988        */
 4989 
 4990       if (init_results < 0)
 4991         {
 4992 #  if 0
 4993           g_error ("An initialization error occurred while "
 4994                    "starting xscreensaver-capplet.\n");
 4995 #  else  /* !0 */
 4996           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
 4997                    real_progname, init_results);
 4998           exit (1);
 4999 #  endif /* !0 */
 5000         }
 5001 
 5002       client = gnome_master_client ();
 5003 
 5004       if (client)
 5005         flags = gnome_client_get_flags (client);
 5006 
 5007       if (flags & GNOME_CLIENT_IS_CONNECTED)
 5008         {
 5009           int token =
 5010             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
 5011                                          gnome_client_get_id (client));
 5012           if (token)
 5013             {
 5014               char *session_args[20];
 5015               int i = 0;
 5016               session_args[i++] = real_progname;
 5017               session_args[i++] = "--capplet";
 5018               session_args[i++] = "--init-session-settings";
 5019               session_args[i] = 0;
 5020               gnome_client_set_priority (client, 20);
 5021               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
 5022               gnome_client_set_restart_command (client, i, session_args);
 5023             }
 5024           else
 5025             {
 5026               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
 5027             }
 5028 
 5029           gnome_client_flush (client);
 5030         }
 5031 
 5032       if (init_results == 1)
 5033     {
 5034       system ("xscreensaver -nosplash &");
 5035       return 0;
 5036     }
 5037 
 5038     }
 5039   else
 5040 # endif /* HAVE_CRAPPLET */
 5041     {
 5042       gtk_init (&argc, &argv);
 5043     }
 5044 
 5045 
 5046   /* We must read exactly the same resources as xscreensaver.
 5047      That means we must have both the same progclass *and* progname,
 5048      at least as far as the resource database is concerned.  So,
 5049      put "xscreensaver" in argv[0] while initializing Xt.
 5050    */
 5051   argv[0] = "xscreensaver";
 5052   progname = argv[0];
 5053 
 5054 
 5055   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
 5056    */
 5057   XtToolkitInitialize ();
 5058   app = XtCreateApplicationContext ();
 5059   dpy = GDK_DISPLAY();
 5060   XtAppSetFallbackResources (app, defaults);
 5061   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
 5062   toplevel_shell = XtAppCreateShell (progname, progclass,
 5063                                      applicationShellWidgetClass,
 5064                                      dpy, 0, 0);
 5065 
 5066   dpy = XtDisplay (toplevel_shell);
 5067   db = XtDatabase (dpy);
 5068   XtGetApplicationNameAndClass (dpy, (char **) &progname, &progclass);
 5069   XSetErrorHandler (demo_ehandler);
 5070 
 5071   /* Let's just ignore these.  They seem to confuse Irix Gtk... */
 5072   signal (SIGPIPE, SIG_IGN);
 5073 
 5074   /* After doing Xt-style command-line processing, complain about any
 5075      unrecognized command-line arguments.
 5076    */
 5077   for (i = 1; i < argc; i++)
 5078     {
 5079       char *str = argv[i];
 5080       if (str[0] == '-' && str[1] == '-')
 5081     str++;
 5082       if (!strcmp (str, "-prefs"))
 5083     prefs_p = True;
 5084       else if (!strcmp (str, "-settings"))
 5085     settings_p = True;
 5086       else if (crapplet_p)
 5087         /* There are lots of random args that we don't care about when we're
 5088            started as a crapplet, so just ignore unknown args in that case. */
 5089         ;
 5090       else
 5091     {
 5092       fprintf (stderr, _("%s: unknown option: %s\n"), real_progname,
 5093                    argv[i]);
 5094           fprintf (stderr, "%s: %s\n", real_progname, usage);
 5095           exit (1);
 5096     }
 5097     }
 5098 
 5099   /* Load the init file, which may end up consulting the X resource database
 5100      and the site-wide app-defaults file.  Note that at this point, it's
 5101      important that `progname' be "xscreensaver", rather than whatever
 5102      was in argv[0].
 5103    */
 5104   p->db = db;
 5105   s->nscreens = screen_count (dpy);
 5106 
 5107   init_xscreensaver_atoms (dpy);
 5108   hack_environment (s);  /* must be before initialize_sort_map() */
 5109 
 5110   load_init_file (dpy, p);
 5111   initialize_sort_map (s);
 5112 
 5113   /* Now that Xt has been initialized, and the resources have been read,
 5114      we can set our `progname' variable to something more in line with
 5115      reality.
 5116    */
 5117   progname = real_progname;
 5118 
 5119 
 5120 #if 0
 5121   /* Print out all the resources we read. */
 5122   {
 5123     XrmName name = { 0 };
 5124     XrmClass class = { 0 };
 5125     int count = 0;
 5126     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
 5127               (POINTER) &count);
 5128   }
 5129 #endif
 5130 
 5131   init_xscreensaver_atoms (dpy);
 5132 
 5133   /* Create the window and all its widgets.
 5134    */
 5135   s->base_widget     = create_xscreensaver_demo ();
 5136   s->popup_widget    = create_xscreensaver_settings_dialog ();
 5137   s->toplevel_widget = s->base_widget;
 5138 
 5139 
 5140   /* Set the main window's title. */
 5141   {
 5142     char *base_title = _("Screensaver Preferences");
 5143     char *v = (char *) strdup(strchr(screensaver_id, ' '));
 5144     char *s1, *s2, *s3, *s4;
 5145     s1 = (char *) strchr(v,  ' '); s1++;
 5146     s2 = (char *) strchr(s1, ' ');
 5147     s3 = (char *) strchr(v,  '('); s3++;
 5148     s4 = (char *) strchr(s3, ')');
 5149     *s2 = 0;
 5150     *s4 = 0;
 5151 
 5152     window_title = (char *) malloc (strlen (base_title) +
 5153                                     strlen (progclass) +
 5154                                     strlen (s1) + strlen (s3) +
 5155                                     100);
 5156     sprintf (window_title, "%s  (%s %s, %s)", base_title, progclass, s1, s3);
 5157     gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
 5158     gtk_window_set_title (GTK_WINDOW (s->popup_widget),    window_title);
 5159     free (v);
 5160   }
 5161 
 5162   /* Adjust the (invisible) notebooks on the popup dialog... */
 5163   {
 5164     GtkNotebook *notebook =
 5165       GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
 5166     GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
 5167     int page = 0;
 5168 
 5169 # ifdef HAVE_XML
 5170     gtk_widget_hide (std);
 5171 # else  /* !HAVE_XML */
 5172     /* Make the advanced page be the only one available. */
 5173     gtk_widget_set_sensitive (std, False);
 5174     std = GTK_WIDGET (name_to_widget (s, "adv_button"));
 5175     gtk_widget_hide (std);
 5176     std = GTK_WIDGET (name_to_widget (s, "reset_button"));
 5177     gtk_widget_hide (std);
 5178     page = 1;
 5179 # endif /* !HAVE_XML */
 5180 
 5181     gtk_notebook_set_page (notebook, page);
 5182     gtk_notebook_set_show_tabs (notebook, False);
 5183   }
 5184 
 5185   /* Various other widget initializations...
 5186    */
 5187   gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
 5188                       GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
 5189                       (gpointer) s);
 5190   gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
 5191                       GTK_SIGNAL_FUNC (wm_popup_close_cb),
 5192                       (gpointer) s);
 5193 
 5194   populate_hack_list (s);
 5195   populate_prefs_page (s);
 5196   sensitize_demo_widgets (s, False);
 5197   fix_text_entry_sizes (s);
 5198   scroll_to_current_hack (s);
 5199 
 5200   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
 5201                       "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
 5202                       (gpointer) s);
 5203 
 5204 #ifndef HAVE_GTK2
 5205   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
 5206                       "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
 5207                       (gpointer) s);
 5208   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
 5209                       "map", GTK_SIGNAL_FUNC(map_next_button_cb),
 5210                       (gpointer) s);
 5211 #endif /* !HAVE_GTK2 */
 5212 
 5213   /* Hook up callbacks to the items on the mode menu. */
 5214   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "mode_menu")),
 5215                       "changed", GTK_SIGNAL_FUNC (mode_menu_item_cb),
 5216                       (gpointer) s);
 5217   if (s->nscreens <= 1)
 5218     {
 5219       GtkComboBox *opt = GTK_COMBO_BOX (name_to_widget (s, "mode_menu"));
 5220       GtkTreeModel *list = gtk_combo_box_get_model (opt);
 5221       unsigned int i;
 5222       for (i = 0; i < countof(mode_menu_order); i++)
 5223         {
 5224           /* The "random-same" mode menu item does not appear unless
 5225              there are multiple screens.
 5226            */
 5227           if (mode_menu_order[i] == RANDOM_HACKS_SAME)
 5228             {
 5229               GtkTreeIter iter;
 5230               gtk_tree_model_iter_nth_child (list, &iter, NULL, i);
 5231               gtk_list_store_remove (GTK_LIST_STORE (list), &iter);
 5232               break;
 5233             }
 5234         }
 5235 
 5236         /* recompute option-menu size */
 5237         gtk_widget_unrealize (GTK_WIDGET (opt));
 5238         gtk_widget_realize (GTK_WIDGET (opt));
 5239     }
 5240 
 5241 
 5242   /* Handle the -prefs command-line argument. */
 5243   if (prefs_p)
 5244     {
 5245       GtkNotebook *notebook =
 5246         GTK_NOTEBOOK (name_to_widget (s, "notebook"));
 5247       gtk_notebook_set_page (notebook, 1);
 5248     }
 5249 
 5250 # ifdef HAVE_CRAPPLET
 5251   if (crapplet_p)
 5252     {
 5253       GtkWidget *capplet;
 5254       GtkWidget *outer_vbox;
 5255 
 5256       gtk_widget_hide (s->toplevel_widget);
 5257 
 5258       capplet = capplet_widget_new ();
 5259 
 5260       /* Make there be a "Close" button instead of "OK" and "Cancel" */
 5261 # ifdef HAVE_CRAPPLET_IMMEDIATE
 5262       capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
 5263 # endif /* HAVE_CRAPPLET_IMMEDIATE */
 5264       /* In crapplet-mode, take off the menubar. */
 5265       gtk_widget_hide (name_to_widget (s, "menubar"));
 5266 
 5267       /* Reparent our top-level container to be a child of the capplet
 5268          window.
 5269        */
 5270       outer_vbox = GTK_BIN (s->toplevel_widget)->child;
 5271       gtk_widget_ref (outer_vbox);
 5272       gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
 5273                             outer_vbox);
 5274       STFU GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
 5275       gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
 5276 
 5277       /* Find the window above us, and set the title and close handler. */
 5278       {
 5279         GtkWidget *window = capplet;
 5280         while (window && !GTK_IS_WINDOW (window))
 5281           window = GET_PARENT (window);
 5282         if (window)
 5283           {
 5284             gtk_window_set_title (GTK_WINDOW (window), window_title);
 5285             gtk_signal_connect (GTK_OBJECT (window), "delete_event",
 5286                                 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
 5287                                 (gpointer) s);
 5288           }
 5289       }
 5290 
 5291       s->toplevel_widget = capplet;
 5292     }
 5293 # endif /* HAVE_CRAPPLET */
 5294 
 5295 
 5296   /* The Gnome folks hate the menubar.  I think it's important to have access
 5297      to the commands on the File menu (Restart Daemon, etc.) and to the
 5298      About and Documentation commands on the Help menu.
 5299    */
 5300 #if 0
 5301 #ifdef HAVE_GTK2
 5302   gtk_widget_hide (name_to_widget (s, "menubar"));
 5303 #endif
 5304 #endif
 5305 
 5306   free (window_title);
 5307   window_title = 0;
 5308 
 5309 #ifdef HAVE_GTK2
 5310   /* After picking the default size, allow -geometry to override it. */
 5311   if (geom)
 5312     gtk_window_parse_geometry (GTK_WINDOW (s->toplevel_widget), geom);
 5313 #endif
 5314 
 5315   gtk_widget_show (s->toplevel_widget);
 5316   init_icon (GET_WINDOW (GTK_WIDGET (s->toplevel_widget)));  /* after `show' */
 5317   fix_preview_visual (s);
 5318 
 5319   /* Realize page zero, so that we can diddle the scrollbar when the
 5320      user tabs back to it -- otherwise, the current hack isn't scrolled
 5321      to the first time they tab back there, when started with "-prefs".
 5322      (Though it is if they then tab away, and back again.)
 5323 
 5324      #### Bah!  This doesn't work.  Gtk eats my ass!  Someone who
 5325      #### understands this crap, explain to me how to make this work.
 5326   */
 5327   gtk_widget_realize (name_to_widget (s, "demos_table"));
 5328 
 5329 
 5330   gtk_timeout_add (60 * 1000, check_blanked_timer, s);
 5331 
 5332 
 5333   /* Handle the --settings command-line argument. */
 5334   if (settings_p)
 5335     gtk_timeout_add (500, settings_timer, 0);
 5336 
 5337 
 5338   /* Issue any warnings about the running xscreensaver daemon. */
 5339   if (! s->debug_p)
 5340     the_network_is_not_the_computer (s);
 5341 
 5342 
 5343   if (time ((time_t *) 0) - XSCREENSAVER_RELEASED > 60*60*24*30*17)
 5344     warning_dialog (s->toplevel_widget,
 5345       _("Warning:\n\n"
 5346         "This version of xscreensaver is VERY OLD!\n"
 5347         "Please upgrade!\n"
 5348         "\n"
 5349         "https://www.jwz.org/xscreensaver/\n"
 5350         "\n"
 5351         "(If this is the latest version that your distro ships, then\n"
 5352         "your distro is doing you a disservice. Build from source.)\n"
 5353         ),
 5354       D_NONE, 7);
 5355 
 5356   /* Run the Gtk event loop, and not the Xt event loop.  This means that
 5357      if there were Xt timers or fds registered, they would never get serviced,
 5358      and if there were any Xt widgets, they would never have events delivered.
 5359      Fortunately, we're using Gtk for all of the UI, and only initialized
 5360      Xt so that we could process the command line and use the X resource
 5361      manager.
 5362    */
 5363   s->initializing_p = False;
 5364 
 5365   /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
 5366      after we start up.  Otherwise, it always appears scrolled to the top
 5367      when in crapplet-mode. */
 5368   gtk_timeout_add (500, delayed_scroll_kludge, s);
 5369 
 5370 
 5371 #if 1
 5372   /* Load every configurator in turn, to scan them for errors all at once. */
 5373   if (s->debug_p)
 5374     {
 5375       int i;
 5376       for (i = 0; i < p->screenhacks_count; i++)
 5377         {
 5378           screenhack *hack = p->screenhacks[i];
 5379           conf_data *d = load_configurator (hack->command, s->debug_p);
 5380           if (d) free_conf_data (d);
 5381         }
 5382     }
 5383 #endif
 5384 
 5385 
 5386 # ifdef HAVE_CRAPPLET
 5387   if (crapplet_p)
 5388     capplet_gtk_main ();
 5389   else
 5390 # endif /* HAVE_CRAPPLET */
 5391     gtk_main ();
 5392 
 5393   kill_preview_subproc (s, False);
 5394   exit (0);
 5395 }
 5396 
 5397 #endif /* HAVE_GTK -- whole file */