From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[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       free(msg);
559       return False;
560     }
561
562   head = msg;
563   while (head)
564     {
565       char name[20];
566       char *s = strchr (head, '\n');
567       if (s) *s = 0;
568
569       sprintf (name, "label%d", i++);
570
571       {
572         label = gtk_label_new (head);
573 #ifdef HAVE_GTK2
574         gtk_label_set_selectable (GTK_LABEL (label), TRUE);
575 #endif /* HAVE_GTK2 */
576
577 #ifndef HAVE_GTK2
578         if (i == 1)
579           {
580             GTK_WIDGET (label)->style =
581               gtk_style_copy (GTK_WIDGET (label)->style);
582             GTK_WIDGET (label)->style->font =
583               gdk_font_load (get_string_resource("warning_dialog.headingFont",
584                                                  "Dialog.Font"));
585             gtk_widget_set_style (GTK_WIDGET (label),
586                                   GTK_WIDGET (label)->style);
587           }
588 #endif /* !HAVE_GTK2 */
589         if (center <= 0)
590           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
591         gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
592                             label, TRUE, TRUE, 0);
593         gtk_widget_show (label);
594       }
595
596       if (s)
597         head = s+1;
598       else
599         head = 0;
600
601       center--;
602     }
603
604   label = gtk_label_new ("");
605   gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
606                       label, TRUE, TRUE, 0);
607   gtk_widget_show (label);
608
609   label = gtk_hbutton_box_new ();
610   gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))),
611                       label, TRUE, TRUE, 0);
612
613 #ifdef HAVE_GTK2
614   if (button_type != D_NONE)
615     {
616       cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
617       gtk_container_add (GTK_CONTAINER (label), cancel);
618     }
619
620   ok = gtk_button_new_from_stock (GTK_STOCK_OK);
621   gtk_container_add (GTK_CONTAINER (label), ok);
622
623 #else /* !HAVE_GTK2 */
624
625   ok = gtk_button_new_with_label ("OK");
626   gtk_container_add (GTK_CONTAINER (label), ok);
627
628   if (button_type != D_NONE)
629     {
630       cancel = gtk_button_new_with_label ("Cancel");
631       gtk_container_add (GTK_CONTAINER (label), cancel);
632     }
633
634 #endif /* !HAVE_GTK2 */
635
636   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
637   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
638   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
639   SET_CAN_DEFAULT (ok);
640   gtk_widget_show (ok);
641   gtk_widget_grab_focus (ok);
642
643   if (cancel)
644     {
645       SET_CAN_DEFAULT (cancel);
646       gtk_widget_show (cancel);
647     }
648   gtk_widget_show (label);
649   gtk_widget_show (dialog);
650
651   if (button_type != D_NONE)
652     {
653       GtkSignalFunc fn;
654       switch (button_type) {
655       case D_LAUNCH: fn = GTK_SIGNAL_FUNC (warning_dialog_restart_cb); break;
656       case D_GNOME:  fn = GTK_SIGNAL_FUNC (warning_dialog_killg_cb);   break;
657       case D_KDE:    fn = GTK_SIGNAL_FUNC (warning_dialog_killk_cb);   break;
658       default: abort(); break;
659       }
660       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked", fn, 
661                                  (gpointer) dialog);
662       gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
663                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
664                                  (gpointer) dialog);
665     }
666   else
667     {
668       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
669                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
670                                  (gpointer) dialog);
671     }
672
673   gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)),
674                                 GET_WINDOW (GTK_WIDGET (parent)));
675
676 #ifdef HAVE_GTK2
677   gtk_window_present (GTK_WINDOW (dialog));
678 #else  /* !HAVE_GTK2 */
679   gdk_window_show (GTK_WIDGET (dialog)->window);
680   gdk_window_raise (GTK_WIDGET (dialog)->window);
681 #endif /* !HAVE_GTK2 */
682
683   free (msg);
684   return True;
685 }
686
687
688 static void
689 run_cmd (state *s, Atom command, int arg)
690 {
691   char *err = 0;
692   int status;
693
694   flush_dialog_changes_and_save (s);
695   status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
696
697   /* Kludge: ignore the spurious "window unexpectedly deleted" errors... */
698   if (status < 0 && err && strstr (err, "unexpectedly deleted"))
699     status = 0;
700
701   if (status < 0)
702     {
703       char buf [255];
704       if (err)
705         sprintf (buf, "Error:\n\n%s", err);
706       else
707         strcpy (buf, "Unknown error!");
708       warning_dialog (s->toplevel_widget, buf, D_NONE, 100);
709     }
710   if (err) free (err);
711
712   sensitize_menu_items (s, True);
713   force_dialog_repaint (s);
714 }
715
716
717 static void
718 run_hack (state *s, int list_elt, Bool report_errors_p)
719 {
720   int hack_number;
721   char *err = 0;
722   int status;
723
724   if (list_elt < 0) return;
725   hack_number = s->list_elt_to_hack_number[list_elt];
726
727   flush_dialog_changes_and_save (s);
728   schedule_preview (s, 0);
729
730   status = xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
731                                  False, &err);
732
733   if (status < 0 && report_errors_p)
734     {
735       if (xscreensaver_running_p (s))
736         {
737           /* Kludge: ignore the spurious "window unexpectedly deleted"
738              errors... */
739           if (err && strstr (err, "unexpectedly deleted"))
740             status = 0;
741
742           if (status < 0)
743             {
744               char buf [255];
745               if (err)
746                 sprintf (buf, "Error:\n\n%s", err);
747               else
748                 strcpy (buf, "Unknown error!");
749               warning_dialog (s->toplevel_widget, buf, D_NONE, 100);
750             }
751         }
752       else
753         {
754           /* The error is that the daemon isn't running;
755              offer to restart it.
756            */
757           const char *d = DisplayString (GDK_DISPLAY());
758           char msg [1024];
759           sprintf (msg,
760                    _("Warning:\n\n"
761                      "The XScreenSaver daemon doesn't seem to be running\n"
762                      "on display \"%s\".  Launch it now?"),
763                    d);
764           warning_dialog (s->toplevel_widget, msg, D_LAUNCH, 1);
765         }
766     }
767
768   if (err) free (err);
769
770   sensitize_menu_items (s, False);
771 }
772
773
774 \f
775 /* Button callbacks
776
777    According to Eric Lassauge, this G_MODULE_EXPORT crud is needed to make
778    libglade work on Cygwin; apparently all Glade callbacks need this magic
779    extra declaration.  I do not pretend to understand.
780  */
781
782 G_MODULE_EXPORT void
783 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
784 {
785   state *s = global_state_kludge;  /* I hate C so much... */
786   flush_dialog_changes_and_save (s);
787   kill_preview_subproc (s, False);
788   gtk_main_quit ();
789 }
790
791 static gboolean
792 wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
793 {
794   state *s = (state *) data;
795   flush_dialog_changes_and_save (s);
796   gtk_main_quit ();
797   return TRUE;
798 }
799
800
801 G_MODULE_EXPORT void
802 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
803 {
804   char msg [2048];
805   char *vers = strdup (screensaver_id + 4);
806   char *s, *s2;
807   char copy[1024];
808   char year[5];
809   char *desc = _("For updates, check http://www.jwz.org/xscreensaver/");
810
811   s = strchr (vers, ',');
812   *s = 0;
813   s += 2;
814
815   s2 = vers;
816   s2 = strrchr (vers, '-');
817   s2++;
818   strncpy (year, s2, 4);
819   year[4] = 0;
820
821   /* Ole Laursen <olau@hardworking.dk> says "don't use _() here because
822      non-ASCII characters aren't allowed in localizable string keys."
823      (I don't want to just use (c) instead of Â© because that doesn't
824      look as good in the plain-old default Latin1 "C" locale.)
825    */
826 #ifdef HAVE_GTK2
827   sprintf(copy, ("Copyright \xC2\xA9 1991-%s %s"), year, s);
828 #else  /* !HAVE_GTK2 */
829   sprintf(copy, ("Copyright \251 1991-%s %s"), year, s);
830 #endif /* !HAVE_GTK2 */
831
832   sprintf (msg, "%s\n\n%s", copy, desc);
833
834   /* I can't make gnome_about_new() work here -- it starts dying in
835      gdk_imlib_get_visual() under gnome_about_new().  If this worked,
836      then this might be the thing to do:
837
838      #ifdef HAVE_CRAPPLET
839      {
840        const gchar *auth[] = { 0 };
841        GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
842                                            "xscreensaver.xpm");
843        gtk_widget_show (about);
844      }
845      #else / * GTK but not GNOME * /
846       ...
847    */
848   {
849     GdkColormap *colormap;
850     GdkPixmap *gdkpixmap;
851     GdkBitmap *mask;
852
853     GtkWidget *dialog = gtk_dialog_new ();
854     GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
855     GtkWidget *parent = GTK_WIDGET (menuitem);
856     while (GET_PARENT (parent))
857       parent = GET_PARENT (parent);
858
859     hbox = gtk_hbox_new (FALSE, 20);
860     gtk_box_pack_start (GTK_BOX (GET_CONTENT_AREA (GTK_DIALOG (dialog))),
861                         hbox, TRUE, TRUE, 0);
862
863     colormap = gtk_widget_get_colormap (parent);
864     gdkpixmap =
865       gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
866                                              (gchar **) logo_180_xpm);
867     icon = gtk_pixmap_new (gdkpixmap, mask);
868     gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
869
870     gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
871
872     vbox = gtk_vbox_new (FALSE, 0);
873     gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
874
875     label1 = gtk_label_new (vers);
876     gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
877     gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
878     gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
879
880 #ifndef HAVE_GTK2
881     GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
882     GTK_WIDGET (label1)->style->font =
883       gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
884     gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
885 #endif /* HAVE_GTK2 */
886
887     label2 = gtk_label_new (msg);
888     gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
889     gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
890     gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
891
892 #ifndef HAVE_GTK2
893     GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
894     GTK_WIDGET (label2)->style->font =
895       gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
896     gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
897 #endif /* HAVE_GTK2 */
898
899     hb = gtk_hbutton_box_new ();
900
901     gtk_box_pack_start (GTK_BOX (GET_ACTION_AREA (GTK_DIALOG (dialog))),
902                         hb, TRUE, TRUE, 0);
903
904 #ifdef HAVE_GTK2
905     ok = gtk_button_new_from_stock (GTK_STOCK_OK);
906 #else /* !HAVE_GTK2 */
907     ok = gtk_button_new_with_label (_("OK"));
908 #endif /* !HAVE_GTK2 */
909     gtk_container_add (GTK_CONTAINER (hb), ok);
910
911     gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
912     gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
913     gtk_window_set_title (GTK_WINDOW (dialog), progclass);
914
915     gtk_widget_show (hbox);
916     gtk_widget_show (icon);
917     gtk_widget_show (vbox);
918     gtk_widget_show (label1);
919     gtk_widget_show (label2);
920     gtk_widget_show (hb);
921     gtk_widget_show (ok);
922     gtk_widget_show (dialog);
923
924     gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
925                                GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
926                                (gpointer) dialog);
927     gdk_window_set_transient_for (GET_WINDOW (GTK_WIDGET (dialog)),
928                                   GET_WINDOW (GTK_WIDGET (parent)));
929     gdk_window_show (GET_WINDOW (GTK_WIDGET (dialog)));
930     gdk_window_raise (GET_WINDOW (GTK_WIDGET (dialog)));
931   }
932 }
933
934
935 G_MODULE_EXPORT void
936 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
937 {
938   state *s = global_state_kludge;  /* I hate C so much... */
939   saver_preferences *p = &s->prefs;
940   char *help_command;
941
942   if (!p->help_url || !*p->help_url)
943     {
944       warning_dialog (s->toplevel_widget,
945                       _("Error:\n\n"
946                         "No Help URL has been specified.\n"), D_NONE, 100);
947       return;
948     }
949
950   help_command = (char *) malloc (strlen (p->load_url_command) +
951                                   (strlen (p->help_url) * 4) + 20);
952   strcpy (help_command, "( ");
953   sprintf (help_command + strlen(help_command),
954            p->load_url_command,
955            p->help_url, p->help_url, p->help_url, p->help_url);
956   strcat (help_command, " ) &");
957   if (system (help_command) < 0)
958     fprintf (stderr, "%s: fork error\n", blurb());
959   free (help_command);
960 }
961
962
963 G_MODULE_EXPORT void
964 file_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
965 {
966   state *s = global_state_kludge;  /* I hate C so much... */
967   sensitize_menu_items (s, False);
968 }
969
970
971 G_MODULE_EXPORT void
972 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
973 {
974   state *s = global_state_kludge;  /* I hate C so much... */
975   run_cmd (s, XA_ACTIVATE, 0);
976 }
977
978
979 G_MODULE_EXPORT void
980 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
981 {
982   state *s = global_state_kludge;  /* I hate C so much... */
983   run_cmd (s, XA_LOCK, 0);
984 }
985
986
987 G_MODULE_EXPORT void
988 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
989 {
990   state *s = global_state_kludge;  /* I hate C so much... */
991   run_cmd (s, XA_EXIT, 0);
992 }
993
994
995 G_MODULE_EXPORT void
996 restart_menu_cb (GtkWidget *widget, gpointer user_data)
997 {
998   state *s = global_state_kludge;  /* I hate C so much... */
999   flush_dialog_changes_and_save (s);
1000   xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
1001   sleep (1);
1002   if (system ("xscreensaver -nosplash &") < 0)
1003     fprintf (stderr, "%s: fork error\n", blurb());
1004
1005   await_xscreensaver (s);
1006 }
1007
1008 static Bool
1009 xscreensaver_running_p (state *s)
1010 {
1011   Display *dpy = GDK_DISPLAY();
1012   char *rversion = 0;
1013   server_xscreensaver_version (dpy, &rversion, 0, 0);
1014   if (!rversion)
1015     return False;
1016   free (rversion);
1017   return True;
1018 }
1019
1020 static void
1021 await_xscreensaver (state *s)
1022 {
1023   int countdown = 5;
1024   Bool ok = False;
1025
1026   while (!ok && (--countdown > 0))
1027     if (xscreensaver_running_p (s))
1028       ok = True;
1029     else
1030       sleep (1);    /* If it's not there yet, wait a second... */
1031
1032   sensitize_menu_items (s, True);
1033
1034   if (! ok)
1035     {
1036       /* Timed out, no screensaver running. */
1037
1038       char buf [1024];
1039       Bool root_p = (geteuid () == 0);
1040       
1041       strcpy (buf, 
1042               _("Error:\n\n"
1043                 "The xscreensaver daemon did not start up properly.\n"
1044                 "\n"));
1045
1046       if (root_p)
1047
1048 # ifdef __GNUC__
1049         __extension__     /* don't warn about "string length is greater than
1050                              the length ISO C89 compilers are required to
1051                              support" in the following expression... */
1052 # endif
1053         strcat (buf, STFU
1054           _("You are running as root.  This usually means that xscreensaver\n"
1055             "was unable to contact your X server because access control is\n"
1056             "turned on.  Try running this command:\n"
1057             "\n"
1058             "                        xhost +localhost\n"
1059             "\n"
1060             "and then selecting `File / Restart Daemon'.\n"
1061             "\n"
1062             "Note that turning off access control will allow anyone logged\n"
1063             "on to this machine to access your screen, which might be\n"
1064             "considered a security problem.  Please read the xscreensaver\n"
1065             "manual and FAQ for more information.\n"
1066             "\n"
1067             "You shouldn't run X as root. Instead, you should log in as a\n"
1068             "normal user, and `su' as necessary."));
1069       else
1070         strcat (buf, _("Please check your $PATH and permissions."));
1071
1072       warning_dialog (s->toplevel_widget, buf, D_NONE, 1);
1073     }
1074
1075   force_dialog_repaint (s);
1076 }
1077
1078
1079 static int
1080 selected_list_element (state *s)
1081 {
1082   return s->_selected_list_element;
1083 }
1084
1085
1086 static int
1087 demo_write_init_file (state *s, saver_preferences *p)
1088 {
1089   Display *dpy = GDK_DISPLAY();
1090
1091 #if 0
1092   /* #### try to figure out why shit keeps getting reordered... */
1093   if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow"))
1094     abort();
1095 #endif
1096
1097   if (!write_init_file (dpy, p, s->short_version, False))
1098     {
1099       if (s->debug_p)
1100         fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
1101       return 0;
1102     }
1103   else
1104     {
1105       const char *f = init_file_name();
1106       if (!f || !*f)
1107         warning_dialog (s->toplevel_widget,
1108                         _("Error:\n\nCouldn't determine init file name!\n"),
1109                         D_NONE, 100);
1110       else
1111         {
1112           char *b = (char *) malloc (strlen(f) + 1024);
1113           sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
1114           warning_dialog (s->toplevel_widget, b, D_NONE, 100);
1115           free (b);
1116         }
1117       return -1;
1118     }
1119 }
1120
1121
1122 G_MODULE_EXPORT void
1123 run_this_cb (GtkButton *button, gpointer user_data)
1124 {
1125   state *s = global_state_kludge;  /* I hate C so much... */
1126   int list_elt = selected_list_element (s);
1127   if (list_elt < 0) return;
1128   if (!flush_dialog_changes_and_save (s))
1129     run_hack (s, list_elt, True);
1130 }
1131
1132
1133 G_MODULE_EXPORT void
1134 manual_cb (GtkButton *button, gpointer user_data)
1135 {
1136   Display *dpy = GDK_DISPLAY();
1137   state *s = global_state_kludge;  /* I hate C so much... */
1138   saver_preferences *p = &s->prefs;
1139   GtkWidget *list_widget = name_to_widget (s, "list");
1140   int list_elt = selected_list_element (s);
1141   int hack_number;
1142   char *name, *name2, *cmd, *str;
1143   char *oname = 0;
1144   if (list_elt < 0) return;
1145   hack_number = s->list_elt_to_hack_number[list_elt];
1146
1147   flush_dialog_changes_and_save (s);
1148   ensure_selected_item_visible (list_widget);
1149
1150   name = strdup (p->screenhacks[hack_number]->command);
1151   name2 = name;
1152   oname = name;
1153   while (isspace (*name2)) name2++;
1154   str = name2;
1155   while (*str && !isspace (*str)) str++;
1156   *str = 0;
1157   str = strrchr (name2, '/');
1158   if (str) name2 = str+1;
1159
1160   cmd = get_string_resource (dpy, "manualCommand", "ManualCommand");
1161   if (cmd)
1162     {
1163       char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
1164       strcpy (cmd2, "( ");
1165       sprintf (cmd2 + strlen (cmd2),
1166                cmd,
1167                name2, name2, name2, name2);
1168       strcat (cmd2, " ) &");
1169       if (system (cmd2) < 0)
1170         fprintf (stderr, "%s: fork error\n", blurb());
1171       free (cmd2);
1172     }
1173   else
1174     {
1175       warning_dialog (GTK_WIDGET (button),
1176                       _("Error:\n\nno `manualCommand' resource set."),
1177                       D_NONE, 100);
1178     }
1179
1180   free (oname);
1181 }
1182
1183
1184 static void
1185 force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
1186 {
1187   GtkWidget *parent = name_to_widget (s, "scroller");
1188   gboolean was = GET_SENSITIVE (parent);
1189 #ifdef HAVE_GTK2
1190   GtkTreeIter iter;
1191   GtkTreeModel *model;
1192   GtkTreeSelection *selection;
1193 #endif /* HAVE_GTK2 */
1194
1195   if (!was) gtk_widget_set_sensitive (parent, True);
1196 #ifdef HAVE_GTK2
1197   model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1198   g_assert (model);
1199   if (gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt))
1200     {
1201       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
1202       gtk_tree_selection_select_iter (selection, &iter);
1203     }
1204 #else  /* !HAVE_GTK2 */
1205   gtk_list_select_item (GTK_LIST (list), list_elt);
1206 #endif /* !HAVE_GTK2 */
1207   if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
1208   if (!was) gtk_widget_set_sensitive (parent, False);
1209 }
1210
1211
1212 G_MODULE_EXPORT void
1213 run_next_cb (GtkButton *button, gpointer user_data)
1214 {
1215   state *s = global_state_kludge;  /* I hate C so much... */
1216   /* saver_preferences *p = &s->prefs; */
1217   Bool ops = s->preview_suppressed_p;
1218
1219   GtkWidget *list_widget = name_to_widget (s, "list");
1220   int list_elt = selected_list_element (s);
1221
1222   if (list_elt < 0)
1223     list_elt = 0;
1224   else
1225     list_elt++;
1226
1227   if (list_elt >= s->list_count)
1228     list_elt = 0;
1229
1230   s->preview_suppressed_p = True;
1231
1232   flush_dialog_changes_and_save (s);
1233   force_list_select_item (s, list_widget, list_elt, True);
1234   populate_demo_window (s, list_elt);
1235   run_hack (s, list_elt, False);
1236
1237   s->preview_suppressed_p = ops;
1238 }
1239
1240
1241 G_MODULE_EXPORT void
1242 run_prev_cb (GtkButton *button, gpointer user_data)
1243 {
1244   state *s = global_state_kludge;  /* I hate C so much... */
1245   /* saver_preferences *p = &s->prefs; */
1246   Bool ops = s->preview_suppressed_p;
1247
1248   GtkWidget *list_widget = name_to_widget (s, "list");
1249   int list_elt = selected_list_element (s);
1250
1251   if (list_elt < 0)
1252     list_elt = s->list_count - 1;
1253   else
1254     list_elt--;
1255
1256   if (list_elt < 0)
1257     list_elt = s->list_count - 1;
1258
1259   s->preview_suppressed_p = True;
1260
1261   flush_dialog_changes_and_save (s);
1262   force_list_select_item (s, list_widget, list_elt, True);
1263   populate_demo_window (s, list_elt);
1264   run_hack (s, list_elt, False);
1265
1266   s->preview_suppressed_p = ops;
1267 }
1268
1269
1270 /* Writes the given settings into prefs.
1271    Returns true if there was a change, False otherwise.
1272    command and/or visual may be 0, or enabled_p may be -1, meaning "no change".
1273  */
1274 static Bool
1275 flush_changes (state *s,
1276                int list_elt,
1277                int enabled_p,
1278                const char *command,
1279                const char *visual)
1280 {
1281   saver_preferences *p = &s->prefs;
1282   Bool changed = False;
1283   screenhack *hack;
1284   int hack_number;
1285   if (list_elt < 0 || list_elt >= s->list_count)
1286     abort();
1287
1288   hack_number = s->list_elt_to_hack_number[list_elt];
1289   hack = p->screenhacks[hack_number];
1290
1291   if (enabled_p != -1 &&
1292       enabled_p != hack->enabled_p)
1293     {
1294       hack->enabled_p = enabled_p;
1295       changed = True;
1296       if (s->debug_p)
1297         fprintf (stderr, "%s: \"%s\": enabled => %d\n",
1298                  blurb(), hack->name, enabled_p);
1299     }
1300
1301   if (command)
1302     {
1303       if (!hack->command || !!strcmp (command, hack->command))
1304         {
1305           if (hack->command) free (hack->command);
1306           hack->command = strdup (command);
1307           changed = True;
1308           if (s->debug_p)
1309             fprintf (stderr, "%s: \"%s\": command => \"%s\"\n",
1310                      blurb(), hack->name, command);
1311         }
1312     }
1313
1314   if (visual)
1315     {
1316       const char *ov = hack->visual;
1317       if (!ov || !*ov) ov = "any";
1318       if (!*visual) visual = "any";
1319       if (!!strcasecmp (visual, ov))
1320         {
1321           if (hack->visual) free (hack->visual);
1322           hack->visual = strdup (visual);
1323           changed = True;
1324           if (s->debug_p)
1325             fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n",
1326                      blurb(), hack->name, visual);
1327         }
1328     }
1329
1330   return changed;
1331 }
1332
1333
1334 /* Helper for the text fields that contain time specifications:
1335    this parses the text, and does error checking.
1336  */
1337 static void 
1338 hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
1339 {
1340   if (*line)
1341     {
1342       int value;
1343       if (!sec_p || strchr (line, ':'))
1344         value = parse_time ((char *) line, sec_p, True);
1345       else
1346         {
1347           char c;
1348           if (sscanf (line, "%d%c", &value, &c) != 1)
1349             value = -1;
1350           if (!sec_p)
1351             value *= 60;
1352         }
1353
1354       value *= 1000;    /* Time measures in microseconds */
1355       if (value < 0)
1356         {
1357           char b[255];
1358           sprintf (b,
1359                    _("Error:\n\n"
1360                      "Unparsable time format: \"%s\"\n"),
1361                    line);
1362           warning_dialog (s->toplevel_widget, b, D_NONE, 100);
1363         }
1364       else
1365         *store = value;
1366     }
1367 }
1368
1369
1370 static Bool
1371 directory_p (const char *path)
1372 {
1373   struct stat st;
1374   if (!path || !*path)
1375     return False;
1376   else if (stat (path, &st))
1377     return False;
1378   else if (!S_ISDIR (st.st_mode))
1379     return False;
1380   else
1381     return True;
1382 }
1383
1384 static Bool
1385 file_p (const char *path)
1386 {
1387   struct stat st;
1388   if (!path || !*path)
1389     return False;
1390   else if (stat (path, &st))
1391     return False;
1392   else if (S_ISDIR (st.st_mode))
1393     return False;
1394   else
1395     return True;
1396 }
1397
1398 static char *
1399 normalize_directory (const char *path)
1400 {
1401   int L;
1402   char *p2, *s;
1403   if (!path || !*path) return 0;
1404   L = strlen (path);
1405   p2 = (char *) malloc (L + 2);
1406   strcpy (p2, path);
1407   if (p2[L-1] == '/')  /* remove trailing slash */
1408     p2[--L] = 0;
1409
1410   for (s = p2; s && *s; s++)
1411     {
1412       if (*s == '/' &&
1413           (!strncmp (s, "/../", 4) ||                   /* delete "XYZ/../" */
1414            !strncmp (s, "/..\000", 4)))                 /* delete "XYZ/..$" */
1415         {
1416           char *s0 = s;
1417           while (s0 > p2 && s0[-1] != '/')
1418             s0--;
1419           if (s0 > p2)
1420             {
1421               s0--;
1422               s += 3;
1423               /* strcpy (s0, s); */
1424               memmove(s0, s, strlen(s) + 1);
1425               s = s0-1;
1426             }
1427         }
1428       else if (*s == '/' && !strncmp (s, "/./", 3)) {   /* delete "/./" */
1429         /* strcpy (s, s+2), s--; */
1430         memmove(s, s+2, strlen(s+2) + 1);
1431         s--;
1432        }
1433       else if (*s == '/' && !strncmp (s, "/.\000", 3))  /* delete "/.$" */
1434         *s = 0, s--;
1435     }
1436
1437   /*
1438     Normalize consecutive slashes.
1439     Ignore doubled slashes after ":" to avoid mangling URLs.
1440   */
1441
1442   for (s = p2; s && *s; s++){
1443     if (*s == ':') continue;
1444     if (!s[1] || !s[2]) continue;
1445     while (s[1] == '/' && s[2] == '/')
1446       /* strcpy (s+1, s+2); */
1447       memmove (s+1, s+2, strlen(s+2) + 1);
1448   }
1449
1450   /* and strip trailing whitespace for good measure. */
1451   L = strlen(p2);
1452   while (isspace(p2[L-1]))
1453     p2[--L] = 0;
1454
1455   return p2;
1456 }
1457
1458
1459 #ifdef HAVE_GTK2
1460
1461 typedef struct {
1462   state *s;
1463   int i;
1464   Bool *changed;
1465 } FlushForeachClosure;
1466
1467 static gboolean
1468 flush_checkbox  (GtkTreeModel *model,
1469                  GtkTreePath *path,
1470                  GtkTreeIter *iter,
1471                  gpointer data)
1472 {
1473   FlushForeachClosure *closure = data;
1474   gboolean checked;
1475
1476   gtk_tree_model_get (model, iter,
1477                       COL_ENABLED, &checked,
1478                       -1);
1479
1480   if (flush_changes (closure->s, closure->i,
1481                      checked, 0, 0))
1482     *closure->changed = True;
1483   
1484   closure->i++;
1485
1486   /* don't remove row */
1487   return FALSE;
1488 }
1489
1490 #endif /* HAVE_GTK2 */
1491
1492 /* Flush out any changes made in the main dialog window (where changes
1493    take place immediately: clicking on a checkbox causes the init file
1494    to be written right away.)
1495  */
1496 static Bool
1497 flush_dialog_changes_and_save (state *s)
1498 {
1499   saver_preferences *p = &s->prefs;
1500   saver_preferences P2, *p2 = &P2;
1501 #ifdef HAVE_GTK2
1502   GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
1503   GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
1504   FlushForeachClosure closure;
1505 #else /* !HAVE_GTK2 */
1506   GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
1507   GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
1508   int i;
1509 #endif /* !HAVE_GTK2 */
1510   static Bool already_warned_about_missing_image_directory = False; /* very long name... */
1511
1512   Bool changed = False;
1513   GtkWidget *w;
1514
1515   if (s->saving_p) return False;
1516   s->saving_p = True;
1517
1518   *p2 = *p;
1519
1520   /* Flush any checkbox changes in the list down into the prefs struct.
1521    */
1522 #ifdef HAVE_GTK2
1523   closure.s = s;
1524   closure.changed = &changed;
1525   closure.i = 0;
1526   gtk_tree_model_foreach (model, flush_checkbox, &closure);
1527
1528 #else /* !HAVE_GTK2 */
1529
1530   for (i = 0; kids; kids = kids->next, i++)
1531     {
1532       GtkWidget *line = GTK_WIDGET (kids->data);
1533       GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
1534       GtkWidget *line_check =
1535         GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
1536       Bool checked =
1537         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check));
1538
1539       if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
1540         changed = True;
1541     }
1542 #endif /* ~HAVE_GTK2 */
1543
1544   /* Flush the non-hack-specific settings down into the prefs struct.
1545    */
1546
1547 # define SECONDS(FIELD,NAME) \
1548     w = name_to_widget (s, (NAME)); \
1549     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True)
1550
1551 # define MINUTES(FIELD,NAME) \
1552     w = name_to_widget (s, (NAME)); \
1553     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False)
1554
1555 # define CHECKBOX(FIELD,NAME) \
1556     w = name_to_widget (s, (NAME)); \
1557     (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w))
1558
1559 # define PATHNAME(FIELD,NAME) \
1560     w = name_to_widget (s, (NAME)); \
1561     (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
1562
1563 # define TEXT(FIELD,NAME) \
1564     w = name_to_widget (s, (NAME)); \
1565     (FIELD) = (char *) gtk_entry_get_text (GTK_ENTRY (w))
1566
1567   MINUTES  (&p2->timeout,         "timeout_spinbutton");
1568   MINUTES  (&p2->cycle,           "cycle_spinbutton");
1569   CHECKBOX (p2->lock_p,           "lock_button");
1570   MINUTES  (&p2->lock_timeout,    "lock_spinbutton");
1571
1572   CHECKBOX (p2->dpms_enabled_p,   "dpms_button");
1573   CHECKBOX (p2->dpms_quickoff_p,  "dpms_quickoff_button");
1574   MINUTES  (&p2->dpms_standby,    "dpms_standby_spinbutton");
1575   MINUTES  (&p2->dpms_suspend,    "dpms_suspend_spinbutton");
1576   MINUTES  (&p2->dpms_off,        "dpms_off_spinbutton");
1577
1578   CHECKBOX (p2->grab_desktop_p,   "grab_desk_button");
1579   CHECKBOX (p2->grab_video_p,     "grab_video_button");
1580   CHECKBOX (p2->random_image_p,   "grab_image_button");
1581   PATHNAME (p2->image_directory,  "image_text");
1582
1583 #if 0
1584   CHECKBOX (p2->verbose_p,        "verbose_button");
1585   CHECKBOX (p2->capture_stderr_p, "capture_button");
1586   CHECKBOX (p2->splash_p,         "splash_button");
1587 #endif
1588
1589   {
1590     Bool v = False;
1591     CHECKBOX (v, "text_host_radio");     if (v) p2->tmode = TEXT_DATE;
1592     CHECKBOX (v, "text_radio");          if (v) p2->tmode = TEXT_LITERAL;
1593     CHECKBOX (v, "text_file_radio");     if (v) p2->tmode = TEXT_FILE;
1594     CHECKBOX (v, "text_program_radio");  if (v) p2->tmode = TEXT_PROGRAM;
1595     CHECKBOX (v, "text_url_radio");      if (v) p2->tmode = TEXT_URL;
1596     TEXT     (p2->text_literal, "text_entry");
1597     PATHNAME (p2->text_file,    "text_file_entry");
1598     PATHNAME (p2->text_program, "text_program_entry");
1599     PATHNAME (p2->text_program, "text_program_entry");
1600     TEXT     (p2->text_url,     "text_url_entry");
1601   }
1602
1603   CHECKBOX (p2->install_cmap_p,   "install_button");
1604   CHECKBOX (p2->fade_p,           "fade_button");
1605   CHECKBOX (p2->unfade_p,         "unfade_button");
1606   SECONDS  (&p2->fade_seconds,    "fade_spinbutton");
1607
1608 # undef SECONDS
1609 # undef MINUTES
1610 # undef CHECKBOX
1611 # undef PATHNAME
1612 # undef TEXT
1613
1614   /* Warn if the image directory doesn't exist, when:
1615      - not being warned before
1616      - image directory is changed and the directory doesn't exist
1617      - image directory does not begin with http://
1618    */
1619   if (p2->image_directory &&
1620       *p2->image_directory &&
1621       !directory_p (p2->image_directory) &&
1622        strncmp(p2->image_directory, "http://", 6) &&
1623         ( !already_warned_about_missing_image_directory ||
1624           ( p->image_directory &&
1625             *p->image_directory &&
1626             strcmp(p->image_directory, p2->image_directory)
1627           )
1628         )
1629       )
1630     {
1631       char b[255];
1632       sprintf (b, "Warning:\n\n" "Directory does not exist: \"%s\"\n",
1633                p2->image_directory);
1634       if (warning_dialog (s->toplevel_widget, b, D_NONE, 100))
1635         already_warned_about_missing_image_directory = True;
1636     }
1637
1638
1639   /* Map the mode menu to `saver_mode' enum values. */
1640   {
1641     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
1642     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1643     GtkWidget *selected = gtk_menu_get_active (menu);
1644     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1645     int menu_elt = g_list_index (kids, (gpointer) selected);
1646     if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
1647     p2->mode = mode_menu_order[menu_elt];
1648   }
1649
1650   if (p2->mode == ONE_HACK)
1651     {
1652       int list_elt = selected_list_element (s);
1653       p2->selected_hack = (list_elt >= 0
1654                            ? s->list_elt_to_hack_number[list_elt]
1655                            : -1);
1656     }
1657
1658 # define COPY(field, name) \
1659   if (p->field != p2->field) { \
1660     changed = True; \
1661     if (s->debug_p) \
1662       fprintf (stderr, "%s: %s => %d\n", blurb(), name, (int) p2->field); \
1663   } \
1664   p->field = p2->field
1665
1666   COPY(mode,             "mode");
1667   COPY(selected_hack,    "selected_hack");
1668
1669   COPY(timeout,        "timeout");
1670   COPY(cycle,          "cycle");
1671   COPY(lock_p,         "lock_p");
1672   COPY(lock_timeout,   "lock_timeout");
1673
1674   COPY(dpms_enabled_p,  "dpms_enabled_p");
1675   COPY(dpms_quickoff_p, "dpms_quickoff_enabled_p");
1676   COPY(dpms_standby,    "dpms_standby");
1677   COPY(dpms_suspend,    "dpms_suspend");
1678   COPY(dpms_off,        "dpms_off");
1679
1680 #if 0
1681   COPY(verbose_p,        "verbose_p");
1682   COPY(capture_stderr_p, "capture_stderr_p");
1683   COPY(splash_p,         "splash_p");
1684 #endif
1685
1686   COPY(tmode,            "tmode");
1687
1688   COPY(install_cmap_p,   "install_cmap_p");
1689   COPY(fade_p,           "fade_p");
1690   COPY(unfade_p,         "unfade_p");
1691   COPY(fade_seconds,     "fade_seconds");
1692
1693   COPY(grab_desktop_p, "grab_desktop_p");
1694   COPY(grab_video_p,   "grab_video_p");
1695   COPY(random_image_p, "random_image_p");
1696
1697 # undef COPY
1698
1699 # define COPYSTR(FIELD,NAME) \
1700   if (!p->FIELD || \
1701       !p2->FIELD || \
1702       strcmp(p->FIELD, p2->FIELD)) \
1703     { \
1704       changed = True; \
1705       if (s->debug_p) \
1706         fprintf (stderr, "%s: %s => \"%s\"\n", blurb(), NAME, p2->FIELD); \
1707     } \
1708   if (p->FIELD && p->FIELD != p2->FIELD) \
1709     free (p->FIELD); \
1710   p->FIELD = p2->FIELD; \
1711   p2->FIELD = 0
1712
1713   COPYSTR(image_directory, "image_directory");
1714   COPYSTR(text_literal,    "text_literal");
1715   COPYSTR(text_file,       "text_file");
1716   COPYSTR(text_program,    "text_program");
1717   COPYSTR(text_url,        "text_url");
1718 # undef COPYSTR
1719
1720   populate_prefs_page (s);
1721
1722   if (changed)
1723     {
1724       Display *dpy = GDK_DISPLAY();
1725       Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
1726       sync_server_dpms_settings (dpy, enabled_p, p->dpms_quickoff_p,
1727                                  p->dpms_standby / 1000,
1728                                  p->dpms_suspend / 1000,
1729                                  p->dpms_off / 1000,
1730                                  False);
1731
1732       changed = demo_write_init_file (s, p);
1733     }
1734
1735   s->saving_p = False;
1736   return changed;
1737 }
1738
1739
1740 /* Flush out any changes made in the popup dialog box (where changes
1741    take place only when the OK button is clicked.)
1742  */
1743 static Bool
1744 flush_popup_changes_and_save (state *s)
1745 {
1746   Bool changed = False;
1747   saver_preferences *p = &s->prefs;
1748   int list_elt = selected_list_element (s);
1749
1750   GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
1751   GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
1752
1753   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
1754   const char *command = gtk_entry_get_text (cmd);
1755
1756   char c;
1757   unsigned long id;
1758
1759   if (s->saving_p) return False;
1760   s->saving_p = True;
1761
1762   if (list_elt < 0)
1763     goto DONE;
1764
1765   if (maybe_reload_init_file (s) != 0)
1766     {
1767       changed = True;
1768       goto DONE;
1769     }
1770
1771   /* Sanity-check and canonicalize whatever the user typed into the combo box.
1772    */
1773   if      (!strcasecmp (visual, ""))                   visual = "";
1774   else if (!strcasecmp (visual, "any"))                visual = "";
1775   else if (!strcasecmp (visual, "default"))            visual = "Default";
1776   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
1777   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
1778   else if (!strcasecmp (visual, "best"))               visual = "Best";
1779   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
1780   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
1781   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
1782   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
1783   else if (!strcasecmp (visual, "color"))              visual = "Color";
1784   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
1785   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
1786   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
1787   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
1788   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
1789   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
1790   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
1791   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
1792   else if (1 == sscanf (visual, " %lu %c", &id, &c))   ;
1793   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
1794   else
1795     {
1796       gdk_beep ();                                /* unparsable */
1797       visual = "";
1798       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
1799     }
1800
1801   changed = flush_changes (s, list_elt, -1, command, visual);
1802   if (changed)
1803     {
1804       changed = demo_write_init_file (s, p);
1805
1806       /* Do this to re-launch the hack if (and only if) the command line
1807          has changed. */
1808       populate_demo_window (s, selected_list_element (s));
1809     }
1810
1811  DONE:
1812   s->saving_p = False;
1813   return changed;
1814 }
1815
1816
1817 G_MODULE_EXPORT void
1818 pref_changed_cb (GtkWidget *widget, gpointer user_data)
1819 {
1820   state *s = global_state_kludge;  /* I hate C so much... */
1821   if (! s->initializing_p)
1822     {
1823       s->initializing_p = True;
1824       flush_dialog_changes_and_save (s);
1825       s->initializing_p = False;
1826     }
1827 }
1828
1829 G_MODULE_EXPORT gboolean
1830 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1831 {
1832   pref_changed_cb (widget, user_data);
1833   return FALSE;
1834 }
1835
1836 /* Callback on menu items in the "mode" options menu.
1837  */
1838 G_MODULE_EXPORT void
1839 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
1840 {
1841   state *s = (state *) user_data;
1842   saver_preferences *p = &s->prefs;
1843   GtkWidget *list = name_to_widget (s, "list");
1844   int list_elt;
1845
1846   GList *menu_items =
1847     gtk_container_children (GTK_CONTAINER (GET_PARENT (widget)));
1848   int menu_index = 0;
1849   saver_mode new_mode;
1850
1851   while (menu_items)
1852     {
1853       if (menu_items->data == widget)
1854         break;
1855       menu_index++;
1856       menu_items = menu_items->next;
1857     }
1858   if (!menu_items) abort();
1859
1860   new_mode = mode_menu_order[menu_index];
1861
1862   /* Keep the same list element displayed as before; except if we're
1863      switching *to* "one screensaver" mode from any other mode, set
1864      "the one" to be that which is currently selected.
1865    */
1866   list_elt = selected_list_element (s);
1867   if (new_mode == ONE_HACK)
1868     p->selected_hack = s->list_elt_to_hack_number[list_elt];
1869
1870   {
1871     saver_mode old_mode = p->mode;
1872     p->mode = new_mode;
1873     populate_demo_window (s, list_elt);
1874     force_list_select_item (s, list, list_elt, True);
1875     p->mode = old_mode;  /* put it back, so the init file gets written */
1876   }
1877
1878   pref_changed_cb (widget, user_data);
1879 }
1880
1881
1882 G_MODULE_EXPORT void
1883 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
1884                 gint page_num, gpointer user_data)
1885 {
1886   state *s = global_state_kludge;  /* I hate C so much... */
1887   pref_changed_cb (GTK_WIDGET (notebook), user_data);
1888
1889   /* If we're switching to page 0, schedule the current hack to be run.
1890      Otherwise, schedule it to stop. */
1891   if (page_num == 0)
1892     populate_demo_window (s, selected_list_element (s));
1893   else
1894     schedule_preview (s, 0);
1895 }
1896
1897 #ifdef HAVE_GTK2
1898 static void
1899 list_activated_cb (GtkTreeView       *list,
1900                    GtkTreePath       *path,
1901                    GtkTreeViewColumn *column,
1902                    gpointer           data)
1903 {
1904   state *s = data;
1905   char *str;
1906   int list_elt;
1907
1908   g_return_if_fail (!gdk_pointer_is_grabbed ());
1909
1910   str = gtk_tree_path_to_string (path);
1911   list_elt = strtol (str, NULL, 10);
1912   g_free (str);
1913
1914   if (list_elt >= 0)
1915     run_hack (s, list_elt, True);
1916 }
1917
1918 static void
1919 list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
1920 {
1921   state *s = (state *)data;
1922   GtkTreeModel *model;
1923   GtkTreeIter iter;
1924   GtkTreePath *path;
1925   char *str;
1926   int list_elt;
1927  
1928   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1929     return;
1930
1931   path = gtk_tree_model_get_path (model, &iter);
1932   str = gtk_tree_path_to_string (path);
1933   list_elt = strtol (str, NULL, 10);
1934
1935   gtk_tree_path_free (path);
1936   g_free (str);
1937
1938   populate_demo_window (s, list_elt);
1939   flush_dialog_changes_and_save (s);
1940
1941   /* Re-populate the Settings window any time a new item is selected
1942      in the list, in case both windows are currently visible.
1943    */
1944   populate_popup_window (s);
1945 }
1946
1947 #else /* !HAVE_GTK2 */
1948
1949 static time_t last_doubleclick_time = 0;   /* FMH!  This is to suppress the
1950                                               list_select_cb that comes in
1951                                               *after* we've double-clicked.
1952                                             */
1953
1954 static gint
1955 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
1956                      gpointer data)
1957 {
1958   state *s = (state *) data;
1959   if (event->type == GDK_2BUTTON_PRESS)
1960     {
1961       GtkList *list = GTK_LIST (name_to_widget (s, "list"));
1962       int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
1963
1964       last_doubleclick_time = time ((time_t *) 0);
1965
1966       if (list_elt >= 0)
1967         run_hack (s, list_elt, True);
1968     }
1969
1970   return FALSE;
1971 }
1972
1973
1974 static void
1975 list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
1976 {
1977   state *s = (state *) data;
1978   time_t now = time ((time_t *) 0);
1979
1980   if (now >= last_doubleclick_time + 2)
1981     {
1982       int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
1983       populate_demo_window (s, list_elt);
1984       flush_dialog_changes_and_save (s);
1985     }
1986 }
1987
1988 static void
1989 list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
1990 {
1991   state *s = (state *) data;
1992   populate_demo_window (s, -1);
1993   flush_dialog_changes_and_save (s);
1994 }
1995
1996 #endif /* !HAVE_GTK2 */
1997
1998
1999 /* Called when the checkboxes that are in the left column of the
2000    scrolling list are clicked.  This both populates the right pane
2001    (just as clicking on the label (really, listitem) does) and
2002    also syncs this checkbox with  the right pane Enabled checkbox.
2003  */
2004 static void
2005 list_checkbox_cb (
2006 #ifdef HAVE_GTK2
2007                   GtkCellRendererToggle *toggle,
2008                   gchar                 *path_string,
2009 #else  /* !HAVE_GTK2 */
2010                   GtkWidget *cb,
2011 #endif /* !HAVE_GTK2 */
2012                   gpointer               data)
2013 {
2014   state *s = (state *) data;
2015
2016 #ifdef HAVE_GTK2
2017   GtkScrolledWindow *scroller =
2018     GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
2019   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2020   GtkTreeModel *model = gtk_tree_view_get_model (list);
2021   GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
2022   GtkTreeIter iter;
2023   gboolean active;
2024 #else /* !HAVE_GTK2 */
2025   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
2026   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
2027
2028   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
2029   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
2030   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
2031 #endif /* !HAVE_GTK2 */
2032   GtkAdjustment *adj;
2033   double scroll_top;
2034
2035   int list_elt;
2036
2037 #ifdef HAVE_GTK2
2038   if (!gtk_tree_model_get_iter (model, &iter, path))
2039     {
2040       g_warning ("bad path: %s", path_string);
2041       return;
2042     }
2043   gtk_tree_path_free (path);
2044
2045   gtk_tree_model_get (model, &iter,
2046                       COL_ENABLED, &active,
2047                       -1);
2048
2049   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
2050                       COL_ENABLED, !active,
2051                       -1);
2052
2053   list_elt = strtol (path_string, NULL, 10);  
2054 #else  /* !HAVE_GTK2 */
2055   list_elt = gtk_list_child_position (list, line);
2056 #endif /* !HAVE_GTK2 */
2057
2058   /* remember previous scroll position of the top of the list */
2059   adj = gtk_scrolled_window_get_vadjustment (scroller);
2060   scroll_top = GET_ADJ_VALUE (adj);
2061
2062   flush_dialog_changes_and_save (s);
2063   force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
2064   populate_demo_window (s, list_elt);
2065   
2066   /* restore the previous scroll position of the top of the list.
2067      this is weak, but I don't really know why it's moving... */
2068   gtk_adjustment_set_value (adj, scroll_top);
2069 }
2070
2071
2072 typedef struct {
2073   state *state;
2074   GtkFileSelection *widget;
2075 } file_selection_data;
2076
2077
2078
2079 static void
2080 store_image_directory (GtkWidget *button, gpointer user_data)
2081 {
2082   file_selection_data *fsd = (file_selection_data *) user_data;
2083   state *s = fsd->state;
2084   GtkFileSelection *selector = fsd->widget;
2085   GtkWidget *top = s->toplevel_widget;
2086   saver_preferences *p = &s->prefs;
2087   const char *path = gtk_file_selection_get_filename (selector);
2088
2089   if (p->image_directory && !strcmp(p->image_directory, path))
2090     return;  /* no change */
2091
2092   /* No warning for URLs. */
2093   if ((!directory_p (path)) && strncmp(path, "http://", 6))
2094     {
2095       char b[255];
2096       sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
2097       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
2098       return;
2099     }
2100
2101   if (p->image_directory) free (p->image_directory);
2102   p->image_directory = normalize_directory (path);
2103
2104   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2105                       (p->image_directory ? p->image_directory : ""));
2106   demo_write_init_file (s, p);
2107 }
2108
2109
2110 static void
2111 store_text_file (GtkWidget *button, gpointer user_data)
2112 {
2113   file_selection_data *fsd = (file_selection_data *) user_data;
2114   state *s = fsd->state;
2115   GtkFileSelection *selector = fsd->widget;
2116   GtkWidget *top = s->toplevel_widget;
2117   saver_preferences *p = &s->prefs;
2118   const char *path = gtk_file_selection_get_filename (selector);
2119
2120   if (p->text_file && !strcmp(p->text_file, path))
2121     return;  /* no change */
2122
2123   if (!file_p (path))
2124     {
2125       char b[255];
2126       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2127       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
2128       return;
2129     }
2130
2131   if (p->text_file) free (p->text_file);
2132   p->text_file = normalize_directory (path);
2133
2134   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2135                       (p->text_file ? p->text_file : ""));
2136   demo_write_init_file (s, p);
2137 }
2138
2139
2140 static void
2141 store_text_program (GtkWidget *button, gpointer user_data)
2142 {
2143   file_selection_data *fsd = (file_selection_data *) user_data;
2144   state *s = fsd->state;
2145   GtkFileSelection *selector = fsd->widget;
2146   /*GtkWidget *top = s->toplevel_widget;*/
2147   saver_preferences *p = &s->prefs;
2148   const char *path = gtk_file_selection_get_filename (selector);
2149
2150   if (p->text_program && !strcmp(p->text_program, path))
2151     return;  /* no change */
2152
2153 # if 0
2154   if (!file_p (path))
2155     {
2156       char b[255];
2157       sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
2158       warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
2159       return;
2160     }
2161 # endif
2162
2163   if (p->text_program) free (p->text_program);
2164   p->text_program = normalize_directory (path);
2165
2166   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2167                       (p->text_program ? p->text_program : ""));
2168   demo_write_init_file (s, p);
2169 }
2170
2171
2172
2173 static void
2174 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
2175 {
2176   file_selection_data *fsd = (file_selection_data *) user_data;
2177   gtk_widget_hide (GTK_WIDGET (fsd->widget));
2178 }
2179
2180 static void
2181 browse_image_dir_ok (GtkWidget *button, gpointer user_data)
2182 {
2183   browse_image_dir_cancel (button, user_data);
2184   store_image_directory (button, user_data);
2185 }
2186
2187 static void
2188 browse_text_file_ok (GtkWidget *button, gpointer user_data)
2189 {
2190   browse_image_dir_cancel (button, user_data);
2191   store_text_file (button, user_data);
2192 }
2193
2194 static void
2195 browse_text_program_ok (GtkWidget *button, gpointer user_data)
2196 {
2197   browse_image_dir_cancel (button, user_data);
2198   store_text_program (button, user_data);
2199 }
2200
2201 static void
2202 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
2203 {
2204   browse_image_dir_cancel (widget, user_data);
2205 }
2206
2207
2208 G_MODULE_EXPORT void
2209 browse_image_dir_cb (GtkButton *button, gpointer user_data)
2210 {
2211   state *s = global_state_kludge;  /* I hate C so much... */
2212   saver_preferences *p = &s->prefs;
2213   static file_selection_data *fsd = 0;
2214
2215   GtkFileSelection *selector = GTK_FILE_SELECTION(
2216     gtk_file_selection_new ("Please select the image directory."));
2217
2218   if (!fsd)
2219     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2220
2221   fsd->widget = selector;
2222   fsd->state = s;
2223
2224   if (p->image_directory && *p->image_directory)
2225     gtk_file_selection_set_filename (selector, p->image_directory);
2226
2227   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2228                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
2229                       (gpointer *) fsd);
2230   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2231                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2232                       (gpointer *) fsd);
2233   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2234                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2235                       (gpointer *) fsd);
2236
2237   gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
2238
2239   gtk_window_set_modal (GTK_WINDOW (selector), True);
2240   gtk_widget_show (GTK_WIDGET (selector));
2241 }
2242
2243
2244 G_MODULE_EXPORT void
2245 browse_text_file_cb (GtkButton *button, gpointer user_data)
2246 {
2247   state *s = global_state_kludge;  /* I hate C so much... */
2248   saver_preferences *p = &s->prefs;
2249   static file_selection_data *fsd = 0;
2250
2251   GtkFileSelection *selector = GTK_FILE_SELECTION(
2252     gtk_file_selection_new ("Please select a text file."));
2253
2254   if (!fsd)
2255     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2256
2257   fsd->widget = selector;
2258   fsd->state = s;
2259
2260   if (p->text_file && *p->text_file)
2261     gtk_file_selection_set_filename (selector, p->text_file);
2262
2263   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2264                       "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
2265                       (gpointer *) fsd);
2266   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2267                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2268                       (gpointer *) fsd);
2269   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2270                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2271                       (gpointer *) fsd);
2272
2273   gtk_window_set_modal (GTK_WINDOW (selector), True);
2274   gtk_widget_show (GTK_WIDGET (selector));
2275 }
2276
2277
2278 G_MODULE_EXPORT void
2279 browse_text_program_cb (GtkButton *button, gpointer user_data)
2280 {
2281   state *s = global_state_kludge;  /* I hate C so much... */
2282   saver_preferences *p = &s->prefs;
2283   static file_selection_data *fsd = 0;
2284
2285   GtkFileSelection *selector = GTK_FILE_SELECTION(
2286     gtk_file_selection_new ("Please select a text-generating program."));
2287
2288   if (!fsd)
2289     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
2290
2291   fsd->widget = selector;
2292   fsd->state = s;
2293
2294   if (p->text_program && *p->text_program)
2295     gtk_file_selection_set_filename (selector, p->text_program);
2296
2297   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
2298                       "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
2299                       (gpointer *) fsd);
2300   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
2301                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
2302                       (gpointer *) fsd);
2303   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
2304                       GTK_SIGNAL_FUNC (browse_image_dir_close),
2305                       (gpointer *) fsd);
2306
2307   gtk_window_set_modal (GTK_WINDOW (selector), True);
2308   gtk_widget_show (GTK_WIDGET (selector));
2309 }
2310
2311
2312
2313
2314
2315 G_MODULE_EXPORT void
2316 settings_cb (GtkButton *button, gpointer user_data)
2317 {
2318   state *s = global_state_kludge;  /* I hate C so much... */
2319   int list_elt = selected_list_element (s);
2320
2321   populate_demo_window (s, list_elt);   /* reset the widget */
2322   populate_popup_window (s);            /* create UI on popup window */
2323   gtk_widget_show (s->popup_widget);
2324 }
2325
2326 static void
2327 settings_sync_cmd_text (state *s)
2328 {
2329 # ifdef HAVE_XML
2330   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2331   char *cmd_line = get_configurator_command_line (s->cdata, False);
2332   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
2333   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
2334   free (cmd_line);
2335 # endif /* HAVE_XML */
2336 }
2337
2338 G_MODULE_EXPORT void
2339 settings_adv_cb (GtkButton *button, gpointer user_data)
2340 {
2341   state *s = global_state_kludge;  /* I hate C so much... */
2342   GtkNotebook *notebook =
2343     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2344
2345   settings_sync_cmd_text (s);
2346   gtk_notebook_set_page (notebook, 1);
2347 }
2348
2349 G_MODULE_EXPORT void
2350 settings_std_cb (GtkButton *button, gpointer user_data)
2351 {
2352   state *s = global_state_kludge;  /* I hate C so much... */
2353   GtkNotebook *notebook =
2354     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2355
2356   /* Re-create UI to reflect the in-progress command-line settings. */
2357   populate_popup_window (s);
2358
2359   gtk_notebook_set_page (notebook, 0);
2360 }
2361
2362 G_MODULE_EXPORT void
2363 settings_reset_cb (GtkButton *button, gpointer user_data)
2364 {
2365 # ifdef HAVE_XML
2366   state *s = global_state_kludge;  /* I hate C so much... */
2367   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2368   char *cmd_line = get_configurator_command_line (s->cdata, True);
2369   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
2370   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
2371   free (cmd_line);
2372   populate_popup_window (s);
2373 # endif /* HAVE_XML */
2374 }
2375
2376 G_MODULE_EXPORT void
2377 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
2378                          gint page_num, gpointer user_data)
2379 {
2380   state *s = global_state_kludge;  /* I hate C so much... */
2381   GtkWidget *adv = name_to_widget (s, "adv_button");
2382   GtkWidget *std = name_to_widget (s, "std_button");
2383
2384   if (page_num == 0)
2385     {
2386       gtk_widget_show (adv);
2387       gtk_widget_hide (std);
2388     }
2389   else if (page_num == 1)
2390     {
2391       gtk_widget_hide (adv);
2392       gtk_widget_show (std);
2393     }
2394   else
2395     abort();
2396 }
2397
2398
2399
2400 G_MODULE_EXPORT void
2401 settings_cancel_cb (GtkButton *button, gpointer user_data)
2402 {
2403   state *s = global_state_kludge;  /* I hate C so much... */
2404   gtk_widget_hide (s->popup_widget);
2405 }
2406
2407 G_MODULE_EXPORT void
2408 settings_ok_cb (GtkButton *button, gpointer user_data)
2409 {
2410   state *s = global_state_kludge;  /* I hate C so much... */
2411   GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
2412   int page = gtk_notebook_get_current_page (notebook);
2413
2414   if (page == 0)
2415     /* Regenerate the command-line from the widget contents before saving.
2416        But don't do this if we're looking at the command-line page already,
2417        or we will blow away what they typed... */
2418     settings_sync_cmd_text (s);
2419
2420   flush_popup_changes_and_save (s);
2421   gtk_widget_hide (s->popup_widget);
2422 }
2423
2424 static gboolean
2425 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
2426 {
2427   state *s = (state *) data;
2428   settings_cancel_cb (0, (gpointer) s);
2429   return TRUE;
2430 }
2431
2432
2433 \f
2434 /* Populating the various widgets
2435  */
2436
2437
2438 /* Returns the number of the last hack run by the server.
2439  */
2440 static int
2441 server_current_hack (void)
2442 {
2443   Atom type;
2444   int format;
2445   unsigned long nitems, bytesafter;
2446   unsigned char *dataP = 0;
2447   Display *dpy = GDK_DISPLAY();
2448   int hack_number = -1;
2449
2450   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
2451                           XA_SCREENSAVER_STATUS,
2452                           0, 3, False, XA_INTEGER,
2453                           &type, &format, &nitems, &bytesafter,
2454                           &dataP)
2455       == Success
2456       && type == XA_INTEGER
2457       && nitems >= 3
2458       && dataP)
2459     {
2460       PROP32 *data = (PROP32 *) dataP;
2461       hack_number = (int) data[2] - 1;
2462     }
2463
2464   if (dataP) XFree (dataP);
2465
2466   return hack_number;
2467 }
2468
2469
2470 /* Finds the number of the last hack that was run, and makes that item be
2471    selected by default.
2472  */
2473 static void
2474 scroll_to_current_hack (state *s)
2475 {
2476   saver_preferences *p = &s->prefs;
2477   int hack_number = -1;
2478
2479   if (p->mode == ONE_HACK)                 /* in "one" mode, use the one */
2480     hack_number = p->selected_hack;
2481   if (hack_number < 0)                     /* otherwise, use the last-run */
2482     hack_number = server_current_hack ();
2483   if (hack_number < 0)                     /* failing that, last "one mode" */
2484     hack_number = p->selected_hack;
2485   if (hack_number < 0)                     /* failing that, newest hack. */
2486     {
2487       /* We should only get here if the user does not have a .xscreensaver
2488          file, and the screen has not been blanked with a hack since X
2489          started up: in other words, this is probably a fresh install.
2490
2491          Instead of just defaulting to hack #0 (in either "programs" or
2492          "alphabetical" order) let's try to default to the last runnable
2493          hack in the "programs" list: this is probably the hack that was
2494          most recently added to the xscreensaver distribution (and so
2495          it's probably the currently-coolest one!)
2496        */
2497       hack_number = p->screenhacks_count-1;
2498       while (hack_number > 0 &&
2499              ! (s->hacks_available_p[hack_number] &&
2500                 p->screenhacks[hack_number]->enabled_p))
2501         hack_number--;
2502     }
2503
2504   if (hack_number >= 0 && hack_number < p->screenhacks_count)
2505     {
2506       int list_elt = s->hack_number_to_list_elt[hack_number];
2507       GtkWidget *list = name_to_widget (s, "list");
2508       force_list_select_item (s, list, list_elt, True);
2509       populate_demo_window (s, list_elt);
2510     }
2511 }
2512
2513
2514 static void
2515 populate_hack_list (state *s)
2516 {
2517   Display *dpy = GDK_DISPLAY();
2518 #ifdef HAVE_GTK2
2519   saver_preferences *p = &s->prefs;
2520   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2521   GtkListStore *model;
2522   GtkTreeSelection *selection;
2523   GtkCellRenderer *ren;
2524   GtkTreeIter iter;
2525   int i;
2526
2527   g_object_get (G_OBJECT (list),
2528                 "model", &model,
2529                 NULL);
2530   if (!model)
2531     {
2532       model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
2533       g_object_set (G_OBJECT (list), "model", model, NULL);
2534       g_object_unref (model);
2535
2536       ren = gtk_cell_renderer_toggle_new ();
2537       gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
2538                                                    _("Use"), ren,
2539                                                    "active", COL_ENABLED,
2540                                                    NULL);
2541
2542       g_signal_connect (ren, "toggled",
2543                         G_CALLBACK (list_checkbox_cb),
2544                         s);
2545
2546       ren = gtk_cell_renderer_text_new ();
2547       gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
2548                                                    _("Screen Saver"), ren,
2549                                                    "markup", COL_NAME,
2550                                                    NULL);
2551
2552       g_signal_connect_after (list, "row_activated",
2553                               G_CALLBACK (list_activated_cb),
2554                               s);
2555
2556       selection = gtk_tree_view_get_selection (list);
2557       g_signal_connect (selection, "changed",
2558                         G_CALLBACK (list_select_changed_cb),
2559                         s);
2560
2561     }
2562
2563   for (i = 0; i < s->list_count; i++)
2564     {
2565       int hack_number = s->list_elt_to_hack_number[i];
2566       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2567       char *pretty_name;
2568       Bool available_p = (hack && s->hacks_available_p [hack_number]);
2569
2570       if (!hack) continue;
2571
2572       /* If we're to suppress uninstalled hacks, check $PATH now. */
2573       if (p->ignore_uninstalled_p && !available_p)
2574         continue;
2575
2576       pretty_name = (hack->name
2577                      ? strdup (hack->name)
2578                      : make_hack_name (dpy, hack->command));
2579
2580       if (!available_p)
2581         {
2582           /* Make the text foreground be the color of insensitive widgets
2583              (but don't actually make it be insensitive, since we still
2584              want to be able to click on it.)
2585            */
2586           GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (list));
2587           GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
2588        /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
2589           char *buf = (char *) malloc (strlen (pretty_name) + 100);
2590
2591           sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
2592                       /*     " background=\"#%02X%02X%02X\""  */
2593                         ">%s</span>",
2594                    fg->red >> 8, fg->green >> 8, fg->blue >> 8,
2595                 /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
2596                    pretty_name);
2597           free (pretty_name);
2598           pretty_name = buf;
2599         }
2600
2601       gtk_list_store_append (model, &iter);
2602       gtk_list_store_set (model, &iter,
2603                           COL_ENABLED, hack->enabled_p,
2604                           COL_NAME, pretty_name,
2605                           -1);
2606       free (pretty_name);
2607     }
2608
2609 #else /* !HAVE_GTK2 */
2610
2611   saver_preferences *p = &s->prefs;
2612   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2613   int i;
2614   for (i = 0; i < s->list_count; i++)
2615     {
2616       int hack_number = s->list_elt_to_hack_number[i];
2617       screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
2618
2619       /* A GtkList must contain only GtkListItems, but those can contain
2620          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
2621          and a Label.  We handle single and double click events on the
2622          line itself, for clicking on the text, but the interior checkbox
2623          also handles its own events.
2624        */
2625       GtkWidget *line;
2626       GtkWidget *line_hbox;
2627       GtkWidget *line_check;
2628       GtkWidget *line_label;
2629       char *pretty_name;
2630       Bool available_p = (hack && s->hacks_available_p [hack_number]);
2631
2632       if (!hack) continue;
2633
2634       /* If we're to suppress uninstalled hacks, check $PATH now. */
2635       if (p->ignore_uninstalled_p && !available_p)
2636         continue;
2637
2638       pretty_name = (hack->name
2639                      ? strdup (hack->name)
2640                      : make_hack_name (hack->command));
2641
2642       line = gtk_list_item_new ();
2643       line_hbox = gtk_hbox_new (FALSE, 0);
2644       line_check = gtk_check_button_new ();
2645       line_label = gtk_label_new (pretty_name);
2646
2647       gtk_container_add (GTK_CONTAINER (line), line_hbox);
2648       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
2649       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
2650
2651       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
2652                                     hack->enabled_p);
2653       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
2654
2655       gtk_widget_show (line_check);
2656       gtk_widget_show (line_label);
2657       gtk_widget_show (line_hbox);
2658       gtk_widget_show (line);
2659
2660       free (pretty_name);
2661
2662       gtk_container_add (GTK_CONTAINER (list), line);
2663       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
2664                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
2665                           (gpointer) s);
2666
2667       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
2668                           GTK_SIGNAL_FUNC (list_checkbox_cb),
2669                           (gpointer) s);
2670
2671       gtk_widget_show (line);
2672
2673       if (!available_p)
2674         {
2675           /* Make the widget be colored like insensitive widgets
2676              (but don't actually make it be insensitive, since we
2677              still want to be able to click on it.)
2678            */
2679           GtkRcStyle *rc_style;
2680           GdkColor fg, bg;
2681
2682           gtk_widget_realize (GTK_WIDGET (line_label));
2683
2684           fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
2685           bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
2686
2687           rc_style = gtk_rc_style_new ();
2688           rc_style->fg[GTK_STATE_NORMAL] = fg;
2689           rc_style->bg[GTK_STATE_NORMAL] = bg;
2690           rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
2691
2692           gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
2693           gtk_rc_style_unref (rc_style);
2694         }
2695     }
2696
2697   gtk_signal_connect (GTK_OBJECT (list), "select_child",
2698                       GTK_SIGNAL_FUNC (list_select_cb),
2699                       (gpointer) s);
2700   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
2701                       GTK_SIGNAL_FUNC (list_unselect_cb),
2702                       (gpointer) s);
2703 #endif /* !HAVE_GTK2 */
2704 }
2705
2706 static void
2707 update_list_sensitivity (state *s)
2708 {
2709   saver_preferences *p = &s->prefs;
2710   Bool sensitive = (p->mode == RANDOM_HACKS ||
2711                     p->mode == RANDOM_HACKS_SAME ||
2712                     p->mode == ONE_HACK);
2713   Bool checkable = (p->mode == RANDOM_HACKS ||
2714                     p->mode == RANDOM_HACKS_SAME);
2715   Bool blankable = (p->mode != DONT_BLANK);
2716
2717 #ifndef HAVE_GTK2
2718   GtkWidget *head     = name_to_widget (s, "col_head_hbox");
2719   GtkWidget *use      = name_to_widget (s, "use_col_frame");
2720 #endif /* HAVE_GTK2 */
2721   GtkWidget *scroller = name_to_widget (s, "scroller");
2722   GtkWidget *buttons  = name_to_widget (s, "next_prev_hbox");
2723   GtkWidget *blanker  = name_to_widget (s, "blanking_table");
2724
2725 #ifdef HAVE_GTK2
2726   GtkTreeView *list      = GTK_TREE_VIEW (name_to_widget (s, "list"));
2727   GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
2728 #else /* !HAVE_GTK2 */
2729   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2730   GList *kids   = gtk_container_children (GTK_CONTAINER (list));
2731
2732   gtk_widget_set_sensitive (GTK_WIDGET (head),     sensitive);
2733 #endif /* !HAVE_GTK2 */
2734   gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
2735   gtk_widget_set_sensitive (GTK_WIDGET (buttons),  sensitive);
2736
2737   gtk_widget_set_sensitive (GTK_WIDGET (blanker),  blankable);
2738
2739 #ifdef HAVE_GTK2
2740   gtk_tree_view_column_set_visible (use, checkable);
2741 #else  /* !HAVE_GTK2 */
2742   if (checkable)
2743     gtk_widget_show (use);   /* the "Use" column header */
2744   else
2745     gtk_widget_hide (use);
2746
2747   while (kids)
2748     {
2749       GtkBin *line = GTK_BIN (kids->data);
2750       GtkContainer *line_hbox = GTK_CONTAINER (line->child);
2751       GtkWidget *line_check =
2752         GTK_WIDGET (gtk_container_children (line_hbox)->data);
2753       
2754       if (checkable)
2755         gtk_widget_show (line_check);
2756       else
2757         gtk_widget_hide (line_check);
2758
2759       kids = kids->next;
2760     }
2761 #endif /* !HAVE_GTK2 */
2762 }
2763
2764
2765 static void
2766 populate_prefs_page (state *s)
2767 {
2768   saver_preferences *p = &s->prefs;
2769
2770   Bool can_lock_p = True;
2771
2772   /* Disable all the "lock" controls if locking support was not provided
2773      at compile-time, or if running on MacOS. */
2774 # if defined(NO_LOCKING) || defined(__APPLE__)
2775   can_lock_p = False;
2776 # endif
2777
2778
2779   /* If there is only one screen, the mode menu contains
2780      "random" but not "random-same".
2781    */
2782   if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
2783     p->mode = RANDOM_HACKS;
2784
2785
2786   /* The file supports timeouts of less than a minute, but the GUI does
2787      not, so throttle the values to be at least one minute (since "0" is
2788      a bad rounding choice...)
2789    */
2790 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
2791   THROTTLE (timeout);
2792   THROTTLE (cycle);
2793   /* THROTTLE (passwd_timeout); */  /* GUI doesn't set this; leave it alone */
2794 # undef THROTTLE
2795
2796 # define FMT_MINUTES(NAME,N) \
2797     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
2798
2799 # define FMT_SECONDS(NAME,N) \
2800     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
2801
2802   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
2803   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
2804   FMT_MINUTES ("lock_spinbutton",         p->lock_timeout);
2805   FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
2806   FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
2807   FMT_MINUTES ("dpms_off_spinbutton",     p->dpms_off);
2808   FMT_SECONDS ("fade_spinbutton",         p->fade_seconds);
2809
2810 # undef FMT_MINUTES
2811 # undef FMT_SECONDS
2812
2813 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
2814   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
2815                                 (ACTIVEP))
2816
2817   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
2818 #if 0
2819   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
2820   TOGGLE_ACTIVE ("capture_button",    p->capture_stderr_p);
2821   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
2822 #endif
2823   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
2824   TOGGLE_ACTIVE ("dpms_quickoff_button", p->dpms_quickoff_p);
2825   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
2826   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
2827   TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
2828   TOGGLE_ACTIVE ("install_button",    p->install_cmap_p);
2829   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
2830   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
2831
2832   switch (p->tmode)
2833     {
2834     case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio",         True); break;
2835     case TEXT_FILE:    TOGGLE_ACTIVE ("text_file_radio",    True); break;
2836     case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
2837     case TEXT_URL:     TOGGLE_ACTIVE ("text_url_radio",     True); break;
2838     default:           TOGGLE_ACTIVE ("text_host_radio",    True); break;
2839     }
2840
2841 # undef TOGGLE_ACTIVE
2842
2843   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2844                       (p->image_directory ? p->image_directory : ""));
2845   gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
2846                             p->random_image_p);
2847   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
2848                             p->random_image_p);
2849
2850   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
2851                       (p->text_literal ? p->text_literal : ""));
2852   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
2853                       (p->text_file ? p->text_file : ""));
2854   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
2855                       (p->text_program ? p->text_program : ""));
2856   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
2857                       (p->text_url ? p->text_url : ""));
2858
2859   gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
2860                             p->tmode == TEXT_LITERAL);
2861   gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
2862                             p->tmode == TEXT_FILE);
2863   gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
2864                             p->tmode == TEXT_FILE);
2865   gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
2866                             p->tmode == TEXT_PROGRAM);
2867   gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
2868                             p->tmode == TEXT_PROGRAM);
2869   gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
2870                             p->tmode == TEXT_URL);
2871
2872
2873   /* Map the `saver_mode' enum to mode menu to values. */
2874   {
2875     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
2876
2877     int i;
2878     for (i = 0; i < countof(mode_menu_order); i++)
2879       if (mode_menu_order[i] == p->mode)
2880         break;
2881     gtk_option_menu_set_history (opt, i);
2882     update_list_sensitivity (s);
2883   }
2884
2885   {
2886     Bool found_any_writable_cells = False;
2887     Bool fading_possible = False;
2888     Bool dpms_supported = False;
2889
2890     Display *dpy = GDK_DISPLAY();
2891     int nscreens = ScreenCount(dpy);  /* real screens, not Xinerama */
2892     int i;
2893     for (i = 0; i < nscreens; i++)
2894       {
2895         Screen *s = ScreenOfDisplay (dpy, i);
2896         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
2897           {
2898             found_any_writable_cells = True;
2899             break;
2900           }
2901       }
2902
2903     fading_possible = found_any_writable_cells;
2904 #ifdef HAVE_XF86VMODE_GAMMA
2905     fading_possible = True;
2906 #endif
2907
2908 #ifdef HAVE_DPMS_EXTENSION
2909     {
2910       int op = 0, event = 0, error = 0;
2911       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
2912         dpms_supported = True;
2913     }
2914 #endif /* HAVE_DPMS_EXTENSION */
2915
2916
2917 # define SENSITIZE(NAME,SENSITIVEP) \
2918     gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
2919
2920     /* Blanking and Locking
2921      */
2922     SENSITIZE ("lock_button",     can_lock_p);
2923     SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
2924     SENSITIZE ("lock_mlabel",     can_lock_p && p->lock_p);
2925
2926     /* DPMS
2927      */
2928 dpms_supported=1;
2929     SENSITIZE ("dpms_frame",              dpms_supported);
2930     SENSITIZE ("dpms_button",             dpms_supported);
2931     SENSITIZE ("dpms_quickoff_button",    dpms_supported);
2932
2933     SENSITIZE ("dpms_standby_label",      dpms_supported && p->dpms_enabled_p);
2934     SENSITIZE ("dpms_standby_mlabel",     dpms_supported && p->dpms_enabled_p);
2935     SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
2936     SENSITIZE ("dpms_suspend_label",      dpms_supported && p->dpms_enabled_p);
2937     SENSITIZE ("dpms_suspend_mlabel",     dpms_supported && p->dpms_enabled_p);
2938     SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
2939     SENSITIZE ("dpms_off_label",          dpms_supported && p->dpms_enabled_p);
2940     SENSITIZE ("dpms_off_mlabel",         dpms_supported && p->dpms_enabled_p);
2941     SENSITIZE ("dpms_off_spinbutton",     dpms_supported && p->dpms_enabled_p);
2942
2943     /* Colormaps
2944      */
2945     SENSITIZE ("cmap_frame",      found_any_writable_cells || fading_possible);
2946     SENSITIZE ("install_button",  found_any_writable_cells);
2947     SENSITIZE ("fade_button",     fading_possible);
2948     SENSITIZE ("unfade_button",   fading_possible);
2949
2950     SENSITIZE ("fade_label",      (fading_possible &&
2951                                    (p->fade_p || p->unfade_p)));
2952     SENSITIZE ("fade_spinbutton", (fading_possible &&
2953                                    (p->fade_p || p->unfade_p)));
2954
2955 # undef SENSITIZE
2956   }
2957 }
2958
2959
2960 static void
2961 populate_popup_window (state *s)
2962 {
2963   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
2964   char *doc_string = 0;
2965
2966   /* #### not in Gtk 1.2
2967   gtk_label_set_selectable (doc);
2968    */
2969
2970 # ifdef HAVE_XML
2971   if (s->cdata)
2972     {
2973       free_conf_data (s->cdata);
2974       s->cdata = 0;
2975     }
2976
2977   {
2978     saver_preferences *p = &s->prefs;
2979     int list_elt = selected_list_element (s);
2980     int hack_number = (list_elt >= 0 && list_elt < s->list_count
2981                        ? s->list_elt_to_hack_number[list_elt]
2982                        : -1);
2983     screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2984     if (hack)
2985       {
2986         GtkWidget *parent = name_to_widget (s, "settings_vbox");
2987         GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2988         const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
2989         s->cdata = load_configurator (cmd_line, s->debug_p);
2990         if (s->cdata && s->cdata->widget)
2991           gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
2992                               TRUE, TRUE, 0);
2993       }
2994   }
2995
2996   doc_string = (s->cdata
2997                 ? s->cdata->description
2998                 : 0);
2999 # else  /* !HAVE_XML */
3000   doc_string = _("Descriptions not available: no XML support compiled in.");
3001 # endif /* !HAVE_XML */
3002
3003   gtk_label_set_text (doc, (doc_string
3004                             ? _(doc_string)
3005                             : _("No description available.")));
3006 }
3007
3008
3009 static void
3010 sensitize_demo_widgets (state *s, Bool sensitive_p)
3011 {
3012   const char *names[] = { "demo", "settings",
3013                           "cmd_label", "cmd_text", "manual",
3014                           "visual", "visual_combo" };
3015   int i;
3016   for (i = 0; i < countof(names); i++)
3017     {
3018       GtkWidget *w = name_to_widget (s, names[i]);
3019       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
3020     }
3021 }
3022
3023
3024 static void
3025 sensitize_menu_items (state *s, Bool force_p)
3026 {
3027   static Bool running_p = False;
3028   static time_t last_checked = 0;
3029   time_t now = time ((time_t *) 0);
3030   const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
3031                           /* "demo" */ };
3032   int i;
3033
3034   if (force_p || now > last_checked + 10)   /* check every 10 seconds */
3035     {
3036       running_p = xscreensaver_running_p (s);
3037       last_checked = time ((time_t *) 0);
3038     }
3039
3040   for (i = 0; i < countof(names); i++)
3041     {
3042       GtkWidget *w = name_to_widget (s, names[i]);
3043       gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
3044     }
3045 }
3046
3047
3048 /* When the File menu is de-posted after a "Restart Daemon" command,
3049    the window underneath doesn't repaint for some reason.  I guess this
3050    is a bug in exposure handling in GTK or GDK.  This works around it.
3051  */
3052 static void
3053 force_dialog_repaint (state *s)
3054 {
3055 #if 1
3056   /* Tell GDK to invalidate and repaint the whole window.
3057    */
3058   GdkWindow *w = GET_WINDOW (s->toplevel_widget);
3059   GdkRegion *region = gdk_region_new ();
3060   GdkRectangle rect;
3061   rect.x = rect.y = 0;
3062   rect.width = rect.height = 32767;
3063   gdk_region_union_with_rect (region, &rect);
3064   gdk_window_invalidate_region (w, region, True);
3065   gdk_region_destroy (region);
3066   gdk_window_process_updates (w, True);
3067 #else
3068   /* Force the server to send an exposure event by creating and then
3069      destroying a window as a child of the top level shell.
3070    */
3071   Display *dpy = GDK_DISPLAY();
3072   Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
3073   Window w;
3074   XWindowAttributes xgwa;
3075   XGetWindowAttributes (dpy, parent, &xgwa);
3076   w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
3077   XMapRaised (dpy, w);
3078   XDestroyWindow (dpy, w);
3079   XSync (dpy, False);
3080 #endif
3081 }
3082
3083
3084 /* Even though we've given these text fields a maximum number of characters,
3085    their default size is still about 30 characters wide -- so measure out
3086    a string in their font, and resize them to just fit that.
3087  */
3088 static void
3089 fix_text_entry_sizes (state *s)
3090 {
3091   GtkWidget *w;
3092
3093 # if 0   /* appears no longer necessary with Gtk 1.2.10 */
3094   const char * const spinbuttons[] = {
3095     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
3096     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
3097     "dpms_off_spinbutton",
3098     "-fade_spinbutton" };
3099   int i;
3100   int width = 0;
3101
3102   for (i = 0; i < countof(spinbuttons); i++)
3103     {
3104       const char *n = spinbuttons[i];
3105       int cols = 4;
3106       while (*n == '-') n++, cols--;
3107       w = GTK_WIDGET (name_to_widget (s, n));
3108       width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
3109       gtk_widget_set_usize (w, width, -2);
3110     }
3111
3112   /* Now fix the width of the combo box.
3113    */
3114   w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
3115   w = GTK_COMBO (w)->entry;
3116   width = gdk_string_width (w->style->font, "PseudoColor___");
3117   gtk_widget_set_usize (w, width, -2);
3118
3119   /* Now fix the width of the file entry text.
3120    */
3121   w = GTK_WIDGET (name_to_widget (s, "image_text"));
3122   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
3123   gtk_widget_set_usize (w, width, -2);
3124
3125   /* Now fix the width of the command line text.
3126    */
3127   w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
3128   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
3129   gtk_widget_set_usize (w, width, -2);
3130
3131 # endif /* 0 */
3132
3133   /* Now fix the height of the list widget:
3134      make it default to being around 10 text-lines high instead of 4.
3135    */
3136   w = GTK_WIDGET (name_to_widget (s, "list"));
3137   {
3138     int lines = 10;
3139     int height;
3140     int leading = 3;  /* approximate is ok... */
3141     int border = 2;
3142
3143 #ifdef HAVE_GTK2
3144     PangoFontMetrics *pain =
3145       pango_context_get_metrics (gtk_widget_get_pango_context (w),
3146                                  gtk_widget_get_style (w)->font_desc,
3147                                  gtk_get_default_language ());
3148     height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
3149                            pango_font_metrics_get_descent (pain));
3150 #else  /* !HAVE_GTK2 */
3151     height = w->style->font->ascent + w->style->font->descent;
3152 #endif /* !HAVE_GTK2 */
3153
3154     height += leading;
3155     height *= lines;
3156     height += border * 2;
3157     w = GTK_WIDGET (name_to_widget (s, "scroller"));
3158     gtk_widget_set_usize (w, -2, height);
3159   }
3160 }
3161
3162
3163 #ifndef HAVE_GTK2
3164 \f
3165 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
3166  */
3167
3168 static char *up_arrow_xpm[] = {
3169   "15 15 4 1",
3170   "     c None",
3171   "-    c #FFFFFF",
3172   "+    c #D6D6D6",
3173   "@    c #000000",
3174
3175   "       @       ",
3176   "       @       ",
3177   "      -+@      ",
3178   "      -+@      ",
3179   "     -+++@     ",
3180   "     -+++@     ",
3181   "    -+++++@    ",
3182   "    -+++++@    ",
3183   "   -+++++++@   ",
3184   "   -+++++++@   ",
3185   "  -+++++++++@  ",
3186   "  -+++++++++@  ",
3187   " -+++++++++++@ ",
3188   " @@@@@@@@@@@@@ ",
3189   "               ",
3190
3191   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3192      the end of the array (Gtk 1.2.5.) */
3193   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3194   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3195 };
3196
3197 static char *down_arrow_xpm[] = {
3198   "15 15 4 1",
3199   "     c None",
3200   "-    c #FFFFFF",
3201   "+    c #D6D6D6",
3202   "@    c #000000",
3203
3204   "               ",
3205   " ------------- ",
3206   " -+++++++++++@ ",
3207   "  -+++++++++@  ",
3208   "  -+++++++++@  ",
3209   "   -+++++++@   ",
3210   "   -+++++++@   ",
3211   "    -+++++@    ",
3212   "    -+++++@    ",
3213   "     -+++@     ",
3214   "     -+++@     ",
3215   "      -+@      ",
3216   "      -+@      ",
3217   "       @       ",
3218   "       @       ",
3219
3220   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
3221      the end of the array (Gtk 1.2.5.) */
3222   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
3223   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
3224 };
3225
3226 static void
3227 pixmapify_button (state *s, int down_p)
3228 {
3229   GdkPixmap *pixmap;
3230   GdkBitmap *mask;
3231   GtkWidget *pixmapwid;
3232   GtkStyle *style;
3233   GtkWidget *w;
3234
3235   w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
3236   style = gtk_widget_get_style (w);
3237   mask = 0;
3238   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
3239                                          &style->bg[GTK_STATE_NORMAL],
3240                                          (down_p
3241                                           ? (gchar **) down_arrow_xpm
3242                                           : (gchar **) up_arrow_xpm));
3243   pixmapwid = gtk_pixmap_new (pixmap, mask);
3244   gtk_widget_show (pixmapwid);
3245   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
3246   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
3247 }
3248
3249 static void
3250 map_next_button_cb (GtkWidget *w, gpointer user_data)
3251 {
3252   state *s = (state *) user_data;
3253   pixmapify_button (s, 1);
3254 }
3255
3256 static void
3257 map_prev_button_cb (GtkWidget *w, gpointer user_data)
3258 {
3259   state *s = (state *) user_data;
3260   pixmapify_button (s, 0);
3261 }
3262 #endif /* !HAVE_GTK2 */
3263
3264 \f
3265 #ifndef HAVE_GTK2
3266 /* Work around a Gtk bug that causes label widgets to wrap text too early.
3267  */
3268
3269 static void
3270 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
3271                                              GtkAllocation *allocation,
3272                                              void *foo)
3273 {
3274   GtkRequisition req;
3275   GtkWidgetAuxInfo *aux_info;
3276
3277   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
3278
3279   aux_info->width = allocation->width;
3280   aux_info->height = -2;
3281   aux_info->x = -1;
3282   aux_info->y = -1;
3283
3284   gtk_widget_size_request (label, &req);
3285 }
3286
3287 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
3288  */
3289 static void
3290 eschew_gtk_lossage (GtkLabel *label)
3291 {
3292   GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
3293   aux_info->width = GTK_WIDGET (label)->allocation.width;
3294   aux_info->height = -2;
3295   aux_info->x = -1;
3296   aux_info->y = -1;
3297
3298   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
3299
3300   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
3301                       GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
3302                       0);
3303
3304   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
3305
3306   gtk_widget_queue_resize (GTK_WIDGET (label));
3307 }
3308 #endif /* !HAVE_GTK2 */
3309
3310
3311 static void
3312 populate_demo_window (state *s, int list_elt)
3313 {
3314   Display *dpy = GDK_DISPLAY();
3315   saver_preferences *p = &s->prefs;
3316   screenhack *hack;
3317   char *pretty_name;
3318   GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
3319   GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "opt_frame"));
3320   GtkEntry *cmd    = GTK_ENTRY (name_to_widget (s, "cmd_text"));
3321   GtkCombo *vis    = GTK_COMBO (name_to_widget (s, "visual_combo"));
3322   GtkWidget *list  = GTK_WIDGET (name_to_widget (s, "list"));
3323
3324   if (p->mode == BLANK_ONLY)
3325     {
3326       hack = 0;
3327       pretty_name = strdup (_("Blank Screen"));
3328       schedule_preview (s, 0);
3329     }
3330   else if (p->mode == DONT_BLANK)
3331     {
3332       hack = 0;
3333       pretty_name = strdup (_("Screen Saver Disabled"));
3334       schedule_preview (s, 0);
3335     }
3336   else
3337     {
3338       int hack_number = (list_elt >= 0 && list_elt < s->list_count
3339                          ? s->list_elt_to_hack_number[list_elt]
3340                          : -1);
3341       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
3342
3343       pretty_name = (hack
3344                      ? (hack->name
3345                         ? strdup (hack->name)
3346                         : make_hack_name (dpy, hack->command))
3347                      : 0);
3348
3349       if (hack)
3350         schedule_preview (s, hack->command);
3351       else
3352         schedule_preview (s, 0);
3353     }
3354
3355   if (!pretty_name)
3356     pretty_name = strdup (_("Preview"));
3357
3358   gtk_frame_set_label (frame1, _(pretty_name));
3359   gtk_frame_set_label (frame2, _(pretty_name));
3360
3361   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
3362   gtk_entry_set_position (cmd, 0);
3363
3364   {
3365     char title[255];
3366     sprintf (title, _("%s: %.100s Settings"),
3367              progclass, (pretty_name ? pretty_name : "???"));
3368     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
3369   }
3370
3371   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
3372                       (hack
3373                        ? (hack->visual && *hack->visual
3374                           ? hack->visual
3375                           : _("Any"))
3376                        : ""));
3377
3378   sensitize_demo_widgets (s, (hack ? True : False));
3379
3380   if (pretty_name) free (pretty_name);
3381
3382   ensure_selected_item_visible (list);
3383
3384   s->_selected_list_element = list_elt;
3385 }
3386
3387
3388 static void
3389 widget_deleter (GtkWidget *widget, gpointer data)
3390 {
3391   /* #### Well, I want to destroy these widgets, but if I do that, they get
3392      referenced again, and eventually I get a SEGV.  So instead of
3393      destroying them, I'll just hide them, and leak a bunch of memory
3394      every time the disk file changes.  Go go go Gtk!
3395
3396      #### Ok, that's a lie, I get a crash even if I just hide the widget
3397      and don't ever delete it.  Fuck!
3398    */
3399 #if 0
3400   gtk_widget_destroy (widget);
3401 #else
3402   gtk_widget_hide (widget);
3403 #endif
3404 }
3405
3406
3407 static char **sort_hack_cmp_names_kludge;
3408 static int
3409 sort_hack_cmp (const void *a, const void *b)
3410 {
3411   if (a == b)
3412     return 0;
3413   else
3414     {
3415       int aa = *(int *) a;
3416       int bb = *(int *) b;
3417       const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
3418       return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
3419                      (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
3420     }
3421 }
3422
3423
3424 static void
3425 initialize_sort_map (state *s)
3426 {
3427   Display *dpy = GDK_DISPLAY();
3428   saver_preferences *p = &s->prefs;
3429   int i, j;
3430
3431   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
3432   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
3433   if (s->hacks_available_p) free (s->hacks_available_p);
3434
3435   s->list_elt_to_hack_number = (int *)
3436     calloc (sizeof(int), p->screenhacks_count + 1);
3437   s->hack_number_to_list_elt = (int *)
3438     calloc (sizeof(int), p->screenhacks_count + 1);
3439   s->hacks_available_p = (Bool *)
3440     calloc (sizeof(Bool), p->screenhacks_count + 1);
3441   s->total_available = 0;
3442
3443   /* Check which hacks actually exist on $PATH
3444    */
3445   for (i = 0; i < p->screenhacks_count; i++)
3446     {
3447       screenhack *hack = p->screenhacks[i];
3448       int on = on_path_p (hack->command) ? 1 : 0;
3449       s->hacks_available_p[i] = on;
3450       s->total_available += on;
3451     }
3452
3453   /* Initialize list->hack table to unsorted mapping, omitting nonexistent
3454      hacks, if desired.
3455    */
3456   j = 0;
3457   for (i = 0; i < p->screenhacks_count; i++)
3458     {
3459       if (!p->ignore_uninstalled_p ||
3460           s->hacks_available_p[i])
3461         s->list_elt_to_hack_number[j++] = i;
3462     }
3463   s->list_count = j;
3464
3465   for (; j < p->screenhacks_count; j++)
3466     s->list_elt_to_hack_number[j] = -1;
3467
3468
3469   /* Generate list of sortable names (once)
3470    */
3471   sort_hack_cmp_names_kludge = (char **)
3472     calloc (sizeof(char *), p->screenhacks_count);
3473   for (i = 0; i < p->screenhacks_count; i++)
3474     {
3475       screenhack *hack = p->screenhacks[i];
3476       char *name = (hack->name && *hack->name
3477                     ? strdup (hack->name)
3478                     : make_hack_name (dpy, hack->command));
3479       char *str;
3480       for (str = name; *str; str++)
3481         *str = tolower(*str);
3482       sort_hack_cmp_names_kludge[i] = name;
3483     }
3484
3485   /* Sort list->hack map alphabetically
3486    */
3487   qsort (s->list_elt_to_hack_number,
3488          p->screenhacks_count,
3489          sizeof(*s->list_elt_to_hack_number),
3490          sort_hack_cmp);
3491
3492   /* Free names
3493    */
3494   for (i = 0; i < p->screenhacks_count; i++)
3495     free (sort_hack_cmp_names_kludge[i]);
3496   free (sort_hack_cmp_names_kludge);
3497   sort_hack_cmp_names_kludge = 0;
3498
3499   /* Build inverse table */
3500   for (i = 0; i < p->screenhacks_count; i++)
3501     {
3502       int n = s->list_elt_to_hack_number[i];
3503       if (n != -1)
3504         s->hack_number_to_list_elt[n] = i;
3505     }
3506 }
3507
3508
3509 static int
3510 maybe_reload_init_file (state *s)
3511 {
3512   Display *dpy = GDK_DISPLAY();
3513   saver_preferences *p = &s->prefs;
3514   int status = 0;
3515
3516   static Bool reentrant_lock = False;
3517   if (reentrant_lock) return 0;
3518   reentrant_lock = True;
3519
3520   if (init_file_changed_p (p))
3521     {
3522       const char *f = init_file_name();
3523       char *b;
3524       int list_elt;
3525       GtkWidget *list;
3526
3527       if (!f || !*f) return 0;
3528       b = (char *) malloc (strlen(f) + 1024);
3529       sprintf (b,
3530                _("Warning:\n\n"
3531                  "file \"%s\" has changed, reloading.\n"),
3532                f);
3533       warning_dialog (s->toplevel_widget, b, D_NONE, 100);
3534       free (b);
3535
3536       load_init_file (dpy, p);
3537       initialize_sort_map (s);
3538
3539       list_elt = selected_list_element (s);
3540       list = name_to_widget (s, "list");
3541       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
3542       populate_hack_list (s);
3543       force_list_select_item (s, list, list_elt, True);
3544       populate_prefs_page (s);
3545       populate_demo_window (s, list_elt);
3546       ensure_selected_item_visible (list);
3547
3548       status = 1;
3549     }
3550
3551   reentrant_lock = False;
3552   return status;
3553 }
3554
3555
3556 \f
3557 /* Making the preview window have the right X visual (so that GL works.)
3558  */
3559
3560 static Visual *get_best_gl_visual (state *);
3561
3562 static GdkVisual *
3563 x_visual_to_gdk_visual (Visual *xv)
3564 {
3565   GList *gvs = gdk_list_visuals();
3566   if (!xv) return gdk_visual_get_system();
3567   for (; gvs; gvs = gvs->next)
3568     {
3569       GdkVisual *gv = (GdkVisual *) gvs->data;
3570       if (xv == GDK_VISUAL_XVISUAL (gv))
3571         return gv;
3572     }
3573   fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
3574            blurb(), (unsigned long) xv->visualid);
3575   abort();
3576 }
3577
3578 static void
3579 clear_preview_window (state *s)
3580 {
3581   GtkWidget *p;
3582   GdkWindow *window;
3583   GtkStyle  *style;
3584
3585   if (!s->toplevel_widget) return;  /* very early */
3586   p = name_to_widget (s, "preview");
3587   window = GET_WINDOW (p);
3588
3589   if (!window) return;
3590
3591   /* Flush the widget background down into the window, in case a subproc
3592      has changed it. */
3593   style = gtk_widget_get_style (p);
3594   gdk_window_set_background (window, &style->bg[GTK_STATE_NORMAL]);
3595   gdk_window_clear (window);
3596
3597   {
3598     int list_elt = selected_list_element (s);
3599     int hack_number = (list_elt >= 0
3600                        ? s->list_elt_to_hack_number[list_elt]
3601                        : -1);
3602     Bool available_p = (hack_number >= 0
3603                         ? s->hacks_available_p [hack_number]
3604                         : True);
3605     Bool nothing_p = (s->total_available < 5);
3606
3607 #ifdef HAVE_GTK2
3608     GtkWidget *notebook = name_to_widget (s, "preview_notebook");
3609     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
3610                            (s->running_preview_error_p
3611                             ? (available_p ? 1 :
3612                                nothing_p ? 3 : 2)
3613                             : 0));
3614 #else /* !HAVE_GTK2 */
3615     if (s->running_preview_error_p)
3616       {
3617         const char * const lines1[] = { N_("No Preview"), N_("Available") };
3618         const char * const lines2[] = { N_("Not"), N_("Installed") };
3619         int nlines = countof(lines1);
3620         int lh = p->style->font->ascent + p->style->font->descent;
3621         int y, i;
3622         gint w, h;
3623
3624         const char * const *lines = (available_p ? lines1 : lines2);
3625
3626         gdk_window_get_size (window, &w, &h);
3627         y = (h - (lh * nlines)) / 2;
3628         y += p->style->font->ascent;
3629         for (i = 0; i < nlines; i++)
3630           {
3631             int sw = gdk_string_width (p->style->font, _(lines[i]));
3632             int x = (w - sw) / 2;
3633             gdk_draw_string (window, p->style->font,
3634                              p->style->fg_gc[GTK_STATE_NORMAL],
3635                              x, y, _(lines[i]));
3636             y += lh;
3637           }
3638       }
3639 #endif /* !HAVE_GTK2 */
3640   }
3641
3642   gdk_flush ();
3643 }
3644
3645
3646 static void
3647 reset_preview_window (state *s)
3648 {
3649   /* On some systems (most recently, MacOS X) OpenGL programs get confused
3650      when you kill one and re-start another on the same window.  So maybe
3651      it's best to just always destroy and recreate the preview window
3652      when changing hacks, instead of always trying to reuse the same one?
3653    */
3654   GtkWidget *pr = name_to_widget (s, "preview");
3655   if (GET_REALIZED (pr))
3656     {
3657       GdkWindow *window = GET_WINDOW (pr);
3658       Window oid = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3659       Window id;
3660       gtk_widget_hide (pr);
3661       gtk_widget_unrealize (pr);
3662       gtk_widget_realize (pr);
3663       gtk_widget_show (pr);
3664       id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3665       if (s->debug_p)
3666         fprintf (stderr, "%s: window id 0x%X -> 0x%X\n", blurb(),
3667                  (unsigned int) oid,
3668                  (unsigned int) id);
3669     }
3670 }
3671
3672
3673 static void
3674 fix_preview_visual (state *s)
3675 {
3676   GtkWidget *widget = name_to_widget (s, "preview");
3677   Visual *xvisual = get_best_gl_visual (s);
3678   GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
3679   GdkVisual *dvisual = gdk_visual_get_system();
3680   GdkColormap *cmap = (visual == dvisual
3681                        ? gdk_colormap_get_system ()
3682                        : gdk_colormap_new (visual, False));
3683
3684   if (s->debug_p)
3685     fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
3686              (visual == dvisual ? "default" : "non-default"),
3687              (xvisual ? (unsigned long) xvisual->visualid : 0L));
3688
3689   if (!GET_REALIZED (widget) ||
3690       gtk_widget_get_visual (widget) != visual)
3691     {
3692       gtk_widget_unrealize (widget);
3693       gtk_widget_set_visual (widget, visual);
3694       gtk_widget_set_colormap (widget, cmap);
3695       gtk_widget_realize (widget);
3696     }
3697
3698   /* Set the Widget colors to be white-on-black. */
3699   {
3700     GdkWindow *window = GET_WINDOW (widget);
3701     GtkStyle *style = gtk_style_copy (gtk_widget_get_style (widget));
3702     GdkColormap *cmap = gtk_widget_get_colormap (widget);
3703     GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
3704     GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
3705     GdkGC *fgc = gdk_gc_new(window);
3706     GdkGC *bgc = gdk_gc_new(window);
3707     if (!gdk_color_white (cmap, fg)) abort();
3708     if (!gdk_color_black (cmap, bg)) abort();
3709     gdk_gc_set_foreground (fgc, fg);
3710     gdk_gc_set_background (fgc, bg);
3711     gdk_gc_set_foreground (bgc, bg);
3712     gdk_gc_set_background (bgc, fg);
3713     style->fg_gc[GTK_STATE_NORMAL] = fgc;
3714     style->bg_gc[GTK_STATE_NORMAL] = fgc;
3715     gtk_widget_set_style (widget, style);
3716
3717     /* For debugging purposes, put a title on the window (so that
3718        it can be easily found in the output of "xwininfo -tree".)
3719      */
3720     gdk_window_set_title (window, "Preview");
3721   }
3722
3723   gtk_widget_show (widget);
3724 }
3725
3726 \f
3727 /* Subprocesses
3728  */
3729
3730 static char *
3731 subproc_pretty_name (state *s)
3732 {
3733   if (s->running_preview_cmd)
3734     {
3735       char *ps = strdup (s->running_preview_cmd);
3736       char *ss = strchr (ps, ' ');
3737       if (ss) *ss = 0;
3738       ss = strrchr (ps, '/');
3739       if (!ss)
3740         ss = ps;
3741       else
3742         {
3743           ss = strdup (ss+1);
3744           free (ps);
3745         }
3746       return ss;
3747     }
3748   else
3749     return strdup ("???");
3750 }
3751
3752
3753 static void
3754 reap_zombies (state *s)
3755 {
3756   int wait_status = 0;
3757   pid_t pid;
3758   while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
3759     {
3760       if (s->debug_p)
3761         {
3762           if (pid == s->running_preview_pid)
3763             {
3764               char *ss = subproc_pretty_name (s);
3765               fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
3766                        (unsigned long) pid, ss);
3767               free (ss);
3768             }
3769           else
3770             fprintf (stderr, "%s: pid %lu died\n", blurb(),
3771                      (unsigned long) pid);
3772         }
3773     }
3774 }
3775
3776
3777 /* Mostly lifted from driver/subprocs.c */
3778 static Visual *
3779 get_best_gl_visual (state *s)
3780 {
3781   Display *dpy = GDK_DISPLAY();
3782   pid_t forked;
3783   int fds [2];
3784   int in, out;
3785   char buf[1024];
3786
3787   char *av[10];
3788   int ac = 0;
3789
3790   av[ac++] = "xscreensaver-gl-helper";
3791   av[ac] = 0;
3792
3793   if (pipe (fds))
3794     {
3795       perror ("error creating pipe:");
3796       return 0;
3797     }
3798
3799   in = fds [0];
3800   out = fds [1];
3801
3802   switch ((int) (forked = fork ()))
3803     {
3804     case -1:
3805       {
3806         sprintf (buf, "%s: couldn't fork", blurb());
3807         perror (buf);
3808         exit (1);
3809       }
3810     case 0:
3811       {
3812         int stdout_fd = 1;
3813
3814         close (in);  /* don't need this one */
3815         close (ConnectionNumber (dpy));         /* close display fd */
3816
3817         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
3818           {
3819             perror ("could not dup() a new stdout:");
3820             return 0;
3821           }
3822
3823         execvp (av[0], av);                     /* shouldn't return. */
3824
3825         if (errno != ENOENT)
3826           {
3827             /* Ignore "no such file or directory" errors, unless verbose.
3828                Issue all other exec errors, though. */
3829             sprintf (buf, "%s: running %s", blurb(), av[0]);
3830             perror (buf);
3831           }
3832
3833         /* Note that one must use _exit() instead of exit() in procs forked
3834            off of Gtk programs -- Gtk installs an atexit handler that has a
3835            copy of the X connection (which we've already closed, for safety.)
3836            If one uses exit() instead of _exit(), then one sometimes gets a
3837            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
3838         */
3839         _exit (1);                              /* exits fork */
3840         break;
3841       }
3842     default:
3843       {
3844         int result = 0;
3845         int wait_status = 0;
3846
3847         FILE *f = fdopen (in, "r");
3848         unsigned int v = 0;
3849         char c;
3850
3851         close (out);  /* don't need this one */
3852
3853         *buf = 0;
3854         if (!fgets (buf, sizeof(buf)-1, f))
3855           *buf = 0;
3856         fclose (f);
3857
3858         /* Wait for the child to die. */
3859         waitpid (-1, &wait_status, 0);
3860
3861         if (1 == sscanf (buf, "0x%x %c", &v, &c))
3862           result = (int) v;
3863
3864         if (result == 0)
3865           {
3866             if (s->debug_p)
3867               fprintf (stderr, "%s: %s did not report a GL visual!\n",
3868                        blurb(), av[0]);
3869             return 0;
3870           }
3871         else
3872           {
3873             Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
3874             if (s->debug_p)
3875               fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
3876                        blurb(), av[0], result);
3877             if (!v) abort();
3878             return v;
3879           }
3880       }
3881     }
3882
3883   abort();
3884 }
3885
3886
3887 static void
3888 kill_preview_subproc (state *s, Bool reset_p)
3889 {
3890   s->running_preview_error_p = False;
3891
3892   reap_zombies (s);
3893   clear_preview_window (s);
3894
3895   if (s->subproc_check_timer_id)
3896     {
3897       gtk_timeout_remove (s->subproc_check_timer_id);
3898       s->subproc_check_timer_id = 0;
3899       s->subproc_check_countdown = 0;
3900     }
3901
3902   if (s->running_preview_pid)
3903     {
3904       int status = kill (s->running_preview_pid, SIGTERM);
3905       char *ss = subproc_pretty_name (s);
3906
3907       if (status < 0)
3908         {
3909           if (errno == ESRCH)
3910             {
3911               if (s->debug_p)
3912                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
3913                          blurb(), (unsigned long) s->running_preview_pid, ss);
3914             }
3915           else
3916             {
3917               char buf [1024];
3918               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
3919                        blurb(), (unsigned long) s->running_preview_pid, ss);
3920               perror (buf);
3921             }
3922         }
3923       else {
3924         int endstatus;
3925         waitpid(s->running_preview_pid, &endstatus, 0);
3926         if (s->debug_p)
3927           fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
3928                    (unsigned long) s->running_preview_pid, ss);
3929       }
3930
3931       free (ss);
3932       s->running_preview_pid = 0;
3933       if (s->running_preview_cmd) free (s->running_preview_cmd);
3934       s->running_preview_cmd = 0;
3935     }
3936
3937   reap_zombies (s);
3938
3939   if (reset_p)
3940     {
3941       reset_preview_window (s);
3942       clear_preview_window (s);
3943     }
3944 }
3945
3946
3947 /* Immediately and unconditionally launches the given process,
3948    after appending the -window-id option; sets running_preview_pid.
3949  */
3950 static void
3951 launch_preview_subproc (state *s)
3952 {
3953   saver_preferences *p = &s->prefs;
3954   Window id;
3955   char *new_cmd = 0;
3956   pid_t forked;
3957   const char *cmd = s->desired_preview_cmd;
3958
3959   GtkWidget *pr = name_to_widget (s, "preview");
3960   GdkWindow *window;
3961
3962   reset_preview_window (s);
3963
3964   window = GET_WINDOW (pr);
3965
3966   s->running_preview_error_p = False;
3967
3968   if (s->preview_suppressed_p)
3969     {
3970       kill_preview_subproc (s, False);
3971       goto DONE;
3972     }
3973
3974   new_cmd = malloc (strlen (cmd) + 40);
3975
3976   id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3977   if (id == 0)
3978     {
3979       /* No window id?  No command to run. */
3980       free (new_cmd);
3981       new_cmd = 0;
3982     }
3983   else
3984     {
3985       strcpy (new_cmd, cmd);
3986       sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
3987                (unsigned int) id);
3988     }
3989
3990   kill_preview_subproc (s, False);
3991   if (! new_cmd)
3992     {
3993       s->running_preview_error_p = True;
3994       clear_preview_window (s);
3995       goto DONE;
3996     }
3997
3998   switch ((int) (forked = fork ()))
3999     {
4000     case -1:
4001       {
4002         char buf[255];
4003         sprintf (buf, "%s: couldn't fork", blurb());
4004         perror (buf);
4005         s->running_preview_error_p = True;
4006         goto DONE;
4007         break;
4008       }
4009     case 0:
4010       {
4011         close (ConnectionNumber (GDK_DISPLAY()));
4012
4013         hack_subproc_environment (id, s->debug_p);
4014
4015         usleep (250000);  /* pause for 1/4th second before launching, to give
4016                              the previous program time to die and flush its X
4017                              buffer, so we don't get leftover turds on the
4018                              window. */
4019
4020         exec_command (p->shell, new_cmd, p->nice_inferior);
4021         /* Don't bother printing an error message when we are unable to
4022            exec subprocesses; we handle that by polling the pid later.
4023
4024            Note that one must use _exit() instead of exit() in procs forked
4025            off of Gtk programs -- Gtk installs an atexit handler that has a
4026            copy of the X connection (which we've already closed, for safety.)
4027            If one uses exit() instead of _exit(), then one sometimes gets a
4028            spurious "Gdk-ERROR: Fatal IO error on X server" error message.
4029         */
4030         _exit (1);  /* exits child fork */
4031         break;
4032
4033       default:
4034
4035         if (s->running_preview_cmd) free (s->running_preview_cmd);
4036         s->running_preview_cmd = strdup (s->desired_preview_cmd);
4037         s->running_preview_pid = forked;
4038
4039         if (s->debug_p)
4040           {
4041             char *ss = subproc_pretty_name (s);
4042             fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
4043                      (unsigned long) forked, ss);
4044             free (ss);
4045           }
4046         break;
4047       }
4048     }
4049
4050   schedule_preview_check (s);
4051
4052  DONE:
4053   if (new_cmd) free (new_cmd);
4054   new_cmd = 0;
4055 }
4056
4057
4058 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
4059  */
4060 static void
4061 hack_environment (state *s)
4062 {
4063   static const char *def_path =
4064 # ifdef DEFAULT_PATH_PREFIX
4065     DEFAULT_PATH_PREFIX;
4066 # else
4067     "";
4068 # endif
4069
4070   Display *dpy = GDK_DISPLAY();
4071   const char *odpy = DisplayString (dpy);
4072   char *ndpy = (char *) malloc(strlen(odpy) + 20);
4073   strcpy (ndpy, "DISPLAY=");
4074   strcat (ndpy, odpy);
4075   if (putenv (ndpy))
4076     abort ();
4077
4078   if (s->debug_p)
4079     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
4080
4081   /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
4082      2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
4083      So we must leak it (and/or the previous setting).  Yay.
4084    */
4085
4086   if (def_path && *def_path)
4087     {
4088       const char *opath = getenv("PATH");
4089       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
4090       strcpy (npath, "PATH=");
4091       strcat (npath, def_path);
4092       strcat (npath, ":");
4093       strcat (npath, opath);
4094
4095       if (putenv (npath))
4096         abort ();
4097       /* do not free(npath) -- see above */
4098
4099       if (s->debug_p)
4100         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
4101     }
4102 }
4103
4104
4105 static void
4106 hack_subproc_environment (Window preview_window_id, Bool debug_p)
4107 {
4108   /* Store a window ID in $XSCREENSAVER_WINDOW -- this isn't strictly
4109      necessary yet, but it will make programs work if we had invoked
4110      them with "-root" and not with "-window-id" -- which, of course,
4111      doesn't happen.
4112    */
4113   char *nssw = (char *) malloc (40);
4114   sprintf (nssw, "XSCREENSAVER_WINDOW=0x%X", (unsigned int) preview_window_id);
4115
4116   /* Allegedly, BSD 4.3 didn't have putenv(), but nobody runs such systems
4117      any more, right?  It's not Posix, but everyone seems to have it. */
4118   if (putenv (nssw))
4119     abort ();
4120
4121   if (debug_p)
4122     fprintf (stderr, "%s: %s\n", blurb(), nssw);
4123
4124   /* do not free(nssw) -- see above */
4125 }
4126
4127
4128 /* Called from a timer:
4129    Launches the currently-chosen subprocess, if it's not already running.
4130    If there's a different process running, kills it.
4131  */
4132 static int
4133 update_subproc_timer (gpointer data)
4134 {
4135   state *s = (state *) data;
4136   if (! s->desired_preview_cmd)
4137     kill_preview_subproc (s, True);
4138   else if (!s->running_preview_cmd ||
4139            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
4140     launch_preview_subproc (s);
4141
4142   s->subproc_timer_id = 0;
4143   return FALSE;  /* do not re-execute timer */
4144 }
4145
4146 static int
4147 settings_timer (gpointer data)
4148 {
4149   settings_cb (0, 0);
4150   return FALSE;
4151 }
4152
4153
4154 /* Call this when you think you might want a preview process running.
4155    It will set a timer that will actually launch that program a second
4156    from now, if you haven't changed your mind (to avoid double-click
4157    spazzing, etc.)  `cmd' may be null meaning "no process".
4158  */
4159 static void
4160 schedule_preview (state *s, const char *cmd)
4161 {
4162   int delay = 1000 * 0.5;   /* 1/2 second hysteresis */
4163
4164   if (s->debug_p)
4165     {
4166       if (cmd)
4167         fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
4168       else
4169         fprintf (stderr, "%s: scheduling preview death\n", blurb());
4170     }
4171
4172   if (s->desired_preview_cmd) free (s->desired_preview_cmd);
4173   s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
4174
4175   if (s->subproc_timer_id)
4176     gtk_timeout_remove (s->subproc_timer_id);
4177   s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
4178 }
4179
4180
4181 /* Called from a timer:
4182    Checks to see if the subproc that should be running, actually is.
4183  */
4184 static int
4185 check_subproc_timer (gpointer data)
4186 {
4187   state *s = (state *) data;
4188   Bool again_p = True;
4189
4190   if (s->running_preview_error_p ||   /* already dead */
4191       s->running_preview_pid <= 0)
4192     {
4193       again_p = False;
4194     }
4195   else
4196     {
4197       int status;
4198       reap_zombies (s);
4199       status = kill (s->running_preview_pid, 0);
4200       if (status < 0 && errno == ESRCH)
4201         s->running_preview_error_p = True;
4202
4203       if (s->debug_p)
4204         {
4205           char *ss = subproc_pretty_name (s);
4206           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
4207                    (unsigned long) s->running_preview_pid, ss,
4208                    (s->running_preview_error_p ? "dead" : "alive"));
4209           free (ss);
4210         }
4211
4212       if (s->running_preview_error_p)
4213         {
4214           clear_preview_window (s);
4215           again_p = False;
4216         }
4217     }
4218
4219   /* Otherwise, it's currently alive.  We might be checking again, or we
4220      might be satisfied. */
4221
4222   if (--s->subproc_check_countdown <= 0)
4223     again_p = False;
4224
4225   if (again_p)
4226     return TRUE;     /* re-execute timer */
4227   else
4228     {
4229       s->subproc_check_timer_id = 0;
4230       s->subproc_check_countdown = 0;
4231       return FALSE;  /* do not re-execute timer */
4232     }
4233 }
4234
4235
4236 /* Call this just after launching a subprocess.
4237    This sets a timer that will, five times a second for two seconds,
4238    check whether the program is still running.  The assumption here
4239    is that if the process didn't stay up for more than a couple of
4240    seconds, then either the program doesn't exist, or it doesn't
4241    take a -window-id argument.
4242  */
4243 static void
4244 schedule_preview_check (state *s)
4245 {
4246   int seconds = 2;
4247   int ticks = 5;
4248
4249   if (s->debug_p)
4250     fprintf (stderr, "%s: scheduling check\n", blurb());
4251
4252   if (s->subproc_check_timer_id)
4253     gtk_timeout_remove (s->subproc_check_timer_id);
4254   s->subproc_check_timer_id =
4255     gtk_timeout_add (1000 / ticks,
4256                      check_subproc_timer, (gpointer) s);
4257   s->subproc_check_countdown = ticks * seconds;
4258 }
4259
4260
4261 static Bool
4262 screen_blanked_p (void)
4263 {
4264   Atom type;
4265   int format;
4266   unsigned long nitems, bytesafter;
4267   unsigned char *dataP = 0;
4268   Display *dpy = GDK_DISPLAY();
4269   Bool blanked_p = False;
4270
4271   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
4272                           XA_SCREENSAVER_STATUS,
4273                           0, 3, False, XA_INTEGER,
4274                           &type, &format, &nitems, &bytesafter,
4275                           &dataP)
4276       == Success
4277       && type == XA_INTEGER
4278       && nitems >= 3
4279       && dataP)
4280     {
4281       Atom *data = (Atom *) dataP;
4282       blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
4283     }
4284
4285   if (dataP) XFree (dataP);
4286
4287   return blanked_p;
4288 }
4289
4290 /* Wake up every now and then and see if the screen is blanked.
4291    If it is, kill off the small-window demo -- no point in wasting
4292    cycles by running two screensavers at once...
4293  */
4294 static int
4295 check_blanked_timer (gpointer data)
4296 {
4297   state *s = (state *) data;
4298   Bool blanked_p = screen_blanked_p ();
4299   if (blanked_p && s->running_preview_pid)
4300     {
4301       if (s->debug_p)
4302         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
4303       kill_preview_subproc (s, True);
4304     }
4305
4306   return True;  /* re-execute timer */
4307 }
4308
4309
4310 /* How many screens are there (including Xinerama.)
4311  */
4312 static int
4313 screen_count (Display *dpy)
4314 {
4315   int nscreens = ScreenCount(dpy);
4316 # ifdef HAVE_XINERAMA
4317   if (nscreens <= 1)
4318     {
4319       int event_number, error_number;
4320       if (XineramaQueryExtension (dpy, &event_number, &error_number) &&
4321           XineramaIsActive (dpy))
4322         {
4323           XineramaScreenInfo *xsi = XineramaQueryScreens (dpy, &nscreens);
4324           if (xsi) XFree (xsi);
4325         }
4326     }
4327 # endif /* HAVE_XINERAMA */
4328
4329   return nscreens;
4330 }
4331
4332 \f
4333 /* Setting window manager icon
4334  */
4335
4336 static void
4337 init_icon (GdkWindow *window)
4338 {
4339   GdkBitmap *mask = 0;
4340   GdkPixmap *pixmap =
4341     gdk_pixmap_create_from_xpm_d (window, &mask, 0,
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 */