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