http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.05.tar.gz
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2002 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 ENABLE_NLS
28 # include <locale.h>
29 #endif /* ENABLE_NLS */
30
31 #ifndef VMS
32 # include <pwd.h>               /* for getpwuid() */
33 #else /* VMS */
34 # include "vms-pwd.h"
35 #endif /* VMS */
36
37 #ifdef HAVE_UNAME
38 # include <sys/utsname.h>       /* for uname() */
39 #endif /* HAVE_UNAME */
40
41 #include <stdio.h>
42 #include <time.h>
43 #include <sys/stat.h>
44 #include <sys/time.h>
45
46
47 #include <signal.h>
48 #include <errno.h>
49 #ifdef HAVE_SYS_WAIT_H
50 # include <sys/wait.h>          /* for waitpid() and associated macros */
51 #endif
52
53
54 #include <X11/Xproto.h>         /* for CARD32 */
55 #include <X11/Xatom.h>          /* for XA_INTEGER */
56 #include <X11/Intrinsic.h>
57 #include <X11/StringDefs.h>
58
59 /* We don't actually use any widget internals, but these are included
60    so that gdb will have debug info for the widgets... */
61 #include <X11/IntrinsicP.h>
62 #include <X11/ShellP.h>
63
64 #ifdef HAVE_XMU
65 # ifndef VMS
66 #  include <X11/Xmu/Error.h>
67 # else /* VMS */
68 #  include <Xmu/Error.h>
69 # endif
70 #else
71 # include "xmu.h"
72 #endif
73
74 #include <gtk/gtk.h>
75
76 #ifdef HAVE_CRAPPLET
77 # include <gnome.h>
78 # include <capplet-widget.h>
79 #endif
80
81 #include <gdk/gdkx.h>
82
83 #ifdef HAVE_GTK2
84 #include <glade/glade-xml.h>
85 #endif /* HAVE_GTK2 */
86
87 #if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
88 # define GLADE_DIR DEFAULT_ICONDIR
89 #endif
90 #if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR)
91 # define DEFAULT_ICONDIR GLADE_DIR
92 #endif
93
94
95 #include "version.h"
96 #include "prefs.h"
97 #include "resources.h"          /* for parse_time() */
98 #include "visual.h"             /* for has_writable_cells() */
99 #include "remote.h"             /* for xscreensaver_command() */
100 #include "usleep.h"
101
102 #include "logo-50.xpm"
103 #include "logo-180.xpm"
104
105 #include "demo-Gtk-widgets.h"
106 #include "demo-Gtk-support.h"
107 #include "demo-Gtk-conf.h"
108
109 #include <stdio.h>
110 #include <string.h>
111 #include <ctype.h>
112
113 #ifdef HAVE_GTK2
114 enum {
115   COL_ENABLED,
116   COL_NAME,
117   COL_LAST
118 };
119 #endif /* HAVE_GTK2 */
120
121 /* from exec.c */
122 extern void exec_command (const char *shell, const char *command, int nice);
123
124 #undef countof
125 #define countof(x) (sizeof((x))/sizeof((*x)))
126
127
128 char *progname = 0;
129 char *progclass = "XScreenSaver";
130 XrmDatabase db;
131
132 /* The order of the items in the mode menu. */
133 static int mode_menu_order[] = {
134   DONT_BLANK, BLANK_ONLY, ONE_HACK, RANDOM_HACKS };
135
136
137 typedef struct {
138
139   char *short_version;          /* version number of this xscreensaver build */
140
141   GtkWidget *toplevel_widget;   /* the main window */
142   GtkWidget *base_widget;       /* root of our hierarchy (for name lookups) */
143   GtkWidget *popup_widget;      /* the "Settings" dialog */
144   conf_data *cdata;             /* private data for per-hack configuration */
145
146 #ifdef HAVE_GTK2
147   GladeXML *glade_ui;           /* Glade UI file */
148 #endif /* HAVE_GTK2 */
149
150   Bool debug_p;                 /* whether to print diagnostics */
151   Bool initializing_p;          /* flag for breaking recursion loops */
152   Bool saving_p;                /* flag for breaking recursion loops */
153
154   char *desired_preview_cmd;    /* subprocess we intend to run */
155   char *running_preview_cmd;    /* subprocess we are currently running */
156   pid_t running_preview_pid;    /* pid of forked subproc (might be dead) */
157   Bool running_preview_error_p; /* whether the pid died abnormally */
158
159   Bool preview_suppressed_p;    /* flag meaning "don't launch subproc" */
160   int subproc_timer_id;         /* timer to delay subproc launch */
161   int subproc_check_timer_id;   /* timer to check whether it started up */
162   int subproc_check_countdown;  /* how many more checks left */
163
164   int *list_elt_to_hack_number; /* table for sorting the hack list */
165   int *hack_number_to_list_elt; /* the inverse table */
166
167   int _selected_list_element;   /* don't use this: call
168                                    selected_list_element() instead */
169
170   saver_preferences prefs;
171
172 } state;
173
174
175 /* Total fucking evilness due to the fact that it's rocket science to get
176    a closure object of our own down into the various widget callbacks. */
177 static state *global_state_kludge;
178
179 Atom XA_VROOT;
180 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
181 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
182 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
183
184
185 static void populate_demo_window (state *, int list_elt);
186 static void populate_prefs_page (state *);
187 static void populate_popup_window (state *);
188
189 static Bool flush_dialog_changes_and_save (state *);
190 static Bool flush_popup_changes_and_save (state *);
191
192 static int maybe_reload_init_file (state *);
193 static void await_xscreensaver (state *);
194
195 static void schedule_preview (state *, const char *cmd);
196 static void kill_preview_subproc (state *);
197 static void schedule_preview_check (state *);
198
199
200 \f
201 /* Some random utility functions
202  */
203
204 const char *
205 blurb (void)
206 {
207   time_t now = time ((time_t *) 0);
208   char *ct = (char *) ctime (&now);
209   static char buf[255];
210   int n = strlen(progname);
211   if (n > 100) n = 99;
212   strncpy(buf, progname, n);
213   buf[n++] = ':';
214   buf[n++] = ' ';
215   strncpy(buf+n, ct+11, 8);
216   strcpy(buf+n+9, ": ");
217   return buf;
218 }
219
220
221 static GtkWidget *
222 name_to_widget (state *s, const char *name)
223 {
224   GtkWidget *w;
225   if (!s) abort();
226   if (!name) abort();
227   if (!*name) abort();
228
229 #ifdef HAVE_GTK2
230   if (!s->glade_ui)
231     {
232       s->glade_ui = glade_xml_new (GLADE_DIR "/xscreensaver-demo.glade2",
233                                    NULL, NULL);
234       if (!s->glade_ui)
235         {
236           fprintf (stderr,
237                    "%s: could not load glade file"
238                    " \"%s/xscreensaver-demo.glade2\"\n",
239                    blurb(), GLADE_DIR);
240           exit (-1);
241         }
242       glade_xml_signal_autoconnect (s->glade_ui);
243     }
244
245   w = glade_xml_get_widget (s->glade_ui, name);
246
247 #else /* !HAVE_GTK2 */
248
249   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->base_widget),
250                                          name);
251   if (w) return w;
252   w = (GtkWidget *) gtk_object_get_data (GTK_OBJECT (s->popup_widget),
253                                          name);
254 #endif /* HAVE_GTK2 */
255   if (w) return w;
256
257   fprintf (stderr, "%s: no widget \"%s\"\n", blurb(), name);
258   abort();
259 }
260
261
262 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
263    Takes a scroller, viewport, or list as an argument.
264  */
265 static void
266 ensure_selected_item_visible (GtkWidget *widget)
267 {
268 #ifdef HAVE_GTK2
269   GtkTreePath *path;
270   GtkTreeSelection *selection;
271   GtkTreeIter iter;
272   GtkTreeModel *model;
273   
274   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
275   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
276     return;
277
278   path = gtk_tree_model_get_path (model, &iter);
279   
280   gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (widget),
281                                 path, NULL, FALSE, 0.0, 0.0);
282 #else /* !HAVE_GTK2 */
283
284   GtkScrolledWindow *scroller = 0;
285   GtkViewport *vp = 0;
286   GtkList *list_widget = 0;
287   GList *slist;
288   GList *kids;
289   int nkids = 0;
290   GtkWidget *selected = 0;
291   int list_elt = -1;
292   GtkAdjustment *adj;
293   gint parent_h, child_y, child_h, children_h, ignore;
294   double ratio_t, ratio_b;
295
296   if (GTK_IS_SCROLLED_WINDOW (widget))
297     {
298       scroller = GTK_SCROLLED_WINDOW (widget);
299       vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
300       list_widget = GTK_LIST (GTK_BIN(vp)->child);
301     }
302   else if (GTK_IS_VIEWPORT (widget))
303     {
304       vp = GTK_VIEWPORT (widget);
305       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
306       list_widget = GTK_LIST (GTK_BIN(vp)->child);
307     }
308   else if (GTK_IS_LIST (widget))
309     {
310       list_widget = GTK_LIST (widget);
311       vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
312       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
313     }
314   else
315     abort();
316
317   slist = list_widget->selection;
318   selected = (slist ? GTK_WIDGET (slist->data) : 0);
319   if (!selected)
320     return;
321
322   list_elt = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
323
324   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
325        kids; kids = kids->next)
326     nkids++;
327
328   adj = gtk_scrolled_window_get_vadjustment (scroller);
329
330   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
331                            &ignore, &ignore, &ignore, &parent_h, &ignore);
332   gdk_window_get_geometry (GTK_WIDGET(selected)->window,
333                            &ignore, &child_y, &ignore, &child_h, &ignore);
334   children_h = nkids * child_h;
335
336   ratio_t = ((double) child_y) / ((double) children_h);
337   ratio_b = ((double) child_y + child_h) / ((double) children_h);
338
339   if (adj->upper == 0.0)  /* no items in list */
340     return;
341
342   if (ratio_t < (adj->value / adj->upper) ||
343       ratio_b > ((adj->value + adj->page_size) / adj->upper))
344     {
345       double target;
346       int slop = parent_h * 0.75; /* how much to overshoot by */
347
348       if (ratio_t < (adj->value / adj->upper))
349         {
350           double ratio_w = ((double) parent_h) / ((double) children_h);
351           double ratio_l = (ratio_b - ratio_t);
352           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
353           target += slop;
354         }
355       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
356         {
357           target = ratio_t * adj->upper;
358           target -= slop;
359         }
360
361       if (target > adj->upper - adj->page_size)
362         target = adj->upper - adj->page_size;
363       if (target < 0)
364         target = 0;
365
366       gtk_adjustment_set_value (adj, target);
367     }
368 #endif /* !HAVE_GTK2 */
369 }
370
371 static void
372 warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
373 {
374   GtkWidget *shell = GTK_WIDGET (user_data);
375   while (shell->parent)
376     shell = shell->parent;
377   gtk_widget_destroy (GTK_WIDGET (shell));
378 }
379
380
381 void restart_menu_cb (GtkWidget *widget, gpointer user_data);
382
383 static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
384 {
385   restart_menu_cb (widget, user_data);
386   warning_dialog_dismiss_cb (widget, user_data);
387 }
388
389 static void
390 warning_dialog (GtkWidget *parent, const char *message,
391                 Boolean restart_button_p, int center)
392 {
393   char *msg = strdup (message);
394   char *head;
395
396   GtkWidget *dialog = gtk_dialog_new ();
397   GtkWidget *label = 0;
398   GtkWidget *ok = 0;
399   GtkWidget *cancel = 0;
400   int i = 0;
401
402   while (parent && !parent->window)
403     parent = parent->parent;
404
405   if (!GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */
406     {
407       fprintf (stderr, "%s: too early for dialog?\n", progname);
408       return;
409     }
410
411   head = msg;
412   while (head)
413     {
414       char name[20];
415       char *s = strchr (head, '\n');
416       if (s) *s = 0;
417
418       sprintf (name, "label%d", i++);
419
420       {
421         label = gtk_label_new (head);
422 #ifdef HAVE_GTK2
423         gtk_label_set_selectable (GTK_LABEL (label), TRUE);
424 #endif /* HAVE_GTK2 */
425
426 #ifndef HAVE_GTK2
427         if (i == 1)
428           {
429             GTK_WIDGET (label)->style =
430               gtk_style_copy (GTK_WIDGET (label)->style);
431             GTK_WIDGET (label)->style->font =
432               gdk_font_load (get_string_resource("warning_dialog.headingFont",
433                                                  "Dialog.Font"));
434             gtk_widget_set_style (GTK_WIDGET (label),
435                                   GTK_WIDGET (label)->style);
436           }
437 #endif /* !HAVE_GTK2 */
438         if (center <= 0)
439           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
440         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
441                             label, TRUE, TRUE, 0);
442         gtk_widget_show (label);
443       }
444
445       if (s)
446         head = s+1;
447       else
448         head = 0;
449
450       center--;
451     }
452
453   label = gtk_label_new ("");
454   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
455                       label, TRUE, TRUE, 0);
456   gtk_widget_show (label);
457
458   label = gtk_hbutton_box_new ();
459   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
460                       label, TRUE, TRUE, 0);
461
462 #ifdef HAVE_GTK2
463   if (restart_button_p)
464     {
465       cancel = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
466       gtk_container_add (GTK_CONTAINER (label), cancel);
467     }
468
469   ok = gtk_button_new_from_stock (GTK_STOCK_OK);
470   gtk_container_add (GTK_CONTAINER (label), ok);
471
472 #else /* !HAVE_GTK2 */
473
474   ok = gtk_button_new_with_label ("OK");
475   gtk_container_add (GTK_CONTAINER (label), ok);
476
477   if (restart_button_p)
478     {
479       cancel = gtk_button_new_with_label ("Cancel");
480       gtk_container_add (GTK_CONTAINER (label), cancel);
481     }
482
483 #endif /* !HAVE_GTK2 */
484
485   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
486   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
487   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
488   GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
489   gtk_widget_show (ok);
490   gtk_widget_grab_focus (ok);
491
492   if (cancel)
493     {
494       GTK_WIDGET_SET_FLAGS (cancel, GTK_CAN_DEFAULT); 
495       gtk_widget_show (cancel);
496     }
497   gtk_widget_show (label);
498   gtk_widget_show (dialog);
499
500   if (restart_button_p)
501     {
502       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
503                                  GTK_SIGNAL_FUNC (warning_dialog_restart_cb),
504                                  (gpointer) dialog);
505       gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
506                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
507                                  (gpointer) dialog);
508     }
509   else
510     {
511       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
512                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
513                                  (gpointer) dialog);
514     }
515
516   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
517                                 GTK_WIDGET (parent)->window);
518
519 #ifdef HAVE_GTK2
520   gtk_window_present (GTK_WINDOW (dialog));
521 #else  /* !HAVE_GTK2 */
522   gdk_window_show (GTK_WIDGET (dialog)->window);
523   gdk_window_raise (GTK_WIDGET (dialog)->window);
524 #endif /* !HAVE_GTK2 */
525
526   free (msg);
527 }
528
529
530 static void
531 run_cmd (state *s, Atom command, int arg)
532 {
533   char *err = 0;
534   int status;
535
536   flush_dialog_changes_and_save (s);
537   status = xscreensaver_command (GDK_DISPLAY(), command, arg, False, &err);
538   if (status < 0)
539     {
540       char buf [255];
541       if (err)
542         sprintf (buf, "Error:\n\n%s", err);
543       else
544         strcpy (buf, "Unknown error!");
545       warning_dialog (s->toplevel_widget, buf, False, 100);
546     }
547   if (err) free (err);
548 }
549
550
551 static void
552 run_hack (state *s, int list_elt, Bool report_errors_p)
553 {
554   int hack_number;
555   if (list_elt < 0) return;
556   hack_number = s->list_elt_to_hack_number[list_elt];
557
558   flush_dialog_changes_and_save (s);
559   schedule_preview (s, 0);
560   if (report_errors_p)
561     run_cmd (s, XA_DEMO, hack_number + 1);
562   else
563     {
564       char *s = 0;
565       xscreensaver_command (GDK_DISPLAY(), XA_DEMO, hack_number + 1,
566                             False, &s);
567       if (s) free (s);
568     }
569 }
570
571
572 \f
573 /* Button callbacks
574  */
575
576 void
577 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
578 {
579   state *s = global_state_kludge;  /* I hate C so much... */
580   flush_dialog_changes_and_save (s);
581   kill_preview_subproc (s);
582   gtk_main_quit ();
583 }
584
585 static void
586 wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
587 {
588   state *s = (state *) data;
589   flush_dialog_changes_and_save (s);
590   gtk_main_quit ();
591 }
592
593
594 void
595 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
596 {
597   char msg [2048];
598   char *vers = strdup (screensaver_id + 4);
599   char *s;
600   char copy[1024];
601   char *desc = _("For updates, check http://www.jwz.org/xscreensaver/");
602
603   s = strchr (vers, ',');
604   *s = 0;
605   s += 2;
606
607 #ifdef HAVE_GTK2
608   sprintf(copy, _("Copyright \xC2\xA9 1991-2002 %s"), s);
609 #else  /* !HAVE_GTK2 */
610   sprintf(copy, _("Copyright \251 1991-2002 %s"), s);
611 #endif /* !HAVE_GTK2 */
612
613   sprintf (msg, "%s\n\n%s", copy, desc);
614
615   /* I can't make gnome_about_new() work here -- it starts dying in
616      gdk_imlib_get_visual() under gnome_about_new().  If this worked,
617      then this might be the thing to do:
618
619      #ifdef HAVE_CRAPPLET
620      {
621        const gchar *auth[] = { 0 };
622        GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
623                                            "xscreensaver.xpm");
624        gtk_widget_show (about);
625      }
626      #else / * GTK but not GNOME * /
627       ...
628    */
629   {
630     GdkColormap *colormap;
631     GdkPixmap *gdkpixmap;
632     GdkBitmap *mask;
633
634     GtkWidget *dialog = gtk_dialog_new ();
635     GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
636     GtkWidget *parent = GTK_WIDGET (menuitem);
637     while (parent->parent)
638       parent = parent->parent;
639
640     hbox = gtk_hbox_new (FALSE, 20);
641     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
642                         hbox, TRUE, TRUE, 0);
643
644     colormap = gtk_widget_get_colormap (parent);
645     gdkpixmap =
646       gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
647                                              (gchar **) logo_180_xpm);
648     icon = gtk_pixmap_new (gdkpixmap, mask);
649     gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
650
651     gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
652
653     vbox = gtk_vbox_new (FALSE, 0);
654     gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
655
656     label1 = gtk_label_new (vers);
657     gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
658     gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
659     gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
660
661 #ifndef HAVE_GTK2
662     GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
663     GTK_WIDGET (label1)->style->font =
664       gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
665     gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
666 #endif /* HAVE_GTK2 */
667
668     label2 = gtk_label_new (msg);
669     gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
670     gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
671     gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
672
673 #ifndef HAVE_GTK2
674     GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
675     GTK_WIDGET (label2)->style->font =
676       gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
677     gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
678 #endif /* HAVE_GTK2 */
679
680     hb = gtk_hbutton_box_new ();
681
682     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
683                         hb, TRUE, TRUE, 0);
684
685 #ifdef HAVE_GTK2
686     ok = gtk_button_new_from_stock (GTK_STOCK_OK);
687 #else /* !HAVE_GTK2 */
688     ok = gtk_button_new_with_label (_("OK"));
689 #endif /* !HAVE_GTK2 */
690     gtk_container_add (GTK_CONTAINER (hb), ok);
691
692     gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
693     gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
694     gtk_window_set_title (GTK_WINDOW (dialog), progclass);
695
696     gtk_widget_show (hbox);
697     gtk_widget_show (icon);
698     gtk_widget_show (vbox);
699     gtk_widget_show (label1);
700     gtk_widget_show (label2);
701     gtk_widget_show (hb);
702     gtk_widget_show (ok);
703     gtk_widget_show (dialog);
704
705     gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
706                                GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
707                                (gpointer) dialog);
708     gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
709                                   GTK_WIDGET (parent)->window);
710     gdk_window_show (GTK_WIDGET (dialog)->window);
711     gdk_window_raise (GTK_WIDGET (dialog)->window);
712   }
713 }
714
715
716 void
717 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
718 {
719   state *s = global_state_kludge;  /* I hate C so much... */
720   saver_preferences *p = &s->prefs;
721   char *help_command;
722
723   if (!p->help_url || !*p->help_url)
724     {
725       warning_dialog (s->toplevel_widget,
726                       _("Error:\n\n"
727                         "No Help URL has been specified.\n"), False, 100);
728       return;
729     }
730
731   help_command = (char *) malloc (strlen (p->load_url_command) +
732                                   (strlen (p->help_url) * 2) + 20);
733   strcpy (help_command, "( ");
734   sprintf (help_command + strlen(help_command),
735            p->load_url_command, p->help_url, p->help_url);
736   strcat (help_command, " ) &");
737   system (help_command);
738   free (help_command);
739 }
740
741
742 void
743 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
744 {
745   state *s = global_state_kludge;  /* I hate C so much... */
746   run_cmd (s, XA_ACTIVATE, 0);
747 }
748
749
750 void
751 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
752 {
753   state *s = global_state_kludge;  /* I hate C so much... */
754   run_cmd (s, XA_LOCK, 0);
755 }
756
757
758 void
759 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
760 {
761   state *s = global_state_kludge;  /* I hate C so much... */
762   run_cmd (s, XA_EXIT, 0);
763 }
764
765
766 void
767 restart_menu_cb (GtkWidget *widget, gpointer user_data)
768 {
769   state *s = global_state_kludge;  /* I hate C so much... */
770   flush_dialog_changes_and_save (s);
771   xscreensaver_command (GDK_DISPLAY(), XA_EXIT, 0, False, NULL);
772   sleep (1);
773   system ("xscreensaver -nosplash &");
774
775   await_xscreensaver (s);
776 }
777
778 static void
779 await_xscreensaver (state *s)
780 {
781   int countdown = 5;
782
783   Display *dpy = GDK_DISPLAY();
784   /*  GtkWidget *dialog = 0;*/
785   char *rversion = 0;
786
787   while (!rversion && (--countdown > 0))
788     {
789       /* Check for the version of the running xscreensaver... */
790       server_xscreensaver_version (dpy, &rversion, 0, 0);
791
792       /* If it's not there yet, wait a second... */
793       if (!rversion)
794         sleep (1);
795     }
796
797 /*  if (dialog) gtk_widget_destroy (dialog);*/
798
799   if (rversion)
800     {
801       /* Got it. */
802       free (rversion);
803     }
804   else
805     {
806       /* Timed out, no screensaver running. */
807
808       char buf [1024];
809       Bool root_p = (geteuid () == 0);
810       
811       strcpy (buf, 
812               _("Error:\n\n"
813                 "The xscreensaver daemon did not start up properly.\n"
814                 "\n"));
815
816       if (root_p)
817         strcat (buf,
818           _("You are running as root.  This usually means that xscreensaver\n"
819             "was unable to contact your X server because access control is\n"
820             "turned on.  Try running this command:\n"
821             "\n"
822             "                        xhost +localhost\n"
823             "\n"
824             "and then selecting `File / Restart Daemon'.\n"
825             "\n"
826             "Note that turning off access control will allow anyone logged\n"
827             "on to this machine to access your screen, which might be\n"
828             "considered a security problem.  Please read the xscreensaver\n"
829             "manual and FAQ for more information.\n"
830             "\n"
831             "You shouldn't run X as root. Instead, you should log in as a\n"
832             "normal user, and `su' as necessary."));
833       else
834         strcat (buf, _("Please check your $PATH and permissions."));
835
836       warning_dialog (s->toplevel_widget, buf, False, 1);
837     }
838 }
839
840
841 static int
842 selected_list_element (state *s)
843 {
844   return s->_selected_list_element;
845 }
846
847
848 static int
849 demo_write_init_file (state *s, saver_preferences *p)
850 {
851
852 #if 0
853   /* #### try to figure out why shit keeps getting reordered... */
854   if (strcmp (s->prefs.screenhacks[0]->name, "DNA Lounge Slideshow"))
855     abort();
856 #endif
857
858   if (!write_init_file (p, s->short_version, False))
859     {
860       if (s->debug_p)
861         fprintf (stderr, "%s: wrote %s\n", blurb(), init_file_name());
862       return 0;
863     }
864   else
865     {
866       const char *f = init_file_name();
867       if (!f || !*f)
868         warning_dialog (s->toplevel_widget,
869                         _("Error:\n\nCouldn't determine init file name!\n"),
870                         False, 100);
871       else
872         {
873           char *b = (char *) malloc (strlen(f) + 1024);
874           sprintf (b, _("Error:\n\nCouldn't write %s\n"), f);
875           warning_dialog (s->toplevel_widget, b, False, 100);
876           free (b);
877         }
878       return -1;
879     }
880 }
881
882
883 void
884 run_this_cb (GtkButton *button, gpointer user_data)
885 {
886   state *s = global_state_kludge;  /* I hate C so much... */
887   int list_elt = selected_list_element (s);
888   if (list_elt < 0) return;
889   if (!flush_dialog_changes_and_save (s))
890     run_hack (s, list_elt, True);
891 }
892
893
894 void
895 manual_cb (GtkButton *button, gpointer user_data)
896 {
897   state *s = global_state_kludge;  /* I hate C so much... */
898   saver_preferences *p = &s->prefs;
899   GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
900   int list_elt = selected_list_element (s);
901   int hack_number;
902   char *name, *name2, *cmd, *str;
903   if (list_elt < 0) return;
904   hack_number = s->list_elt_to_hack_number[list_elt];
905
906   flush_dialog_changes_and_save (s);
907   ensure_selected_item_visible (GTK_WIDGET (list_widget));
908
909   name = strdup (p->screenhacks[hack_number]->command);
910   name2 = name;
911   while (isspace (*name2)) name2++;
912   str = name2;
913   while (*str && !isspace (*str)) str++;
914   *str = 0;
915   str = strrchr (name2, '/');
916   if (str) name = str+1;
917
918   cmd = get_string_resource ("manualCommand", "ManualCommand");
919   if (cmd)
920     {
921       char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100);
922       strcpy (cmd2, "( ");
923       sprintf (cmd2 + strlen (cmd2),
924                cmd,
925                name2, name2, name2, name2);
926       strcat (cmd2, " ) &");
927       system (cmd2);
928       free (cmd2);
929     }
930   else
931     {
932       warning_dialog (GTK_WIDGET (button),
933                       _("Error:\n\nno `manualCommand' resource set."),
934                       False, 100);
935     }
936
937   free (name);
938 }
939
940
941 static void
942 force_list_select_item (state *s, GtkWidget *list, int list_elt, Bool scroll_p)
943 {
944   GtkWidget *parent = name_to_widget (s, "scroller");
945   Bool was = GTK_WIDGET_IS_SENSITIVE (parent);
946 #ifdef HAVE_GTK2
947   GtkTreeIter iter;
948   GtkTreeModel *model;
949   GtkTreeSelection *selection;
950 #endif /* HAVE_GTK2 */
951
952   if (!was) gtk_widget_set_sensitive (parent, True);
953 #ifdef HAVE_GTK2
954   model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
955   g_assert (model);
956   gtk_tree_model_iter_nth_child (model, &iter, NULL, list_elt);
957   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
958   gtk_tree_selection_select_iter (selection, &iter);
959 #else  /* !HAVE_GTK2 */
960   gtk_list_select_item (GTK_LIST (list), list_elt);
961 #endif /* !HAVE_GTK2 */
962   if (scroll_p) ensure_selected_item_visible (GTK_WIDGET (list));
963   if (!was) gtk_widget_set_sensitive (parent, False);
964 }
965
966
967 void
968 run_next_cb (GtkButton *button, gpointer user_data)
969 {
970   state *s = global_state_kludge;  /* I hate C so much... */
971   saver_preferences *p = &s->prefs;
972   Bool ops = s->preview_suppressed_p;
973
974   GtkWidget *list_widget = name_to_widget (s, "list");
975   int list_elt = selected_list_element (s);
976
977   if (list_elt < 0)
978     list_elt = 0;
979   else
980     list_elt++;
981
982   if (list_elt >= p->screenhacks_count)
983     list_elt = 0;
984
985   s->preview_suppressed_p = True;
986
987   flush_dialog_changes_and_save (s);
988   force_list_select_item (s, list_widget, list_elt, True);
989   populate_demo_window (s, list_elt);
990   run_hack (s, list_elt, False);
991
992   s->preview_suppressed_p = ops;
993 }
994
995
996 void
997 run_prev_cb (GtkButton *button, gpointer user_data)
998 {
999   state *s = global_state_kludge;  /* I hate C so much... */
1000   saver_preferences *p = &s->prefs;
1001   Bool ops = s->preview_suppressed_p;
1002
1003   GtkWidget *list_widget = name_to_widget (s, "list");
1004   int list_elt = selected_list_element (s);
1005
1006   if (list_elt < 0)
1007     list_elt = p->screenhacks_count - 1;
1008   else
1009     list_elt--;
1010
1011   if (list_elt < 0)
1012     list_elt = p->screenhacks_count - 1;
1013
1014   s->preview_suppressed_p = True;
1015
1016   flush_dialog_changes_and_save (s);
1017   force_list_select_item (s, list_widget, list_elt, True);
1018   populate_demo_window (s, list_elt);
1019   run_hack (s, list_elt, False);
1020
1021   s->preview_suppressed_p = ops;
1022 }
1023
1024
1025 /* Writes the given settings into prefs.
1026    Returns true if there was a change, False otherwise.
1027    command and/or visual may be 0, or enabled_p may be -1, meaning "no change".
1028  */
1029 static Bool
1030 flush_changes (state *s,
1031                int list_elt,
1032                int enabled_p,
1033                const char *command,
1034                const char *visual)
1035 {
1036   saver_preferences *p = &s->prefs;
1037   Bool changed = False;
1038   screenhack *hack;
1039   int hack_number;
1040   if (list_elt < 0 || list_elt >= p->screenhacks_count)
1041     abort();
1042
1043   hack_number = s->list_elt_to_hack_number[list_elt];
1044   hack = p->screenhacks[hack_number];
1045
1046   if (enabled_p != -1 &&
1047       enabled_p != hack->enabled_p)
1048     {
1049       hack->enabled_p = enabled_p;
1050       changed = True;
1051       if (s->debug_p)
1052         fprintf (stderr, "%s: \"%s\": enabled => %d\n",
1053                  blurb(), hack->name, enabled_p);
1054     }
1055
1056   if (command)
1057     {
1058       if (!hack->command || !!strcmp (command, hack->command))
1059         {
1060           if (hack->command) free (hack->command);
1061           hack->command = strdup (command);
1062           changed = True;
1063           if (s->debug_p)
1064             fprintf (stderr, "%s: \"%s\": command => \"%s\"\n",
1065                      blurb(), hack->name, command);
1066         }
1067     }
1068
1069   if (visual)
1070     {
1071       const char *ov = hack->visual;
1072       if (!ov || !*ov) ov = "any";
1073       if (!*visual) visual = "any";
1074       if (!!strcasecmp (visual, ov))
1075         {
1076           if (hack->visual) free (hack->visual);
1077           hack->visual = strdup (visual);
1078           changed = True;
1079           if (s->debug_p)
1080             fprintf (stderr, "%s: \"%s\": visual => \"%s\"\n",
1081                      blurb(), hack->name, visual);
1082         }
1083     }
1084
1085   return changed;
1086 }
1087
1088
1089 /* Helper for the text fields that contain time specifications:
1090    this parses the text, and does error checking.
1091  */
1092 static void 
1093 hack_time_text (state *s, const char *line, Time *store, Bool sec_p)
1094 {
1095   if (*line)
1096     {
1097       int value;
1098       if (!sec_p || strchr (line, ':'))
1099         value = parse_time ((char *) line, sec_p, True);
1100       else
1101         {
1102           char c;
1103           if (sscanf (line, "%u%c", &value, &c) != 1)
1104             value = -1;
1105           if (!sec_p)
1106             value *= 60;
1107         }
1108
1109       value *= 1000;    /* Time measures in microseconds */
1110       if (value < 0)
1111         {
1112           char b[255];
1113           sprintf (b,
1114                    _("Error:\n\n"
1115                      "Unparsable time format: \"%s\"\n"),
1116                    line);
1117           warning_dialog (s->toplevel_widget, b, False, 100);
1118         }
1119       else
1120         *store = value;
1121     }
1122 }
1123
1124
1125 static Bool
1126 directory_p (const char *path)
1127 {
1128   struct stat st;
1129   if (!path || !*path)
1130     return False;
1131   else if (stat (path, &st))
1132     return False;
1133   else if (!S_ISDIR (st.st_mode))
1134     return False;
1135   else
1136     return True;
1137 }
1138
1139 static char *
1140 normalize_directory (const char *path)
1141 {
1142   int L;
1143   char *p2, *s;
1144   if (!path) return 0;
1145   L = strlen (path);;
1146   p2 = (char *) malloc (L + 2);
1147   strcpy (p2, path);
1148   if (p2[L-1] == '/')  /* remove trailing slash */
1149     p2[--L] = 0;
1150
1151   for (s = p2; s && *s; s++)
1152     {
1153       if (*s == '/' &&
1154           (!strncmp (s, "/../", 4) ||                   /* delete "XYZ/../" */
1155            !strncmp (s, "/..\000", 4)))                 /* delete "XYZ/..$" */
1156         {
1157           char *s0 = s;
1158           while (s0 > p2 && s0[-1] != '/')
1159             s0--;
1160           if (s0 > p2)
1161             {
1162               s0--;
1163               s += 3;
1164               strcpy (s0, s);
1165               s = s0-1;
1166             }
1167         }
1168       else if (*s == '/' && !strncmp (s, "/./", 3))     /* delete "/./" */
1169         strcpy (s, s+2), s--;
1170       else if (*s == '/' && !strncmp (s, "/.\000", 3))  /* delete "/.$" */
1171         *s = 0, s--;
1172     }
1173
1174   for (s = p2; s && *s; s++)            /* normalize consecutive slashes */
1175     while (s[0] == '/' && s[1] == '/')
1176       strcpy (s, s+1);
1177
1178   /* and strip trailing whitespace for good measure. */
1179   L = strlen(p2);
1180   while (isspace(p2[L-1]))
1181     p2[--L] = 0;
1182
1183   return p2;
1184 }
1185
1186
1187 #ifdef HAVE_GTK2
1188
1189 typedef struct {
1190   state *s;
1191   int i;
1192   Bool *changed;
1193 } FlushForeachClosure;
1194
1195 static gboolean
1196 flush_checkbox  (GtkTreeModel *model,
1197                  GtkTreePath *path,
1198                  GtkTreeIter *iter,
1199                  gpointer data)
1200 {
1201   FlushForeachClosure *closure = data;
1202   gboolean checked;
1203
1204   gtk_tree_model_get (model, iter,
1205                       COL_ENABLED, &checked,
1206                       -1);
1207
1208   if (flush_changes (closure->s, closure->i,
1209                      checked, 0, 0))
1210     *closure->changed = True;
1211   
1212   closure->i++;
1213
1214   /* don't remove row */
1215   return FALSE;
1216 }
1217
1218 #endif /* HAVE_GTK2 */
1219
1220 /* Flush out any changes made in the main dialog window (where changes
1221    take place immediately: clicking on a checkbox causes the init file
1222    to be written right away.)
1223  */
1224 static Bool
1225 flush_dialog_changes_and_save (state *s)
1226 {
1227   saver_preferences *p = &s->prefs;
1228   saver_preferences P2, *p2 = &P2;
1229 #ifdef HAVE_GTK2
1230   GtkTreeView *list_widget = GTK_TREE_VIEW (name_to_widget (s, "list"));
1231   GtkTreeModel *model = gtk_tree_view_get_model (list_widget);
1232   FlushForeachClosure closure;
1233 #else /* !HAVE_GTK2 */
1234   GtkList *list_widget = GTK_LIST (name_to_widget (s, "list"));
1235   GList *kids = gtk_container_children (GTK_CONTAINER (list_widget));
1236   int i;
1237 #endif /* !HAVE_GTK2 */
1238
1239   Bool changed = False;
1240   GtkWidget *w;
1241
1242   if (s->saving_p) return False;
1243   s->saving_p = True;
1244
1245   *p2 = *p;
1246
1247   /* Flush any checkbox changes in the list down into the prefs struct.
1248    */
1249 #ifdef HAVE_GTK2
1250   closure.s = s;
1251   closure.changed = &changed;
1252   closure.i = 0;
1253   gtk_tree_model_foreach (model, flush_checkbox, &closure);
1254
1255 #else /* !HAVE_GTK2 */
1256
1257   for (i = 0; kids; kids = kids->next, i++)
1258     {
1259       GtkWidget *line = GTK_WIDGET (kids->data);
1260       GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
1261       GtkWidget *line_check =
1262         GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
1263       Bool checked =
1264         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (line_check));
1265
1266       if (flush_changes (s, i, (checked ? 1 : 0), 0, 0))
1267         changed = True;
1268     }
1269 #endif /* ~HAVE_GTK2 */
1270
1271   /* Flush the non-hack-specific settings down into the prefs struct.
1272    */
1273
1274 # define SECONDS(FIELD,NAME) \
1275     w = name_to_widget (s, (NAME)); \
1276     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), True)
1277
1278 # define MINUTES(FIELD,NAME) \
1279     w = name_to_widget (s, (NAME)); \
1280     hack_time_text (s, gtk_entry_get_text (GTK_ENTRY (w)), (FIELD), False)
1281
1282 # define CHECKBOX(FIELD,NAME) \
1283     w = name_to_widget (s, (NAME)); \
1284     (FIELD) = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (w))
1285
1286 # define PATHNAME(FIELD,NAME) \
1287     w = name_to_widget (s, (NAME)); \
1288     (FIELD) = normalize_directory (gtk_entry_get_text (GTK_ENTRY (w)))
1289
1290   MINUTES  (&p2->timeout,         "timeout_spinbutton");
1291   MINUTES  (&p2->cycle,           "cycle_spinbutton");
1292   CHECKBOX (p2->lock_p,           "lock_button");
1293   MINUTES  (&p2->lock_timeout,    "lock_spinbutton");
1294
1295   CHECKBOX (p2->dpms_enabled_p,  "dpms_button");
1296   MINUTES  (&p2->dpms_standby,    "dpms_standby_spinbutton");
1297   MINUTES  (&p2->dpms_suspend,    "dpms_suspend_spinbutton");
1298   MINUTES  (&p2->dpms_off,        "dpms_off_spinbutton");
1299
1300   CHECKBOX (p2->grab_desktop_p,   "grab_desk_button");
1301   CHECKBOX (p2->grab_video_p,     "grab_video_button");
1302   CHECKBOX (p2->random_image_p,   "grab_image_button");
1303   PATHNAME (p2->image_directory,  "image_text");
1304
1305   CHECKBOX (p2->verbose_p,        "verbose_button");
1306   CHECKBOX (p2->capture_stderr_p, "capture_button");
1307   CHECKBOX (p2->splash_p,         "splash_button");
1308
1309   CHECKBOX (p2->install_cmap_p,   "install_button");
1310   CHECKBOX (p2->fade_p,           "fade_button");
1311   CHECKBOX (p2->unfade_p,         "unfade_button");
1312   SECONDS  (&p2->fade_seconds,    "fade_spinbutton");
1313
1314 # undef SECONDS
1315 # undef MINUTES
1316 # undef CHECKBOX
1317 # undef PATHNAME
1318
1319   /* Warn if the image directory doesn't exist.
1320    */
1321   if (p2->image_directory &&
1322       *p2->image_directory &&
1323       !directory_p (p2->image_directory))
1324     {
1325       char b[255];
1326       sprintf (b, "Error:\n\n" "Directory does not exist: \"%s\"\n",
1327                p2->image_directory);
1328       warning_dialog (s->toplevel_widget, b, False, 100);
1329     }
1330
1331
1332   /* Map the mode menu to `saver_mode' enum values. */
1333   {
1334     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
1335     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
1336     GtkWidget *selected = gtk_menu_get_active (menu);
1337     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
1338     int menu_elt = g_list_index (kids, (gpointer) selected);
1339     if (menu_elt < 0 || menu_elt >= countof(mode_menu_order)) abort();
1340     p2->mode = mode_menu_order[menu_elt];
1341   }
1342
1343   if (p2->mode == ONE_HACK)
1344     {
1345       int list_elt = selected_list_element (s);
1346       p2->selected_hack = (list_elt >= 0
1347                            ? s->list_elt_to_hack_number[list_elt]
1348                            : -1);
1349     }
1350
1351 # define COPY(field, name) \
1352   if (p->field != p2->field) { \
1353     changed = True; \
1354     if (s->debug_p) \
1355       fprintf (stderr, "%s: %s => %d\n", blurb(), name, p2->field); \
1356   } \
1357   p->field = p2->field
1358
1359   COPY(mode,             "mode");
1360   COPY(selected_hack,    "selected_hack");
1361
1362   COPY(timeout,        "timeout");
1363   COPY(cycle,          "cycle");
1364   COPY(lock_p,         "lock_p");
1365   COPY(lock_timeout,   "lock_timeout");
1366
1367   COPY(dpms_enabled_p, "dpms_enabled_p");
1368   COPY(dpms_standby,   "dpms_standby");
1369   COPY(dpms_suspend,   "dpms_suspend");
1370   COPY(dpms_off,       "dpms_off");
1371
1372   COPY(verbose_p,        "verbose_p");
1373   COPY(capture_stderr_p, "capture_stderr_p");
1374   COPY(splash_p,         "splash_p");
1375
1376   COPY(install_cmap_p,   "install_cmap_p");
1377   COPY(fade_p,           "fade_p");
1378   COPY(unfade_p,         "unfade_p");
1379   COPY(fade_seconds,     "fade_seconds");
1380
1381   COPY(grab_desktop_p, "grab_desktop_p");
1382   COPY(grab_video_p,   "grab_video_p");
1383   COPY(random_image_p, "random_image_p");
1384
1385 # undef COPY
1386
1387   if (!p->image_directory ||
1388       !p2->image_directory ||
1389       strcmp(p->image_directory, p2->image_directory))
1390     {
1391       changed = True;
1392       if (s->debug_p)
1393         fprintf (stderr, "%s: image_directory => \"%s\"\n",
1394                  blurb(), p2->image_directory);
1395     }
1396   if (p->image_directory && p->image_directory != p2->image_directory)
1397     free (p->image_directory);
1398   p->image_directory = p2->image_directory;
1399   p2->image_directory = 0;
1400
1401   populate_prefs_page (s);
1402
1403   if (changed)
1404     {
1405       Display *dpy = GDK_DISPLAY();
1406       Bool enabled_p = (p->dpms_enabled_p && p->mode != DONT_BLANK);
1407       sync_server_dpms_settings (dpy, enabled_p,
1408                                  p->dpms_standby / 1000,
1409                                  p->dpms_suspend / 1000,
1410                                  p->dpms_off / 1000,
1411                                  False);
1412
1413       changed = demo_write_init_file (s, p);
1414     }
1415
1416   s->saving_p = False;
1417   return changed;
1418 }
1419
1420
1421 /* Flush out any changes made in the popup dialog box (where changes
1422    take place only when the OK button is clicked.)
1423  */
1424 static Bool
1425 flush_popup_changes_and_save (state *s)
1426 {
1427   Bool changed = False;
1428   saver_preferences *p = &s->prefs;
1429   int list_elt = selected_list_element (s);
1430
1431   GtkEntry *cmd = GTK_ENTRY (name_to_widget (s, "cmd_text"));
1432   GtkCombo *vis = GTK_COMBO (name_to_widget (s, "visual_combo"));
1433
1434   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
1435   const char *command = gtk_entry_get_text (cmd);
1436
1437   char c;
1438   unsigned long id;
1439
1440   if (s->saving_p) return False;
1441   s->saving_p = True;
1442
1443   if (list_elt < 0)
1444     goto DONE;
1445
1446   if (maybe_reload_init_file (s) != 0)
1447     {
1448       changed = True;
1449       goto DONE;
1450     }
1451
1452   /* Sanity-check and canonicalize whatever the user typed into the combo box.
1453    */
1454   if      (!strcasecmp (visual, ""))                   visual = "";
1455   else if (!strcasecmp (visual, "any"))                visual = "";
1456   else if (!strcasecmp (visual, "default"))            visual = "Default";
1457   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
1458   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
1459   else if (!strcasecmp (visual, "best"))               visual = "Best";
1460   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
1461   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
1462   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
1463   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
1464   else if (!strcasecmp (visual, "color"))              visual = "Color";
1465   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
1466   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
1467   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
1468   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
1469   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
1470   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
1471   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
1472   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
1473   else if (1 == sscanf (visual, " %ld %c", &id, &c))   ;
1474   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
1475   else
1476     {
1477       gdk_beep ();                                /* unparsable */
1478       visual = "";
1479       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), _("Any"));
1480     }
1481
1482   changed = flush_changes (s, list_elt, -1, command, visual);
1483   if (changed)
1484     {
1485       changed = demo_write_init_file (s, p);
1486
1487       /* Do this to re-launch the hack if (and only if) the command line
1488          has changed. */
1489       populate_demo_window (s, selected_list_element (s));
1490     }
1491
1492  DONE:
1493   s->saving_p = False;
1494   return changed;
1495 }
1496
1497
1498 void
1499 pref_changed_cb (GtkWidget *widget, gpointer user_data)
1500 {
1501   state *s = global_state_kludge;  /* I hate C so much... */
1502   if (! s->initializing_p)
1503     {
1504       s->initializing_p = True;
1505       flush_dialog_changes_and_save (s);
1506       s->initializing_p = False;
1507     }
1508 }
1509
1510 gboolean
1511 pref_changed_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1512 {
1513   pref_changed_cb (widget, user_data);
1514   return FALSE;
1515 }
1516
1517 /* Callback on menu items in the "mode" options menu.
1518  */
1519 void
1520 mode_menu_item_cb (GtkWidget *widget, gpointer user_data)
1521 {
1522   state *s = (state *) user_data;
1523   saver_preferences *p = &s->prefs;
1524   GtkWidget *list = name_to_widget (s, "list");
1525   int list_elt;
1526
1527   GList *menu_items = gtk_container_children (GTK_CONTAINER (widget->parent));
1528   int menu_index = 0;
1529   saver_mode new_mode;
1530   int old_selected = p->selected_hack;
1531
1532   while (menu_items)
1533     {
1534       if (menu_items->data == widget)
1535         break;
1536       menu_index++;
1537       menu_items = menu_items->next;
1538     }
1539   if (!menu_items) abort();
1540
1541   new_mode = mode_menu_order[menu_index];
1542
1543   /* Keep the same list element displayed as before; except if we're
1544      switching *to* "one screensaver" mode from any other mode, scroll
1545      to and select "the one".
1546    */
1547   list_elt = -1;
1548   if (new_mode == ONE_HACK)
1549     list_elt = (p->selected_hack >= 0
1550                 ? s->hack_number_to_list_elt[p->selected_hack]
1551                 : -1);
1552
1553   if (list_elt < 0)
1554     list_elt = selected_list_element (s);
1555
1556   {
1557     saver_mode old_mode = p->mode;
1558     p->mode = new_mode;
1559     populate_demo_window (s, list_elt);
1560     force_list_select_item (s, list, list_elt, True);
1561     p->mode = old_mode;  /* put it back, so the init file gets written */
1562   }
1563
1564   pref_changed_cb (widget, user_data);
1565
1566   if (old_selected != p->selected_hack)
1567     abort();    /* dammit, not again... */
1568 }
1569
1570
1571 void
1572 switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
1573                 gint page_num, gpointer user_data)
1574 {
1575   state *s = global_state_kludge;  /* I hate C so much... */
1576   pref_changed_cb (GTK_WIDGET (notebook), user_data);
1577
1578   /* If we're switching to page 0, schedule the current hack to be run.
1579      Otherwise, schedule it to stop. */
1580   if (page_num == 0)
1581     populate_demo_window (s, selected_list_element (s));
1582   else
1583     schedule_preview (s, 0);
1584 }
1585
1586 #ifdef HAVE_GTK2
1587 static void
1588 list_activated_cb (GtkTreeView       *list,
1589                    GtkTreePath       *path,
1590                    GtkTreeViewColumn *column,
1591                    gpointer           data)
1592 {
1593   state *s = data;
1594   char *str;
1595   int list_elt;
1596
1597   g_return_if_fail (!gdk_pointer_is_grabbed ());
1598
1599   str = gtk_tree_path_to_string (path);
1600   list_elt = strtol (str, NULL, 10);
1601   g_free (str);
1602
1603   if (list_elt >= 0)
1604     run_hack (s, list_elt, True);
1605 }
1606
1607 static void
1608 list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
1609 {
1610   state *s = (state *)data;
1611   GtkTreeModel *model;
1612   GtkTreeIter iter;
1613   GtkTreePath *path;
1614   char *str;
1615   int list_elt;
1616  
1617   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1618     return;
1619
1620   path = gtk_tree_model_get_path (model, &iter);
1621   str = gtk_tree_path_to_string (path);
1622   list_elt = strtol (str, NULL, 10);
1623
1624   gtk_tree_path_free (path);
1625   g_free (str);
1626
1627   populate_demo_window (s, list_elt);
1628   flush_dialog_changes_and_save (s);
1629 }
1630
1631 #else /* !HAVE_GTK2 */
1632
1633 static time_t last_doubleclick_time = 0;   /* FMH!  This is to suppress the
1634                                               list_select_cb that comes in
1635                                               *after* we've double-clicked.
1636                                             */
1637
1638 static gint
1639 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
1640                      gpointer data)
1641 {
1642   state *s = (state *) data;
1643   if (event->type == GDK_2BUTTON_PRESS)
1644     {
1645       GtkList *list = GTK_LIST (name_to_widget (s, "list"));
1646       int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
1647
1648       last_doubleclick_time = time ((time_t *) 0);
1649
1650       if (list_elt >= 0)
1651         run_hack (s, list_elt, True);
1652     }
1653
1654   return FALSE;
1655 }
1656
1657
1658 static void
1659 list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
1660 {
1661   state *s = (state *) data;
1662   time_t now = time ((time_t *) 0);
1663
1664   if (now >= last_doubleclick_time + 2)
1665     {
1666       int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
1667       populate_demo_window (s, list_elt);
1668       flush_dialog_changes_and_save (s);
1669     }
1670 }
1671
1672 static void
1673 list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
1674 {
1675   state *s = (state *) data;
1676   populate_demo_window (s, -1);
1677   flush_dialog_changes_and_save (s);
1678 }
1679
1680 #endif /* !HAVE_GTK2 */
1681
1682
1683 /* Called when the checkboxes that are in the left column of the
1684    scrolling list are clicked.  This both populates the right pane
1685    (just as clicking on the label (really, listitem) does) and
1686    also syncs this checkbox with  the right pane Enabled checkbox.
1687  */
1688 static void
1689 list_checkbox_cb (
1690 #ifdef HAVE_GTK2
1691                   GtkCellRendererToggle *toggle,
1692                   gchar                 *path_string,
1693 #else  /* !HAVE_GTK2 */
1694                   GtkWidget *cb,
1695 #endif /* !HAVE_GTK2 */
1696                   gpointer               data)
1697 {
1698   state *s = (state *) data;
1699
1700 #ifdef HAVE_GTK2
1701   GtkScrolledWindow *scroller =
1702     GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
1703   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
1704   GtkTreeModel *model = gtk_tree_view_get_model (list);
1705   GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
1706   GtkTreeIter iter;
1707   gboolean active;
1708 #else /* !HAVE_GTK2 */
1709   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
1710   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
1711
1712   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
1713   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
1714   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
1715 #endif /* !HAVE_GTK2 */
1716   GtkAdjustment *adj;
1717   double scroll_top;
1718
1719   int list_elt;
1720
1721 #ifdef HAVE_GTK2
1722   if (!gtk_tree_model_get_iter (model, &iter, path))
1723     {
1724       g_warning ("bad path: %s", path_string);
1725       return;
1726     }
1727   gtk_tree_path_free (path);
1728
1729   gtk_tree_model_get (model, &iter,
1730                       COL_ENABLED, &active,
1731                       -1);
1732
1733   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1734                       COL_ENABLED, !active,
1735                       -1);
1736
1737   list_elt = strtol (path_string, NULL, 10);  
1738 #else  /* !HAVE_GTK2 */
1739   list_elt = gtk_list_child_position (list, line);
1740 #endif /* !HAVE_GTK2 */
1741
1742   /* remember previous scroll position of the top of the list */
1743   adj = gtk_scrolled_window_get_vadjustment (scroller);
1744   scroll_top = adj->value;
1745
1746   flush_dialog_changes_and_save (s);
1747   force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
1748   populate_demo_window (s, list_elt);
1749   
1750   /* restore the previous scroll position of the top of the list.
1751      this is weak, but I don't really know why it's moving... */
1752   gtk_adjustment_set_value (adj, scroll_top);
1753 }
1754
1755
1756 typedef struct {
1757   state *state;
1758   GtkFileSelection *widget;
1759 } file_selection_data;
1760
1761
1762
1763 static void
1764 store_image_directory (GtkWidget *button, gpointer user_data)
1765 {
1766   file_selection_data *fsd = (file_selection_data *) user_data;
1767   state *s = fsd->state;
1768   GtkFileSelection *selector = fsd->widget;
1769   GtkWidget *top = s->toplevel_widget;
1770   saver_preferences *p = &s->prefs;
1771   const char *path = gtk_file_selection_get_filename (selector);
1772
1773   if (p->image_directory && !strcmp(p->image_directory, path))
1774     return;  /* no change */
1775
1776   if (!directory_p (path))
1777     {
1778       char b[255];
1779       sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
1780       warning_dialog (GTK_WIDGET (top), b, False, 100);
1781       return;
1782     }
1783
1784   if (p->image_directory) free (p->image_directory);
1785   p->image_directory = normalize_directory (path);
1786
1787   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
1788                       (p->image_directory ? p->image_directory : ""));
1789   demo_write_init_file (s, p);
1790 }
1791
1792
1793 static void
1794 browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
1795 {
1796   file_selection_data *fsd = (file_selection_data *) user_data;
1797   gtk_widget_hide (GTK_WIDGET (fsd->widget));
1798 }
1799
1800 static void
1801 browse_image_dir_ok (GtkWidget *button, gpointer user_data)
1802 {
1803   browse_image_dir_cancel (button, user_data);
1804   store_image_directory (button, user_data);
1805 }
1806
1807 static void
1808 browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
1809 {
1810   browse_image_dir_cancel (widget, user_data);
1811 }
1812
1813
1814 void
1815 browse_image_dir_cb (GtkButton *button, gpointer user_data)
1816 {
1817   state *s = global_state_kludge;  /* I hate C so much... */
1818   saver_preferences *p = &s->prefs;
1819   static file_selection_data *fsd = 0;
1820
1821   GtkFileSelection *selector = GTK_FILE_SELECTION(
1822     gtk_file_selection_new ("Please select the image directory."));
1823
1824   if (!fsd)
1825     fsd = (file_selection_data *) malloc (sizeof (*fsd));  
1826
1827   fsd->widget = selector;
1828   fsd->state = s;
1829
1830   if (p->image_directory && *p->image_directory)
1831     gtk_file_selection_set_filename (selector, p->image_directory);
1832
1833   gtk_signal_connect (GTK_OBJECT (selector->ok_button),
1834                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
1835                       (gpointer *) fsd);
1836   gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
1837                       "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
1838                       (gpointer *) fsd);
1839   gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
1840                       GTK_SIGNAL_FUNC (browse_image_dir_close),
1841                       (gpointer *) fsd);
1842
1843   gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
1844
1845   gtk_window_set_modal (GTK_WINDOW (selector), True);
1846   gtk_widget_show (GTK_WIDGET (selector));
1847 }
1848
1849
1850 void
1851 settings_cb (GtkButton *button, gpointer user_data)
1852 {
1853   state *s = global_state_kludge;  /* I hate C so much... */
1854   int list_elt = selected_list_element (s);
1855
1856   populate_demo_window (s, list_elt);   /* reset the widget */
1857   populate_popup_window (s);            /* create UI on popup window */
1858   gtk_widget_show (s->popup_widget);
1859 }
1860
1861 static void
1862 settings_sync_cmd_text (state *s)
1863 {
1864 # ifdef HAVE_XML
1865   GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
1866   char *cmd_line = get_configurator_command_line (s->cdata);
1867   gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
1868   gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
1869   free (cmd_line);
1870 # endif /* HAVE_XML */
1871 }
1872
1873 void
1874 settings_adv_cb (GtkButton *button, gpointer user_data)
1875 {
1876   state *s = global_state_kludge;  /* I hate C so much... */
1877   GtkNotebook *notebook =
1878     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
1879
1880   settings_sync_cmd_text (s);
1881   gtk_notebook_set_page (notebook, 1);
1882 }
1883
1884 void
1885 settings_std_cb (GtkButton *button, gpointer user_data)
1886 {
1887   state *s = global_state_kludge;  /* I hate C so much... */
1888   GtkNotebook *notebook =
1889     GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
1890
1891   /* Re-create UI to reflect the in-progress command-line settings. */
1892   populate_popup_window (s);
1893
1894   gtk_notebook_set_page (notebook, 0);
1895 }
1896
1897 void
1898 settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
1899                          gint page_num, gpointer user_data)
1900 {
1901   state *s = global_state_kludge;  /* I hate C so much... */
1902   GtkWidget *adv = name_to_widget (s, "adv_button");
1903   GtkWidget *std = name_to_widget (s, "std_button");
1904
1905   if (page_num == 0)
1906     {
1907       gtk_widget_show (adv);
1908       gtk_widget_hide (std);
1909     }
1910   else if (page_num == 1)
1911     {
1912       gtk_widget_hide (adv);
1913       gtk_widget_show (std);
1914     }
1915   else
1916     abort();
1917 }
1918
1919
1920
1921 void
1922 settings_cancel_cb (GtkButton *button, gpointer user_data)
1923 {
1924   state *s = global_state_kludge;  /* I hate C so much... */
1925   gtk_widget_hide (s->popup_widget);
1926 }
1927
1928 void
1929 settings_ok_cb (GtkButton *button, gpointer user_data)
1930 {
1931   state *s = global_state_kludge;  /* I hate C so much... */
1932   GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
1933   int page = gtk_notebook_get_current_page (notebook);
1934
1935   if (page == 0)
1936     /* Regenerate the command-line from the widget contents before saving.
1937        But don't do this if we're looking at the command-line page already,
1938        or we will blow away what they typed... */
1939     settings_sync_cmd_text (s);
1940
1941   flush_popup_changes_and_save (s);
1942   gtk_widget_hide (s->popup_widget);
1943 }
1944
1945 static void
1946 wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
1947 {
1948   state *s = (state *) data;
1949   settings_cancel_cb (0, (gpointer) s);
1950 }
1951
1952
1953 \f
1954 /* Populating the various widgets
1955  */
1956
1957
1958 /* Returns the number of the last hack run by the server.
1959  */
1960 static int
1961 server_current_hack (void)
1962 {
1963   Atom type;
1964   int format;
1965   unsigned long nitems, bytesafter;
1966   CARD32 *data = 0;
1967   Display *dpy = GDK_DISPLAY();
1968   int hack_number = -1;
1969
1970   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
1971                           XA_SCREENSAVER_STATUS,
1972                           0, 3, False, XA_INTEGER,
1973                           &type, &format, &nitems, &bytesafter,
1974                           (unsigned char **) &data)
1975       == Success
1976       && type == XA_INTEGER
1977       && nitems >= 3
1978       && data)
1979     hack_number = (int) data[2] - 1;
1980
1981   if (data) free (data);
1982
1983   return hack_number;
1984 }
1985
1986
1987 /* Finds the number of the last hack to run, and makes that item be
1988    selected by default.
1989  */
1990 static void
1991 scroll_to_current_hack (state *s)
1992 {
1993   saver_preferences *p = &s->prefs;
1994   int hack_number;
1995
1996   if (p->mode == ONE_HACK)
1997     hack_number = p->selected_hack;
1998   else
1999     hack_number = server_current_hack ();
2000
2001   if (hack_number >= 0 && hack_number < p->screenhacks_count)
2002     {
2003       int list_elt = s->hack_number_to_list_elt[hack_number];
2004       GtkWidget *list = name_to_widget (s, "list");
2005       force_list_select_item (s, list, list_elt, True);
2006       populate_demo_window (s, list_elt);
2007     }
2008 }
2009
2010
2011 static void
2012 populate_hack_list (state *s)
2013 {
2014 #ifdef HAVE_GTK2
2015   saver_preferences *p = &s->prefs;
2016   GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
2017   GtkListStore *model;
2018   GtkTreeSelection *selection;
2019   GtkCellRenderer *ren;
2020   GtkTreeIter iter;
2021   int i;
2022
2023   g_object_get (G_OBJECT (list),
2024                 "model", &model,
2025                 NULL);
2026   if (!model)
2027     {
2028       model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
2029       g_object_set (G_OBJECT (list), "model", model, NULL);
2030       g_object_unref (model);
2031
2032       ren = gtk_cell_renderer_toggle_new ();
2033       gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
2034                                                    _("Use"), ren,
2035                                                    "active", COL_ENABLED,
2036                                                    NULL);
2037
2038       g_signal_connect (ren, "toggled",
2039                         G_CALLBACK (list_checkbox_cb),
2040                         s);
2041
2042       ren = gtk_cell_renderer_text_new ();
2043       gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
2044                                                    _("Screen Saver"), ren,
2045                                                    "text", COL_NAME,
2046                                                    NULL);
2047
2048       g_signal_connect_after (list, "row_activated",
2049                               G_CALLBACK (list_activated_cb),
2050                               s);
2051
2052       selection = gtk_tree_view_get_selection (list);
2053       g_signal_connect (selection, "changed",
2054                         G_CALLBACK (list_select_changed_cb),
2055                         s);
2056
2057     }
2058
2059   for (i = 0; i < p->screenhacks_count; i++)
2060     {
2061       screenhack *hack = p->screenhacks[s->list_elt_to_hack_number[i]];
2062
2063       char *pretty_name = (hack->name
2064                            ? strdup (hack->name)
2065                            : make_hack_name (hack->command));
2066
2067       gtk_list_store_append (model, &iter);
2068       gtk_list_store_set (model, &iter,
2069                           COL_ENABLED, hack->enabled_p,
2070                           COL_NAME, pretty_name,
2071                           -1);
2072
2073       free (pretty_name);
2074     }
2075
2076 #else /* !HAVE_GTK2 */
2077
2078   saver_preferences *p = &s->prefs;
2079   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2080   int i;
2081   for (i = 0; i < p->screenhacks_count; i++)
2082     {
2083       screenhack *hack = p->screenhacks[s->list_elt_to_hack_number[i]];
2084
2085       /* A GtkList must contain only GtkListItems, but those can contain
2086          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
2087          and a Label.  We handle single and double click events on the
2088          line itself, for clicking on the text, but the interior checkbox
2089          also handles its own events.
2090        */
2091       GtkWidget *line;
2092       GtkWidget *line_hbox;
2093       GtkWidget *line_check;
2094       GtkWidget *line_label;
2095
2096       char *pretty_name = (hack->name
2097                            ? strdup (hack->name)
2098                            : make_hack_name (hack->command));
2099
2100       line = gtk_list_item_new ();
2101       line_hbox = gtk_hbox_new (FALSE, 0);
2102       line_check = gtk_check_button_new ();
2103       line_label = gtk_label_new (pretty_name);
2104
2105       gtk_container_add (GTK_CONTAINER (line), line_hbox);
2106       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
2107       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
2108
2109       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
2110                                     hack->enabled_p);
2111       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
2112
2113       gtk_widget_show (line_check);
2114       gtk_widget_show (line_label);
2115       gtk_widget_show (line_hbox);
2116       gtk_widget_show (line);
2117
2118       free (pretty_name);
2119
2120       gtk_container_add (GTK_CONTAINER (list), line);
2121       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
2122                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
2123                           (gpointer) s);
2124
2125       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
2126                           GTK_SIGNAL_FUNC (list_checkbox_cb),
2127                           (gpointer) s);
2128
2129 #if 0 /* #### */
2130       GTK_WIDGET (GTK_BIN(line)->child)->style =
2131         gtk_style_copy (GTK_WIDGET (text_line)->style);
2132 #endif
2133       gtk_widget_show (line);
2134     }
2135
2136   gtk_signal_connect (GTK_OBJECT (list), "select_child",
2137                       GTK_SIGNAL_FUNC (list_select_cb),
2138                       (gpointer) s);
2139   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
2140                       GTK_SIGNAL_FUNC (list_unselect_cb),
2141                       (gpointer) s);
2142 #endif /* !HAVE_GTK2 */
2143 }
2144
2145 static void
2146 update_list_sensitivity (state *s)
2147 {
2148   saver_preferences *p = &s->prefs;
2149   Bool sensitive = (p->mode == RANDOM_HACKS || p->mode == ONE_HACK);
2150   Bool checkable = (p->mode == RANDOM_HACKS);
2151   Bool blankable = (p->mode != DONT_BLANK);
2152
2153 #ifndef HAVE_GTK2
2154   GtkWidget *head     = name_to_widget (s, "col_head_hbox");
2155   GtkWidget *use      = name_to_widget (s, "use_col_frame");
2156 #endif /* HAVE_GTK2 */
2157   GtkWidget *scroller = name_to_widget (s, "scroller");
2158   GtkWidget *buttons  = name_to_widget (s, "next_prev_hbox");
2159   GtkWidget *blanker  = name_to_widget (s, "blanking_table");
2160
2161 #ifdef HAVE_GTK2
2162   GtkTreeView *list      = GTK_TREE_VIEW (name_to_widget (s, "list"));
2163   GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
2164 #else /* !HAVE_GTK2 */
2165   GtkList *list = GTK_LIST (name_to_widget (s, "list"));
2166   GList *kids   = gtk_container_children (GTK_CONTAINER (list));
2167
2168   gtk_widget_set_sensitive (GTK_WIDGET (head),     sensitive);
2169 #endif /* !HAVE_GTK2 */
2170   gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
2171   gtk_widget_set_sensitive (GTK_WIDGET (buttons),  sensitive);
2172
2173   gtk_widget_set_sensitive (GTK_WIDGET (blanker),  blankable);
2174
2175 #ifdef HAVE_GTK2
2176   gtk_tree_view_column_set_visible (use, checkable);
2177 #else  /* !HAVE_GTK2 */
2178   if (checkable)
2179     gtk_widget_show (use);   /* the "Use" column header */
2180   else
2181     gtk_widget_hide (use);
2182
2183   while (kids)
2184     {
2185       GtkBin *line = GTK_BIN (kids->data);
2186       GtkContainer *line_hbox = GTK_CONTAINER (line->child);
2187       GtkWidget *line_check =
2188         GTK_WIDGET (gtk_container_children (line_hbox)->data);
2189       
2190       if (checkable)
2191         gtk_widget_show (line_check);
2192       else
2193         gtk_widget_hide (line_check);
2194
2195       kids = kids->next;
2196     }
2197 #endif /* !HAVE_GTK2 */
2198 }
2199
2200
2201 static void
2202 populate_prefs_page (state *s)
2203 {
2204   saver_preferences *p = &s->prefs;
2205
2206   /* The file supports timeouts of less than a minute, but the GUI does
2207      not, so throttle the values to be at least one minute (since "0" is
2208      a bad rounding choice...)
2209    */
2210 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
2211   THROTTLE (timeout);
2212   THROTTLE (cycle);
2213   THROTTLE (passwd_timeout);
2214 # undef THROTTLE
2215
2216 # define FMT_MINUTES(NAME,N) \
2217     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
2218
2219 # define FMT_SECONDS(NAME,N) \
2220     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
2221
2222   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
2223   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
2224   FMT_MINUTES ("lock_spinbutton",         p->lock_timeout);
2225   FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
2226   FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
2227   FMT_MINUTES ("dpms_off_spinbutton",     p->dpms_off);
2228   FMT_SECONDS ("fade_spinbutton",         p->fade_seconds);
2229
2230 # undef FMT_MINUTES
2231 # undef FMT_SECONDS
2232
2233 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
2234   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
2235                                 (ACTIVEP))
2236
2237   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
2238   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
2239   TOGGLE_ACTIVE ("capture_button",    p->capture_stderr_p);
2240   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
2241   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
2242   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
2243   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
2244   TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
2245   TOGGLE_ACTIVE ("install_button",    p->install_cmap_p);
2246   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
2247   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
2248
2249 # undef TOGGLE_ACTIVE
2250
2251   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2252                       (p->image_directory ? p->image_directory : ""));
2253   gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
2254                             p->random_image_p);
2255   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
2256                             p->random_image_p);
2257
2258   /* Map the `saver_mode' enum to mode menu to values. */
2259   {
2260     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
2261
2262     int i;
2263     for (i = 0; i < countof(mode_menu_order); i++)
2264       if (mode_menu_order[i] == p->mode)
2265         break;
2266     gtk_option_menu_set_history (opt, i);
2267     update_list_sensitivity (s);
2268   }
2269
2270   {
2271     Bool found_any_writable_cells = False;
2272     Bool dpms_supported = False;
2273
2274     Display *dpy = GDK_DISPLAY();
2275     int nscreens = ScreenCount(dpy);
2276     int i;
2277     for (i = 0; i < nscreens; i++)
2278       {
2279         Screen *s = ScreenOfDisplay (dpy, i);
2280         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
2281           {
2282             found_any_writable_cells = True;
2283             break;
2284           }
2285       }
2286
2287 #ifdef HAVE_XF86VMODE_GAMMA
2288     found_any_writable_cells = True;  /* if we can gamma fade, go for it */
2289 #endif
2290
2291 #ifdef HAVE_DPMS_EXTENSION
2292     {
2293       int op = 0, event = 0, error = 0;
2294       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
2295         dpms_supported = True;
2296     }
2297 #endif /* HAVE_DPMS_EXTENSION */
2298
2299
2300 # define SENSITIZE(NAME,SENSITIVEP) \
2301     gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
2302
2303     /* Blanking and Locking
2304      */
2305     SENSITIZE ("lock_spinbutton", p->lock_p);
2306     SENSITIZE ("lock_mlabel",     p->lock_p);
2307
2308     /* DPMS
2309      */
2310     SENSITIZE ("dpms_frame",              dpms_supported);
2311     SENSITIZE ("dpms_button",             dpms_supported);
2312     SENSITIZE ("dpms_standby_label",      dpms_supported && p->dpms_enabled_p);
2313     SENSITIZE ("dpms_standby_mlabel",     dpms_supported && p->dpms_enabled_p);
2314     SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
2315     SENSITIZE ("dpms_suspend_label",      dpms_supported && p->dpms_enabled_p);
2316     SENSITIZE ("dpms_suspend_mlabel",     dpms_supported && p->dpms_enabled_p);
2317     SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
2318     SENSITIZE ("dpms_off_label",          dpms_supported && p->dpms_enabled_p);
2319     SENSITIZE ("dpms_off_mlabel",         dpms_supported && p->dpms_enabled_p);
2320     SENSITIZE ("dpms_off_spinbutton",     dpms_supported && p->dpms_enabled_p);
2321
2322     /* Colormaps
2323      */
2324     SENSITIZE ("cmap_frame",      found_any_writable_cells);
2325     SENSITIZE ("install_button",  found_any_writable_cells);
2326     SENSITIZE ("fade_button",     found_any_writable_cells);
2327     SENSITIZE ("unfade_button",   found_any_writable_cells);
2328
2329     SENSITIZE ("fade_label",      (found_any_writable_cells &&
2330                                    (p->fade_p || p->unfade_p)));
2331     SENSITIZE ("fade_spinbutton", (found_any_writable_cells &&
2332                                    (p->fade_p || p->unfade_p)));
2333
2334 # undef SENSITIZE
2335   }
2336 }
2337
2338
2339 static void
2340 populate_popup_window (state *s)
2341 {
2342   saver_preferences *p = &s->prefs;
2343   GtkWidget *parent = name_to_widget (s, "settings_vbox");
2344   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
2345   int list_elt = selected_list_element (s);
2346   int hack_number = (list_elt >= 0 && list_elt < p->screenhacks_count
2347                      ? s->list_elt_to_hack_number[list_elt]
2348                      : -1);
2349   screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2350   char *doc_string = 0;
2351
2352   /* #### not in Gtk 1.2
2353   gtk_label_set_selectable (doc);
2354    */
2355
2356 # ifdef HAVE_XML
2357   if (s->cdata)
2358     {
2359       free_conf_data (s->cdata);
2360       s->cdata = 0;
2361     }
2362
2363   if (hack)
2364     {
2365       GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2366       const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
2367       s->cdata = load_configurator (cmd_line, s->debug_p);
2368       if (s->cdata && s->cdata->widget)
2369         gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget, TRUE, TRUE, 0);
2370     }
2371
2372   doc_string = (s->cdata
2373                 ? s->cdata->description
2374                 : 0);
2375 # else  /* !HAVE_XML */
2376   doc_string = _("Descriptions not available: no XML support compiled in.");
2377 # endif /* !HAVE_XML */
2378
2379   gtk_label_set_text (doc, (doc_string
2380                             ? _(doc_string)
2381                             : _("No description available.")));
2382 }
2383
2384
2385 static void
2386 sensitize_demo_widgets (state *s, Bool sensitive_p)
2387 {
2388   const char *names1[] = { "demo", "settings" };
2389   const char *names2[] = { "cmd_label", "cmd_text", "manual",
2390                            "visual", "visual_combo" };
2391   int i;
2392   for (i = 0; i < countof(names1); i++)
2393     {
2394       GtkWidget *w = name_to_widget (s, names1[i]);
2395       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2396     }
2397   for (i = 0; i < countof(names2); i++)
2398     {
2399       GtkWidget *w = name_to_widget (s, names2[i]);
2400       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2401     }
2402 }
2403
2404
2405 /* Even though we've given these text fields a maximum number of characters,
2406    their default size is still about 30 characters wide -- so measure out
2407    a string in their font, and resize them to just fit that.
2408  */
2409 static void
2410 fix_text_entry_sizes (state *s)
2411 {
2412 #ifdef FIXME
2413   const char * const spinbuttons[] = {
2414     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
2415     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
2416     "dpms_off_spinbutton",
2417     "-fade_spinbutton" };
2418   int i;
2419   int width = 0;
2420   GtkWidget *w;
2421
2422   for (i = 0; i < countof(spinbuttons); i++)
2423     {
2424       const char *n = spinbuttons[i];
2425       int cols = 4;
2426       while (*n == '-') n++, cols--;
2427       w = GTK_WIDGET (name_to_widget (s, n));
2428       width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
2429       gtk_widget_set_usize (w, width, -2);
2430     }
2431
2432   /* Now fix the width of the combo box.
2433    */
2434   w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
2435   w = GTK_COMBO (w)->entry;
2436   width = gdk_string_width (w->style->font, "PseudoColor___");
2437   gtk_widget_set_usize (w, width, -2);
2438
2439   /* Now fix the width of the file entry text.
2440    */
2441   w = GTK_WIDGET (name_to_widget (s, "image_text"));
2442   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
2443   gtk_widget_set_usize (w, width, -2);
2444
2445   /* Now fix the width of the command line text.
2446    */
2447   w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2448   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
2449   gtk_widget_set_usize (w, width, -2);
2450
2451   /* Now fix the height of the list.
2452    */
2453   {
2454     int lines = 10;
2455     int height;
2456     int leading = 3;  /* approximate is ok... */
2457     int border = 2;
2458     w = GTK_WIDGET (name_to_widget (s, "list"));
2459     height = w->style->font->ascent + w->style->font->descent;
2460     height += leading;
2461     height *= lines;
2462     height += border * 2;
2463     w = GTK_WIDGET (name_to_widget (s, "scroller"));
2464     gtk_widget_set_usize (w, -2, height);
2465   }
2466 #endif
2467 }
2468
2469
2470 #ifndef HAVE_GTK2
2471 \f
2472 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
2473  */
2474
2475 static char *up_arrow_xpm[] = {
2476   "15 15 4 1",
2477   "     c None",
2478   "-    c #FFFFFF",
2479   "+    c #D6D6D6",
2480   "@    c #000000",
2481
2482   "       @       ",
2483   "       @       ",
2484   "      -+@      ",
2485   "      -+@      ",
2486   "     -+++@     ",
2487   "     -+++@     ",
2488   "    -+++++@    ",
2489   "    -+++++@    ",
2490   "   -+++++++@   ",
2491   "   -+++++++@   ",
2492   "  -+++++++++@  ",
2493   "  -+++++++++@  ",
2494   " -+++++++++++@ ",
2495   " @@@@@@@@@@@@@ ",
2496   "               ",
2497
2498   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
2499      the end of the array (Gtk 1.2.5.) */
2500   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
2501   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
2502 };
2503
2504 static char *down_arrow_xpm[] = {
2505   "15 15 4 1",
2506   "     c None",
2507   "-    c #FFFFFF",
2508   "+    c #D6D6D6",
2509   "@    c #000000",
2510
2511   "               ",
2512   " ------------- ",
2513   " -+++++++++++@ ",
2514   "  -+++++++++@  ",
2515   "  -+++++++++@  ",
2516   "   -+++++++@   ",
2517   "   -+++++++@   ",
2518   "    -+++++@    ",
2519   "    -+++++@    ",
2520   "     -+++@     ",
2521   "     -+++@     ",
2522   "      -+@      ",
2523   "      -+@      ",
2524   "       @       ",
2525   "       @       ",
2526
2527   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
2528      the end of the array (Gtk 1.2.5.) */
2529   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
2530   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
2531 };
2532
2533 static void
2534 pixmapify_button (state *s, int down_p)
2535 {
2536   GdkPixmap *pixmap;
2537   GdkBitmap *mask;
2538   GtkWidget *pixmapwid;
2539   GtkStyle *style;
2540   GtkWidget *w;
2541
2542   w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
2543   style = gtk_widget_get_style (w);
2544   mask = 0;
2545   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
2546                                          &style->bg[GTK_STATE_NORMAL],
2547                                          (down_p
2548                                           ? (gchar **) down_arrow_xpm
2549                                           : (gchar **) up_arrow_xpm));
2550   pixmapwid = gtk_pixmap_new (pixmap, mask);
2551   gtk_widget_show (pixmapwid);
2552   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
2553   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
2554 }
2555
2556 static void
2557 map_next_button_cb (GtkWidget *w, gpointer user_data)
2558 {
2559   state *s = (state *) user_data;
2560   pixmapify_button (s, 1);
2561 }
2562
2563 static void
2564 map_prev_button_cb (GtkWidget *w, gpointer user_data)
2565 {
2566   state *s = (state *) user_data;
2567   pixmapify_button (s, 0);
2568 }
2569 #endif /* !HAVE_GTK2 */
2570
2571 \f
2572 /* Work around a Gtk bug that causes label widgets to wrap text too early.
2573  */
2574
2575 static void
2576 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
2577                                              GtkAllocation *allocation,
2578                                              void *foo)
2579 {
2580   GtkRequisition req;
2581   GtkWidgetAuxInfo *aux_info;
2582
2583   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
2584
2585   aux_info->width = allocation->width;
2586   aux_info->height = -2;
2587   aux_info->x = -1;
2588   aux_info->y = -1;
2589
2590   gtk_widget_size_request (label, &req);
2591 }
2592
2593
2594 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
2595  */
2596 static void
2597 eschew_gtk_lossage (GtkLabel *label)
2598 {
2599   GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
2600   aux_info->width = GTK_WIDGET (label)->allocation.width;
2601   aux_info->height = -2;
2602   aux_info->x = -1;
2603   aux_info->y = -1;
2604
2605   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
2606
2607   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
2608                       GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
2609                       0);
2610
2611   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
2612
2613   gtk_widget_queue_resize (GTK_WIDGET (label));
2614 }
2615
2616
2617 static void
2618 populate_demo_window (state *s, int list_elt)
2619 {
2620   saver_preferences *p = &s->prefs;
2621   screenhack *hack;
2622   char *pretty_name;
2623   GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
2624   GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "doc_frame"));
2625   GtkEntry *cmd    = GTK_ENTRY (name_to_widget (s, "cmd_text"));
2626   GtkCombo *vis    = GTK_COMBO (name_to_widget (s, "visual_combo"));
2627   GtkWidget *list  = GTK_WIDGET (name_to_widget (s, "list"));
2628
2629   if (p->mode == BLANK_ONLY)
2630     {
2631       hack = 0;
2632       pretty_name = strdup (_("Blank Screen"));
2633       schedule_preview (s, 0);
2634     }
2635   else if (p->mode == DONT_BLANK)
2636     {
2637       hack = 0;
2638       pretty_name = strdup (_("Screen Saver Disabled"));
2639       schedule_preview (s, 0);
2640     }
2641   else
2642     {
2643       int hack_number = (list_elt >= 0 && list_elt < p->screenhacks_count
2644                          ? s->list_elt_to_hack_number[list_elt]
2645                          : -1);
2646       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2647
2648       pretty_name = (hack
2649                      ? (hack->name
2650                         ? strdup (hack->name)
2651                         : make_hack_name (hack->command))
2652                      : 0);
2653
2654       if (hack)
2655         schedule_preview (s, hack->command);
2656       else
2657         schedule_preview (s, 0);
2658     }
2659
2660   if (!pretty_name)
2661     pretty_name = strdup (_("Preview"));
2662
2663   gtk_frame_set_label (frame1, pretty_name);
2664   gtk_frame_set_label (frame2, pretty_name);
2665
2666   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
2667   gtk_entry_set_position (cmd, 0);
2668
2669   {
2670     char title[255];
2671     sprintf (title, "%s: %.100s Settings",
2672              progclass, (pretty_name ? pretty_name : "???"));
2673     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
2674   }
2675
2676   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
2677                       (hack
2678                        ? (hack->visual && *hack->visual
2679                           ? hack->visual
2680                           : _("Any"))
2681                        : ""));
2682
2683   sensitize_demo_widgets (s, (hack ? True : False));
2684
2685   if (pretty_name) free (pretty_name);
2686
2687   ensure_selected_item_visible (list);
2688
2689   s->_selected_list_element = list_elt;
2690 }
2691
2692
2693 static void
2694 widget_deleter (GtkWidget *widget, gpointer data)
2695 {
2696   /* #### Well, I want to destroy these widgets, but if I do that, they get
2697      referenced again, and eventually I get a SEGV.  So instead of
2698      destroying them, I'll just hide them, and leak a bunch of memory
2699      every time the disk file changes.  Go go go Gtk!
2700
2701      #### Ok, that's a lie, I get a crash even if I just hide the widget
2702      and don't ever delete it.  Fuck!
2703    */
2704 #if 0
2705   gtk_widget_destroy (widget);
2706 #else
2707   gtk_widget_hide (widget);
2708 #endif
2709 }
2710
2711
2712 static char **sort_hack_cmp_names_kludge;
2713 static int
2714 sort_hack_cmp (const void *a, const void *b)
2715 {
2716   if (a == b)
2717     return 0;
2718   else
2719     return strcmp (sort_hack_cmp_names_kludge[*(int *) a],
2720                    sort_hack_cmp_names_kludge[*(int *) b]);
2721 }
2722
2723
2724 static void
2725 initialize_sort_map (state *s)
2726 {
2727   saver_preferences *p = &s->prefs;
2728   int i;
2729
2730   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
2731   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
2732
2733   s->list_elt_to_hack_number = (int *)
2734     calloc (sizeof(int), p->screenhacks_count + 1);
2735   s->hack_number_to_list_elt = (int *)
2736     calloc (sizeof(int), p->screenhacks_count + 1);
2737
2738   /* Initialize table to 1:1 mapping */
2739   for (i = 0; i < p->screenhacks_count; i++)
2740     s->list_elt_to_hack_number[i] = i;
2741
2742   /* Generate list of names (once)
2743    */
2744   sort_hack_cmp_names_kludge = (char **)
2745     calloc (sizeof(char *), p->screenhacks_count);
2746   for (i = 0; i < p->screenhacks_count; i++)
2747     {
2748       screenhack *hack = p->screenhacks[i];
2749       char *name = (hack->name && *hack->name
2750                     ? strdup (hack->name)
2751                     : make_hack_name (hack->command));
2752       char *str;
2753       for (str = name; *str; str++)
2754         *str = tolower(*str);
2755       sort_hack_cmp_names_kludge[i] = name;
2756     }
2757
2758   /* Sort alphabetically
2759    */
2760   qsort (s->list_elt_to_hack_number,
2761          p->screenhacks_count,
2762          sizeof(*s->list_elt_to_hack_number),
2763          sort_hack_cmp);
2764
2765   /* Free names
2766    */
2767   for (i = 0; i < p->screenhacks_count; i++)
2768     free (sort_hack_cmp_names_kludge[i]);
2769   free (sort_hack_cmp_names_kludge);
2770   sort_hack_cmp_names_kludge = 0;
2771
2772   /* Build inverse table */
2773   for (i = 0; i < p->screenhacks_count; i++)
2774     s->hack_number_to_list_elt[s->list_elt_to_hack_number[i]] = i;
2775 }
2776
2777
2778 static int
2779 maybe_reload_init_file (state *s)
2780 {
2781   saver_preferences *p = &s->prefs;
2782   int status = 0;
2783
2784   static Bool reentrant_lock = False;
2785   if (reentrant_lock) return 0;
2786   reentrant_lock = True;
2787
2788   if (init_file_changed_p (p))
2789     {
2790       const char *f = init_file_name();
2791       char *b;
2792       int list_elt;
2793       GtkWidget *list;
2794
2795       if (!f || !*f) return 0;
2796       b = (char *) malloc (strlen(f) + 1024);
2797       sprintf (b,
2798                _("Warning:\n\n"
2799                  "file \"%s\" has changed, reloading.\n"),
2800                f);
2801       warning_dialog (s->toplevel_widget, b, False, 100);
2802       free (b);
2803
2804       load_init_file (p);
2805       initialize_sort_map (s);
2806
2807       list_elt = selected_list_element (s);
2808       list = name_to_widget (s, "list");
2809       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
2810       populate_hack_list (s);
2811       force_list_select_item (s, list, list_elt, True);
2812       populate_prefs_page (s);
2813       populate_demo_window (s, list_elt);
2814       ensure_selected_item_visible (list);
2815
2816       status = 1;
2817     }
2818
2819   reentrant_lock = False;
2820   return status;
2821 }
2822
2823
2824 \f
2825 /* Making the preview window have the right X visual (so that GL works.)
2826  */
2827
2828 static Visual *get_best_gl_visual (state *);
2829
2830 static GdkVisual *
2831 x_visual_to_gdk_visual (Visual *xv)
2832 {
2833   GList *gvs = gdk_list_visuals();
2834   if (!xv) return gdk_visual_get_system();
2835   for (; gvs; gvs = gvs->next)
2836     {
2837       GdkVisual *gv = (GdkVisual *) gvs->data;
2838       if (xv == GDK_VISUAL_XVISUAL (gv))
2839         return gv;
2840     }
2841   fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
2842            blurb(), (unsigned long) xv->visualid);
2843   abort();
2844 }
2845
2846 static void
2847 clear_preview_window (state *s)
2848 {
2849   GtkWidget *p;
2850   GdkWindow *window;
2851
2852   if (!s->toplevel_widget) return;  /* very early */
2853   p = name_to_widget (s, "preview");
2854   window = p->window;
2855
2856   if (!window) return;
2857
2858   /* Flush the widget background down into the window, in case a subproc
2859      has changed it. */
2860   gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]);
2861   gdk_window_clear (window);
2862
2863 #ifdef HAVE_GTK2
2864   {
2865     GtkWidget *notebook;
2866
2867     notebook = name_to_widget (s, "preview_notebook");
2868     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
2869                            s->running_preview_error_p
2870                            ? 1 : 0);
2871   }
2872 #else /* !HAVE_GTK2 */
2873   if (s->running_preview_error_p)
2874     {
2875       const char * const lines[] = { N_("No Preview"), N_("Available") };
2876       int lh = p->style->font->ascent + p->style->font->descent;
2877       int y, i;
2878       gint w, h;
2879       gdk_window_get_size (window, &w, &h);
2880       y = (h - (lh * countof(lines))) / 2;
2881       y += p->style->font->ascent;
2882       for (i = 0; i < countof(lines); i++)
2883         {
2884           int sw = gdk_string_width (p->style->font, _(lines[i]));
2885           int x = (w - sw) / 2;
2886           gdk_draw_string (window, p->style->font,
2887                            p->style->fg_gc[GTK_STATE_NORMAL],
2888                            x, y, _(lines[i]));
2889           y += lh;
2890         }
2891     }
2892 #endif /* !HAVE_GTK2 */
2893
2894   gdk_flush ();
2895 }
2896
2897
2898 static void
2899 fix_preview_visual (state *s)
2900 {
2901   GtkWidget *widget = name_to_widget (s, "preview");
2902   Visual *xvisual = get_best_gl_visual (s);
2903   GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
2904   GdkVisual *dvisual = gdk_visual_get_system();
2905   GdkColormap *cmap = (visual == dvisual
2906                        ? gdk_colormap_get_system ()
2907                        : gdk_colormap_new (visual, False));
2908
2909   if (s->debug_p)
2910     fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
2911              (visual == dvisual ? "default" : "non-default"),
2912              (xvisual ? (unsigned long) xvisual->visualid : 0L));
2913
2914   if (!GTK_WIDGET_REALIZED (widget) ||
2915       gtk_widget_get_visual (widget) != visual)
2916     {
2917       gtk_widget_unrealize (widget);
2918       gtk_widget_set_visual (widget, visual);
2919       gtk_widget_set_colormap (widget, cmap);
2920       gtk_widget_realize (widget);
2921     }
2922
2923   /* Set the Widget colors to be white-on-black. */
2924   {
2925     GdkWindow *window = widget->window;
2926     GtkStyle *style = gtk_style_copy (widget->style);
2927     GdkColormap *cmap = gtk_widget_get_colormap (widget);
2928     GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
2929     GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
2930     GdkGC *fgc = gdk_gc_new(window);
2931     GdkGC *bgc = gdk_gc_new(window);
2932     if (!gdk_color_white (cmap, fg)) abort();
2933     if (!gdk_color_black (cmap, bg)) abort();
2934     gdk_gc_set_foreground (fgc, fg);
2935     gdk_gc_set_background (fgc, bg);
2936     gdk_gc_set_foreground (bgc, bg);
2937     gdk_gc_set_background (bgc, fg);
2938     style->fg_gc[GTK_STATE_NORMAL] = fgc;
2939     style->bg_gc[GTK_STATE_NORMAL] = fgc;
2940     gtk_widget_set_style (widget, style);
2941   }
2942
2943   gtk_widget_show (widget);
2944 }
2945
2946 \f
2947 /* Subprocesses
2948  */
2949
2950 static char *
2951 subproc_pretty_name (state *s)
2952 {
2953   if (s->running_preview_cmd)
2954     {
2955       char *ps = strdup (s->running_preview_cmd);
2956       char *ss = strchr (ps, ' ');
2957       if (ss) *ss = 0;
2958       ss = strrchr (ps, '/');
2959       if (ss) *ss = 0;
2960       else ss = ps;
2961       return ss;
2962     }
2963   else
2964     return strdup ("???");
2965 }
2966
2967
2968 static void
2969 reap_zombies (state *s)
2970 {
2971   int wait_status = 0;
2972   pid_t pid;
2973   while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
2974     {
2975       if (s->debug_p)
2976         {
2977           if (pid == s->running_preview_pid)
2978             {
2979               char *ss = subproc_pretty_name (s);
2980               fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(), pid, ss);
2981               free (ss);
2982             }
2983           else
2984             fprintf (stderr, "%s: pid %lu died\n", blurb(), pid);
2985         }
2986     }
2987 }
2988
2989
2990 /* Mostly lifted from driver/subprocs.c */
2991 static Visual *
2992 get_best_gl_visual (state *s)
2993 {
2994   Display *dpy = GDK_DISPLAY();
2995   pid_t forked;
2996   int fds [2];
2997   int in, out;
2998   char buf[1024];
2999
3000   char *av[10];
3001   int ac = 0;
3002
3003   av[ac++] = "xscreensaver-gl-helper";
3004   av[ac] = 0;
3005
3006   if (pipe (fds))
3007     {
3008       perror ("error creating pipe:");
3009       return 0;
3010     }
3011
3012   in = fds [0];
3013   out = fds [1];
3014
3015   switch ((int) (forked = fork ()))
3016     {
3017     case -1:
3018       {
3019         sprintf (buf, "%s: couldn't fork", blurb());
3020         perror (buf);
3021         exit (1);
3022       }
3023     case 0:
3024       {
3025         int stdout_fd = 1;
3026
3027         close (in);  /* don't need this one */
3028         close (ConnectionNumber (dpy));         /* close display fd */
3029
3030         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
3031           {
3032             perror ("could not dup() a new stdout:");
3033             return 0;
3034           }
3035
3036         execvp (av[0], av);                     /* shouldn't return. */
3037
3038         if (errno != ENOENT)
3039           {
3040             /* Ignore "no such file or directory" errors, unless verbose.
3041                Issue all other exec errors, though. */
3042             sprintf (buf, "%s: running %s", blurb(), av[0]);
3043             perror (buf);
3044           }
3045         exit (1);                               /* exits fork */
3046         break;
3047       }
3048     default:
3049       {
3050         int result = 0;
3051         int wait_status = 0;
3052
3053         FILE *f = fdopen (in, "r");
3054         unsigned long v = 0;
3055         char c;
3056
3057         close (out);  /* don't need this one */
3058
3059         *buf = 0;
3060         fgets (buf, sizeof(buf)-1, f);
3061         fclose (f);
3062
3063         /* Wait for the child to die. */
3064         waitpid (-1, &wait_status, 0);
3065
3066         if (1 == sscanf (buf, "0x%x %c", &v, &c))
3067           result = (int) v;
3068
3069         if (result == 0)
3070           {
3071             if (s->debug_p)
3072               fprintf (stderr, "%s: %s did not report a GL visual!\n",
3073                        blurb(), av[0]);
3074             return 0;
3075           }
3076         else
3077           {
3078             Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
3079             if (s->debug_p)
3080               fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
3081                        blurb(), av[0], result);
3082             if (!v) abort();
3083             return v;
3084           }
3085       }
3086     }
3087
3088   abort();
3089 }
3090
3091
3092 static void
3093 kill_preview_subproc (state *s)
3094 {
3095   s->running_preview_error_p = False;
3096
3097   reap_zombies (s);
3098   clear_preview_window (s);
3099
3100   if (s->subproc_check_timer_id)
3101     {
3102       gtk_timeout_remove (s->subproc_check_timer_id);
3103       s->subproc_check_timer_id = 0;
3104       s->subproc_check_countdown = 0;
3105     }
3106
3107   if (s->running_preview_pid)
3108     {
3109       int status = kill (s->running_preview_pid, SIGTERM);
3110       char *ss = subproc_pretty_name (s);
3111
3112       if (status < 0)
3113         {
3114           if (errno == ESRCH)
3115             {
3116               if (s->debug_p)
3117                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
3118                          blurb(), s->running_preview_pid, ss);
3119             }
3120           else
3121             {
3122               char buf [1024];
3123               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
3124                        blurb(), s->running_preview_pid, ss);
3125               perror (buf);
3126             }
3127         }
3128       else if (s->debug_p)
3129         fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
3130                  s->running_preview_pid, ss);
3131
3132       free (ss);
3133       s->running_preview_pid = 0;
3134       if (s->running_preview_cmd) free (s->running_preview_cmd);
3135       s->running_preview_cmd = 0;
3136     }
3137
3138   reap_zombies (s);
3139 }
3140
3141
3142 /* Immediately and unconditionally launches the given process,
3143    after appending the -window-id option; sets running_preview_pid.
3144  */
3145 static void
3146 launch_preview_subproc (state *s)
3147 {
3148   saver_preferences *p = &s->prefs;
3149   Window id;
3150   char *new_cmd = 0;
3151   pid_t forked;
3152   const char *cmd = s->desired_preview_cmd;
3153
3154   GtkWidget *pr = name_to_widget (s, "preview");
3155   GdkWindow *window = pr->window;
3156
3157   s->running_preview_error_p = False;
3158
3159   if (s->preview_suppressed_p)
3160     {
3161       kill_preview_subproc (s);
3162       goto DONE;
3163     }
3164
3165   new_cmd = malloc (strlen (cmd) + 40);
3166
3167   id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3168   if (id == 0)
3169     {
3170       /* No window id?  No command to run. */
3171       free (new_cmd);
3172       new_cmd = 0;
3173     }
3174   else
3175     {
3176       strcpy (new_cmd, cmd);
3177       sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X", id);
3178     }
3179
3180   kill_preview_subproc (s);
3181   if (! new_cmd)
3182     {
3183       s->running_preview_error_p = True;
3184       clear_preview_window (s);
3185       goto DONE;
3186     }
3187
3188   switch ((int) (forked = fork ()))
3189     {
3190     case -1:
3191       {
3192         char buf[255];
3193         sprintf (buf, "%s: couldn't fork", blurb());
3194         perror (buf);
3195         s->running_preview_error_p = True;
3196         goto DONE;
3197         break;
3198       }
3199     case 0:
3200       {
3201         close (ConnectionNumber (GDK_DISPLAY()));
3202
3203         usleep (250000);  /* pause for 1/4th second before launching, to give
3204                              the previous program time to die and flush its X
3205                              buffer, so we don't get leftover turds on the
3206                              window. */
3207
3208         exec_command (p->shell, new_cmd, p->nice_inferior);
3209         /* Don't bother printing an error message when we are unable to
3210            exec subprocesses; we handle that by polling the pid later. */
3211         exit (1);  /* exits child fork */
3212         break;
3213
3214       default:
3215
3216         if (s->running_preview_cmd) free (s->running_preview_cmd);
3217         s->running_preview_cmd = strdup (s->desired_preview_cmd);
3218         s->running_preview_pid = forked;
3219
3220         if (s->debug_p)
3221           {
3222             char *ss = subproc_pretty_name (s);
3223             fprintf (stderr, "%s: forked %lu (%s)\n", blurb(), forked, ss);
3224             free (ss);
3225           }
3226         break;
3227       }
3228     }
3229
3230   schedule_preview_check (s);
3231
3232  DONE:
3233   if (new_cmd) free (new_cmd);
3234   new_cmd = 0;
3235 }
3236
3237
3238 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
3239  */
3240 static void
3241 hack_environment (state *s)
3242 {
3243   static const char *def_path =
3244 # ifdef DEFAULT_PATH_PREFIX
3245     DEFAULT_PATH_PREFIX;
3246 # else
3247     "";
3248 # endif
3249
3250   Display *dpy = GDK_DISPLAY();
3251   const char *odpy = DisplayString (dpy);
3252   char *ndpy = (char *) malloc(strlen(odpy) + 20);
3253   strcpy (ndpy, "DISPLAY=");
3254   strcat (ndpy, odpy);
3255   if (putenv (ndpy))
3256     abort ();
3257
3258   if (s->debug_p)
3259     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
3260
3261   /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
3262      2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
3263      So we must leak it (and/or the previous setting).  Yay.
3264    */
3265
3266   if (def_path && *def_path)
3267     {
3268       const char *opath = getenv("PATH");
3269       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
3270       strcpy (npath, "PATH=");
3271       strcat (npath, def_path);
3272       strcat (npath, ":");
3273       strcat (npath, opath);
3274
3275       if (putenv (npath))
3276         abort ();
3277       /* do not free(npath) -- see above */
3278
3279       if (s->debug_p)
3280         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
3281     }
3282 }
3283
3284
3285 /* Called from a timer:
3286    Launches the currently-chosen subprocess, if it's not already running.
3287    If there's a different process running, kills it.
3288  */
3289 static int
3290 update_subproc_timer (gpointer data)
3291 {
3292   state *s = (state *) data;
3293   if (! s->desired_preview_cmd)
3294     kill_preview_subproc (s);
3295   else if (!s->running_preview_cmd ||
3296            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
3297     launch_preview_subproc (s);
3298
3299   s->subproc_timer_id = 0;
3300   return FALSE;  /* do not re-execute timer */
3301 }
3302
3303
3304 /* Call this when you think you might want a preview process running.
3305    It will set a timer that will actually launch that program a second
3306    from now, if you haven't changed your mind (to avoid double-click
3307    spazzing, etc.)  `cmd' may be null meaning "no process".
3308  */
3309 static void
3310 schedule_preview (state *s, const char *cmd)
3311 {
3312   int delay = 1000 * 0.5;   /* 1/2 second hysteresis */
3313
3314   if (s->debug_p)
3315     {
3316       if (cmd)
3317         fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
3318       else
3319         fprintf (stderr, "%s: scheduling preview death\n", blurb());
3320     }
3321
3322   if (s->desired_preview_cmd) free (s->desired_preview_cmd);
3323   s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
3324
3325   if (s->subproc_timer_id)
3326     gtk_timeout_remove (s->subproc_timer_id);
3327   s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
3328 }
3329
3330
3331 /* Called from a timer:
3332    Checks to see if the subproc that should be running, actually is.
3333  */
3334 static int
3335 check_subproc_timer (gpointer data)
3336 {
3337   state *s = (state *) data;
3338   Bool again_p = True;
3339
3340   if (s->running_preview_error_p ||   /* already dead */
3341       s->running_preview_pid <= 0)
3342     {
3343       again_p = False;
3344     }
3345   else
3346     {
3347       int status;
3348       reap_zombies (s);
3349       status = kill (s->running_preview_pid, 0);
3350       if (status < 0 && errno == ESRCH)
3351         s->running_preview_error_p = True;
3352
3353       if (s->debug_p)
3354         {
3355           char *ss = subproc_pretty_name (s);
3356           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
3357                    s->running_preview_pid, ss,
3358                    (s->running_preview_error_p ? "dead" : "alive"));
3359           free (ss);
3360         }
3361
3362       if (s->running_preview_error_p)
3363         {
3364           clear_preview_window (s);
3365           again_p = False;
3366         }
3367     }
3368
3369   /* Otherwise, it's currently alive.  We might be checking again, or we
3370      might be satisfied. */
3371
3372   if (--s->subproc_check_countdown <= 0)
3373     again_p = False;
3374
3375   if (again_p)
3376     return TRUE;     /* re-execute timer */
3377   else
3378     {
3379       s->subproc_check_timer_id = 0;
3380       s->subproc_check_countdown = 0;
3381       return FALSE;  /* do not re-execute timer */
3382     }
3383 }
3384
3385
3386 /* Call this just after launching a subprocess.
3387    This sets a timer that will, five times a second for two seconds,
3388    check whether the program is still running.  The assumption here
3389    is that if the process didn't stay up for more than a couple of
3390    seconds, then either the program doesn't exist, or it doesn't
3391    take a -window-id argument.
3392  */
3393 static void
3394 schedule_preview_check (state *s)
3395 {
3396   int seconds = 2;
3397   int ticks = 5;
3398
3399   if (s->debug_p)
3400     fprintf (stderr, "%s: scheduling check\n", blurb());
3401
3402   if (s->subproc_check_timer_id)
3403     gtk_timeout_remove (s->subproc_check_timer_id);
3404   s->subproc_check_timer_id =
3405     gtk_timeout_add (1000 / ticks,
3406                      check_subproc_timer, (gpointer) s);
3407   s->subproc_check_countdown = ticks * seconds;
3408 }
3409
3410
3411 static Bool
3412 screen_blanked_p (void)
3413 {
3414   Atom type;
3415   int format;
3416   unsigned long nitems, bytesafter;
3417   CARD32 *data = 0;
3418   Display *dpy = GDK_DISPLAY();
3419   Bool blanked_p = False;
3420
3421   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
3422                           XA_SCREENSAVER_STATUS,
3423                           0, 3, False, XA_INTEGER,
3424                           &type, &format, &nitems, &bytesafter,
3425                           (unsigned char **) &data)
3426       == Success
3427       && type == XA_INTEGER
3428       && nitems >= 3
3429       && data)
3430     blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
3431
3432   if (data) free (data);
3433
3434   return blanked_p;
3435 }
3436
3437 /* Wake up every now and then and see if the screen is blanked.
3438    If it is, kill off the small-window demo -- no point in wasting
3439    cycles by running two screensavers at once...
3440  */
3441 static int
3442 check_blanked_timer (gpointer data)
3443 {
3444   state *s = (state *) data;
3445   Bool blanked_p = screen_blanked_p ();
3446   if (blanked_p && s->running_preview_pid)
3447     {
3448       if (s->debug_p)
3449         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
3450       kill_preview_subproc (s);
3451     }
3452
3453   return True;  /* re-execute timer */
3454 }
3455
3456 \f
3457 /* Setting window manager icon
3458  */
3459
3460 static void
3461 init_icon (GdkWindow *window)
3462 {
3463   GdkBitmap *mask = 0;
3464   GdkColor transp;
3465   GdkPixmap *pixmap =
3466     gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
3467                                   (gchar **) logo_50_xpm);
3468   if (pixmap)
3469     gdk_window_set_icon (window, 0, pixmap, mask);
3470 }
3471
3472 \f
3473 /* The main demo-mode command loop.
3474  */
3475
3476 #if 0
3477 static Bool
3478 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
3479         XrmRepresentation *type, XrmValue *value, XPointer closure)
3480 {
3481   int i;
3482   for (i = 0; quarks[i]; i++)
3483     {
3484       if (bindings[i] == XrmBindTightly)
3485         fprintf (stderr, (i == 0 ? "" : "."));
3486       else if (bindings[i] == XrmBindLoosely)
3487         fprintf (stderr, "*");
3488       else
3489         fprintf (stderr, " ??? ");
3490       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
3491     }
3492
3493   fprintf (stderr, ": %s\n", (char *) value->addr);
3494
3495   return False;
3496 }
3497 #endif
3498
3499
3500 static void
3501 the_network_is_not_the_computer (state *s)
3502 {
3503   Display *dpy = GDK_DISPLAY();
3504   char *rversion = 0, *ruser = 0, *rhost = 0;
3505   char *luser, *lhost;
3506   char *msg = 0;
3507   struct passwd *p = getpwuid (getuid ());
3508   const char *d = DisplayString (dpy);
3509
3510 # if defined(HAVE_UNAME)
3511   struct utsname uts;
3512   if (uname (&uts) < 0)
3513     lhost = "<UNKNOWN>";
3514   else
3515     lhost = uts.nodename;
3516 # elif defined(VMS)
3517   strcpy (lhost, getenv("SYS$NODE"));
3518 # else  /* !HAVE_UNAME && !VMS */
3519   strcat (lhost, "<UNKNOWN>");
3520 # endif /* !HAVE_UNAME && !VMS */
3521
3522   if (p && p->pw_name)
3523     luser = p->pw_name;
3524   else
3525     luser = "???";
3526
3527   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
3528
3529   /* Make a buffer that's big enough for a number of copies of all the
3530      strings, plus some. */
3531   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
3532                                (ruser ? strlen(ruser) : 0) +
3533                                (rhost ? strlen(rhost) : 0) +
3534                                strlen(lhost) +
3535                                strlen(luser) +
3536                                strlen(d) +
3537                                1024));
3538   *msg = 0;
3539
3540   if (!rversion || !*rversion)
3541     {
3542       sprintf (msg,
3543                _("Warning:\n\n"
3544                  "The XScreenSaver daemon doesn't seem to be running\n"
3545                  "on display \"%s\".  Launch it now?"),
3546                d);
3547     }
3548   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
3549     {
3550       /* Warn that the two processes are running as different users.
3551        */
3552       sprintf(msg,
3553             _("Warning:\n\n"
3554               "%s is running as user \"%s\" on host \"%s\".\n"
3555               "But the xscreensaver managing display \"%s\"\n"
3556               "is running as user \"%s\" on host \"%s\".\n"
3557               "\n"
3558               "Since they are different users, they won't be reading/writing\n"
3559               "the same ~/.xscreensaver file, so %s isn't\n"
3560               "going to work right.\n"
3561               "\n"
3562               "You should either re-run %s as \"%s\", or re-run\n"
3563               "xscreensaver as \"%s\".\n"
3564               "\n"
3565               "Restart the xscreensaver daemon now?\n"),
3566               blurb(), luser, lhost,
3567               d,
3568               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
3569               blurb(),
3570               blurb(), (ruser ? ruser : "???"),
3571               luser);
3572     }
3573   else if (rhost && *rhost && !!strcmp (rhost, lhost))
3574     {
3575       /* Warn that the two processes are running on different hosts.
3576        */
3577       sprintf (msg,
3578               _("Warning:\n\n"
3579                "%s is running as user \"%s\" on host \"%s\".\n"
3580                "But the xscreensaver managing display \"%s\"\n"
3581                "is running as user \"%s\" on host \"%s\".\n"
3582                "\n"
3583                "If those two machines don't share a file system (that is,\n"
3584                "if they don't see the same ~%s/.xscreensaver file) then\n"
3585                "%s won't work right.\n"
3586                "\n"
3587                "Restart the daemon on \"%s\" as \"%s\" now?\n"),
3588                blurb(), luser, lhost,
3589                d,
3590                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
3591                luser,
3592                blurb(),
3593                lhost, luser);
3594     }
3595   else if (!!strcmp (rversion, s->short_version))
3596     {
3597       /* Warn that the version numbers don't match.
3598        */
3599       sprintf (msg,
3600              _("Warning:\n\n"
3601                "This is %s version %s.\n"
3602                "But the xscreensaver managing display \"%s\"\n"
3603                "is version %s.  This could cause problems.\n"
3604                "\n"
3605                "Restart the xscreensaver daemon now?\n"),
3606                progname, s->short_version,
3607                d,
3608                rversion);
3609     }
3610
3611
3612   if (*msg)
3613     warning_dialog (s->toplevel_widget, msg, True, 1);
3614
3615   if (rversion) free (rversion);
3616   if (ruser) free (ruser);
3617   if (rhost) free (rhost);
3618   free (msg);
3619 }
3620
3621
3622 /* We use this error handler so that X errors are preceeded by the name
3623    of the program that generated them.
3624  */
3625 static int
3626 demo_ehandler (Display *dpy, XErrorEvent *error)
3627 {
3628   state *s = global_state_kludge;  /* I hate C so much... */
3629   fprintf (stderr, "\nX error in %s:\n", blurb());
3630   XmuPrintDefaultErrorMessage (dpy, error, stderr);
3631   kill_preview_subproc (s);
3632   exit (-1);
3633   return 0;
3634 }
3635
3636
3637 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
3638    of the program that generated them; and also that we can ignore one
3639    particular bogus error message that Gdk madly spews.
3640  */
3641 static void
3642 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
3643                const gchar *message, gpointer user_data)
3644 {
3645   /* Ignore the message "Got event for unknown window: 0x...".
3646      Apparently some events are coming in for the xscreensaver window
3647      (presumably reply events related to the ClientMessage) and Gdk
3648      feels the need to complain about them.  So, just suppress any
3649      messages that look like that one.
3650    */
3651   if (strstr (message, "unknown window"))
3652     return;
3653
3654   fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
3655            (log_domain ? log_domain : progclass),
3656            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
3657             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
3658             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
3659             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
3660             log_level == G_LOG_LEVEL_INFO     ? "info" :
3661             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
3662            message,
3663            ((!*message || message[strlen(message)-1] != '\n')
3664             ? "\n" : ""));
3665 }
3666
3667
3668 static char *defaults[] = {
3669 #include "XScreenSaver_ad.h"
3670  0
3671 };
3672
3673 #if 0
3674 #ifdef HAVE_CRAPPLET
3675 static struct poptOption crapplet_options[] = {
3676   {NULL, '\0', 0, NULL, 0}
3677 };
3678 #endif /* HAVE_CRAPPLET */
3679 #endif /* 0 */
3680
3681 const char *usage = "[--display dpy] [--prefs]"
3682 # ifdef HAVE_CRAPPLET
3683                     " [--crapplet]"
3684 # endif
3685             "\n\t\t   [--debug] [--sync] [--no-xshm]";
3686
3687 static void
3688 map_popup_window_cb (GtkWidget *w, gpointer user_data)
3689 {
3690   state *s = (state *) user_data;
3691   Boolean oi = s->initializing_p;
3692   GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
3693   s->initializing_p = True;
3694   eschew_gtk_lossage (label);
3695   s->initializing_p = oi;
3696 }
3697
3698
3699 #if 0
3700 static void
3701 print_widget_tree (GtkWidget *w, int depth)
3702 {
3703   int i;
3704   for (i = 0; i < depth; i++)
3705     fprintf (stderr, "  ");
3706   fprintf (stderr, "%s\n", gtk_widget_get_name (w));
3707
3708   if (GTK_IS_LIST (w))
3709     {
3710       for (i = 0; i < depth+1; i++)
3711         fprintf (stderr, "  ");
3712       fprintf (stderr, "...list kids...\n");
3713     }
3714   else if (GTK_IS_CONTAINER (w))
3715     {
3716       GList *kids = gtk_container_children (GTK_CONTAINER (w));
3717       while (kids)
3718         {
3719           print_widget_tree (GTK_WIDGET (kids->data), depth+1);
3720           kids = kids->next;
3721         }
3722     }
3723 }
3724 #endif /* 0 */
3725
3726 static int
3727 delayed_scroll_kludge (gpointer data)
3728 {
3729   state *s = (state *) data;
3730   GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
3731   ensure_selected_item_visible (w);
3732
3733   /* Oh, this is just fucking lovely, too. */
3734   w = GTK_WIDGET (name_to_widget (s, "preview"));
3735   gtk_widget_hide (w);
3736   gtk_widget_show (w);
3737
3738   return FALSE;  /* do not re-execute timer */
3739 }
3740
3741 #ifdef HAVE_GTK2
3742
3743 GtkWidget *
3744 create_xscreensaver_demo (void)
3745 {
3746   GtkWidget *nb;
3747
3748   nb = name_to_widget (global_state_kludge, "preview_notebook");
3749   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
3750
3751   return name_to_widget (global_state_kludge, "xscreensaver_demo");
3752 }
3753
3754 GtkWidget *
3755 create_xscreensaver_settings_dialog (void)
3756 {
3757   GtkWidget *w, *box;
3758
3759   box = name_to_widget (global_state_kludge, "dialog_action_area");
3760
3761   w = name_to_widget (global_state_kludge, "adv_button");
3762   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
3763
3764   w = name_to_widget (global_state_kludge, "std_button");
3765   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
3766
3767   return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
3768 }
3769
3770 #endif /* HAVE_GTK2 */
3771
3772 int
3773 main (int argc, char **argv)
3774 {
3775   XtAppContext app;
3776   state S, *s;
3777   saver_preferences *p;
3778   Bool prefs = False;
3779   int i;
3780   Display *dpy;
3781   Widget toplevel_shell;
3782   char *real_progname = argv[0];
3783   char window_title[255];
3784   Bool crapplet_p = False;
3785   char *str;
3786
3787 #ifdef ENABLE_NLS
3788   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
3789   textdomain (GETTEXT_PACKAGE);
3790
3791 # ifdef HAVE_GTK2
3792   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
3793 # else  /* !HAVE_GTK2 */
3794   if (!setlocale (LC_ALL, ""))
3795     fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
3796 # endif /* !HAVE_GTK2 */
3797
3798 #endif /* ENABLE_NLS */
3799
3800   str = strrchr (real_progname, '/');
3801   if (str) real_progname = str+1;
3802
3803   s = &S;
3804   memset (s, 0, sizeof(*s));
3805   s->initializing_p = True;
3806   p = &s->prefs;
3807
3808   global_state_kludge = s;  /* I hate C so much... */
3809
3810   progname = real_progname;
3811
3812   s->short_version = (char *) malloc (5);
3813   memcpy (s->short_version, screensaver_id + 17, 4);
3814   s->short_version [4] = 0;
3815
3816
3817   /* Register our error message logger for every ``log domain'' known.
3818      There's no way to do this globally, so I grepped the Gtk/Gdk sources
3819      for all of the domains that seem to be in use.
3820   */
3821   {
3822     const char * const domains[] = { 0,
3823                                      "Gtk", "Gdk", "GLib", "GModule",
3824                                      "GThread", "Gnome", "GnomeUI" };
3825     for (i = 0; i < countof(domains); i++)
3826       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
3827   }
3828
3829 #ifdef DEFAULT_ICONDIR  /* from -D on compile line */
3830 # ifndef HAVE_GTK2
3831   {
3832     const char *dir = DEFAULT_ICONDIR;
3833     if (*dir) add_pixmap_directory (dir);
3834   }
3835 # endif /* !HAVE_GTK2 */
3836 #endif /* DEFAULT_ICONDIR */
3837
3838   /* This is gross, but Gtk understands --display and not -display...
3839    */
3840   for (i = 1; i < argc; i++)
3841     if (argv[i][0] && argv[i][1] && 
3842         !strncmp(argv[i], "-display", strlen(argv[i])))
3843       argv[i] = "--display";
3844
3845
3846   /* We need to parse this arg really early... Sigh. */
3847   for (i = 1; i < argc; i++)
3848     if (argv[i] &&
3849         (!strcmp(argv[i], "--crapplet") ||
3850          !strcmp(argv[i], "--capplet")))
3851       {
3852 # if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
3853         int j;
3854         crapplet_p = True;
3855         for (j = i; j < argc; j++)  /* remove it from the list */
3856           argv[j] = argv[j+1];
3857         argc--;
3858 # else  /* !HAVE_CRAPPLET && !HAVE_GTK2 */
3859         fprintf (stderr, "%s: not compiled with --crapplet support\n",
3860                  real_progname);
3861         fprintf (stderr, "%s: %s\n", real_progname, usage);
3862         exit (1);
3863 # endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
3864       }
3865   else if (argv[i] &&
3866            (!strcmp(argv[i], "--debug") ||
3867             !strcmp(argv[i], "-debug") ||
3868             !strcmp(argv[i], "-d")))
3869     {
3870       int j;
3871       s->debug_p = True;
3872       for (j = i; j < argc; j++)  /* remove it from the list */
3873         argv[j] = argv[j+1];
3874       argc--;
3875     }
3876
3877   /* Let Gtk open the X connection, then initialize Xt to use that
3878      same connection.  Doctor Frankenstein would be proud.
3879    */
3880 # ifdef HAVE_CRAPPLET
3881   if (crapplet_p)
3882     {
3883       GnomeClient *client;
3884       GnomeClientFlags flags = 0;
3885
3886       int init_results = gnome_capplet_init ("screensaver-properties",
3887                                              s->short_version,
3888                                              argc, argv, NULL, 0, NULL);
3889       /* init_results is:
3890          0 upon successful initialization;
3891          1 if --init-session-settings was passed on the cmdline;
3892          2 if --ignore was passed on the cmdline;
3893         -1 on error.
3894
3895          So the 1 signifies just to init the settings, and quit, basically.
3896          (Meaning launch the xscreensaver daemon.)
3897        */
3898
3899       if (init_results < 0)
3900         {
3901 #  if 0
3902           g_error ("An initialization error occurred while "
3903                    "starting xscreensaver-capplet.\n");
3904 #  else  /* !0 */
3905           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
3906                    real_progname, init_results);
3907           exit (1);
3908 #  endif /* !0 */
3909         }
3910
3911       client = gnome_master_client ();
3912
3913       if (client)
3914         flags = gnome_client_get_flags (client);
3915
3916       if (flags & GNOME_CLIENT_IS_CONNECTED)
3917         {
3918           int token =
3919             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
3920                                          gnome_client_get_id (client));
3921           if (token)
3922             {
3923               char *session_args[20];
3924               int i = 0;
3925               session_args[i++] = real_progname;
3926               session_args[i++] = "--capplet";
3927               session_args[i++] = "--init-session-settings";
3928               session_args[i] = 0;
3929               gnome_client_set_priority (client, 20);
3930               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
3931               gnome_client_set_restart_command (client, i, session_args);
3932             }
3933           else
3934             {
3935               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
3936             }
3937
3938           gnome_client_flush (client);
3939         }
3940
3941       if (init_results == 1)
3942         {
3943           system ("xscreensaver -nosplash &");
3944           return 0;
3945         }
3946
3947     }
3948   else
3949 # endif /* HAVE_CRAPPLET */
3950     {
3951       gtk_init (&argc, &argv);
3952     }
3953
3954
3955   /* We must read exactly the same resources as xscreensaver.
3956      That means we must have both the same progclass *and* progname,
3957      at least as far as the resource database is concerned.  So,
3958      put "xscreensaver" in argv[0] while initializing Xt.
3959    */
3960   argv[0] = "xscreensaver";
3961   progname = argv[0];
3962
3963
3964   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
3965    */
3966   XtToolkitInitialize ();
3967   app = XtCreateApplicationContext ();
3968   dpy = GDK_DISPLAY();
3969   XtAppSetFallbackResources (app, defaults);
3970   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
3971   toplevel_shell = XtAppCreateShell (progname, progclass,
3972                                      applicationShellWidgetClass,
3973                                      dpy, 0, 0);
3974
3975   dpy = XtDisplay (toplevel_shell);
3976   db = XtDatabase (dpy);
3977   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
3978   XSetErrorHandler (demo_ehandler);
3979
3980   /* Let's just ignore these.  They seem to confuse Irix Gtk... */
3981   signal (SIGPIPE, SIG_IGN);
3982
3983   /* After doing Xt-style command-line processing, complain about any
3984      unrecognized command-line arguments.
3985    */
3986   for (i = 1; i < argc; i++)
3987     {
3988       char *str = argv[i];
3989       if (str[0] == '-' && str[1] == '-')
3990         str++;
3991       if (!strcmp (str, "-prefs"))
3992         prefs = True;
3993       else if (crapplet_p)
3994         /* There are lots of random args that we don't care about when we're
3995            started as a crapplet, so just ignore unknown args in that case. */
3996         ;
3997       else
3998         {
3999           fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, argv[i]);
4000           fprintf (stderr, "%s: %s\n", real_progname, usage);
4001           exit (1);
4002         }
4003     }
4004
4005   /* Load the init file, which may end up consulting the X resource database
4006      and the site-wide app-defaults file.  Note that at this point, it's
4007      important that `progname' be "xscreensaver", rather than whatever
4008      was in argv[0].
4009    */
4010   p->db = db;
4011   load_init_file (p);
4012   initialize_sort_map (s);
4013
4014   /* Now that Xt has been initialized, and the resources have been read,
4015      we can set our `progname' variable to something more in line with
4016      reality.
4017    */
4018   progname = real_progname;
4019
4020
4021 #if 0
4022   /* Print out all the resources we read. */
4023   {
4024     XrmName name = { 0 };
4025     XrmClass class = { 0 };
4026     int count = 0;
4027     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
4028                           (POINTER) &count);
4029   }
4030 #endif
4031
4032
4033   /* Intern the atoms that xscreensaver_command() needs.
4034    */
4035   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
4036   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
4037   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
4038   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
4039   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
4040   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
4041   XA_SELECT = XInternAtom (dpy, "SELECT", False);
4042   XA_DEMO = XInternAtom (dpy, "DEMO", False);
4043   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
4044   XA_BLANK = XInternAtom (dpy, "BLANK", False);
4045   XA_LOCK = XInternAtom (dpy, "LOCK", False);
4046   XA_EXIT = XInternAtom (dpy, "EXIT", False);
4047   XA_RESTART = XInternAtom (dpy, "RESTART", False);
4048
4049
4050   /* Create the window and all its widgets.
4051    */
4052   s->base_widget     = create_xscreensaver_demo ();
4053   s->popup_widget    = create_xscreensaver_settings_dialog ();
4054   s->toplevel_widget = s->base_widget;
4055
4056
4057   /* Set the main window's title. */
4058   {
4059     char *v = (char *) strdup(strchr(screensaver_id, ' '));
4060     char *s1, *s2, *s3, *s4;
4061     s1 = (char *) strchr(v,  ' '); s1++;
4062     s2 = (char *) strchr(s1, ' ');
4063     s3 = (char *) strchr(v,  '('); s3++;
4064     s4 = (char *) strchr(s3, ')');
4065     *s2 = 0;
4066     *s4 = 0;
4067     sprintf (window_title, "%.50s %.50s, %.50s", progclass, s1, s3);
4068     gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
4069     gtk_window_set_title (GTK_WINDOW (s->popup_widget),    window_title);
4070     free (v);
4071   }
4072
4073   /* Adjust the (invisible) notebooks on the popup dialog... */
4074   {
4075     GtkNotebook *notebook =
4076       GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
4077     GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
4078     int page = 0;
4079
4080 # ifdef HAVE_XML
4081     gtk_widget_hide (std);
4082 # else  /* !HAVE_XML */
4083     /* Make the advanced page be the only one available. */
4084     gtk_widget_set_sensitive (std, False);
4085     std = GTK_WIDGET (name_to_widget (s, "adv_button"));
4086     gtk_widget_hide (std);
4087     page = 1;
4088 # endif /* !HAVE_XML */
4089
4090     gtk_notebook_set_page (notebook, page);
4091     gtk_notebook_set_show_tabs (notebook, False);
4092   }
4093
4094   /* Various other widget initializations...
4095    */
4096   gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
4097                       GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
4098                       (gpointer) s);
4099   gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
4100                       GTK_SIGNAL_FUNC (wm_popup_close_cb),
4101                       (gpointer) s);
4102
4103   populate_hack_list (s);
4104   populate_prefs_page (s);
4105   sensitize_demo_widgets (s, False);
4106   fix_text_entry_sizes (s);
4107   scroll_to_current_hack (s);
4108
4109   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
4110                       "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
4111                       (gpointer) s);
4112
4113 #ifndef HAVE_GTK2
4114   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
4115                       "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
4116                       (gpointer) s);
4117   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
4118                       "map", GTK_SIGNAL_FUNC(map_next_button_cb),
4119                       (gpointer) s);
4120 #endif /* !HAVE_GTK2 */
4121
4122   /* Hook up callbacks to the items on the mode menu. */
4123   {
4124     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
4125     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
4126     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
4127     for (; kids; kids = kids->next)
4128       gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
4129                           GTK_SIGNAL_FUNC (mode_menu_item_cb),
4130                           (gpointer) s);
4131   }
4132
4133
4134   /* Handle the -prefs command-line argument. */
4135   if (prefs)
4136     {
4137       GtkNotebook *notebook =
4138         GTK_NOTEBOOK (name_to_widget (s, "notebook"));
4139       gtk_notebook_set_page (notebook, 1);
4140     }
4141
4142 # ifdef HAVE_CRAPPLET
4143   if (crapplet_p)
4144     {
4145       GtkWidget *capplet;
4146       GtkWidget *outer_vbox;
4147
4148       gtk_widget_hide (s->toplevel_widget);
4149
4150       capplet = capplet_widget_new ();
4151
4152       /* Make there be a "Close" button instead of "OK" and "Cancel" */
4153 # ifdef HAVE_CRAPPLET_IMMEDIATE
4154       capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
4155 # endif /* HAVE_CRAPPLET_IMMEDIATE */
4156
4157       /* In crapplet-mode, take off the menubar. */
4158       gtk_widget_hide (name_to_widget (s, "menubar"));
4159
4160       /* Reparent our top-level container to be a child of the capplet
4161          window.
4162        */
4163       outer_vbox = GTK_BIN (s->toplevel_widget)->child;
4164       gtk_widget_ref (outer_vbox);
4165       gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
4166                             outer_vbox);
4167       GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
4168       gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
4169
4170       /* Find the window above us, and set the title and close handler. */
4171       {
4172         GtkWidget *window = capplet;
4173         while (window && !GTK_IS_WINDOW (window))
4174           window = window->parent;
4175         if (window)
4176           {
4177             gtk_window_set_title (GTK_WINDOW (window), window_title);
4178             gtk_signal_connect (GTK_OBJECT (window), "delete_event",
4179                                 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
4180                                 (gpointer) s);
4181           }
4182       }
4183
4184       s->toplevel_widget = capplet;
4185     }
4186 # endif /* HAVE_CRAPPLET */
4187
4188
4189   gtk_widget_show (s->toplevel_widget);
4190   init_icon (GTK_WIDGET (s->toplevel_widget)->window);  /* after `show' */
4191   hack_environment (s);
4192   fix_preview_visual (s);
4193
4194   /* Realize page zero, so that we can diddle the scrollbar when the
4195      user tabs back to it -- otherwise, the current hack isn't scrolled
4196      to the first time they tab back there, when started with "-prefs".
4197      (Though it is if they then tab away, and back again.)
4198
4199      #### Bah!  This doesn't work.  Gtk eats my ass!  Someone who
4200      #### understands this crap, explain to me how to make this work.
4201   */
4202   gtk_widget_realize (name_to_widget (s, "demos_table"));
4203
4204
4205   gtk_timeout_add (60 * 1000, check_blanked_timer, s);
4206
4207
4208   /* Issue any warnings about the running xscreensaver daemon. */
4209   the_network_is_not_the_computer (s);
4210
4211
4212   /* Run the Gtk event loop, and not the Xt event loop.  This means that
4213      if there were Xt timers or fds registered, they would never get serviced,
4214      and if there were any Xt widgets, they would never have events delivered.
4215      Fortunately, we're using Gtk for all of the UI, and only initialized
4216      Xt so that we could process the command line and use the X resource
4217      manager.
4218    */
4219   s->initializing_p = False;
4220
4221   /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
4222      after we start up.  Otherwise, it always appears scrolled to the top
4223      when in crapplet-mode. */
4224   gtk_timeout_add (500, delayed_scroll_kludge, s);
4225
4226
4227 #if 0
4228   /* Load every configurator in turn, to scan them for errors all at once. */
4229   {
4230     int i;
4231     for (i = 0; i < p->screenhacks_count; i++)
4232       {
4233         screenhack *hack = p->screenhacks[s->hack_number_to_list_elt[i]];
4234         conf_data *d = load_configurator (hack->command, False);
4235         if (d) free_conf_data (d);
4236       }
4237   }
4238 #endif
4239
4240
4241 # ifdef HAVE_CRAPPLET
4242   if (crapplet_p)
4243     capplet_gtk_main ();
4244   else
4245 # endif /* HAVE_CRAPPLET */
4246     gtk_main ();
4247
4248   kill_preview_subproc (s);
4249   exit (0);
4250 }
4251
4252 #endif /* HAVE_GTK -- whole file */