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