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