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