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