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