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