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