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