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