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