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