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