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