a1d05ca89273a439ce78e065c73a21698cd5631f
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2002 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #ifdef HAVE_GTK /* whole file */
18
19 #include <xscreensaver-intl.h>
20
21 #include <stdlib.h>
22
23 #ifdef HAVE_UNISTD_H
24 # include <unistd.h>
25 #endif
26
27 #ifdef ENABLE_NLS
28 # include <locale.h>
29 #endif /* ENABLE_NLS */
30
31 #ifndef VMS
32 # include <pwd.h>               /* for getpwuid() */
33 #else /* VMS */
34 # include "vms-pwd.h"
35 #endif /* VMS */
36
37 #ifdef HAVE_UNAME
38 # include <sys/utsname.h>       /* for uname() */
39 #endif /* HAVE_UNAME */
40
41 #include <stdio.h>
42 #include <time.h>
43 #include <sys/stat.h>
44 #include <sys/time.h>
45
46
47 #include <signal.h>
48 #include <errno.h>
49 #ifdef HAVE_SYS_WAIT_H
50 # include <sys/wait.h>          /* for waitpid() and associated macros */
51 #endif
52
53
54 #include <X11/Xproto.h>         /* for CARD32 */
55 #include <X11/Xatom.h>          /* for XA_INTEGER */
56 #include <X11/Intrinsic.h>
57 #include <X11/StringDefs.h>
58
59 /* We don't actually use any widget internals, but these are included
60    so that gdb will have debug info for the widgets... */
61 #include <X11/IntrinsicP.h>
62 #include <X11/ShellP.h>
63
64 #ifdef HAVE_XMU
65 # ifndef VMS
66 #  include <X11/Xmu/Error.h>
67 # else /* VMS */
68 #  include <Xmu/Error.h>
69 # endif
70 #else
71 # include "xmu.h"
72 #endif
73
74 #include <gtk/gtk.h>
75
76 #ifdef HAVE_CRAPPLET
77 # include <gnome.h>
78 # include <capplet-widget.h>
79 #endif
80
81 #include <gdk/gdkx.h>
82
83 #ifdef HAVE_GTK2
84 #include <glade/glade-xml.h>
85 #endif /* HAVE_GTK2 */
86
87 #if defined(DEFAULT_ICONDIR) && !defined(GLADE_DIR)
88 # define GLADE_DIR DEFAULT_ICONDIR
89 #endif
90 #if !defined(DEFAULT_ICONDIR) && defined(GLADE_DIR)
91 # define DEFAULT_ICONDIR GLADE_DIR
92 #endif
93
94 #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   /* The file supports timeouts of less than a minute, but the GUI does
2349      not, so throttle the values to be at least one minute (since "0" is
2350      a bad rounding choice...)
2351    */
2352 # define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
2353   THROTTLE (timeout);
2354   THROTTLE (cycle);
2355   THROTTLE (passwd_timeout);
2356 # undef THROTTLE
2357
2358 # define FMT_MINUTES(NAME,N) \
2359     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
2360
2361 # define FMT_SECONDS(NAME,N) \
2362     gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
2363
2364   FMT_MINUTES ("timeout_spinbutton",      p->timeout);
2365   FMT_MINUTES ("cycle_spinbutton",        p->cycle);
2366   FMT_MINUTES ("lock_spinbutton",         p->lock_timeout);
2367   FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
2368   FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
2369   FMT_MINUTES ("dpms_off_spinbutton",     p->dpms_off);
2370   FMT_SECONDS ("fade_spinbutton",         p->fade_seconds);
2371
2372 # undef FMT_MINUTES
2373 # undef FMT_SECONDS
2374
2375 # define TOGGLE_ACTIVE(NAME,ACTIVEP) \
2376   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
2377                                 (ACTIVEP))
2378
2379   TOGGLE_ACTIVE ("lock_button",       p->lock_p);
2380   TOGGLE_ACTIVE ("verbose_button",    p->verbose_p);
2381   TOGGLE_ACTIVE ("capture_button",    p->capture_stderr_p);
2382   TOGGLE_ACTIVE ("splash_button",     p->splash_p);
2383   TOGGLE_ACTIVE ("dpms_button",       p->dpms_enabled_p);
2384   TOGGLE_ACTIVE ("grab_desk_button",  p->grab_desktop_p);
2385   TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
2386   TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
2387   TOGGLE_ACTIVE ("install_button",    p->install_cmap_p);
2388   TOGGLE_ACTIVE ("fade_button",       p->fade_p);
2389   TOGGLE_ACTIVE ("unfade_button",     p->unfade_p);
2390
2391 # undef TOGGLE_ACTIVE
2392
2393   gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
2394                       (p->image_directory ? p->image_directory : ""));
2395   gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
2396                             p->random_image_p);
2397   gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
2398                             p->random_image_p);
2399
2400   /* Map the `saver_mode' enum to mode menu to values. */
2401   {
2402     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
2403
2404     int i;
2405     for (i = 0; i < countof(mode_menu_order); i++)
2406       if (mode_menu_order[i] == p->mode)
2407         break;
2408     gtk_option_menu_set_history (opt, i);
2409     update_list_sensitivity (s);
2410   }
2411
2412   {
2413     Bool found_any_writable_cells = False;
2414     Bool dpms_supported = False;
2415
2416     Display *dpy = GDK_DISPLAY();
2417     int nscreens = ScreenCount(dpy);
2418     int i;
2419     for (i = 0; i < nscreens; i++)
2420       {
2421         Screen *s = ScreenOfDisplay (dpy, i);
2422         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
2423           {
2424             found_any_writable_cells = True;
2425             break;
2426           }
2427       }
2428
2429 #ifdef HAVE_XF86VMODE_GAMMA
2430     found_any_writable_cells = True;  /* if we can gamma fade, go for it */
2431 #endif
2432
2433 #ifdef HAVE_DPMS_EXTENSION
2434     {
2435       int op = 0, event = 0, error = 0;
2436       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
2437         dpms_supported = True;
2438     }
2439 #endif /* HAVE_DPMS_EXTENSION */
2440
2441
2442 # define SENSITIZE(NAME,SENSITIVEP) \
2443     gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
2444
2445     /* Blanking and Locking
2446      */
2447     SENSITIZE ("lock_spinbutton", p->lock_p);
2448     SENSITIZE ("lock_mlabel",     p->lock_p);
2449
2450     /* DPMS
2451      */
2452     SENSITIZE ("dpms_frame",              dpms_supported);
2453     SENSITIZE ("dpms_button",             dpms_supported);
2454     SENSITIZE ("dpms_standby_label",      dpms_supported && p->dpms_enabled_p);
2455     SENSITIZE ("dpms_standby_mlabel",     dpms_supported && p->dpms_enabled_p);
2456     SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
2457     SENSITIZE ("dpms_suspend_label",      dpms_supported && p->dpms_enabled_p);
2458     SENSITIZE ("dpms_suspend_mlabel",     dpms_supported && p->dpms_enabled_p);
2459     SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
2460     SENSITIZE ("dpms_off_label",          dpms_supported && p->dpms_enabled_p);
2461     SENSITIZE ("dpms_off_mlabel",         dpms_supported && p->dpms_enabled_p);
2462     SENSITIZE ("dpms_off_spinbutton",     dpms_supported && p->dpms_enabled_p);
2463
2464     /* Colormaps
2465      */
2466     SENSITIZE ("cmap_frame",      found_any_writable_cells);
2467     SENSITIZE ("install_button",  found_any_writable_cells);
2468     SENSITIZE ("fade_button",     found_any_writable_cells);
2469     SENSITIZE ("unfade_button",   found_any_writable_cells);
2470
2471     SENSITIZE ("fade_label",      (found_any_writable_cells &&
2472                                    (p->fade_p || p->unfade_p)));
2473     SENSITIZE ("fade_spinbutton", (found_any_writable_cells &&
2474                                    (p->fade_p || p->unfade_p)));
2475
2476 # undef SENSITIZE
2477   }
2478 }
2479
2480
2481 static void
2482 populate_popup_window (state *s)
2483 {
2484   GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
2485   char *doc_string = 0;
2486
2487   /* #### not in Gtk 1.2
2488   gtk_label_set_selectable (doc);
2489    */
2490
2491 # ifdef HAVE_XML
2492   if (s->cdata)
2493     {
2494       free_conf_data (s->cdata);
2495       s->cdata = 0;
2496     }
2497
2498   {
2499     saver_preferences *p = &s->prefs;
2500     int list_elt = selected_list_element (s);
2501     int hack_number = (list_elt >= 0 && list_elt < s->list_count
2502                        ? s->list_elt_to_hack_number[list_elt]
2503                        : -1);
2504     screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2505     if (hack)
2506       {
2507         GtkWidget *parent = name_to_widget (s, "settings_vbox");
2508         GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2509         const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
2510         s->cdata = load_configurator (cmd_line, s->debug_p);
2511         if (s->cdata && s->cdata->widget)
2512           gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
2513                               TRUE, TRUE, 0);
2514       }
2515   }
2516
2517   doc_string = (s->cdata
2518                 ? s->cdata->description
2519                 : 0);
2520 # else  /* !HAVE_XML */
2521   doc_string = _("Descriptions not available: no XML support compiled in.");
2522 # endif /* !HAVE_XML */
2523
2524   gtk_label_set_text (doc, (doc_string
2525                             ? _(doc_string)
2526                             : _("No description available.")));
2527 }
2528
2529
2530 static void
2531 sensitize_demo_widgets (state *s, Bool sensitive_p)
2532 {
2533   const char *names1[] = { "demo", "settings" };
2534   const char *names2[] = { "cmd_label", "cmd_text", "manual",
2535                            "visual", "visual_combo" };
2536   int i;
2537   for (i = 0; i < countof(names1); i++)
2538     {
2539       GtkWidget *w = name_to_widget (s, names1[i]);
2540       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2541     }
2542   for (i = 0; i < countof(names2); i++)
2543     {
2544       GtkWidget *w = name_to_widget (s, names2[i]);
2545       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
2546     }
2547 }
2548
2549
2550 /* Even though we've given these text fields a maximum number of characters,
2551    their default size is still about 30 characters wide -- so measure out
2552    a string in their font, and resize them to just fit that.
2553  */
2554 static void
2555 fix_text_entry_sizes (state *s)
2556 {
2557   GtkWidget *w;
2558
2559 # if 0   /* appears no longer necessary with Gtk 1.2.10 */
2560   const char * const spinbuttons[] = {
2561     "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
2562     "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
2563     "dpms_off_spinbutton",
2564     "-fade_spinbutton" };
2565   int i;
2566   int width = 0;
2567
2568   for (i = 0; i < countof(spinbuttons); i++)
2569     {
2570       const char *n = spinbuttons[i];
2571       int cols = 4;
2572       while (*n == '-') n++, cols--;
2573       w = GTK_WIDGET (name_to_widget (s, n));
2574       width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
2575       gtk_widget_set_usize (w, width, -2);
2576     }
2577
2578   /* Now fix the width of the combo box.
2579    */
2580   w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
2581   w = GTK_COMBO (w)->entry;
2582   width = gdk_string_width (w->style->font, "PseudoColor___");
2583   gtk_widget_set_usize (w, width, -2);
2584
2585   /* Now fix the width of the file entry text.
2586    */
2587   w = GTK_WIDGET (name_to_widget (s, "image_text"));
2588   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
2589   gtk_widget_set_usize (w, width, -2);
2590
2591   /* Now fix the width of the command line text.
2592    */
2593   w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
2594   width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
2595   gtk_widget_set_usize (w, width, -2);
2596
2597 # endif /* 0 */
2598
2599   /* Now fix the height of the list widget:
2600      make it default to being around 10 text-lines high instead of 4.
2601    */
2602   w = GTK_WIDGET (name_to_widget (s, "list"));
2603   {
2604     int lines = 10;
2605     int height;
2606     int leading = 3;  /* approximate is ok... */
2607     int border = 2;
2608
2609 #ifdef HAVE_GTK2
2610     PangoFontMetrics *pain =
2611       pango_context_get_metrics (gtk_widget_get_pango_context (w),
2612                                  w->style->font_desc,
2613                                  gtk_get_default_language ());
2614     height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
2615                            pango_font_metrics_get_descent (pain));
2616 #else  /* !HAVE_GTK2 */
2617     height = w->style->font->ascent + w->style->font->descent;
2618 #endif /* !HAVE_GTK2 */
2619
2620     height += leading;
2621     height *= lines;
2622     height += border * 2;
2623     w = GTK_WIDGET (name_to_widget (s, "scroller"));
2624     gtk_widget_set_usize (w, -2, height);
2625   }
2626 }
2627
2628
2629 #ifndef HAVE_GTK2
2630 \f
2631 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
2632  */
2633
2634 static char *up_arrow_xpm[] = {
2635   "15 15 4 1",
2636   "     c None",
2637   "-    c #FFFFFF",
2638   "+    c #D6D6D6",
2639   "@    c #000000",
2640
2641   "       @       ",
2642   "       @       ",
2643   "      -+@      ",
2644   "      -+@      ",
2645   "     -+++@     ",
2646   "     -+++@     ",
2647   "    -+++++@    ",
2648   "    -+++++@    ",
2649   "   -+++++++@   ",
2650   "   -+++++++@   ",
2651   "  -+++++++++@  ",
2652   "  -+++++++++@  ",
2653   " -+++++++++++@ ",
2654   " @@@@@@@@@@@@@ ",
2655   "               ",
2656
2657   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
2658      the end of the array (Gtk 1.2.5.) */
2659   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
2660   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
2661 };
2662
2663 static char *down_arrow_xpm[] = {
2664   "15 15 4 1",
2665   "     c None",
2666   "-    c #FFFFFF",
2667   "+    c #D6D6D6",
2668   "@    c #000000",
2669
2670   "               ",
2671   " ------------- ",
2672   " -+++++++++++@ ",
2673   "  -+++++++++@  ",
2674   "  -+++++++++@  ",
2675   "   -+++++++@   ",
2676   "   -+++++++@   ",
2677   "    -+++++@    ",
2678   "    -+++++@    ",
2679   "     -+++@     ",
2680   "     -+++@     ",
2681   "      -+@      ",
2682   "      -+@      ",
2683   "       @       ",
2684   "       @       ",
2685
2686   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
2687      the end of the array (Gtk 1.2.5.) */
2688   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
2689   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
2690 };
2691
2692 static void
2693 pixmapify_button (state *s, int down_p)
2694 {
2695   GdkPixmap *pixmap;
2696   GdkBitmap *mask;
2697   GtkWidget *pixmapwid;
2698   GtkStyle *style;
2699   GtkWidget *w;
2700
2701   w = GTK_WIDGET (name_to_widget (s, (down_p ? "next" : "prev")));
2702   style = gtk_widget_get_style (w);
2703   mask = 0;
2704   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
2705                                          &style->bg[GTK_STATE_NORMAL],
2706                                          (down_p
2707                                           ? (gchar **) down_arrow_xpm
2708                                           : (gchar **) up_arrow_xpm));
2709   pixmapwid = gtk_pixmap_new (pixmap, mask);
2710   gtk_widget_show (pixmapwid);
2711   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
2712   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
2713 }
2714
2715 static void
2716 map_next_button_cb (GtkWidget *w, gpointer user_data)
2717 {
2718   state *s = (state *) user_data;
2719   pixmapify_button (s, 1);
2720 }
2721
2722 static void
2723 map_prev_button_cb (GtkWidget *w, gpointer user_data)
2724 {
2725   state *s = (state *) user_data;
2726   pixmapify_button (s, 0);
2727 }
2728 #endif /* !HAVE_GTK2 */
2729
2730 \f
2731 /* Work around a Gtk bug that causes label widgets to wrap text too early.
2732  */
2733
2734 static void
2735 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
2736                                              GtkAllocation *allocation,
2737                                              void *foo)
2738 {
2739   GtkRequisition req;
2740   GtkWidgetAuxInfo *aux_info;
2741
2742   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
2743
2744   aux_info->width = allocation->width;
2745   aux_info->height = -2;
2746   aux_info->x = -1;
2747   aux_info->y = -1;
2748
2749   gtk_widget_size_request (label, &req);
2750 }
2751
2752
2753 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
2754  */
2755 static void
2756 eschew_gtk_lossage (GtkLabel *label)
2757 {
2758   GtkWidgetAuxInfo *aux_info = g_new0 (GtkWidgetAuxInfo, 1);
2759   aux_info->width = GTK_WIDGET (label)->allocation.width;
2760   aux_info->height = -2;
2761   aux_info->x = -1;
2762   aux_info->y = -1;
2763
2764   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
2765
2766   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
2767                       GTK_SIGNAL_FUNC (you_are_not_a_unique_or_beautiful_snowflake),
2768                       0);
2769
2770   gtk_widget_set_usize (GTK_WIDGET (label), -2, -2);
2771
2772   gtk_widget_queue_resize (GTK_WIDGET (label));
2773 }
2774
2775
2776 static void
2777 populate_demo_window (state *s, int list_elt)
2778 {
2779   saver_preferences *p = &s->prefs;
2780   screenhack *hack;
2781   char *pretty_name;
2782   GtkFrame *frame1 = GTK_FRAME (name_to_widget (s, "preview_frame"));
2783   GtkFrame *frame2 = GTK_FRAME (name_to_widget (s, "doc_frame"));
2784   GtkEntry *cmd    = GTK_ENTRY (name_to_widget (s, "cmd_text"));
2785   GtkCombo *vis    = GTK_COMBO (name_to_widget (s, "visual_combo"));
2786   GtkWidget *list  = GTK_WIDGET (name_to_widget (s, "list"));
2787
2788   if (p->mode == BLANK_ONLY)
2789     {
2790       hack = 0;
2791       pretty_name = strdup (_("Blank Screen"));
2792       schedule_preview (s, 0);
2793     }
2794   else if (p->mode == DONT_BLANK)
2795     {
2796       hack = 0;
2797       pretty_name = strdup (_("Screen Saver Disabled"));
2798       schedule_preview (s, 0);
2799     }
2800   else
2801     {
2802       int hack_number = (list_elt >= 0 && list_elt < s->list_count
2803                          ? s->list_elt_to_hack_number[list_elt]
2804                          : -1);
2805       hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
2806
2807       pretty_name = (hack
2808                      ? (hack->name
2809                         ? strdup (hack->name)
2810                         : make_hack_name (hack->command))
2811                      : 0);
2812
2813       if (hack)
2814         schedule_preview (s, hack->command);
2815       else
2816         schedule_preview (s, 0);
2817     }
2818
2819   if (!pretty_name)
2820     pretty_name = strdup (_("Preview"));
2821
2822   gtk_frame_set_label (frame1, pretty_name);
2823   gtk_frame_set_label (frame2, pretty_name);
2824
2825   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
2826   gtk_entry_set_position (cmd, 0);
2827
2828   {
2829     char title[255];
2830     sprintf (title, "%s: %.100s Settings",
2831              progclass, (pretty_name ? pretty_name : "???"));
2832     gtk_window_set_title (GTK_WINDOW (s->popup_widget), title);
2833   }
2834
2835   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
2836                       (hack
2837                        ? (hack->visual && *hack->visual
2838                           ? hack->visual
2839                           : _("Any"))
2840                        : ""));
2841
2842   sensitize_demo_widgets (s, (hack ? True : False));
2843
2844   if (pretty_name) free (pretty_name);
2845
2846   ensure_selected_item_visible (list);
2847
2848   s->_selected_list_element = list_elt;
2849 }
2850
2851
2852 static void
2853 widget_deleter (GtkWidget *widget, gpointer data)
2854 {
2855   /* #### Well, I want to destroy these widgets, but if I do that, they get
2856      referenced again, and eventually I get a SEGV.  So instead of
2857      destroying them, I'll just hide them, and leak a bunch of memory
2858      every time the disk file changes.  Go go go Gtk!
2859
2860      #### Ok, that's a lie, I get a crash even if I just hide the widget
2861      and don't ever delete it.  Fuck!
2862    */
2863 #if 0
2864   gtk_widget_destroy (widget);
2865 #else
2866   gtk_widget_hide (widget);
2867 #endif
2868 }
2869
2870
2871 static char **sort_hack_cmp_names_kludge;
2872 static int
2873 sort_hack_cmp (const void *a, const void *b)
2874 {
2875   if (a == b)
2876     return 0;
2877   else
2878     {
2879       int aa = *(int *) a;
2880       int bb = *(int *) b;
2881       const char last[] = "\377\377\377\377\377\377\377\377\377\377\377";
2882       return strcmp ((aa < 0 ? last : sort_hack_cmp_names_kludge[aa]),
2883                      (bb < 0 ? last : sort_hack_cmp_names_kludge[bb]));
2884     }
2885 }
2886
2887
2888 static void
2889 initialize_sort_map (state *s)
2890 {
2891   saver_preferences *p = &s->prefs;
2892   int i, j;
2893
2894   if (s->list_elt_to_hack_number) free (s->list_elt_to_hack_number);
2895   if (s->hack_number_to_list_elt) free (s->hack_number_to_list_elt);
2896   if (s->hacks_available_p) free (s->hacks_available_p);
2897
2898   s->list_elt_to_hack_number = (int *)
2899     calloc (sizeof(int), p->screenhacks_count + 1);
2900   s->hack_number_to_list_elt = (int *)
2901     calloc (sizeof(int), p->screenhacks_count + 1);
2902   s->hacks_available_p = (Bool *)
2903     calloc (sizeof(Bool), p->screenhacks_count + 1);
2904
2905   /* Check which hacks actually exist on $PATH
2906    */
2907   for (i = 0; i < p->screenhacks_count; i++)
2908     {
2909       screenhack *hack = p->screenhacks[i];
2910       s->hacks_available_p[i] = on_path_p (hack->command);
2911     }
2912
2913   /* Initialize list->hack table to unsorted mapping, omitting nonexistent
2914      hacks, if desired.
2915    */
2916   j = 0;
2917   for (i = 0; i < p->screenhacks_count; i++)
2918     {
2919       if (!p->ignore_uninstalled_p ||
2920           s->hacks_available_p[i])
2921         s->list_elt_to_hack_number[j++] = i;
2922     }
2923   s->list_count = j;
2924
2925   for (; j < p->screenhacks_count; j++)
2926     s->list_elt_to_hack_number[j] = -1;
2927
2928
2929   /* Generate list of sortable names (once)
2930    */
2931   sort_hack_cmp_names_kludge = (char **)
2932     calloc (sizeof(char *), p->screenhacks_count);
2933   for (i = 0; i < p->screenhacks_count; i++)
2934     {
2935       screenhack *hack = p->screenhacks[i];
2936       char *name = (hack->name && *hack->name
2937                     ? strdup (hack->name)
2938                     : make_hack_name (hack->command));
2939       char *str;
2940       for (str = name; *str; str++)
2941         *str = tolower(*str);
2942       sort_hack_cmp_names_kludge[i] = name;
2943     }
2944
2945   /* Sort list->hack map alphabetically
2946    */
2947   qsort (s->list_elt_to_hack_number,
2948          p->screenhacks_count,
2949          sizeof(*s->list_elt_to_hack_number),
2950          sort_hack_cmp);
2951
2952   /* Free names
2953    */
2954   for (i = 0; i < p->screenhacks_count; i++)
2955     free (sort_hack_cmp_names_kludge[i]);
2956   free (sort_hack_cmp_names_kludge);
2957   sort_hack_cmp_names_kludge = 0;
2958
2959   /* Build inverse table */
2960   for (i = 0; i < p->screenhacks_count; i++)
2961     s->hack_number_to_list_elt[s->list_elt_to_hack_number[i]] = i;
2962 }
2963
2964
2965 static int
2966 maybe_reload_init_file (state *s)
2967 {
2968   saver_preferences *p = &s->prefs;
2969   int status = 0;
2970
2971   static Bool reentrant_lock = False;
2972   if (reentrant_lock) return 0;
2973   reentrant_lock = True;
2974
2975   if (init_file_changed_p (p))
2976     {
2977       const char *f = init_file_name();
2978       char *b;
2979       int list_elt;
2980       GtkWidget *list;
2981
2982       if (!f || !*f) return 0;
2983       b = (char *) malloc (strlen(f) + 1024);
2984       sprintf (b,
2985                _("Warning:\n\n"
2986                  "file \"%s\" has changed, reloading.\n"),
2987                f);
2988       warning_dialog (s->toplevel_widget, b, False, 100);
2989       free (b);
2990
2991       load_init_file (p);
2992       initialize_sort_map (s);
2993
2994       list_elt = selected_list_element (s);
2995       list = name_to_widget (s, "list");
2996       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
2997       populate_hack_list (s);
2998       force_list_select_item (s, list, list_elt, True);
2999       populate_prefs_page (s);
3000       populate_demo_window (s, list_elt);
3001       ensure_selected_item_visible (list);
3002
3003       status = 1;
3004     }
3005
3006   reentrant_lock = False;
3007   return status;
3008 }
3009
3010
3011 \f
3012 /* Making the preview window have the right X visual (so that GL works.)
3013  */
3014
3015 static Visual *get_best_gl_visual (state *);
3016
3017 static GdkVisual *
3018 x_visual_to_gdk_visual (Visual *xv)
3019 {
3020   GList *gvs = gdk_list_visuals();
3021   if (!xv) return gdk_visual_get_system();
3022   for (; gvs; gvs = gvs->next)
3023     {
3024       GdkVisual *gv = (GdkVisual *) gvs->data;
3025       if (xv == GDK_VISUAL_XVISUAL (gv))
3026         return gv;
3027     }
3028   fprintf (stderr, "%s: couldn't convert X Visual 0x%lx to a GdkVisual\n",
3029            blurb(), (unsigned long) xv->visualid);
3030   abort();
3031 }
3032
3033 static void
3034 clear_preview_window (state *s)
3035 {
3036   GtkWidget *p;
3037   GdkWindow *window;
3038
3039   if (!s->toplevel_widget) return;  /* very early */
3040   p = name_to_widget (s, "preview");
3041   window = p->window;
3042
3043   if (!window) return;
3044
3045   /* Flush the widget background down into the window, in case a subproc
3046      has changed it. */
3047   gdk_window_set_background (window, &p->style->bg[GTK_STATE_NORMAL]);
3048   gdk_window_clear (window);
3049
3050   {
3051     int list_elt = selected_list_element (s);
3052     int hack_number = (list_elt >= 0
3053                        ? s->list_elt_to_hack_number[list_elt]
3054                        : -1);
3055     Bool available_p = (hack_number >= 0
3056                         ? s->hacks_available_p [hack_number]
3057                         : True);
3058 #ifdef HAVE_GTK2
3059     GtkWidget *notebook = name_to_widget (s, "preview_notebook");
3060     gtk_notebook_set_page (GTK_NOTEBOOK (notebook),
3061                            (s->running_preview_error_p
3062                             ? (available_p ? 1 : 2)
3063                             : 0));
3064 #else /* !HAVE_GTK2 */
3065     if (s->running_preview_error_p)
3066       {
3067         const char * const lines1[] = { N_("No Preview"), N_("Available") };
3068         const char * const lines2[] = { N_("Not"), N_("Installed") };
3069         int nlines = countof(lines1);
3070         int lh = p->style->font->ascent + p->style->font->descent;
3071         int y, i;
3072         gint w, h;
3073
3074         const char * const *lines = (available_p ? lines1 : lines2);
3075
3076         gdk_window_get_size (window, &w, &h);
3077         y = (h - (lh * nlines)) / 2;
3078         y += p->style->font->ascent;
3079         for (i = 0; i < nlines; i++)
3080           {
3081             int sw = gdk_string_width (p->style->font, _(lines[i]));
3082             int x = (w - sw) / 2;
3083             gdk_draw_string (window, p->style->font,
3084                              p->style->fg_gc[GTK_STATE_NORMAL],
3085                              x, y, _(lines[i]));
3086             y += lh;
3087           }
3088       }
3089 #endif /* !HAVE_GTK2 */
3090   }
3091
3092   gdk_flush ();
3093 }
3094
3095
3096 static void
3097 fix_preview_visual (state *s)
3098 {
3099   GtkWidget *widget = name_to_widget (s, "preview");
3100   Visual *xvisual = get_best_gl_visual (s);
3101   GdkVisual *visual = x_visual_to_gdk_visual (xvisual);
3102   GdkVisual *dvisual = gdk_visual_get_system();
3103   GdkColormap *cmap = (visual == dvisual
3104                        ? gdk_colormap_get_system ()
3105                        : gdk_colormap_new (visual, False));
3106
3107   if (s->debug_p)
3108     fprintf (stderr, "%s: using %s visual 0x%lx\n", blurb(),
3109              (visual == dvisual ? "default" : "non-default"),
3110              (xvisual ? (unsigned long) xvisual->visualid : 0L));
3111
3112   if (!GTK_WIDGET_REALIZED (widget) ||
3113       gtk_widget_get_visual (widget) != visual)
3114     {
3115       gtk_widget_unrealize (widget);
3116       gtk_widget_set_visual (widget, visual);
3117       gtk_widget_set_colormap (widget, cmap);
3118       gtk_widget_realize (widget);
3119     }
3120
3121   /* Set the Widget colors to be white-on-black. */
3122   {
3123     GdkWindow *window = widget->window;
3124     GtkStyle *style = gtk_style_copy (widget->style);
3125     GdkColormap *cmap = gtk_widget_get_colormap (widget);
3126     GdkColor *fg = &style->fg[GTK_STATE_NORMAL];
3127     GdkColor *bg = &style->bg[GTK_STATE_NORMAL];
3128     GdkGC *fgc = gdk_gc_new(window);
3129     GdkGC *bgc = gdk_gc_new(window);
3130     if (!gdk_color_white (cmap, fg)) abort();
3131     if (!gdk_color_black (cmap, bg)) abort();
3132     gdk_gc_set_foreground (fgc, fg);
3133     gdk_gc_set_background (fgc, bg);
3134     gdk_gc_set_foreground (bgc, bg);
3135     gdk_gc_set_background (bgc, fg);
3136     style->fg_gc[GTK_STATE_NORMAL] = fgc;
3137     style->bg_gc[GTK_STATE_NORMAL] = fgc;
3138     gtk_widget_set_style (widget, style);
3139
3140     /* For debugging purposes, put a title on the window (so that
3141        it can be easily found in the output of "xwininfo -tree".)
3142      */
3143     gdk_window_set_title (window, "Preview");
3144   }
3145
3146   gtk_widget_show (widget);
3147 }
3148
3149 \f
3150 /* Subprocesses
3151  */
3152
3153 static char *
3154 subproc_pretty_name (state *s)
3155 {
3156   if (s->running_preview_cmd)
3157     {
3158       char *ps = strdup (s->running_preview_cmd);
3159       char *ss = strchr (ps, ' ');
3160       if (ss) *ss = 0;
3161       ss = strrchr (ps, '/');
3162       if (!ss)
3163         ss = ps;
3164       else
3165         {
3166           ss = strdup (ss+1);
3167           free (ps);
3168         }
3169       return ss;
3170     }
3171   else
3172     return strdup ("???");
3173 }
3174
3175
3176 static void
3177 reap_zombies (state *s)
3178 {
3179   int wait_status = 0;
3180   pid_t pid;
3181   while ((pid = waitpid (-1, &wait_status, WNOHANG|WUNTRACED)) > 0)
3182     {
3183       if (s->debug_p)
3184         {
3185           if (pid == s->running_preview_pid)
3186             {
3187               char *ss = subproc_pretty_name (s);
3188               fprintf (stderr, "%s: pid %lu (%s) died\n", blurb(),
3189                        (unsigned long) pid, ss);
3190               free (ss);
3191             }
3192           else
3193             fprintf (stderr, "%s: pid %lu died\n", blurb(),
3194                      (unsigned long) pid);
3195         }
3196     }
3197 }
3198
3199
3200 /* Mostly lifted from driver/subprocs.c */
3201 static Visual *
3202 get_best_gl_visual (state *s)
3203 {
3204   Display *dpy = GDK_DISPLAY();
3205   pid_t forked;
3206   int fds [2];
3207   int in, out;
3208   char buf[1024];
3209
3210   char *av[10];
3211   int ac = 0;
3212
3213   av[ac++] = "xscreensaver-gl-helper";
3214   av[ac] = 0;
3215
3216   if (pipe (fds))
3217     {
3218       perror ("error creating pipe:");
3219       return 0;
3220     }
3221
3222   in = fds [0];
3223   out = fds [1];
3224
3225   switch ((int) (forked = fork ()))
3226     {
3227     case -1:
3228       {
3229         sprintf (buf, "%s: couldn't fork", blurb());
3230         perror (buf);
3231         exit (1);
3232       }
3233     case 0:
3234       {
3235         int stdout_fd = 1;
3236
3237         close (in);  /* don't need this one */
3238         close (ConnectionNumber (dpy));         /* close display fd */
3239
3240         if (dup2 (out, stdout_fd) < 0)          /* pipe stdout */
3241           {
3242             perror ("could not dup() a new stdout:");
3243             return 0;
3244           }
3245
3246         execvp (av[0], av);                     /* shouldn't return. */
3247
3248         if (errno != ENOENT)
3249           {
3250             /* Ignore "no such file or directory" errors, unless verbose.
3251                Issue all other exec errors, though. */
3252             sprintf (buf, "%s: running %s", blurb(), av[0]);
3253             perror (buf);
3254           }
3255         exit (1);                               /* exits fork */
3256         break;
3257       }
3258     default:
3259       {
3260         int result = 0;
3261         int wait_status = 0;
3262
3263         FILE *f = fdopen (in, "r");
3264         unsigned int v = 0;
3265         char c;
3266
3267         close (out);  /* don't need this one */
3268
3269         *buf = 0;
3270         fgets (buf, sizeof(buf)-1, f);
3271         fclose (f);
3272
3273         /* Wait for the child to die. */
3274         waitpid (-1, &wait_status, 0);
3275
3276         if (1 == sscanf (buf, "0x%x %c", &v, &c))
3277           result = (int) v;
3278
3279         if (result == 0)
3280           {
3281             if (s->debug_p)
3282               fprintf (stderr, "%s: %s did not report a GL visual!\n",
3283                        blurb(), av[0]);
3284             return 0;
3285           }
3286         else
3287           {
3288             Visual *v = id_to_visual (DefaultScreenOfDisplay (dpy), result);
3289             if (s->debug_p)
3290               fprintf (stderr, "%s: %s says the GL visual is 0x%X.\n",
3291                        blurb(), av[0], result);
3292             if (!v) abort();
3293             return v;
3294           }
3295       }
3296     }
3297
3298   abort();
3299 }
3300
3301
3302 static void
3303 kill_preview_subproc (state *s)
3304 {
3305   s->running_preview_error_p = False;
3306
3307   reap_zombies (s);
3308   clear_preview_window (s);
3309
3310   if (s->subproc_check_timer_id)
3311     {
3312       gtk_timeout_remove (s->subproc_check_timer_id);
3313       s->subproc_check_timer_id = 0;
3314       s->subproc_check_countdown = 0;
3315     }
3316
3317   if (s->running_preview_pid)
3318     {
3319       int status = kill (s->running_preview_pid, SIGTERM);
3320       char *ss = subproc_pretty_name (s);
3321
3322       if (status < 0)
3323         {
3324           if (errno == ESRCH)
3325             {
3326               if (s->debug_p)
3327                 fprintf (stderr, "%s: pid %lu (%s) was already dead.\n",
3328                          blurb(), (unsigned long) s->running_preview_pid, ss);
3329             }
3330           else
3331             {
3332               char buf [1024];
3333               sprintf (buf, "%s: couldn't kill pid %lu (%s)",
3334                        blurb(), (unsigned long) s->running_preview_pid, ss);
3335               perror (buf);
3336             }
3337         }
3338       else if (s->debug_p)
3339         fprintf (stderr, "%s: killed pid %lu (%s)\n", blurb(),
3340                  (unsigned long) s->running_preview_pid, ss);
3341
3342       free (ss);
3343       s->running_preview_pid = 0;
3344       if (s->running_preview_cmd) free (s->running_preview_cmd);
3345       s->running_preview_cmd = 0;
3346     }
3347
3348   reap_zombies (s);
3349 }
3350
3351
3352 /* Immediately and unconditionally launches the given process,
3353    after appending the -window-id option; sets running_preview_pid.
3354  */
3355 static void
3356 launch_preview_subproc (state *s)
3357 {
3358   saver_preferences *p = &s->prefs;
3359   Window id;
3360   char *new_cmd = 0;
3361   pid_t forked;
3362   const char *cmd = s->desired_preview_cmd;
3363
3364   GtkWidget *pr = name_to_widget (s, "preview");
3365   GdkWindow *window = pr->window;
3366
3367   s->running_preview_error_p = False;
3368
3369   if (s->preview_suppressed_p)
3370     {
3371       kill_preview_subproc (s);
3372       goto DONE;
3373     }
3374
3375   new_cmd = malloc (strlen (cmd) + 40);
3376
3377   id = (window ? GDK_WINDOW_XWINDOW (window) : 0);
3378   if (id == 0)
3379     {
3380       /* No window id?  No command to run. */
3381       free (new_cmd);
3382       new_cmd = 0;
3383     }
3384   else
3385     {
3386       strcpy (new_cmd, cmd);
3387       sprintf (new_cmd + strlen (new_cmd), " -window-id 0x%X",
3388                (unsigned int) id);
3389     }
3390
3391   kill_preview_subproc (s);
3392   if (! new_cmd)
3393     {
3394       s->running_preview_error_p = True;
3395       clear_preview_window (s);
3396       goto DONE;
3397     }
3398
3399   switch ((int) (forked = fork ()))
3400     {
3401     case -1:
3402       {
3403         char buf[255];
3404         sprintf (buf, "%s: couldn't fork", blurb());
3405         perror (buf);
3406         s->running_preview_error_p = True;
3407         goto DONE;
3408         break;
3409       }
3410     case 0:
3411       {
3412         close (ConnectionNumber (GDK_DISPLAY()));
3413
3414         usleep (250000);  /* pause for 1/4th second before launching, to give
3415                              the previous program time to die and flush its X
3416                              buffer, so we don't get leftover turds on the
3417                              window. */
3418
3419         exec_command (p->shell, new_cmd, p->nice_inferior);
3420         /* Don't bother printing an error message when we are unable to
3421            exec subprocesses; we handle that by polling the pid later. */
3422         exit (1);  /* exits child fork */
3423         break;
3424
3425       default:
3426
3427         if (s->running_preview_cmd) free (s->running_preview_cmd);
3428         s->running_preview_cmd = strdup (s->desired_preview_cmd);
3429         s->running_preview_pid = forked;
3430
3431         if (s->debug_p)
3432           {
3433             char *ss = subproc_pretty_name (s);
3434             fprintf (stderr, "%s: forked %lu (%s)\n", blurb(),
3435                      (unsigned long) forked, ss);
3436             free (ss);
3437           }
3438         break;
3439       }
3440     }
3441
3442   schedule_preview_check (s);
3443
3444  DONE:
3445   if (new_cmd) free (new_cmd);
3446   new_cmd = 0;
3447 }
3448
3449
3450 /* Modify $DISPLAY and $PATH for the benefit of subprocesses.
3451  */
3452 static void
3453 hack_environment (state *s)
3454 {
3455   static const char *def_path =
3456 # ifdef DEFAULT_PATH_PREFIX
3457     DEFAULT_PATH_PREFIX;
3458 # else
3459     "";
3460 # endif
3461
3462   Display *dpy = GDK_DISPLAY();
3463   const char *odpy = DisplayString (dpy);
3464   char *ndpy = (char *) malloc(strlen(odpy) + 20);
3465   strcpy (ndpy, "DISPLAY=");
3466   strcat (ndpy, odpy);
3467   if (putenv (ndpy))
3468     abort ();
3469
3470   if (s->debug_p)
3471     fprintf (stderr, "%s: %s\n", blurb(), ndpy);
3472
3473   /* don't free(ndpy) -- some implementations of putenv (BSD 4.4, glibc
3474      2.0) copy the argument, but some (libc4,5, glibc 2.1.2) do not.
3475      So we must leak it (and/or the previous setting).  Yay.
3476    */
3477
3478   if (def_path && *def_path)
3479     {
3480       const char *opath = getenv("PATH");
3481       char *npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20);
3482       strcpy (npath, "PATH=");
3483       strcat (npath, def_path);
3484       strcat (npath, ":");
3485       strcat (npath, opath);
3486
3487       if (putenv (npath))
3488         abort ();
3489       /* do not free(npath) -- see above */
3490
3491       if (s->debug_p)
3492         fprintf (stderr, "%s: added \"%s\" to $PATH\n", blurb(), def_path);
3493     }
3494 }
3495
3496
3497 /* Called from a timer:
3498    Launches the currently-chosen subprocess, if it's not already running.
3499    If there's a different process running, kills it.
3500  */
3501 static int
3502 update_subproc_timer (gpointer data)
3503 {
3504   state *s = (state *) data;
3505   if (! s->desired_preview_cmd)
3506     kill_preview_subproc (s);
3507   else if (!s->running_preview_cmd ||
3508            !!strcmp (s->desired_preview_cmd, s->running_preview_cmd))
3509     launch_preview_subproc (s);
3510
3511   s->subproc_timer_id = 0;
3512   return FALSE;  /* do not re-execute timer */
3513 }
3514
3515
3516 /* Call this when you think you might want a preview process running.
3517    It will set a timer that will actually launch that program a second
3518    from now, if you haven't changed your mind (to avoid double-click
3519    spazzing, etc.)  `cmd' may be null meaning "no process".
3520  */
3521 static void
3522 schedule_preview (state *s, const char *cmd)
3523 {
3524   int delay = 1000 * 0.5;   /* 1/2 second hysteresis */
3525
3526   if (s->debug_p)
3527     {
3528       if (cmd)
3529         fprintf (stderr, "%s: scheduling preview \"%s\"\n", blurb(), cmd);
3530       else
3531         fprintf (stderr, "%s: scheduling preview death\n", blurb());
3532     }
3533
3534   if (s->desired_preview_cmd) free (s->desired_preview_cmd);
3535   s->desired_preview_cmd = (cmd ? strdup (cmd) : 0);
3536
3537   if (s->subproc_timer_id)
3538     gtk_timeout_remove (s->subproc_timer_id);
3539   s->subproc_timer_id = gtk_timeout_add (delay, update_subproc_timer, s);
3540 }
3541
3542
3543 /* Called from a timer:
3544    Checks to see if the subproc that should be running, actually is.
3545  */
3546 static int
3547 check_subproc_timer (gpointer data)
3548 {
3549   state *s = (state *) data;
3550   Bool again_p = True;
3551
3552   if (s->running_preview_error_p ||   /* already dead */
3553       s->running_preview_pid <= 0)
3554     {
3555       again_p = False;
3556     }
3557   else
3558     {
3559       int status;
3560       reap_zombies (s);
3561       status = kill (s->running_preview_pid, 0);
3562       if (status < 0 && errno == ESRCH)
3563         s->running_preview_error_p = True;
3564
3565       if (s->debug_p)
3566         {
3567           char *ss = subproc_pretty_name (s);
3568           fprintf (stderr, "%s: timer: pid %lu (%s) is %s\n", blurb(),
3569                    (unsigned long) s->running_preview_pid, ss,
3570                    (s->running_preview_error_p ? "dead" : "alive"));
3571           free (ss);
3572         }
3573
3574       if (s->running_preview_error_p)
3575         {
3576           clear_preview_window (s);
3577           again_p = False;
3578         }
3579     }
3580
3581   /* Otherwise, it's currently alive.  We might be checking again, or we
3582      might be satisfied. */
3583
3584   if (--s->subproc_check_countdown <= 0)
3585     again_p = False;
3586
3587   if (again_p)
3588     return TRUE;     /* re-execute timer */
3589   else
3590     {
3591       s->subproc_check_timer_id = 0;
3592       s->subproc_check_countdown = 0;
3593       return FALSE;  /* do not re-execute timer */
3594     }
3595 }
3596
3597
3598 /* Call this just after launching a subprocess.
3599    This sets a timer that will, five times a second for two seconds,
3600    check whether the program is still running.  The assumption here
3601    is that if the process didn't stay up for more than a couple of
3602    seconds, then either the program doesn't exist, or it doesn't
3603    take a -window-id argument.
3604  */
3605 static void
3606 schedule_preview_check (state *s)
3607 {
3608   int seconds = 2;
3609   int ticks = 5;
3610
3611   if (s->debug_p)
3612     fprintf (stderr, "%s: scheduling check\n", blurb());
3613
3614   if (s->subproc_check_timer_id)
3615     gtk_timeout_remove (s->subproc_check_timer_id);
3616   s->subproc_check_timer_id =
3617     gtk_timeout_add (1000 / ticks,
3618                      check_subproc_timer, (gpointer) s);
3619   s->subproc_check_countdown = ticks * seconds;
3620 }
3621
3622
3623 static Bool
3624 screen_blanked_p (void)
3625 {
3626   Atom type;
3627   int format;
3628   unsigned long nitems, bytesafter;
3629   CARD32 *data = 0;
3630   Display *dpy = GDK_DISPLAY();
3631   Bool blanked_p = False;
3632
3633   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
3634                           XA_SCREENSAVER_STATUS,
3635                           0, 3, False, XA_INTEGER,
3636                           &type, &format, &nitems, &bytesafter,
3637                           (unsigned char **) &data)
3638       == Success
3639       && type == XA_INTEGER
3640       && nitems >= 3
3641       && data)
3642     blanked_p = (data[0] == XA_BLANK || data[0] == XA_LOCK);
3643
3644   if (data) free (data);
3645
3646   return blanked_p;
3647 }
3648
3649 /* Wake up every now and then and see if the screen is blanked.
3650    If it is, kill off the small-window demo -- no point in wasting
3651    cycles by running two screensavers at once...
3652  */
3653 static int
3654 check_blanked_timer (gpointer data)
3655 {
3656   state *s = (state *) data;
3657   Bool blanked_p = screen_blanked_p ();
3658   if (blanked_p && s->running_preview_pid)
3659     {
3660       if (s->debug_p)
3661         fprintf (stderr, "%s: screen is blanked: killing preview\n", blurb());
3662       kill_preview_subproc (s);
3663     }
3664
3665   return True;  /* re-execute timer */
3666 }
3667
3668 \f
3669 /* Setting window manager icon
3670  */
3671
3672 static void
3673 init_icon (GdkWindow *window)
3674 {
3675   GdkBitmap *mask = 0;
3676   GdkColor transp;
3677   GdkPixmap *pixmap =
3678     gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
3679                                   (gchar **) logo_50_xpm);
3680   if (pixmap)
3681     gdk_window_set_icon (window, 0, pixmap, mask);
3682 }
3683
3684 \f
3685 /* The main demo-mode command loop.
3686  */
3687
3688 #if 0
3689 static Bool
3690 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
3691         XrmRepresentation *type, XrmValue *value, XPointer closure)
3692 {
3693   int i;
3694   for (i = 0; quarks[i]; i++)
3695     {
3696       if (bindings[i] == XrmBindTightly)
3697         fprintf (stderr, (i == 0 ? "" : "."));
3698       else if (bindings[i] == XrmBindLoosely)
3699         fprintf (stderr, "*");
3700       else
3701         fprintf (stderr, " ??? ");
3702       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
3703     }
3704
3705   fprintf (stderr, ": %s\n", (char *) value->addr);
3706
3707   return False;
3708 }
3709 #endif
3710
3711
3712 static void
3713 the_network_is_not_the_computer (state *s)
3714 {
3715   Display *dpy = GDK_DISPLAY();
3716   char *rversion = 0, *ruser = 0, *rhost = 0;
3717   char *luser, *lhost;
3718   char *msg = 0;
3719   struct passwd *p = getpwuid (getuid ());
3720   const char *d = DisplayString (dpy);
3721
3722 # if defined(HAVE_UNAME)
3723   struct utsname uts;
3724   if (uname (&uts) < 0)
3725     lhost = "<UNKNOWN>";
3726   else
3727     lhost = uts.nodename;
3728 # elif defined(VMS)
3729   strcpy (lhost, getenv("SYS$NODE"));
3730 # else  /* !HAVE_UNAME && !VMS */
3731   strcat (lhost, "<UNKNOWN>");
3732 # endif /* !HAVE_UNAME && !VMS */
3733
3734   if (p && p->pw_name)
3735     luser = p->pw_name;
3736   else
3737     luser = "???";
3738
3739   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
3740
3741   /* Make a buffer that's big enough for a number of copies of all the
3742      strings, plus some. */
3743   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
3744                                (ruser ? strlen(ruser) : 0) +
3745                                (rhost ? strlen(rhost) : 0) +
3746                                strlen(lhost) +
3747                                strlen(luser) +
3748                                strlen(d) +
3749                                1024));
3750   *msg = 0;
3751
3752   if (!rversion || !*rversion)
3753     {
3754       sprintf (msg,
3755                _("Warning:\n\n"
3756                  "The XScreenSaver daemon doesn't seem to be running\n"
3757                  "on display \"%s\".  Launch it now?"),
3758                d);
3759     }
3760   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
3761     {
3762       /* Warn that the two processes are running as different users.
3763        */
3764       sprintf(msg,
3765             _("Warning:\n\n"
3766               "%s is running as user \"%s\" on host \"%s\".\n"
3767               "But the xscreensaver managing display \"%s\"\n"
3768               "is running as user \"%s\" on host \"%s\".\n"
3769               "\n"
3770               "Since they are different users, they won't be reading/writing\n"
3771               "the same ~/.xscreensaver file, so %s isn't\n"
3772               "going to work right.\n"
3773               "\n"
3774               "You should either re-run %s as \"%s\", or re-run\n"
3775               "xscreensaver as \"%s\".\n"
3776               "\n"
3777               "Restart the xscreensaver daemon now?\n"),
3778               progname, luser, lhost,
3779               d,
3780               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
3781               progname,
3782               progname, (ruser ? ruser : "???"),
3783               luser);
3784     }
3785   else if (rhost && *rhost && !!strcmp (rhost, lhost))
3786     {
3787       /* Warn that the two processes are running on different hosts.
3788        */
3789       sprintf (msg,
3790               _("Warning:\n\n"
3791                "%s is running as user \"%s\" on host \"%s\".\n"
3792                "But the xscreensaver managing display \"%s\"\n"
3793                "is running as user \"%s\" on host \"%s\".\n"
3794                "\n"
3795                "If those two machines don't share a file system (that is,\n"
3796                "if they don't see the same ~%s/.xscreensaver file) then\n"
3797                "%s won't work right.\n"
3798                "\n"
3799                "Restart the daemon on \"%s\" as \"%s\" now?\n"),
3800                progname, luser, lhost,
3801                d,
3802                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
3803                luser,
3804                progname,
3805                lhost, luser);
3806     }
3807   else if (!!strcmp (rversion, s->short_version))
3808     {
3809       /* Warn that the version numbers don't match.
3810        */
3811       sprintf (msg,
3812              _("Warning:\n\n"
3813                "This is %s version %s.\n"
3814                "But the xscreensaver managing display \"%s\"\n"
3815                "is version %s.  This could cause problems.\n"
3816                "\n"
3817                "Restart the xscreensaver daemon now?\n"),
3818                progname, s->short_version,
3819                d,
3820                rversion);
3821     }
3822
3823
3824   if (*msg)
3825     warning_dialog (s->toplevel_widget, msg, True, 1);
3826
3827   if (rversion) free (rversion);
3828   if (ruser) free (ruser);
3829   if (rhost) free (rhost);
3830   free (msg);
3831 }
3832
3833
3834 /* We use this error handler so that X errors are preceeded by the name
3835    of the program that generated them.
3836  */
3837 static int
3838 demo_ehandler (Display *dpy, XErrorEvent *error)
3839 {
3840   state *s = global_state_kludge;  /* I hate C so much... */
3841   fprintf (stderr, "\nX error in %s:\n", blurb());
3842   XmuPrintDefaultErrorMessage (dpy, error, stderr);
3843   kill_preview_subproc (s);
3844   exit (-1);
3845   return 0;
3846 }
3847
3848
3849 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
3850    of the program that generated them; and also that we can ignore one
3851    particular bogus error message that Gdk madly spews.
3852  */
3853 static void
3854 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
3855                const gchar *message, gpointer user_data)
3856 {
3857   /* Ignore the message "Got event for unknown window: 0x...".
3858      Apparently some events are coming in for the xscreensaver window
3859      (presumably reply events related to the ClientMessage) and Gdk
3860      feels the need to complain about them.  So, just suppress any
3861      messages that look like that one.
3862    */
3863   if (strstr (message, "unknown window"))
3864     return;
3865
3866   fprintf (stderr, "%s: %s-%s: %s%s", blurb(),
3867            (log_domain ? log_domain : progclass),
3868            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
3869             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
3870             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
3871             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
3872             log_level == G_LOG_LEVEL_INFO     ? "info" :
3873             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
3874            message,
3875            ((!*message || message[strlen(message)-1] != '\n')
3876             ? "\n" : ""));
3877 }
3878
3879
3880 static char *defaults[] = {
3881 #include "XScreenSaver_ad.h"
3882  0
3883 };
3884
3885 #if 0
3886 #ifdef HAVE_CRAPPLET
3887 static struct poptOption crapplet_options[] = {
3888   {NULL, '\0', 0, NULL, 0}
3889 };
3890 #endif /* HAVE_CRAPPLET */
3891 #endif /* 0 */
3892
3893 const char *usage = "[--display dpy] [--prefs]"
3894 # ifdef HAVE_CRAPPLET
3895                     " [--crapplet]"
3896 # endif
3897             "\n\t\t   [--debug] [--sync] [--no-xshm] [--configdir dir]";
3898
3899 static void
3900 map_popup_window_cb (GtkWidget *w, gpointer user_data)
3901 {
3902   state *s = (state *) user_data;
3903   Boolean oi = s->initializing_p;
3904   GtkLabel *label = GTK_LABEL (name_to_widget (s, "doc"));
3905   s->initializing_p = True;
3906   eschew_gtk_lossage (label);
3907   s->initializing_p = oi;
3908 }
3909
3910
3911 #if 0
3912 static void
3913 print_widget_tree (GtkWidget *w, int depth)
3914 {
3915   int i;
3916   for (i = 0; i < depth; i++)
3917     fprintf (stderr, "  ");
3918   fprintf (stderr, "%s\n", gtk_widget_get_name (w));
3919
3920   if (GTK_IS_LIST (w))
3921     {
3922       for (i = 0; i < depth+1; i++)
3923         fprintf (stderr, "  ");
3924       fprintf (stderr, "...list kids...\n");
3925     }
3926   else if (GTK_IS_CONTAINER (w))
3927     {
3928       GList *kids = gtk_container_children (GTK_CONTAINER (w));
3929       while (kids)
3930         {
3931           print_widget_tree (GTK_WIDGET (kids->data), depth+1);
3932           kids = kids->next;
3933         }
3934     }
3935 }
3936 #endif /* 0 */
3937
3938 static int
3939 delayed_scroll_kludge (gpointer data)
3940 {
3941   state *s = (state *) data;
3942   GtkWidget *w = GTK_WIDGET (name_to_widget (s, "list"));
3943   ensure_selected_item_visible (w);
3944
3945   /* Oh, this is just fucking lovely, too. */
3946   w = GTK_WIDGET (name_to_widget (s, "preview"));
3947   gtk_widget_hide (w);
3948   gtk_widget_show (w);
3949
3950   return FALSE;  /* do not re-execute timer */
3951 }
3952
3953 #ifdef HAVE_GTK2
3954
3955 GtkWidget *
3956 create_xscreensaver_demo (void)
3957 {
3958   GtkWidget *nb;
3959
3960   nb = name_to_widget (global_state_kludge, "preview_notebook");
3961   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (nb), FALSE);
3962
3963   return name_to_widget (global_state_kludge, "xscreensaver_demo");
3964 }
3965
3966 GtkWidget *
3967 create_xscreensaver_settings_dialog (void)
3968 {
3969   GtkWidget *w, *box;
3970
3971   box = name_to_widget (global_state_kludge, "dialog_action_area");
3972
3973   w = name_to_widget (global_state_kludge, "adv_button");
3974   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
3975
3976   w = name_to_widget (global_state_kludge, "std_button");
3977   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (box), w, TRUE);
3978
3979   return name_to_widget (global_state_kludge, "xscreensaver_settings_dialog");
3980 }
3981
3982 #endif /* HAVE_GTK2 */
3983
3984 int
3985 main (int argc, char **argv)
3986 {
3987   XtAppContext app;
3988   state S, *s;
3989   saver_preferences *p;
3990   Bool prefs = False;
3991   int i;
3992   Display *dpy;
3993   Widget toplevel_shell;
3994   char *real_progname = argv[0];
3995   char *window_title;
3996   Bool crapplet_p = False;
3997   char *str;
3998
3999 #ifdef ENABLE_NLS
4000   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
4001   textdomain (GETTEXT_PACKAGE);
4002
4003 # ifdef HAVE_GTK2
4004   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
4005 # else  /* !HAVE_GTK2 */
4006   if (!setlocale (LC_ALL, ""))
4007     fprintf (stderr, "%s: locale not supported by C library\n", real_progname);
4008 # endif /* !HAVE_GTK2 */
4009
4010 #endif /* ENABLE_NLS */
4011
4012   str = strrchr (real_progname, '/');
4013   if (str) real_progname = str+1;
4014
4015   s = &S;
4016   memset (s, 0, sizeof(*s));
4017   s->initializing_p = True;
4018   p = &s->prefs;
4019
4020   global_state_kludge = s;  /* I hate C so much... */
4021
4022   progname = real_progname;
4023
4024   s->short_version = (char *) malloc (5);
4025   memcpy (s->short_version, screensaver_id + 17, 4);
4026   s->short_version [4] = 0;
4027
4028
4029   /* Register our error message logger for every ``log domain'' known.
4030      There's no way to do this globally, so I grepped the Gtk/Gdk sources
4031      for all of the domains that seem to be in use.
4032   */
4033   {
4034     const char * const domains[] = { 0,
4035                                      "Gtk", "Gdk", "GLib", "GModule",
4036                                      "GThread", "Gnome", "GnomeUI" };
4037     for (i = 0; i < countof(domains); i++)
4038       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
4039   }
4040
4041 #ifdef DEFAULT_ICONDIR  /* from -D on compile line */
4042 # ifndef HAVE_GTK2
4043   {
4044     const char *dir = DEFAULT_ICONDIR;
4045     if (*dir) add_pixmap_directory (dir);
4046   }
4047 # endif /* !HAVE_GTK2 */
4048 #endif /* DEFAULT_ICONDIR */
4049
4050   /* This is gross, but Gtk understands --display and not -display...
4051    */
4052   for (i = 1; i < argc; i++)
4053     if (argv[i][0] && argv[i][1] && 
4054         !strncmp(argv[i], "-display", strlen(argv[i])))
4055       argv[i] = "--display";
4056
4057
4058   /* We need to parse this arg really early... Sigh. */
4059   for (i = 1; i < argc; i++)
4060     {
4061       if (argv[i] &&
4062           (!strcmp(argv[i], "--crapplet") ||
4063            !strcmp(argv[i], "--capplet")))
4064         {
4065 # if defined(HAVE_CRAPPLET) || defined(HAVE_GTK2)
4066           int j;
4067           crapplet_p = True;
4068           for (j = i; j < argc; j++)  /* remove it from the list */
4069             argv[j] = argv[j+1];
4070           argc--;
4071 # else  /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4072           fprintf (stderr, "%s: not compiled with --crapplet support\n",
4073                    real_progname);
4074           fprintf (stderr, "%s: %s\n", real_progname, usage);
4075           exit (1);
4076 # endif /* !HAVE_CRAPPLET && !HAVE_GTK2 */
4077         }
4078       else if (argv[i] &&
4079                (!strcmp(argv[i], "--debug") ||
4080                 !strcmp(argv[i], "-debug") ||
4081                 !strcmp(argv[i], "-d")))
4082         {
4083           int j;
4084           s->debug_p = True;
4085           for (j = i; j < argc; j++)  /* remove it from the list */
4086             argv[j] = argv[j+1];
4087           argc--;
4088           i--;
4089         }
4090       else if (argv[i] &&
4091                argc > i+1 &&
4092                *argv[i+1] &&
4093                (!strcmp(argv[i], "--configdir")))
4094         {
4095           int j;
4096           struct stat st;
4097           hack_configuration_path = argv[i+1];
4098           for (j = i; j < argc; j++)  /* remove them from the list */
4099             argv[j] = argv[j+2];
4100           argc -= 2;
4101           i -= 2;
4102
4103           if (0 != stat (hack_configuration_path, &st))
4104             {
4105               char buf[255];
4106               sprintf (buf, "%s: %.200s", blurb(), hack_configuration_path);
4107               perror (buf);
4108               exit (1);
4109             }
4110           else if (!S_ISDIR (st.st_mode))
4111             {
4112               fprintf (stderr, "%s: not a directory: %s\n",
4113                        blurb(), hack_configuration_path);
4114               exit (1);
4115             }
4116         }
4117     }
4118
4119
4120   if (s->debug_p)
4121     fprintf (stderr, "%s: using config directory \"%s\"\n",
4122              progname, hack_configuration_path);
4123
4124
4125   /* Let Gtk open the X connection, then initialize Xt to use that
4126      same connection.  Doctor Frankenstein would be proud.
4127    */
4128 # ifdef HAVE_CRAPPLET
4129   if (crapplet_p)
4130     {
4131       GnomeClient *client;
4132       GnomeClientFlags flags = 0;
4133
4134       int init_results = gnome_capplet_init ("screensaver-properties",
4135                                              s->short_version,
4136                                              argc, argv, NULL, 0, NULL);
4137       /* init_results is:
4138          0 upon successful initialization;
4139          1 if --init-session-settings was passed on the cmdline;
4140          2 if --ignore was passed on the cmdline;
4141         -1 on error.
4142
4143          So the 1 signifies just to init the settings, and quit, basically.
4144          (Meaning launch the xscreensaver daemon.)
4145        */
4146
4147       if (init_results < 0)
4148         {
4149 #  if 0
4150           g_error ("An initialization error occurred while "
4151                    "starting xscreensaver-capplet.\n");
4152 #  else  /* !0 */
4153           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
4154                    real_progname, init_results);
4155           exit (1);
4156 #  endif /* !0 */
4157         }
4158
4159       client = gnome_master_client ();
4160
4161       if (client)
4162         flags = gnome_client_get_flags (client);
4163
4164       if (flags & GNOME_CLIENT_IS_CONNECTED)
4165         {
4166           int token =
4167             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
4168                                          gnome_client_get_id (client));
4169           if (token)
4170             {
4171               char *session_args[20];
4172               int i = 0;
4173               session_args[i++] = real_progname;
4174               session_args[i++] = "--capplet";
4175               session_args[i++] = "--init-session-settings";
4176               session_args[i] = 0;
4177               gnome_client_set_priority (client, 20);
4178               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
4179               gnome_client_set_restart_command (client, i, session_args);
4180             }
4181           else
4182             {
4183               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
4184             }
4185
4186           gnome_client_flush (client);
4187         }
4188
4189       if (init_results == 1)
4190         {
4191           system ("xscreensaver -nosplash &");
4192           return 0;
4193         }
4194
4195     }
4196   else
4197 # endif /* HAVE_CRAPPLET */
4198     {
4199       gtk_init (&argc, &argv);
4200     }
4201
4202
4203   /* We must read exactly the same resources as xscreensaver.
4204      That means we must have both the same progclass *and* progname,
4205      at least as far as the resource database is concerned.  So,
4206      put "xscreensaver" in argv[0] while initializing Xt.
4207    */
4208   argv[0] = "xscreensaver";
4209   progname = argv[0];
4210
4211
4212   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
4213    */
4214   XtToolkitInitialize ();
4215   app = XtCreateApplicationContext ();
4216   dpy = GDK_DISPLAY();
4217   XtAppSetFallbackResources (app, defaults);
4218   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
4219   toplevel_shell = XtAppCreateShell (progname, progclass,
4220                                      applicationShellWidgetClass,
4221                                      dpy, 0, 0);
4222
4223   dpy = XtDisplay (toplevel_shell);
4224   db = XtDatabase (dpy);
4225   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
4226   XSetErrorHandler (demo_ehandler);
4227
4228   /* Let's just ignore these.  They seem to confuse Irix Gtk... */
4229   signal (SIGPIPE, SIG_IGN);
4230
4231   /* After doing Xt-style command-line processing, complain about any
4232      unrecognized command-line arguments.
4233    */
4234   for (i = 1; i < argc; i++)
4235     {
4236       char *str = argv[i];
4237       if (str[0] == '-' && str[1] == '-')
4238         str++;
4239       if (!strcmp (str, "-prefs"))
4240         prefs = True;
4241       else if (crapplet_p)
4242         /* There are lots of random args that we don't care about when we're
4243            started as a crapplet, so just ignore unknown args in that case. */
4244         ;
4245       else
4246         {
4247           fprintf (stderr, _("%s: unknown option: %s\n"), real_progname, argv[i]);
4248           fprintf (stderr, "%s: %s\n", real_progname, usage);
4249           exit (1);
4250         }
4251     }
4252
4253   /* Load the init file, which may end up consulting the X resource database
4254      and the site-wide app-defaults file.  Note that at this point, it's
4255      important that `progname' be "xscreensaver", rather than whatever
4256      was in argv[0].
4257    */
4258   p->db = db;
4259
4260   hack_environment (s);  /* must be before initialize_sort_map() */
4261
4262   load_init_file (p);
4263   initialize_sort_map (s);
4264
4265   /* Now that Xt has been initialized, and the resources have been read,
4266      we can set our `progname' variable to something more in line with
4267      reality.
4268    */
4269   progname = real_progname;
4270
4271
4272 #if 0
4273   /* Print out all the resources we read. */
4274   {
4275     XrmName name = { 0 };
4276     XrmClass class = { 0 };
4277     int count = 0;
4278     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
4279                           (POINTER) &count);
4280   }
4281 #endif
4282
4283
4284   /* Intern the atoms that xscreensaver_command() needs.
4285    */
4286   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
4287   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
4288   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
4289   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
4290   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
4291   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
4292   XA_SELECT = XInternAtom (dpy, "SELECT", False);
4293   XA_DEMO = XInternAtom (dpy, "DEMO", False);
4294   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
4295   XA_BLANK = XInternAtom (dpy, "BLANK", False);
4296   XA_LOCK = XInternAtom (dpy, "LOCK", False);
4297   XA_EXIT = XInternAtom (dpy, "EXIT", False);
4298   XA_RESTART = XInternAtom (dpy, "RESTART", False);
4299
4300
4301   /* Create the window and all its widgets.
4302    */
4303   s->base_widget     = create_xscreensaver_demo ();
4304   s->popup_widget    = create_xscreensaver_settings_dialog ();
4305   s->toplevel_widget = s->base_widget;
4306
4307
4308   /* Set the main window's title. */
4309   {
4310     char *base_title = _("Screensaver Preferences");
4311     char *v = (char *) strdup(strchr(screensaver_id, ' '));
4312     char *s1, *s2, *s3, *s4;
4313     s1 = (char *) strchr(v,  ' '); s1++;
4314     s2 = (char *) strchr(s1, ' ');
4315     s3 = (char *) strchr(v,  '('); s3++;
4316     s4 = (char *) strchr(s3, ')');
4317     *s2 = 0;
4318     *s4 = 0;
4319
4320     window_title = (char *) malloc (strlen (base_title) +
4321                                     strlen (progclass) +
4322                                     strlen (s1) + strlen (s3) +
4323                                     100);
4324     sprintf (window_title, "%s  (%s %s, %s)", base_title, progclass, s1, s3);
4325     gtk_window_set_title (GTK_WINDOW (s->toplevel_widget), window_title);
4326     gtk_window_set_title (GTK_WINDOW (s->popup_widget),    window_title);
4327     free (v);
4328   }
4329
4330   /* Adjust the (invisible) notebooks on the popup dialog... */
4331   {
4332     GtkNotebook *notebook =
4333       GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
4334     GtkWidget *std = GTK_WIDGET (name_to_widget (s, "std_button"));
4335     int page = 0;
4336
4337 # ifdef HAVE_XML
4338     gtk_widget_hide (std);
4339 # else  /* !HAVE_XML */
4340     /* Make the advanced page be the only one available. */
4341     gtk_widget_set_sensitive (std, False);
4342     std = GTK_WIDGET (name_to_widget (s, "adv_button"));
4343     gtk_widget_hide (std);
4344     page = 1;
4345 # endif /* !HAVE_XML */
4346
4347     gtk_notebook_set_page (notebook, page);
4348     gtk_notebook_set_show_tabs (notebook, False);
4349   }
4350
4351   /* Various other widget initializations...
4352    */
4353   gtk_signal_connect (GTK_OBJECT (s->toplevel_widget), "delete_event",
4354                       GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
4355                       (gpointer) s);
4356   gtk_signal_connect (GTK_OBJECT (s->popup_widget), "delete_event",
4357                       GTK_SIGNAL_FUNC (wm_popup_close_cb),
4358                       (gpointer) s);
4359
4360   populate_hack_list (s);
4361   populate_prefs_page (s);
4362   sensitize_demo_widgets (s, False);
4363   fix_text_entry_sizes (s);
4364   scroll_to_current_hack (s);
4365
4366   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "cancel_button")),
4367                       "map", GTK_SIGNAL_FUNC(map_popup_window_cb),
4368                       (gpointer) s);
4369
4370 #ifndef HAVE_GTK2
4371   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "prev")),
4372                       "map", GTK_SIGNAL_FUNC(map_prev_button_cb),
4373                       (gpointer) s);
4374   gtk_signal_connect (GTK_OBJECT (name_to_widget (s, "next")),
4375                       "map", GTK_SIGNAL_FUNC(map_next_button_cb),
4376                       (gpointer) s);
4377 #endif /* !HAVE_GTK2 */
4378
4379   /* Hook up callbacks to the items on the mode menu. */
4380   {
4381     GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
4382     GtkMenu *menu = GTK_MENU (gtk_option_menu_get_menu (opt));
4383     GList *kids = gtk_container_children (GTK_CONTAINER (menu));
4384     for (; kids; kids = kids->next)
4385       gtk_signal_connect (GTK_OBJECT (kids->data), "activate",
4386                           GTK_SIGNAL_FUNC (mode_menu_item_cb),
4387                           (gpointer) s);
4388   }
4389
4390
4391   /* Handle the -prefs command-line argument. */
4392   if (prefs)
4393     {
4394       GtkNotebook *notebook =
4395         GTK_NOTEBOOK (name_to_widget (s, "notebook"));
4396       gtk_notebook_set_page (notebook, 1);
4397     }
4398
4399 # ifdef HAVE_CRAPPLET
4400   if (crapplet_p)
4401     {
4402       GtkWidget *capplet;
4403       GtkWidget *outer_vbox;
4404
4405       gtk_widget_hide (s->toplevel_widget);
4406
4407       capplet = capplet_widget_new ();
4408
4409       /* Make there be a "Close" button instead of "OK" and "Cancel" */
4410 # ifdef HAVE_CRAPPLET_IMMEDIATE
4411       capplet_widget_changes_are_immediate (CAPPLET_WIDGET (capplet));
4412 # endif /* HAVE_CRAPPLET_IMMEDIATE */
4413       /* In crapplet-mode, take off the menubar. */
4414       gtk_widget_hide (name_to_widget (s, "menubar"));
4415
4416       /* Reparent our top-level container to be a child of the capplet
4417          window.
4418        */
4419       outer_vbox = GTK_BIN (s->toplevel_widget)->child;
4420       gtk_widget_ref (outer_vbox);
4421       gtk_container_remove (GTK_CONTAINER (s->toplevel_widget),
4422                             outer_vbox);
4423       GTK_OBJECT_SET_FLAGS (outer_vbox, GTK_FLOATING);
4424       gtk_container_add (GTK_CONTAINER (capplet), outer_vbox);
4425
4426       /* Find the window above us, and set the title and close handler. */
4427       {
4428         GtkWidget *window = capplet;
4429         while (window && !GTK_IS_WINDOW (window))
4430           window = window->parent;
4431         if (window)
4432           {
4433             gtk_window_set_title (GTK_WINDOW (window), window_title);
4434             gtk_signal_connect (GTK_OBJECT (window), "delete_event",
4435                                 GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
4436                                 (gpointer) s);
4437           }
4438       }
4439
4440       s->toplevel_widget = capplet;
4441     }
4442 # endif /* HAVE_CRAPPLET */
4443
4444
4445   /* The Gnome folks hate the menubar.  I think it's important to have access
4446      to the commands on the File menu (Restart Daemon, etc.) and to the
4447      About and Documentation commands on the Help menu.
4448    */
4449 #if 0
4450 #ifdef HAVE_GTK2
4451   gtk_widget_hide (name_to_widget (s, "menubar"));
4452 #endif
4453 #endif
4454
4455   free (window_title);
4456   window_title = 0;
4457
4458
4459   gtk_widget_show (s->toplevel_widget);
4460   init_icon (GTK_WIDGET (s->toplevel_widget)->window);  /* after `show' */
4461   fix_preview_visual (s);
4462
4463   /* Realize page zero, so that we can diddle the scrollbar when the
4464      user tabs back to it -- otherwise, the current hack isn't scrolled
4465      to the first time they tab back there, when started with "-prefs".
4466      (Though it is if they then tab away, and back again.)
4467
4468      #### Bah!  This doesn't work.  Gtk eats my ass!  Someone who
4469      #### understands this crap, explain to me how to make this work.
4470   */
4471   gtk_widget_realize (name_to_widget (s, "demos_table"));
4472
4473
4474   gtk_timeout_add (60 * 1000, check_blanked_timer, s);
4475
4476
4477   /* Issue any warnings about the running xscreensaver daemon. */
4478   the_network_is_not_the_computer (s);
4479
4480
4481   /* Run the Gtk event loop, and not the Xt event loop.  This means that
4482      if there were Xt timers or fds registered, they would never get serviced,
4483      and if there were any Xt widgets, they would never have events delivered.
4484      Fortunately, we're using Gtk for all of the UI, and only initialized
4485      Xt so that we could process the command line and use the X resource
4486      manager.
4487    */
4488   s->initializing_p = False;
4489
4490   /* This totally sucks -- set a timer that whacks the scrollbar 0.5 seconds
4491      after we start up.  Otherwise, it always appears scrolled to the top
4492      when in crapplet-mode. */
4493   gtk_timeout_add (500, delayed_scroll_kludge, s);
4494
4495
4496 #if 0
4497   /* Load every configurator in turn, to scan them for errors all at once. */
4498   {
4499     int i;
4500     for (i = 0; i < p->screenhacks_count; i++)
4501       {
4502         screenhack *hack = p->screenhacks[i];
4503         conf_data *d = load_configurator (hack->command, False);
4504         if (d) free_conf_data (d);
4505       }
4506   }
4507 #endif
4508
4509
4510 # ifdef HAVE_CRAPPLET
4511   if (crapplet_p)
4512     capplet_gtk_main ();
4513   else
4514 # endif /* HAVE_CRAPPLET */
4515     gtk_main ();
4516
4517   kill_preview_subproc (s);
4518   exit (0);
4519 }
4520
4521 #endif /* HAVE_GTK -- whole file */