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