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