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