6564748fce36886fc8ec7e800b2a4f9ace85d6c1
[xscreensaver] / driver / demo-Gtk.c
1 /* demo-Gtk.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2001 Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #ifdef HAVE_GTK /* whole file */
18
19 #include <stdlib.h>
20
21 #ifdef HAVE_UNISTD_H
22 # include <unistd.h>
23 #endif
24
25 #ifndef VMS
26 # include <pwd.h>               /* for getpwuid() */
27 #else /* VMS */
28 # include "vms-pwd.h"
29 #endif /* VMS */
30
31 #ifdef HAVE_UNAME
32 # include <sys/utsname.h>       /* for uname() */
33 #endif /* HAVE_UNAME */
34
35 #include <stdio.h>
36
37 #include <X11/Xproto.h>         /* for CARD32 */
38 #include <X11/Xatom.h>          /* for XA_INTEGER */
39 #include <X11/Intrinsic.h>
40 #include <X11/StringDefs.h>
41
42 /* We don't actually use any widget internals, but these are included
43    so that gdb will have debug info for the widgets... */
44 #include <X11/IntrinsicP.h>
45 #include <X11/ShellP.h>
46
47 #ifdef HAVE_XMU
48 # ifndef VMS
49 #  include <X11/Xmu/Error.h>
50 # else /* VMS */
51 #  include <Xmu/Error.h>
52 # endif
53 #else
54 # include "xmu.h"
55 #endif
56
57 #include <gtk/gtk.h>
58
59 #ifdef HAVE_CRAPPLET
60 # include <gnome.h>
61 # include <capplet-widget.h>
62 #endif
63
64 extern Display *gdk_display;
65
66 #include "version.h"
67 #include "prefs.h"
68 #include "resources.h"          /* for parse_time() */
69 #include "visual.h"             /* for has_writable_cells() */
70 #include "remote.h"             /* for xscreensaver_command() */
71 #include "usleep.h"
72
73 #include "logo-50.xpm"
74 #include "logo-180.xpm"
75
76 #include "demo-Gtk-widgets.h"
77
78 #include <stdio.h>
79 #include <string.h>
80 #include <ctype.h>
81
82 #undef countof
83 #define countof(x) (sizeof((x))/sizeof((*x)))
84
85
86 char *progname = 0;
87 char *progclass = "XScreenSaver";
88 XrmDatabase db;
89
90 static Bool crapplet_p = False;
91 static Bool initializing_p;
92 static GtkWidget *toplevel_widget;
93
94 typedef struct {
95   saver_preferences *a, *b;
96 } prefs_pair;
97
98 static void *global_prefs_pair;  /* I hate C so much... */
99
100 char *blurb (void) { return progname; }
101
102 static char *short_version = 0;
103
104 Atom XA_VROOT;
105 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
106 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
107 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
108
109
110 static void populate_demo_window (GtkWidget *toplevel,
111                                   int which, prefs_pair *pair);
112 static void populate_prefs_page (GtkWidget *top, prefs_pair *pair);
113 static int apply_changes_and_save (GtkWidget *widget);
114 static int maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair);
115 static void await_xscreensaver (GtkWidget *widget);
116
117 \f
118 /* Some random utility functions
119  */
120
121 static GtkWidget *
122 name_to_widget (GtkWidget *widget, const char *name)
123 {
124   return (GtkWidget *) gtk_object_get_data (GTK_OBJECT(toplevel_widget), name);
125 }
126
127
128 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
129    Takes a scroller, viewport, or list as an argument.
130  */
131 static void
132 ensure_selected_item_visible (GtkWidget *widget)
133 {
134   GtkScrolledWindow *scroller = 0;
135   GtkViewport *vp = 0;
136   GtkList *list_widget = 0;
137   GList *slist;
138   GList *kids;
139   int nkids = 0;
140   GtkWidget *selected = 0;
141   int which = -1;
142   GtkAdjustment *adj;
143   gint parent_h, child_y, child_h, children_h, ignore;
144   double ratio_t, ratio_b;
145
146   if (GTK_IS_SCROLLED_WINDOW (widget))
147     {
148       scroller = GTK_SCROLLED_WINDOW (widget);
149       vp = GTK_VIEWPORT (GTK_BIN (scroller)->child);
150       list_widget = GTK_LIST (GTK_BIN(vp)->child);
151     }
152   else if (GTK_IS_VIEWPORT (widget))
153     {
154       vp = GTK_VIEWPORT (widget);
155       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
156       list_widget = GTK_LIST (GTK_BIN(vp)->child);
157     }
158   else if (GTK_IS_LIST (widget))
159     {
160       list_widget = GTK_LIST (widget);
161       vp = GTK_VIEWPORT (GTK_WIDGET (list_widget)->parent);
162       scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
163     }
164   else
165     abort();
166
167   slist = list_widget->selection;
168   selected = (slist ? GTK_WIDGET (slist->data) : 0);
169   if (!selected)
170     return;
171
172   which = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
173
174   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
175        kids; kids = kids->next)
176     nkids++;
177
178   adj = gtk_scrolled_window_get_vadjustment (scroller);
179
180   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
181                            &ignore, &ignore, &ignore, &parent_h, &ignore);
182   gdk_window_get_geometry (GTK_WIDGET(selected)->window,
183                            &ignore, &child_y, &ignore, &child_h, &ignore);
184   children_h = nkids * child_h;
185
186   ratio_t = ((double) child_y) / ((double) children_h);
187   ratio_b = ((double) child_y + child_h) / ((double) children_h);
188
189   if (adj->upper == 0.0)  /* no items in list */
190     return;
191
192   if (ratio_t < (adj->value / adj->upper) ||
193       ratio_b > ((adj->value + adj->page_size) / adj->upper))
194     {
195       double target;
196       int slop = parent_h * 0.75; /* how much to overshoot by */
197
198       if (ratio_t < (adj->value / adj->upper))
199         {
200           double ratio_w = ((double) parent_h) / ((double) children_h);
201           double ratio_l = (ratio_b - ratio_t);
202           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
203           target += slop;
204         }
205       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
206         {
207           target = ratio_t * adj->upper;
208           target -= slop;
209         }
210
211       if (target > adj->upper - adj->page_size)
212         target = adj->upper - adj->page_size;
213       if (target < 0)
214         target = 0;
215
216       gtk_adjustment_set_value (adj, target);
217     }
218 }
219
220 static void
221 warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
222 {
223   GtkWidget *shell = GTK_WIDGET (user_data);
224   while (shell->parent)
225     shell = shell->parent;
226   gtk_widget_destroy (GTK_WIDGET (shell));
227 }
228
229
230 void restart_menu_cb (GtkWidget *widget, gpointer user_data);
231
232 static void warning_dialog_restart_cb (GtkWidget *widget, gpointer user_data)
233 {
234   restart_menu_cb (widget, user_data);
235   warning_dialog_dismiss_cb (widget, user_data);
236 }
237
238 static void
239 warning_dialog (GtkWidget *parent, const char *message,
240                 Boolean restart_button_p, int center)
241 {
242   char *msg = strdup (message);
243   char *head;
244
245   GtkWidget *dialog = gtk_dialog_new ();
246   GtkWidget *label = 0;
247   GtkWidget *ok = 0;
248   GtkWidget *cancel = 0;
249   int i = 0;
250
251   while (parent->parent)
252     parent = parent->parent;
253
254   head = msg;
255   while (head)
256     {
257       char name[20];
258       char *s = strchr (head, '\n');
259       if (s) *s = 0;
260
261       sprintf (name, "label%d", i++);
262
263       {
264         label = gtk_label_new (head);
265
266         if (i == 1)
267           {
268             GTK_WIDGET (label)->style =
269               gtk_style_copy (GTK_WIDGET (label)->style);
270             GTK_WIDGET (label)->style->font =
271               gdk_font_load (get_string_resource("warning_dialog.headingFont",
272                                                  "Dialog.Font"));
273             gtk_widget_set_style (GTK_WIDGET (label),
274                                   GTK_WIDGET (label)->style);
275           }
276
277         if (center <= 0)
278           gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
279         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
280                             label, TRUE, TRUE, 0);
281         gtk_widget_show (label);
282       }
283
284       if (s)
285         head = s+1;
286       else
287         head = 0;
288
289       center--;
290     }
291
292   label = gtk_label_new ("");
293   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
294                       label, TRUE, TRUE, 0);
295   gtk_widget_show (label);
296
297   label = gtk_hbutton_box_new ();
298   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
299                       label, TRUE, TRUE, 0);
300
301   ok = gtk_button_new_with_label ("OK");
302   gtk_container_add (GTK_CONTAINER (label), ok);
303
304   if (restart_button_p)
305     {
306       cancel = gtk_button_new_with_label ("Cancel");
307       gtk_container_add (GTK_CONTAINER (label), cancel);
308     }
309
310   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
311   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
312   gtk_window_set_title (GTK_WINDOW (dialog), progclass);
313   gtk_widget_show (ok);
314   if (cancel)
315     gtk_widget_show (cancel);
316   gtk_widget_show (label);
317   gtk_widget_show (dialog);
318 /*  gtk_window_set_default (GTK_WINDOW (dialog), ok);*/
319
320   if (restart_button_p)
321     {
322       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
323                                  GTK_SIGNAL_FUNC (warning_dialog_restart_cb),
324                                  (gpointer) dialog);
325       gtk_signal_connect_object (GTK_OBJECT (cancel), "clicked",
326                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
327                                  (gpointer) dialog);
328     }
329   else
330     {
331       gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
332                                  GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
333                                  (gpointer) dialog);
334     }
335   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
336                                 GTK_WIDGET (parent)->window);
337
338   gdk_window_show (GTK_WIDGET (dialog)->window);
339   gdk_window_raise (GTK_WIDGET (dialog)->window);
340
341   free (msg);
342 }
343
344
345 static void
346 run_cmd (GtkWidget *widget, Atom command, int arg)
347 {
348   char *err = 0;
349   int status;
350
351   apply_changes_and_save (widget);
352   status = xscreensaver_command (gdk_display, command, arg, False, &err);
353   if (status < 0)
354     {
355       char buf [255];
356       if (err)
357         sprintf (buf, "Error:\n\n%s", err);
358       else
359         strcpy (buf, "Unknown error!");
360       warning_dialog (widget, buf, False, 100);
361     }
362   if (err) free (err);
363 }
364
365
366 static void
367 run_hack (GtkWidget *widget, int which, Bool report_errors_p)
368 {
369   if (which < 0) return;
370   apply_changes_and_save (widget);
371   if (report_errors_p)
372     run_cmd (widget, XA_DEMO, which + 1);
373   else
374     {
375       char *s = 0;
376       xscreensaver_command (gdk_display, XA_DEMO, which + 1, False, &s);
377       if (s) free (s);
378     }
379 }
380
381
382 \f
383 /* Button callbacks
384  */
385
386 void
387 exit_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
388 {
389   apply_changes_and_save (GTK_WIDGET (menuitem));
390   gtk_main_quit ();
391 }
392
393 static void
394 wm_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
395 {
396   apply_changes_and_save (widget);
397   gtk_main_quit ();
398 }
399
400
401 void
402 cut_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
403 {
404   /* #### */
405   warning_dialog (GTK_WIDGET (menuitem),
406                   "Error:\n\n"
407                   "cut unimplemented\n", False, 1);
408 }
409
410
411 void
412 copy_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
413 {
414   /* #### */
415   warning_dialog (GTK_WIDGET (menuitem),
416                   "Error:\n\n"
417                   "copy unimplemented\n", False, 1);
418 }
419
420
421 void
422 paste_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
423 {
424   /* #### */
425   warning_dialog (GTK_WIDGET (menuitem),
426                   "Error:\n\n"
427                   "paste unimplemented\n", False, 1);
428 }
429
430
431 void
432 about_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
433 {
434   char msg [2048];
435   char *vers = strdup (screensaver_id + 4);
436   char *s;
437   char copy[1024];
438   char *desc = "For updates, check http://www.jwz.org/xscreensaver/";
439
440   s = strchr (vers, ',');
441   *s = 0;
442   s += 2;
443
444   sprintf(copy, "Copyright \251 1991-2001 %s", s);
445
446   sprintf (msg, "%s\n\n%s", copy, desc);
447
448   /* I can't make gnome_about_new() work here -- it starts dying in
449      gdk_imlib_get_visual() under gnome_about_new().  If this worked,
450      then this might be the thing to do:
451
452      #ifdef HAVE_CRAPPLET
453      {
454        const gchar *auth[] = { 0 };
455        GtkWidget *about = gnome_about_new (progclass, vers, "", auth, desc,
456                                            "xscreensaver.xpm");
457        gtk_widget_show (about);
458      }
459      #else / * GTK but not GNOME * /
460       ...
461    */
462   {
463     GdkColormap *colormap;
464     GdkPixmap *gdkpixmap;
465     GdkBitmap *mask;
466
467     GtkWidget *dialog = gtk_dialog_new ();
468     GtkWidget *hbox, *icon, *vbox, *label1, *label2, *hb, *ok;
469     GtkWidget *parent = GTK_WIDGET (menuitem);
470     while (parent->parent)
471       parent = parent->parent;
472
473     hbox = gtk_hbox_new (FALSE, 20);
474     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
475                         hbox, TRUE, TRUE, 0);
476
477     colormap = gtk_widget_get_colormap (parent);
478     gdkpixmap =
479       gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, NULL,
480                                              (gchar **) logo_180_xpm);
481     icon = gtk_pixmap_new (gdkpixmap, mask);
482     gtk_misc_set_padding (GTK_MISC (icon), 10, 10);
483
484     gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
485
486     vbox = gtk_vbox_new (FALSE, 0);
487     gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
488
489     label1 = gtk_label_new (vers);
490     gtk_box_pack_start (GTK_BOX (vbox), label1, TRUE, TRUE, 0);
491     gtk_label_set_justify (GTK_LABEL (label1), GTK_JUSTIFY_LEFT);
492     gtk_misc_set_alignment (GTK_MISC (label1), 0.0, 0.75);
493
494     GTK_WIDGET (label1)->style = gtk_style_copy (GTK_WIDGET (label1)->style);
495     GTK_WIDGET (label1)->style->font =
496       gdk_font_load (get_string_resource ("about.headingFont","Dialog.Font"));
497     gtk_widget_set_style (GTK_WIDGET (label1), GTK_WIDGET (label1)->style);
498
499     label2 = gtk_label_new (msg);
500     gtk_box_pack_start (GTK_BOX (vbox), label2, TRUE, TRUE, 0);
501     gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_LEFT);
502     gtk_misc_set_alignment (GTK_MISC (label2), 0.0, 0.25);
503
504     GTK_WIDGET (label2)->style = gtk_style_copy (GTK_WIDGET (label2)->style);
505     GTK_WIDGET (label2)->style->font =
506       gdk_font_load (get_string_resource ("about.bodyFont","Dialog.Font"));
507     gtk_widget_set_style (GTK_WIDGET (label2), GTK_WIDGET (label2)->style);
508
509     hb = gtk_hbutton_box_new ();
510
511     gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
512                         hb, TRUE, TRUE, 0);
513
514     ok = gtk_button_new_with_label ("OK");
515     gtk_container_add (GTK_CONTAINER (hb), ok);
516
517     gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
518     gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
519     gtk_window_set_title (GTK_WINDOW (dialog), progclass);
520
521     gtk_widget_show (hbox);
522     gtk_widget_show (icon);
523     gtk_widget_show (vbox);
524     gtk_widget_show (label1);
525     gtk_widget_show (label2);
526     gtk_widget_show (hb);
527     gtk_widget_show (ok);
528     gtk_widget_show (dialog);
529
530     gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
531                                GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
532                                (gpointer) dialog);
533     gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
534                                   GTK_WIDGET (parent)->window);
535     gdk_window_show (GTK_WIDGET (dialog)->window);
536     gdk_window_raise (GTK_WIDGET (dialog)->window);
537   }
538 }
539
540
541 void
542 doc_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
543 {
544   /* prefs_pair *pair = (prefs_pair *) client_data; */
545   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
546
547   saver_preferences *p =  pair->a;
548   char *help_command;
549
550   if (!p->help_url || !*p->help_url)
551     {
552       warning_dialog (GTK_WIDGET (menuitem),
553                       "Error:\n\n"
554                       "No Help URL has been specified.\n", False, 100);
555       return;
556     }
557
558   help_command = (char *) malloc (strlen (p->load_url_command) +
559                                   (strlen (p->help_url) * 2) + 20);
560   strcpy (help_command, "( ");
561   sprintf (help_command + strlen(help_command),
562            p->load_url_command, p->help_url, p->help_url);
563   strcat (help_command, " ) &");
564   system (help_command);
565   free (help_command);
566 }
567
568
569 void
570 activate_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
571 {
572   run_cmd (GTK_WIDGET (menuitem), XA_ACTIVATE, 0);
573 }
574
575
576 void
577 lock_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
578 {
579   run_cmd (GTK_WIDGET (menuitem), XA_LOCK, 0);
580 }
581
582
583 void
584 kill_menu_cb (GtkMenuItem *menuitem, gpointer user_data)
585 {
586   run_cmd (GTK_WIDGET (menuitem), XA_EXIT, 0);
587 }
588
589
590 void
591 restart_menu_cb (GtkWidget *widget, gpointer user_data)
592 {
593 #if 0
594   run_cmd (GTK_WIDGET (widget), XA_RESTART, 0);
595 #else
596   apply_changes_and_save (GTK_WIDGET (widget));
597   xscreensaver_command (gdk_display, XA_EXIT, 0, False, NULL);
598   sleep (1);
599   system ("xscreensaver -nosplash &");
600 #endif
601
602   await_xscreensaver (GTK_WIDGET (widget));
603 }
604
605 static void
606 await_xscreensaver (GtkWidget *widget)
607 {
608   int countdown = 5;
609
610   Display *dpy = gdk_display;
611   /*  GtkWidget *dialog = 0;*/
612   char *rversion = 0;
613
614   while (!rversion && (--countdown > 0))
615     {
616       /* Check for the version of the running xscreensaver... */
617       server_xscreensaver_version (dpy, &rversion, 0, 0);
618
619       /* If it's not there yet, wait a second... */
620       sleep (1);
621     }
622
623 /*  if (dialog) gtk_widget_destroy (dialog);*/
624
625   if (rversion)
626     {
627       /* Got it. */
628       free (rversion);
629     }
630   else
631     {
632       /* Timed out, no screensaver running. */
633
634       char buf [1024];
635       Bool root_p = (geteuid () == 0);
636       
637       strcpy (buf, 
638               "Error:\n\n"
639               "The xscreensaver daemon did not start up properly.\n"
640               "\n");
641
642       if (root_p)
643         strcat (buf,
644             "You are running as root.  This usually means that xscreensaver\n"
645             "was unable to contact your X server because access control is\n"
646             "turned on.  Try running this command:\n"
647             "\n"
648             "                        xhost +localhost\n"
649             "\n"
650             "and then selecting `File / Restart Daemon'.\n"
651             "\n"
652             "Note that turning off access control will allow anyone logged\n"
653             "on to this machine to access your screen, which might be\n"
654             "considered a security problem.  Please read the xscreensaver\n"
655             "manual and FAQ for more information.\n"
656             "\n"
657             "You shouldn't run X as root. Instead, you should log in as a\n"
658             "normal user, and `su' as necessary.");
659       else
660         strcat (buf, "Please check your $PATH and permissions.");
661
662       warning_dialog (widget, buf, False, 1);
663     }
664 }
665
666
667 static int _selected_hack_number = -1;
668
669 static int
670 selected_hack_number (GtkWidget *toplevel)
671 {
672 #if 0
673   GtkViewport *vp = GTK_VIEWPORT (name_to_widget (toplevel, "viewport"));
674   GtkList *list_widget = GTK_LIST (GTK_BIN(vp)->child);
675   GList *slist = list_widget->selection;
676   GtkWidget *selected = (slist ? GTK_WIDGET (slist->data) : 0);
677   int which = (selected
678                ? gtk_list_child_position (list_widget, GTK_WIDGET (selected))
679                : -1);
680   return which;
681 #else
682   return _selected_hack_number;
683 #endif
684 }
685
686
687 static int
688 demo_write_init_file (GtkWidget *widget, saver_preferences *p)
689 {
690   if (!write_init_file (p, short_version, False))
691     return 0;
692   else
693     {
694       const char *f = init_file_name();
695       if (!f || !*f)
696         warning_dialog (widget,
697                         "Error:\n\nCouldn't determine init file name!\n",
698                         False, 100);
699       else
700         {
701           char *b = (char *) malloc (strlen(f) + 1024);
702           sprintf (b, "Error:\n\nCouldn't write %s\n", f);
703           warning_dialog (widget, b, False, 100);
704           free (b);
705         }
706       return -1;
707     }
708 }
709
710
711 static int
712 apply_changes_and_save_1 (GtkWidget *widget)
713 {
714   /* prefs_pair *pair = (prefs_pair *) client_data; */
715   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
716   saver_preferences *p =  pair->a;
717   GtkList *list_widget =
718     GTK_LIST (name_to_widget (widget, "list"));
719   int which = selected_hack_number (widget);
720
721   GtkEntry *cmd = GTK_ENTRY (name_to_widget (widget, "cmd_text"));
722   GtkToggleButton *enabled =
723     GTK_TOGGLE_BUTTON (name_to_widget (widget, "enabled"));
724   GtkCombo *vis = GTK_COMBO (name_to_widget (widget, "visual_combo"));
725
726   Bool enabled_p = gtk_toggle_button_get_active (enabled);
727   const char *visual = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (vis)->entry));
728   const char *command = gtk_entry_get_text (cmd);
729   
730   char c;
731   unsigned long id;
732
733   if (which < 0) return -1;
734
735   if (maybe_reload_init_file (widget, pair) != 0)
736     return 1;
737
738   /* Sanity-check and canonicalize whatever the user typed into the combo box.
739    */
740   if      (!strcasecmp (visual, ""))                   visual = "";
741   else if (!strcasecmp (visual, "any"))                visual = "";
742   else if (!strcasecmp (visual, "default"))            visual = "Default";
743   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
744   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
745   else if (!strcasecmp (visual, "best"))               visual = "Best";
746   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
747   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
748   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
749   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
750   else if (!strcasecmp (visual, "color"))              visual = "Color";
751   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
752   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
753   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
754   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
755   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
756   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
757   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
758   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
759   else if (1 == sscanf (visual, " %ld %c", &id, &c))   ;
760   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
761   else
762     {
763       gdk_beep ();                                /* unparsable */
764       visual = "";
765       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");
766     }
767
768   ensure_selected_item_visible (GTK_WIDGET (list_widget));
769
770   if (!p->screenhacks[which]->visual)
771     p->screenhacks[which]->visual = strdup ("");
772   if (!p->screenhacks[which]->command)
773     p->screenhacks[which]->command = strdup ("");
774
775   if (p->screenhacks[which]->enabled_p != enabled_p ||
776       !!strcasecmp (p->screenhacks[which]->visual, visual) ||
777       !!strcasecmp (p->screenhacks[which]->command, command))
778     {
779       /* Something was changed -- store results into the struct,
780          and write the file.
781        */
782       free (p->screenhacks[which]->visual);
783       free (p->screenhacks[which]->command);
784       p->screenhacks[which]->visual = strdup (visual);
785       p->screenhacks[which]->command = strdup (command);
786       p->screenhacks[which]->enabled_p = enabled_p;
787
788       return demo_write_init_file (widget, p);
789     }
790
791   /* No changes made */
792   return 0;
793 }
794
795 void prefs_ok_cb (GtkButton *button, gpointer user_data);
796
797 static int
798 apply_changes_and_save (GtkWidget *widget)
799 {
800   prefs_ok_cb ((GtkButton *) widget, 0);
801   return apply_changes_and_save_1 (widget);
802 }
803
804
805 void
806 run_this_cb (GtkButton *button, gpointer user_data)
807 {
808   int which = selected_hack_number (GTK_WIDGET (button));
809   if (which < 0) return;
810   if (0 == apply_changes_and_save (GTK_WIDGET (button)))
811     run_hack (GTK_WIDGET (button), which, True);
812 }
813
814
815 void
816 manual_cb (GtkButton *button, gpointer user_data)
817 {
818   /* prefs_pair *pair = (prefs_pair *) client_data; */
819   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
820   saver_preferences *p =  pair->a;
821   GtkList *list_widget =
822     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
823   int which = selected_hack_number (GTK_WIDGET (button));
824   char *name, *name2, *cmd, *s;
825   if (which < 0) return;
826   apply_changes_and_save (GTK_WIDGET (button));
827   ensure_selected_item_visible (GTK_WIDGET (list_widget));
828
829   name = strdup (p->screenhacks[which]->command);
830   name2 = name;
831   while (isspace (*name2)) name2++;
832   s = name2;
833   while (*s && !isspace (*s)) s++;
834   *s = 0;
835   s = strrchr (name2, '/');
836   if (s) name = s+1;
837
838   cmd = get_string_resource ("manualCommand", "ManualCommand");
839   if (cmd)
840     {
841       char *cmd2 = (char *) malloc (strlen (cmd) + strlen (name2) + 100);
842       strcpy (cmd2, "( ");
843       sprintf (cmd2 + strlen (cmd2),
844                cmd,
845                name2, name2, name2, name2);
846       strcat (cmd2, " ) &");
847       system (cmd2);
848       free (cmd2);
849     }
850   else
851     {
852       warning_dialog (GTK_WIDGET (button),
853                       "Error:\n\nno `manualCommand' resource set.",
854                       False, 100);
855     }
856
857   free (name);
858 }
859
860
861 void
862 run_next_cb (GtkButton *button, gpointer user_data)
863 {
864   /* prefs_pair *pair = (prefs_pair *) client_data; */
865   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
866   saver_preferences *p =  pair->a;
867
868   GtkList *list_widget =
869     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
870   int which = selected_hack_number (GTK_WIDGET (button));
871
872   if (which < 0)
873     which = 0;
874   else
875     which++;
876
877   if (which >= p->screenhacks_count)
878     which = 0;
879
880   apply_changes_and_save (GTK_WIDGET (button));
881   gtk_list_select_item (GTK_LIST (list_widget), which);
882   ensure_selected_item_visible (GTK_WIDGET (list_widget));
883   populate_demo_window (GTK_WIDGET (button), which, pair);
884   run_hack (GTK_WIDGET (button), which, False);
885 }
886
887
888 void
889 run_prev_cb (GtkButton *button, gpointer user_data)
890 {
891   /* prefs_pair *pair = (prefs_pair *) client_data; */
892   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
893   saver_preferences *p =  pair->a;
894
895   GtkList *list_widget =
896     GTK_LIST (name_to_widget (GTK_WIDGET (button), "list"));
897   int which = selected_hack_number (GTK_WIDGET (button));
898
899   if (which < 0)
900     which = p->screenhacks_count - 1;
901   else
902     which--;
903
904   if (which < 0)
905     which = p->screenhacks_count - 1;
906
907   apply_changes_and_save (GTK_WIDGET (button));
908   gtk_list_select_item (GTK_LIST (list_widget), which);
909   ensure_selected_item_visible (GTK_WIDGET (list_widget));
910   populate_demo_window (GTK_WIDGET (button), which, pair);
911   run_hack (GTK_WIDGET (button), which, False);
912 }
913
914
915 /* Helper for the text fields that contain time specifications:
916    this parses the text, and does error checking.
917  */
918 static void 
919 hack_time_text (GtkWidget *widget, const char *line, Time *store, Bool sec_p)
920 {
921   if (*line)
922     {
923       int value;
924       value = parse_time ((char *) line, sec_p, True);
925       value *= 1000;    /* Time measures in microseconds */
926       if (value < 0)
927         {
928           char b[255];
929           sprintf (b,
930                    "Error:\n\n"
931                    "Unparsable time format: \"%s\"\n",
932                    line);
933           warning_dialog (widget, b, False, 100);
934         }
935       else
936         *store = value;
937     }
938 }
939
940
941 void
942 prefs_ok_cb (GtkButton *button, gpointer user_data)
943 {
944   /* prefs_pair *pair = (prefs_pair *) client_data; */
945   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
946
947   saver_preferences *p =  pair->a;
948   saver_preferences *p2 = pair->b;
949   Bool changed = False;
950
951 # define SECONDS(field, name) \
952   hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
953                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
954                   (field), \
955                   True)
956
957 # define MINUTES(field, name) \
958   hack_time_text (GTK_WIDGET(button), gtk_entry_get_text (\
959                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))), \
960                   (field), \
961                   False)
962
963 # define INTEGER(field, name) do { \
964     char *line = gtk_entry_get_text (\
965                     GTK_ENTRY (name_to_widget (GTK_WIDGET(button), (name)))); \
966     unsigned int value; \
967     char c; \
968     if (! *line) \
969       ; \
970     else if (sscanf (line, "%u%c", &value, &c) != 1) \
971       { \
972         char b[255]; \
973         sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", line); \
974         warning_dialog (GTK_WIDGET (button), b, False, 100); \
975       } \
976    else \
977      *(field) = value; \
978   } while(0)
979
980 # define CHECKBOX(field, name) \
981   field = gtk_toggle_button_get_active (\
982              GTK_TOGGLE_BUTTON (name_to_widget (GTK_WIDGET(button), (name))))
983
984   MINUTES (&p2->timeout,          "timeout_text");
985   MINUTES (&p2->cycle,            "cycle_text");
986   CHECKBOX (p2->lock_p,           "lock_button");
987   MINUTES (&p2->lock_timeout,     "lock_text");
988
989   CHECKBOX (p2->dpms_enabled_p,   "dpms_button");
990   MINUTES (&p2->dpms_standby,     "dpms_standby_text");
991   MINUTES (&p2->dpms_suspend,     "dpms_suspend_text");
992   MINUTES (&p2->dpms_off,         "dpms_off_text");
993
994   CHECKBOX (p2->verbose_p,        "verbose_button");
995   CHECKBOX (p2->capture_stderr_p, "capture_button");
996   CHECKBOX (p2->splash_p,         "splash_button");
997
998   CHECKBOX (p2->install_cmap_p,   "install_button");
999   CHECKBOX (p2->fade_p,           "fade_button");
1000   CHECKBOX (p2->unfade_p,         "unfade_button");
1001   SECONDS (&p2->fade_seconds,     "fade_text");
1002
1003 # undef SECONDS
1004 # undef MINUTES
1005 # undef INTEGER
1006 # undef CHECKBOX
1007
1008 # define COPY(field) \
1009   if (p->field != p2->field) changed = True; \
1010   p->field = p2->field
1011
1012   COPY(timeout);
1013   COPY(cycle);
1014   COPY(lock_p);
1015   COPY(lock_timeout);
1016
1017   COPY(dpms_enabled_p);
1018   COPY(dpms_standby);
1019   COPY(dpms_suspend);
1020   COPY(dpms_off);
1021
1022   COPY(verbose_p);
1023   COPY(capture_stderr_p);
1024   COPY(splash_p);
1025
1026   COPY(install_cmap_p);
1027   COPY(fade_p);
1028   COPY(unfade_p);
1029   COPY(fade_seconds);
1030 # undef COPY
1031
1032   populate_prefs_page (GTK_WIDGET (button), pair);
1033
1034   if (changed)
1035     {
1036       Display *dpy = gdk_display;
1037       sync_server_dpms_settings (dpy, p->dpms_enabled_p,
1038                                  p->dpms_standby / 1000,
1039                                  p->dpms_suspend / 1000,
1040                                  p->dpms_off / 1000,
1041                                  False);
1042
1043       demo_write_init_file (GTK_WIDGET (button), p);
1044     }
1045 }
1046
1047
1048 void
1049 prefs_cancel_cb (GtkButton *button, gpointer user_data)
1050 {
1051   /* prefs_pair *pair = (prefs_pair *) client_data; */
1052   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
1053
1054   *pair->b = *pair->a;
1055   populate_prefs_page (GTK_WIDGET (button), pair);
1056 }
1057
1058
1059 void
1060 pref_changed_cb (GtkButton *button, gpointer user_data)
1061 {
1062   if (! initializing_p)
1063     apply_changes_and_save (GTK_WIDGET (button));
1064 }
1065
1066
1067 static gint
1068 list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
1069                      gpointer client_data)
1070 {
1071   if (event->type == GDK_2BUTTON_PRESS)
1072     {
1073       GtkList *list = GTK_LIST (name_to_widget (button, "list"));
1074       int which = gtk_list_child_position (list, GTK_WIDGET (button));
1075
1076       if (which >= 0)
1077         run_hack (GTK_WIDGET (button), which, True);
1078     }
1079
1080   return FALSE;
1081 }
1082
1083
1084 static void
1085 list_select_cb (GtkList *list, GtkWidget *child)
1086 {
1087   /* prefs_pair *pair = (prefs_pair *) client_data; */
1088   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
1089
1090   int which = gtk_list_child_position (list, GTK_WIDGET (child));
1091   apply_changes_and_save (GTK_WIDGET (list));
1092   populate_demo_window (GTK_WIDGET (list), which, pair);
1093 }
1094
1095 static void
1096 list_unselect_cb (GtkList *list, GtkWidget *child)
1097 {
1098   /* prefs_pair *pair = (prefs_pair *) client_data; */
1099   prefs_pair *pair = global_prefs_pair;  /* I hate C so much... */
1100
1101   apply_changes_and_save (GTK_WIDGET (list));
1102   populate_demo_window (GTK_WIDGET (list), -1, pair);
1103 }
1104
1105
1106 static int updating_enabled_cb = 0;  /* kludge to make sure that enabled_cb
1107                                         is only run by user action, not by
1108                                         program action. */
1109
1110 /* Called when the checkboxes that are in the left column of the
1111    scrolling list are clicked.  This both populates the right pane
1112    (just as clicking on the label (really, listitem) does) and
1113    also syncs this checkbox with  the right pane Enabled checkbox.
1114  */
1115 static void
1116 list_checkbox_cb (GtkWidget *cb, gpointer client_data)
1117 {
1118   prefs_pair *pair = (prefs_pair *) client_data;
1119
1120   GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
1121   GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
1122
1123   GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
1124   GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
1125   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
1126   GtkAdjustment *adj;
1127   double scroll_top;
1128
1129   GtkToggleButton *enabled =
1130     GTK_TOGGLE_BUTTON (name_to_widget (cb, "enabled"));
1131
1132   int which = gtk_list_child_position (list, line);
1133
1134   /* remember previous scroll position of the top of the list */
1135   adj = gtk_scrolled_window_get_vadjustment (scroller);
1136   scroll_top = adj->value;
1137
1138   apply_changes_and_save (GTK_WIDGET (list));
1139   gtk_list_select_item (list, which);
1140   /* ensure_selected_item_visible (GTK_WIDGET (list)); */
1141   populate_demo_window (GTK_WIDGET (list), which, pair);
1142   
1143   updating_enabled_cb++;
1144   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (enabled),
1145                                 GTK_TOGGLE_BUTTON (cb)->active);
1146   updating_enabled_cb--;
1147
1148   /* restore the previous scroll position of the top of the list.
1149      this is weak, but I don't really know why it's moving... */
1150   gtk_adjustment_set_value (adj, scroll_top);
1151 }
1152
1153
1154 /* Called when the right pane Enabled checkbox is clicked.  This syncs
1155    the corresponding checkbox inside the scrolling list to the state
1156    of this checkbox.
1157  */
1158 void
1159 enabled_cb (GtkWidget *cb, gpointer client_data)
1160 {
1161   int which = selected_hack_number (cb);
1162   
1163   if (updating_enabled_cb) return;
1164
1165   if (which != -1)
1166     {
1167       GtkList *list = GTK_LIST (name_to_widget (cb, "list"));
1168       GList *kids = GTK_LIST (list)->children;
1169       GtkWidget *line = GTK_WIDGET (g_list_nth_data (kids, which));
1170       GtkWidget *line_hbox = GTK_WIDGET (GTK_BIN (line)->child);
1171       GtkWidget *line_check =
1172         GTK_WIDGET (gtk_container_children (GTK_CONTAINER (line_hbox))->data);
1173
1174       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
1175                                     GTK_TOGGLE_BUTTON (cb)->active);
1176     }
1177 }
1178
1179 \f
1180 /* Populating the various widgets
1181  */
1182
1183
1184 /* Formats a `Time' into "H:MM:SS".  (Time is microseconds.)
1185  */
1186 static void
1187 format_time (char *buf, Time time)
1188 {
1189   int s = time / 1000;
1190   unsigned int h = 0, m = 0;
1191   if (s >= 60)
1192     {
1193       m += (s / 60);
1194       s %= 60;
1195     }
1196   if (m >= 60)
1197     {
1198       h += (m / 60);
1199       m %= 60;
1200     }
1201   sprintf (buf, "%u:%02u:%02u", h, m, s);
1202 }
1203
1204
1205 /* Finds the number of the last hack to run, and makes that item be
1206    selected by default.
1207  */
1208 static void
1209 scroll_to_current_hack (GtkWidget *toplevel, prefs_pair *pair)
1210 {
1211   saver_preferences *p =  pair->a;
1212   Atom type;
1213   int format;
1214   unsigned long nitems, bytesafter;
1215   CARD32 *data = 0;
1216   Display *dpy = gdk_display;
1217   int which = 0;
1218   GtkList *list;
1219
1220   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
1221                           XA_SCREENSAVER_STATUS,
1222                           0, 3, False, XA_INTEGER,
1223                           &type, &format, &nitems, &bytesafter,
1224                           (unsigned char **) &data)
1225       == Success
1226       && type == XA_INTEGER
1227       && nitems >= 3
1228       && data)
1229     which = (int) data[2] - 1;
1230
1231   if (data) free (data);
1232
1233   if (which < 0)
1234     return;
1235
1236   list = GTK_LIST (name_to_widget (toplevel, "list"));
1237   apply_changes_and_save (toplevel);
1238   if (which < p->screenhacks_count)
1239     {
1240       gtk_list_select_item (list, which);
1241       ensure_selected_item_visible (GTK_WIDGET (list));
1242       populate_demo_window (toplevel, which, pair);
1243     }
1244 }
1245
1246
1247
1248 static void
1249 populate_hack_list (GtkWidget *toplevel, prefs_pair *pair)
1250 {
1251   saver_preferences *p =  pair->a;
1252   GtkList *list = GTK_LIST (name_to_widget (toplevel, "list"));
1253   screenhack **hacks = p->screenhacks;
1254   screenhack **h;
1255
1256   for (h = hacks; h && *h; h++)
1257     {
1258       /* A GtkList must contain only GtkListItems, but those can contain
1259          an arbitrary widget.  We add an Hbox, and inside that, a Checkbox
1260          and a Label.  We handle single and double click events on the
1261          line itself, for clicking on the text, but the interior checkbox
1262          also handles its own events.
1263        */
1264       GtkWidget *line;
1265       GtkWidget *line_hbox;
1266       GtkWidget *line_check;
1267       GtkWidget *line_label;
1268
1269       char *pretty_name = (h[0]->name
1270                            ? strdup (h[0]->name)
1271                            : make_hack_name (h[0]->command));
1272
1273       line = gtk_list_item_new ();
1274       line_hbox = gtk_hbox_new (FALSE, 0);
1275       line_check = gtk_check_button_new ();
1276       line_label = gtk_label_new (pretty_name);
1277
1278       gtk_container_add (GTK_CONTAINER (line), line_hbox);
1279       gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
1280       gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
1281
1282       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
1283                                     h[0]->enabled_p);
1284       gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
1285
1286       gtk_widget_show (line_check);
1287       gtk_widget_show (line_label);
1288       gtk_widget_show (line_hbox);
1289       gtk_widget_show (line);
1290
1291       free (pretty_name);
1292
1293       gtk_container_add (GTK_CONTAINER (list), line);
1294       gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
1295                           GTK_SIGNAL_FUNC (list_doubleclick_cb),
1296                           (gpointer) pair);
1297
1298       gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
1299                           GTK_SIGNAL_FUNC (list_checkbox_cb),
1300                           (gpointer) pair);
1301
1302 #if 0 /* #### */
1303       GTK_WIDGET (GTK_BIN(line)->child)->style =
1304         gtk_style_copy (GTK_WIDGET (text_line)->style);
1305 #endif
1306       gtk_widget_show (line);
1307     }
1308
1309   gtk_signal_connect (GTK_OBJECT (list), "select_child",
1310                       GTK_SIGNAL_FUNC (list_select_cb),
1311                       (gpointer) pair);
1312   gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
1313                       GTK_SIGNAL_FUNC (list_unselect_cb),
1314                       (gpointer) pair);
1315 }
1316
1317
1318 static void
1319 populate_prefs_page (GtkWidget *top, prefs_pair *pair)
1320 {
1321   saver_preferences *p =  pair->a;
1322   char s[100];
1323
1324   format_time (s, p->timeout);
1325   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "timeout_text")), s);
1326   format_time (s, p->cycle);
1327   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "cycle_text")), s);
1328   format_time (s, p->lock_timeout);
1329   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "lock_text")), s);
1330
1331   format_time (s, p->dpms_standby);
1332   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "dpms_standby_text")),s);
1333   format_time (s, p->dpms_suspend);
1334   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "dpms_suspend_text")),s);
1335   format_time (s, p->dpms_off);
1336   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "dpms_off_text")), s);
1337
1338   format_time (s, p->fade_seconds);
1339   gtk_entry_set_text (GTK_ENTRY (name_to_widget (top, "fade_text")), s);
1340
1341   gtk_toggle_button_set_active (
1342                    GTK_TOGGLE_BUTTON (name_to_widget (top, "lock_button")),
1343                    p->lock_p);
1344   gtk_toggle_button_set_active (
1345                    GTK_TOGGLE_BUTTON (name_to_widget (top, "verbose_button")),
1346                    p->verbose_p);
1347   gtk_toggle_button_set_active (
1348                    GTK_TOGGLE_BUTTON (name_to_widget (top, "capture_button")),
1349                    p->capture_stderr_p);
1350   gtk_toggle_button_set_active (
1351                    GTK_TOGGLE_BUTTON (name_to_widget (top, "splash_button")),
1352                    p->splash_p);
1353
1354   gtk_toggle_button_set_active (
1355                    GTK_TOGGLE_BUTTON (name_to_widget (top, "dpms_button")),
1356                    p->dpms_enabled_p);
1357
1358   gtk_toggle_button_set_active (
1359                    GTK_TOGGLE_BUTTON (name_to_widget (top, "install_button")),
1360                    p->install_cmap_p);
1361   gtk_toggle_button_set_active (
1362                    GTK_TOGGLE_BUTTON (name_to_widget (top, "fade_button")),
1363                    p->fade_p);
1364   gtk_toggle_button_set_active (
1365                    GTK_TOGGLE_BUTTON (name_to_widget (top, "unfade_button")),
1366                    p->unfade_p);
1367
1368
1369   {
1370     Bool found_any_writable_cells = False;
1371     Bool dpms_supported = False;
1372
1373     Display *dpy = gdk_display;
1374     int nscreens = ScreenCount(dpy);
1375     int i;
1376     for (i = 0; i < nscreens; i++)
1377       {
1378         Screen *s = ScreenOfDisplay (dpy, i);
1379         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
1380           {
1381             found_any_writable_cells = True;
1382             break;
1383           }
1384       }
1385
1386 #ifdef HAVE_DPMS_EXTENSION
1387     {
1388       int op = 0, event = 0, error = 0;
1389       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
1390         dpms_supported = True;
1391     }
1392 #endif /* HAVE_DPMS_EXTENSION */
1393
1394
1395     /* Blanking and Locking
1396      */
1397     gtk_widget_set_sensitive (
1398                            GTK_WIDGET (name_to_widget (top, "lock_label")),
1399                            p->lock_p);
1400     gtk_widget_set_sensitive (
1401                            GTK_WIDGET (name_to_widget (top, "lock_text")),
1402                            p->lock_p);
1403
1404     /* DPMS
1405      */
1406     gtk_widget_set_sensitive (
1407                       GTK_WIDGET (name_to_widget (top, "dpms_frame")),
1408                       dpms_supported);
1409     gtk_widget_set_sensitive (
1410                       GTK_WIDGET (name_to_widget (top, "dpms_button")),
1411                       dpms_supported);
1412     gtk_widget_set_sensitive (
1413                        GTK_WIDGET (name_to_widget (top, "dpms_standby_label")),
1414                        dpms_supported && p->dpms_enabled_p);
1415     gtk_widget_set_sensitive (
1416                        GTK_WIDGET (name_to_widget (top, "dpms_standby_text")),
1417                        dpms_supported && p->dpms_enabled_p);
1418     gtk_widget_set_sensitive (
1419                        GTK_WIDGET (name_to_widget (top, "dpms_suspend_label")),
1420                        dpms_supported && p->dpms_enabled_p);
1421     gtk_widget_set_sensitive (
1422                        GTK_WIDGET (name_to_widget (top, "dpms_suspend_text")),
1423                        dpms_supported && p->dpms_enabled_p);
1424     gtk_widget_set_sensitive (
1425                        GTK_WIDGET (name_to_widget (top, "dpms_off_label")),
1426                        dpms_supported && p->dpms_enabled_p);
1427     gtk_widget_set_sensitive (
1428                        GTK_WIDGET (name_to_widget (top, "dpms_off_text")),
1429                        dpms_supported && p->dpms_enabled_p);
1430
1431     /* Colormaps
1432      */
1433     gtk_widget_set_sensitive (
1434                            GTK_WIDGET (name_to_widget (top, "cmap_frame")),
1435                            found_any_writable_cells);
1436     gtk_widget_set_sensitive (
1437                            GTK_WIDGET (name_to_widget (top, "install_button")),
1438                            found_any_writable_cells);
1439     gtk_widget_set_sensitive (
1440                            GTK_WIDGET (name_to_widget (top, "fade_button")),
1441                            found_any_writable_cells);
1442     gtk_widget_set_sensitive (
1443                            GTK_WIDGET (name_to_widget (top, "unfade_button")),
1444                            found_any_writable_cells);
1445
1446     gtk_widget_set_sensitive (
1447                            GTK_WIDGET (name_to_widget (top, "fade_label")),
1448                            (found_any_writable_cells &&
1449                             (p->fade_p || p->unfade_p)));
1450     gtk_widget_set_sensitive (
1451                            GTK_WIDGET (name_to_widget (top, "fade_text")),
1452                            (found_any_writable_cells &&
1453                             (p->fade_p || p->unfade_p)));
1454   }
1455
1456 }
1457
1458
1459 static void
1460 sensitize_demo_widgets (GtkWidget *toplevel, Bool sensitive_p)
1461 {
1462   const char *names[] = { "cmd_label", "cmd_text", "enabled",
1463                           "visual", "visual_combo",
1464                           "demo", "manual" };
1465   int i;
1466   for (i = 0; i < countof(names); i++)
1467     {
1468       GtkWidget *w = name_to_widget (toplevel, names[i]);
1469       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
1470     }
1471
1472   /* I don't know how to handle these yet... */
1473   {
1474     const char *names2[] = { "cut_menu", "copy_menu", "paste_menu" };
1475     for (i = 0; i < countof(names2); i++)
1476       {
1477         GtkWidget *w = name_to_widget (toplevel, names2[i]);
1478         gtk_widget_set_sensitive (GTK_WIDGET(w), False);
1479       }
1480   }
1481 }
1482
1483
1484 /* Even though we've given these text fields a maximum number of characters,
1485    their default size is still about 30 characters wide -- so measure out
1486    a string in their font, and resize them to just fit that.
1487  */
1488 static void
1489 fix_text_entry_sizes (GtkWidget *toplevel)
1490 {
1491   const char *names[] = { "timeout_text", "cycle_text", "lock_text",
1492                           "dpms_standby_text", "dpms_suspend_text",
1493                           "dpms_off_text", "fade_text" };
1494   int i;
1495   int width = 0;
1496   GtkWidget *w;
1497
1498   for (i = 0; i < countof(names); i++)
1499     {
1500       w = GTK_WIDGET (name_to_widget (toplevel, names[i]));
1501       if (width == 0)
1502         width = gdk_text_width (w->style->font, "00:00:00_", 9);
1503       gtk_widget_set_usize (w, width, -2);
1504     }
1505
1506   /* Now fix the size of the combo box.
1507    */
1508   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "visual_combo"));
1509   w = GTK_COMBO (w)->entry;
1510   width = gdk_text_width (w->style->font, "PseudoColor___", 14);
1511   gtk_widget_set_usize (w, width, -2);
1512
1513 #if 0
1514   /* Now fix the size of the list.
1515    */
1516   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "list"));
1517   width = gdk_text_width (w->style->font, "nnnnnnnnnnnnnnnnnnnnnn", 22);
1518   gtk_widget_set_usize (w, width, -2);
1519 #endif
1520 }
1521
1522
1523
1524 \f
1525 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
1526  */
1527
1528 static char *up_arrow_xpm[] = {
1529   "15 15 4 1",
1530   "     c None",
1531   "-    c #FFFFFF",
1532   "+    c #D6D6D6",
1533   "@    c #000000",
1534
1535   "       @       ",
1536   "       @       ",
1537   "      -+@      ",
1538   "      -+@      ",
1539   "     -+++@     ",
1540   "     -+++@     ",
1541   "    -+++++@    ",
1542   "    -+++++@    ",
1543   "   -+++++++@   ",
1544   "   -+++++++@   ",
1545   "  -+++++++++@  ",
1546   "  -+++++++++@  ",
1547   " -+++++++++++@ ",
1548   " @@@@@@@@@@@@@ ",
1549   "               ",
1550
1551   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1552      the end of the array (Gtk 1.2.5.) */
1553   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1554   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1555 };
1556
1557 static char *down_arrow_xpm[] = {
1558   "15 15 4 1",
1559   "     c None",
1560   "-    c #FFFFFF",
1561   "+    c #D6D6D6",
1562   "@    c #000000",
1563
1564   "               ",
1565   " ------------- ",
1566   " -+++++++++++@ ",
1567   "  -+++++++++@  ",
1568   "  -+++++++++@  ",
1569   "   -+++++++@   ",
1570   "   -+++++++@   ",
1571   "    -+++++@    ",
1572   "    -+++++@    ",
1573   "     -+++@     ",
1574   "     -+++@     ",
1575   "      -+@      ",
1576   "      -+@      ",
1577   "       @       ",
1578   "       @       ",
1579
1580   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1581      the end of the array (Gtk 1.2.5.) */
1582   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1583   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1584 };
1585
1586 static void
1587 pixmapify_button (GtkWidget *toplevel, int down_p)
1588 {
1589   GdkPixmap *pixmap;
1590   GdkBitmap *mask;
1591   GtkWidget *pixmapwid;
1592   GtkStyle *style;
1593   GtkWidget *w;
1594
1595   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel),
1596                                   (down_p ? "next" : "prev")));
1597   style = gtk_widget_get_style (w);
1598   mask = 0;
1599   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
1600                                          &style->bg[GTK_STATE_NORMAL],
1601                                          (down_p
1602                                           ? (gchar **) down_arrow_xpm
1603                                           : (gchar **) up_arrow_xpm));
1604   pixmapwid = gtk_pixmap_new (pixmap, mask);
1605   gtk_widget_show (pixmapwid);
1606   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
1607   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
1608 }
1609
1610 static void
1611 map_next_button_cb (GtkWidget *w, gpointer user_data)
1612 {
1613   pixmapify_button (w, 1);
1614 }
1615
1616 static void
1617 map_prev_button_cb (GtkWidget *w, gpointer user_data)
1618 {
1619   pixmapify_button (w, 0);
1620 }
1621
1622
1623 \f
1624 /* Work around a Gtk bug that causes label widgets to wrap text too early.
1625  */
1626
1627 static void
1628 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
1629                                              GtkAllocation *allocation,
1630                                              void *foo)
1631 {
1632   GtkRequisition req;
1633   GtkWidgetAuxInfo *aux_info;
1634
1635   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
1636
1637   aux_info->width = allocation->width;
1638   aux_info->height = -2;
1639   aux_info->x = -1;
1640   aux_info->y = -1;
1641
1642   gtk_widget_size_request (label, &req);
1643 }
1644
1645
1646 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
1647  */
1648 static void
1649 eschew_gtk_lossage (GtkWidget *toplevel)
1650 {
1651   GtkWidgetAuxInfo *aux_info;
1652   GtkWidget *label = GTK_WIDGET (name_to_widget (toplevel, "doc"));
1653
1654   aux_info = g_new0 (GtkWidgetAuxInfo, 1);
1655   aux_info->width = label->allocation.width;
1656   aux_info->height = -2;
1657   aux_info->x = -1;
1658   aux_info->y = -1;
1659
1660   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
1661
1662   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
1663                       you_are_not_a_unique_or_beautiful_snowflake, NULL);
1664
1665   gtk_widget_queue_resize (label); 
1666 }
1667
1668
1669 char *
1670 get_hack_blurb (screenhack *hack)
1671 {
1672   char *doc_string;
1673   char *prog_name = strdup (hack->command);
1674   char *pretty_name = (hack->name
1675                        ? strdup (hack->name)
1676                        : make_hack_name (hack->command));
1677   char doc_name[255], doc_class[255];
1678   char *s, *s2;
1679
1680   for (s = prog_name; *s && !isspace(*s); s++)
1681     ;
1682   *s = 0;
1683   s = strrchr (prog_name, '/');
1684   if (s) strcpy (prog_name, s+1);
1685
1686   sprintf (doc_name,  "hacks.%s.documentation", pretty_name);
1687   sprintf (doc_class, "hacks.%s.documentation", prog_name);
1688   free (prog_name);
1689   free (pretty_name);
1690
1691   doc_string = get_string_resource (doc_name, doc_class);
1692   if (doc_string)
1693     {
1694       for (s = doc_string; *s; s++)
1695         {
1696           if (*s == '\n')
1697             {
1698               /* skip over whitespace at beginning of line */
1699               s++;
1700               while (*s && (*s == ' ' || *s == '\t'))
1701                 s++;
1702             }
1703           else if (*s == ' ' || *s == '\t')
1704             {
1705               /* compress all other horizontal whitespace. */
1706               *s = ' ';
1707               s++;
1708               for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++)
1709                 ;
1710               if (s2 > s) strcpy (s, s2);
1711               s--;
1712             }
1713         }
1714
1715       while (*s && isspace (*s))      /* Strip trailing whitespace */
1716         *(--s) = 0;
1717
1718       /* Delete whitespace at end of each line. */
1719       for (; s > doc_string; s--)
1720         if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t'))
1721           {
1722             for (s2 = s-1;
1723                  s2 > doc_string && (*s2 == ' ' || *s2 == '\t');
1724                  s2--)
1725               ;
1726             s2++;
1727             if (s2 < s) strcpy (s2, s);
1728             s = s2;
1729           }
1730       
1731       /* Delete leading blank lines. */
1732       for (s = doc_string; *s == '\n'; s++)
1733         ;
1734       if (s > doc_string) strcpy (doc_string, s);
1735     }
1736   else
1737     {
1738       static int doc_installed = 0;
1739       if (doc_installed == 0)
1740         {
1741           if (get_boolean_resource ("hacks.documentation.isInstalled",
1742                                     "hacks.documentation.isInstalled"))
1743             doc_installed = 1;
1744           else
1745             doc_installed = -1;
1746         }
1747
1748       if (doc_installed < 0)
1749         doc_string =
1750           strdup ("Error:\n\n"
1751                   "The documentation strings do not appear to be "
1752                   "installed.  This is probably because there is "
1753                   "an \"XScreenSaver\" app-defaults file installed "
1754                   "that is from an older version of the program. "
1755                   "To fix this problem, delete that file, or "
1756                   "install a current version (either will work.)");
1757       else
1758         doc_string = strdup ("");
1759     }
1760
1761   return doc_string;
1762 }
1763
1764
1765 static void
1766 populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
1767 {
1768   saver_preferences *p = pair->a;
1769   screenhack *hack = (which >= 0 && which < p->screenhacks_count
1770                       ? p->screenhacks[which] : 0);
1771   GtkFrame *frame = GTK_FRAME (name_to_widget (toplevel, "frame"));
1772   GtkLabel *doc = GTK_LABEL (name_to_widget (toplevel, "doc"));
1773   GtkEntry *cmd = GTK_ENTRY (name_to_widget (toplevel, "cmd_text"));
1774   GtkToggleButton *enabled =
1775     GTK_TOGGLE_BUTTON (name_to_widget (toplevel, "enabled"));
1776   GtkCombo *vis = GTK_COMBO (name_to_widget (toplevel, "visual_combo"));
1777
1778   char *pretty_name = (hack
1779                        ? (hack->name
1780                           ? strdup (hack->name)
1781                           : make_hack_name (hack->command))
1782                        : 0);
1783   char *doc_string = hack ? get_hack_blurb (hack) : 0;
1784
1785   gtk_frame_set_label (frame, (pretty_name ? pretty_name : ""));
1786   gtk_label_set_text (doc, (doc_string ? doc_string : ""));
1787   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
1788   gtk_entry_set_position (cmd, 0);
1789
1790   updating_enabled_cb++;
1791   gtk_toggle_button_set_active (enabled, (hack ? hack->enabled_p : False));
1792   updating_enabled_cb--;
1793
1794   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
1795                       (hack
1796                        ? (hack->visual && *hack->visual
1797                           ? hack->visual
1798                           : "Any")
1799                        : ""));
1800
1801   gtk_container_resize_children (GTK_CONTAINER (GTK_WIDGET (doc)->parent));
1802
1803   sensitize_demo_widgets (toplevel, (hack ? True : False));
1804
1805   if (pretty_name) free (pretty_name);
1806   if (doc_string) free (doc_string);
1807
1808   _selected_hack_number = which;
1809 }
1810
1811
1812 static void
1813 widget_deleter (GtkWidget *widget, gpointer data)
1814 {
1815   /* #### Well, I want to destroy these widgets, but if I do that, they get
1816      referenced again, and eventually I get a SEGV.  So instead of
1817      destroying them, I'll just hide them, and leak a bunch of memory
1818      every time the disk file changes.  Go go go Gtk!
1819
1820      #### Ok, that's a lie, I get a crash even if I just hide the widget
1821      and don't ever delete it.  Fuck!
1822    */
1823 #if 0
1824   gtk_widget_destroy (widget);
1825 #else
1826   gtk_widget_hide (widget);
1827 #endif
1828 }
1829
1830
1831 static int
1832 maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair)
1833 {
1834   int status = 0;
1835   saver_preferences *p =  pair->a;
1836
1837   static Bool reentrant_lock = False;
1838   if (reentrant_lock) return 0;
1839   reentrant_lock = True;
1840
1841   if (init_file_changed_p (p))
1842     {
1843       const char *f = init_file_name();
1844       char *b;
1845       int which;
1846       GtkList *list;
1847
1848       if (!f || !*f) return 0;
1849       b = (char *) malloc (strlen(f) + 1024);
1850       sprintf (b,
1851                "Warning:\n\n"
1852                "file \"%s\" has changed, reloading.\n",
1853                f);
1854       warning_dialog (widget, b, False, 100);
1855       free (b);
1856
1857       load_init_file (p);
1858
1859       which = selected_hack_number (widget);
1860       list = GTK_LIST (name_to_widget (widget, "list"));
1861       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
1862       populate_hack_list (widget, pair);
1863       gtk_list_select_item (list, which);
1864       populate_prefs_page (widget, pair);
1865       populate_demo_window (widget, which, pair);
1866       ensure_selected_item_visible (GTK_WIDGET (list));
1867
1868       status = 1;
1869     }
1870
1871   reentrant_lock = False;
1872   return status;
1873 }
1874
1875
1876 \f
1877 /* Setting window manager icon
1878  */
1879
1880 static void
1881 init_icon (GdkWindow *window)
1882 {
1883   GdkBitmap *mask = 0;
1884   GdkColor transp;
1885   GdkPixmap *pixmap =
1886     gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
1887                                   (gchar **) logo_50_xpm);
1888   if (pixmap)
1889     gdk_window_set_icon (window, 0, pixmap, mask);
1890 }
1891
1892 \f
1893 /* The main demo-mode command loop.
1894  */
1895
1896 #if 0
1897 static Bool
1898 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1899         XrmRepresentation *type, XrmValue *value, XPointer closure)
1900 {
1901   int i;
1902   for (i = 0; quarks[i]; i++)
1903     {
1904       if (bindings[i] == XrmBindTightly)
1905         fprintf (stderr, (i == 0 ? "" : "."));
1906       else if (bindings[i] == XrmBindLoosely)
1907         fprintf (stderr, "*");
1908       else
1909         fprintf (stderr, " ??? ");
1910       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1911     }
1912
1913   fprintf (stderr, ": %s\n", (char *) value->addr);
1914
1915   return False;
1916 }
1917 #endif
1918
1919
1920 static void
1921 the_network_is_not_the_computer (GtkWidget *parent)
1922 {
1923   Display *dpy = gdk_display;
1924   char *rversion, *ruser, *rhost;
1925   char *luser, *lhost;
1926   char *msg = 0;
1927   struct passwd *p = getpwuid (getuid ());
1928   const char *d = DisplayString (dpy);
1929
1930 # if defined(HAVE_UNAME)
1931   struct utsname uts;
1932   if (uname (&uts) < 0)
1933     lhost = "<UNKNOWN>";
1934   else
1935     lhost = uts.nodename;
1936 # elif defined(VMS)
1937   strcpy (lhost, getenv("SYS$NODE"));
1938 # else  /* !HAVE_UNAME && !VMS */
1939   strcat (lhost, "<UNKNOWN>");
1940 # endif /* !HAVE_UNAME && !VMS */
1941
1942   if (p && p->pw_name)
1943     luser = p->pw_name;
1944   else
1945     luser = "???";
1946
1947   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1948
1949   /* Make a buffer that's big enough for a number of copies of all the
1950      strings, plus some. */
1951   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1952                                (ruser ? strlen(ruser) : 0) +
1953                                (rhost ? strlen(rhost) : 0) +
1954                                strlen(lhost) +
1955                                strlen(luser) +
1956                                strlen(d) +
1957                                1024));
1958   *msg = 0;
1959
1960   if (!rversion || !*rversion)
1961     {
1962       sprintf (msg,
1963                "Warning:\n\n"
1964                "The XScreenSaver daemon doesn't seem to be running\n"
1965                "on display \"%s\".  Launch it now?",
1966                d);
1967     }
1968   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1969     {
1970       /* Warn that the two processes are running as different users.
1971        */
1972       sprintf(msg,
1973                "Warning:\n\n"
1974               "%s is running as user \"%s\" on host \"%s\".\n"
1975               "But the xscreensaver managing display \"%s\"\n"
1976               "is running as user \"%s\" on host \"%s\".\n"
1977               "\n"
1978               "Since they are different users, they won't be reading/writing\n"
1979               "the same ~/.xscreensaver file, so %s isn't\n"
1980               "going to work right.\n"
1981               "\n"
1982               "You should either re-run %s as \"%s\", or re-run\n"
1983               "xscreensaver as \"%s\".\n"
1984               "\n"
1985               "Restart the xscreensaver daemon now?\n",
1986               progname, luser, lhost,
1987               d,
1988               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1989               progname,
1990               progname, (ruser ? ruser : "???"),
1991               luser);
1992     }
1993   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1994     {
1995       /* Warn that the two processes are running on different hosts.
1996        */
1997       sprintf (msg,
1998                "Warning:\n\n"
1999                "%s is running as user \"%s\" on host \"%s\".\n"
2000                "But the xscreensaver managing display \"%s\"\n"
2001                "is running as user \"%s\" on host \"%s\".\n"
2002                "\n"
2003                "If those two machines don't share a file system (that is,\n"
2004                "if they don't see the same ~%s/.xscreensaver file) then\n"
2005                "%s won't work right.\n"
2006                "\n"
2007                "Restart the daemon on \"%s\" as \"%s\" now?\n",
2008                progname, luser, lhost,
2009                d,
2010                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
2011                luser,
2012                progname,
2013                lhost, luser);
2014     }
2015   else if (!!strcmp (rversion, short_version))
2016     {
2017       /* Warn that the version numbers don't match.
2018        */
2019       sprintf (msg,
2020                "Warning:\n\n"
2021                "This is %s version %s.\n"
2022                "But the xscreensaver managing display \"%s\"\n"
2023                "is version %s.  This could cause problems.\n"
2024               "\n"
2025               "Restart the xscreensaver daemon now?\n",
2026                progname, short_version,
2027                d,
2028                rversion);
2029     }
2030
2031
2032   if (*msg)
2033     warning_dialog (parent, msg, True, 1);
2034
2035   free (msg);
2036 }
2037
2038
2039 /* We use this error handler so that X errors are preceeded by the name
2040    of the program that generated them.
2041  */
2042 static int
2043 demo_ehandler (Display *dpy, XErrorEvent *error)
2044 {
2045   fprintf (stderr, "\nX error in %s:\n", progname);
2046   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
2047     exit (-1);
2048   else
2049     fprintf (stderr, " (nonfatal.)\n");
2050   return 0;
2051 }
2052
2053
2054 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
2055    of the program that generated them; and also that we can ignore one
2056    particular bogus error message that Gdk madly spews.
2057  */
2058 static void
2059 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
2060                const gchar *message, gpointer user_data)
2061 {
2062   /* Ignore the message "Got event for unknown window: 0x...".
2063      Apparently some events are coming in for the xscreensaver window
2064      (presumably reply events related to the ClientMessage) and Gdk
2065      feels the need to complain about them.  So, just suppress any
2066      messages that look like that one.
2067    */
2068   if (strstr (message, "unknown window"))
2069     return;
2070
2071   fprintf (stderr, "%s: %s-%s: %s%s", blurb(), log_domain,
2072            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
2073             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
2074             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
2075             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
2076             log_level == G_LOG_LEVEL_INFO     ? "info" :
2077             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
2078            message,
2079            ((!*message || message[strlen(message)-1] != '\n')
2080             ? "\n" : ""));
2081 }
2082
2083
2084 static char *defaults[] = {
2085 #include "XScreenSaver_ad.h"
2086  0
2087 };
2088
2089 #if 0
2090 #ifdef HAVE_CRAPPLET
2091 static struct poptOption crapplet_options[] = {
2092   {NULL, '\0', 0, NULL, 0}
2093 };
2094 #endif /* HAVE_CRAPPLET */
2095 #endif /* 0 */
2096
2097 #define USAGE() \
2098   fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n", \
2099            real_progname)
2100
2101
2102 static void
2103 map_window_cb (GtkWidget *w, gpointer user_data)
2104 {
2105   Boolean oi = initializing_p;
2106   initializing_p = True;
2107   eschew_gtk_lossage (w);
2108   ensure_selected_item_visible (GTK_WIDGET(name_to_widget(w, "list")));
2109   initializing_p = oi;
2110 }
2111
2112
2113 int
2114 main (int argc, char **argv)
2115 {
2116   XtAppContext app;
2117   prefs_pair Pair, *pair;
2118   saver_preferences P, P2, *p, *p2;
2119   Bool prefs = False;
2120   int i;
2121   Display *dpy;
2122   Widget toplevel_shell;
2123   GtkWidget *gtk_window;
2124   char *real_progname = argv[0];
2125   char *s;
2126
2127   initializing_p = True;
2128
2129   s = strrchr (real_progname, '/');
2130   if (s) real_progname = s+1;
2131
2132   p = &P;
2133   p2 = &P2;
2134   pair = &Pair;
2135   pair->a = p;
2136   pair->b = p2;
2137   memset (p,  0, sizeof (*p));
2138   memset (p2, 0, sizeof (*p2));
2139
2140   global_prefs_pair = pair;  /* I hate C so much... */
2141
2142   progname = real_progname;
2143
2144   short_version = (char *) malloc (5);
2145   memcpy (short_version, screensaver_id + 17, 4);
2146   short_version [4] = 0;
2147
2148
2149   /* Register our error message logger for every ``log domain'' known.
2150      There's no way to do this globally, so I grepped the Gtk/Gdk sources
2151      for all of the domains that seem to be in use.
2152   */
2153   {
2154     const char * const domains[] = { "Gtk", "Gdk", "GLib", "GModule",
2155                                      "GThread", "Gnome", "GnomeUI", 0 };
2156     for (i = 0; domains[i]; i++)
2157       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
2158   }
2159
2160   /* This is gross, but Gtk understands --display and not -display...
2161    */
2162   for (i = 1; i < argc; i++)
2163     if (argv[i][0] && argv[i][1] && 
2164         !strncmp(argv[i], "-display", strlen(argv[i])))
2165       argv[i] = "--display";
2166
2167
2168   /* We need to parse this arg really early... Sigh. */
2169   for (i = 1; i < argc; i++)
2170     if (argv[i] &&
2171         (!strcmp(argv[i], "--crapplet") ||
2172          !strcmp(argv[i], "--capplet")))
2173       {
2174 # ifdef HAVE_CRAPPLET
2175         int j;
2176         crapplet_p = True;
2177         for (j = i; j < argc; j++)  /* remove it from the list */
2178           argv[j] = argv[j+1];
2179         argc--;
2180
2181 # else  /* !HAVE_CRAPPLET */
2182         fprintf (stderr, "%s: not compiled with --crapplet support\n",
2183                  real_progname[i]);
2184         USAGE ();
2185         exit (1);
2186 # endif /* !HAVE_CRAPPLET */
2187       }
2188
2189   /* Let Gtk open the X connection, then initialize Xt to use that
2190      same connection.  Doctor Frankenstein would be proud.
2191    */
2192 # ifdef HAVE_CRAPPLET
2193   if (crapplet_p)
2194     {
2195       GnomeClient *client;
2196       GnomeClientFlags flags = 0;
2197
2198       int init_results = gnome_capplet_init ("screensaver-properties",
2199                                              short_version,
2200                                              argc, argv, NULL, 0, NULL);
2201       /* init_results is:
2202          0 upon successful initialization;
2203          1 if --init-session-settings was passed on the cmdline;
2204          2 if --ignore was passed on the cmdline;
2205         -1 on error.
2206
2207          So the 1 signifies just to init the settings, and quit, basically.
2208          (Meaning launch the xscreensaver daemon.)
2209        */
2210
2211       if (init_results < 0)
2212         {
2213 #  if 0
2214           g_error ("An initialization error occurred while "
2215                    "starting xscreensaver-capplet.\n");
2216 #  else  /* !0 */
2217           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
2218                    real_progname, init_results);
2219           exit (1);
2220 #  endif /* !0 */
2221         }
2222
2223       client = gnome_master_client ();
2224
2225       if (client)
2226         flags = gnome_client_get_flags (client);
2227
2228       if (flags & GNOME_CLIENT_IS_CONNECTED)
2229         {
2230           int token =
2231             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
2232                                          gnome_client_get_id (client));
2233           if (token)
2234             {
2235               char *session_args[20];
2236               int i = 0;
2237               session_args[i++] = real_progname;
2238               session_args[i++] = "--capplet";
2239               session_args[i++] = "--init-session-settings";
2240               session_args[i] = 0;
2241               gnome_client_set_priority (client, 20);
2242               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
2243               gnome_client_set_restart_command (client, i, session_args);
2244             }
2245           else
2246             {
2247               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
2248             }
2249
2250           gnome_client_flush (client);
2251         }
2252
2253       if (init_results == 1)
2254         {
2255           system ("xscreensaver -nosplash &");
2256           return 0;
2257         }
2258
2259     }
2260   else
2261 # endif /* HAVE_CRAPPLET */
2262     {
2263       gtk_init (&argc, &argv);
2264     }
2265
2266
2267   /* We must read exactly the same resources as xscreensaver.
2268      That means we must have both the same progclass *and* progname,
2269      at least as far as the resource database is concerned.  So,
2270      put "xscreensaver" in argv[0] while initializing Xt.
2271    */
2272   argv[0] = "xscreensaver";
2273   progname = argv[0];
2274
2275
2276   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
2277    */
2278   XtToolkitInitialize ();
2279   app = XtCreateApplicationContext ();
2280   dpy = gdk_display;
2281   XtAppSetFallbackResources (app, defaults);
2282   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
2283   toplevel_shell = XtAppCreateShell (progname, progclass,
2284                                      applicationShellWidgetClass,
2285                                      dpy, 0, 0);
2286
2287   dpy = XtDisplay (toplevel_shell);
2288   db = XtDatabase (dpy);
2289   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
2290   XSetErrorHandler (demo_ehandler);
2291
2292
2293   /* After doing Xt-style command-line processing, complain about any
2294      unrecognized command-line arguments.
2295    */
2296   for (i = 1; i < argc; i++)
2297     {
2298       char *s = argv[i];
2299       if (s[0] == '-' && s[1] == '-')
2300         s++;
2301       if (!strcmp (s, "-prefs"))
2302         prefs = True;
2303       else if (crapplet_p)
2304         /* There are lots of random args that we don't care about when we're
2305            started as a crapplet, so just ignore unknown args in that case. */
2306         ;
2307       else
2308         {
2309           fprintf (stderr, "%s: unknown option: %s\n", real_progname, argv[i]);
2310           USAGE ();
2311           exit (1);
2312         }
2313     }
2314
2315   /* Load the init file, which may end up consulting the X resource database
2316      and the site-wide app-defaults file.  Note that at this point, it's
2317      important that `progname' be "xscreensaver", rather than whatever
2318      was in argv[0].
2319    */
2320   p->db = db;
2321   load_init_file (p);
2322   *p2 = *p;
2323
2324   /* Now that Xt has been initialized, and the resources have been read,
2325      we can set our `progname' variable to something more in line with
2326      reality.
2327    */
2328   progname = real_progname;
2329
2330
2331 #if 0
2332   /* Print out all the resources we read. */
2333   {
2334     XrmName name = { 0 };
2335     XrmClass class = { 0 };
2336     int count = 0;
2337     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
2338                           (POINTER) &count);
2339   }
2340 #endif
2341
2342
2343   /* Intern the atoms that xscreensaver_command() needs.
2344    */
2345   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
2346   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
2347   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
2348   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
2349   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
2350   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
2351   XA_SELECT = XInternAtom (dpy, "SELECT", False);
2352   XA_DEMO = XInternAtom (dpy, "DEMO", False);
2353   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
2354   XA_BLANK = XInternAtom (dpy, "BLANK", False);
2355   XA_LOCK = XInternAtom (dpy, "LOCK", False);
2356   XA_EXIT = XInternAtom (dpy, "EXIT", False);
2357   XA_RESTART = XInternAtom (dpy, "RESTART", False);
2358
2359
2360   /* Create the window and all its widgets.
2361    */
2362   gtk_window = create_xscreensaver_demo ();
2363   toplevel_widget = gtk_window;
2364
2365   /* Set the window's title. */
2366   {
2367     char title[255];
2368     char *v = (char *) strdup(strchr(screensaver_id, ' '));
2369     char *s1, *s2, *s3, *s4;
2370     s1 = (char *) strchr(v,  ' '); s1++;
2371     s2 = (char *) strchr(s1, ' ');
2372     s3 = (char *) strchr(v,  '('); s3++;
2373     s4 = (char *) strchr(s3, ')');
2374     *s2 = 0;
2375     *s4 = 0;
2376     sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3);
2377     gtk_window_set_title (GTK_WINDOW (gtk_window), title);
2378     free (v);
2379   }
2380
2381   /* Various other widget initializations...
2382    */
2383   gtk_signal_connect (GTK_OBJECT (gtk_window), "delete_event",
2384                       GTK_SIGNAL_FUNC (wm_close_cb), NULL);
2385
2386   populate_hack_list (gtk_window, pair);
2387   populate_prefs_page (gtk_window, pair);
2388   sensitize_demo_widgets (gtk_window, False);
2389   fix_text_entry_sizes (gtk_window);
2390   scroll_to_current_hack (gtk_window, pair);
2391
2392   gtk_signal_connect (
2393               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "list")),
2394               "map", GTK_SIGNAL_FUNC(map_window_cb), 0);
2395   gtk_signal_connect (
2396               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "prev")),
2397               "map", GTK_SIGNAL_FUNC(map_prev_button_cb), 0);
2398   gtk_signal_connect (
2399               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "next")),
2400               "map", GTK_SIGNAL_FUNC(map_next_button_cb), 0);
2401
2402
2403   /* Handle the -prefs command-line argument. */
2404   if (prefs)
2405     {
2406       GtkNotebook *notebook =
2407         GTK_NOTEBOOK (name_to_widget (gtk_window, "notebook"));
2408       gtk_notebook_set_page (notebook, 1);
2409     }
2410
2411 # ifdef HAVE_CRAPPLET
2412   if (crapplet_p)
2413     {
2414       GtkWidget *capplet;
2415       GtkWidget *top_vbox;
2416
2417       capplet = capplet_widget_new ();
2418
2419       top_vbox = GTK_BIN (gtk_window)->child;
2420
2421       gtk_widget_ref (top_vbox);
2422       gtk_container_remove (GTK_CONTAINER (gtk_window), top_vbox);
2423       GTK_OBJECT_SET_FLAGS (top_vbox, GTK_FLOATING);
2424
2425       /* In crapplet-mode, take off the menubar. */
2426       gtk_widget_hide (name_to_widget (gtk_window, "menubar"));
2427
2428       gtk_container_add (GTK_CONTAINER (capplet), top_vbox);
2429       gtk_widget_show (capplet);
2430       gtk_widget_hide (gtk_window);
2431
2432       /* Hook up the Control Center's redundant Help button, too. */
2433       gtk_signal_connect (GTK_OBJECT (capplet), "help",
2434                           GTK_SIGNAL_FUNC (doc_menu_cb), 0);
2435
2436       /* Issue any warnings about the running xscreensaver daemon. */
2437       the_network_is_not_the_computer (top_vbox);
2438     }
2439   else
2440 # endif /* HAVE_CRAPPLET */
2441     {
2442       gtk_widget_show (gtk_window);
2443       init_icon (GTK_WIDGET(gtk_window)->window);
2444
2445       /* Issue any warnings about the running xscreensaver daemon. */
2446       the_network_is_not_the_computer (gtk_window);
2447     }
2448
2449   /* Run the Gtk event loop, and not the Xt event loop.  This means that
2450      if there were Xt timers or fds registered, they would never get serviced,
2451      and if there were any Xt widgets, they would never have events delivered.
2452      Fortunately, we're using Gtk for all of the UI, and only initialized
2453      Xt so that we could process the command line and use the X resource
2454      manager.
2455    */
2456   initializing_p = False;
2457
2458 # ifdef HAVE_CRAPPLET
2459   if (crapplet_p)
2460     capplet_gtk_main ();
2461   else
2462 # endif /* HAVE_CRAPPLET */
2463     gtk_main ();
2464
2465   exit (0);
2466 }
2467
2468 #endif /* HAVE_GTK -- whole file */