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