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