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