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