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