a94ff1bd06658fb851bb8cac4c10a6c26322d832
[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
1351   gtk_toggle_button_set_active (
1352                    GTK_TOGGLE_BUTTON (name_to_widget (top, "dpms_button")),
1353                    p->dpms_enabled_p);
1354
1355   gtk_toggle_button_set_active (
1356                    GTK_TOGGLE_BUTTON (name_to_widget (top, "install_button")),
1357                    p->install_cmap_p);
1358   gtk_toggle_button_set_active (
1359                    GTK_TOGGLE_BUTTON (name_to_widget (top, "fade_button")),
1360                    p->fade_p);
1361   gtk_toggle_button_set_active (
1362                    GTK_TOGGLE_BUTTON (name_to_widget (top, "unfade_button")),
1363                    p->unfade_p);
1364
1365
1366   {
1367     Bool found_any_writable_cells = False;
1368     Bool dpms_supported = False;
1369
1370     Display *dpy = gdk_display;
1371     int nscreens = ScreenCount(dpy);
1372     int i;
1373     for (i = 0; i < nscreens; i++)
1374       {
1375         Screen *s = ScreenOfDisplay (dpy, i);
1376         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
1377           {
1378             found_any_writable_cells = True;
1379             break;
1380           }
1381       }
1382
1383 #ifdef HAVE_DPMS_EXTENSION
1384     {
1385       int op = 0, event = 0, error = 0;
1386       if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
1387         dpms_supported = True;
1388     }
1389 #endif /* HAVE_DPMS_EXTENSION */
1390
1391
1392     /* Blanking and Locking
1393      */
1394     gtk_widget_set_sensitive (
1395                            GTK_WIDGET (name_to_widget (top, "lock_label")),
1396                            p->lock_p);
1397     gtk_widget_set_sensitive (
1398                            GTK_WIDGET (name_to_widget (top, "lock_text")),
1399                            p->lock_p);
1400
1401     /* DPMS
1402      */
1403     gtk_widget_set_sensitive (
1404                       GTK_WIDGET (name_to_widget (top, "dpms_frame")),
1405                       dpms_supported);
1406     gtk_widget_set_sensitive (
1407                       GTK_WIDGET (name_to_widget (top, "dpms_button")),
1408                       dpms_supported);
1409     gtk_widget_set_sensitive (
1410                        GTK_WIDGET (name_to_widget (top, "dpms_standby_label")),
1411                        dpms_supported && p->dpms_enabled_p);
1412     gtk_widget_set_sensitive (
1413                        GTK_WIDGET (name_to_widget (top, "dpms_standby_text")),
1414                        dpms_supported && p->dpms_enabled_p);
1415     gtk_widget_set_sensitive (
1416                        GTK_WIDGET (name_to_widget (top, "dpms_suspend_label")),
1417                        dpms_supported && p->dpms_enabled_p);
1418     gtk_widget_set_sensitive (
1419                        GTK_WIDGET (name_to_widget (top, "dpms_suspend_text")),
1420                        dpms_supported && p->dpms_enabled_p);
1421     gtk_widget_set_sensitive (
1422                        GTK_WIDGET (name_to_widget (top, "dpms_off_label")),
1423                        dpms_supported && p->dpms_enabled_p);
1424     gtk_widget_set_sensitive (
1425                        GTK_WIDGET (name_to_widget (top, "dpms_off_text")),
1426                        dpms_supported && p->dpms_enabled_p);
1427
1428     /* Colormaps
1429      */
1430     gtk_widget_set_sensitive (
1431                            GTK_WIDGET (name_to_widget (top, "cmap_frame")),
1432                            found_any_writable_cells);
1433     gtk_widget_set_sensitive (
1434                            GTK_WIDGET (name_to_widget (top, "install_button")),
1435                            found_any_writable_cells);
1436     gtk_widget_set_sensitive (
1437                            GTK_WIDGET (name_to_widget (top, "fade_button")),
1438                            found_any_writable_cells);
1439     gtk_widget_set_sensitive (
1440                            GTK_WIDGET (name_to_widget (top, "unfade_button")),
1441                            found_any_writable_cells);
1442
1443     gtk_widget_set_sensitive (
1444                            GTK_WIDGET (name_to_widget (top, "fade_label")),
1445                            (found_any_writable_cells &&
1446                             (p->fade_p || p->unfade_p)));
1447     gtk_widget_set_sensitive (
1448                            GTK_WIDGET (name_to_widget (top, "fade_text")),
1449                            (found_any_writable_cells &&
1450                             (p->fade_p || p->unfade_p)));
1451   }
1452
1453 }
1454
1455
1456 static void
1457 sensitize_demo_widgets (GtkWidget *toplevel, Bool sensitive_p)
1458 {
1459   const char *names[] = { "cmd_label", "cmd_text", "enabled",
1460                           "visual", "visual_combo",
1461                           "demo", "manual" };
1462   int i;
1463   for (i = 0; i < countof(names); i++)
1464     {
1465       GtkWidget *w = name_to_widget (toplevel, names[i]);
1466       gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
1467     }
1468
1469   /* I don't know how to handle these yet... */
1470   {
1471     const char *names2[] = { "cut_menu", "copy_menu", "paste_menu" };
1472     for (i = 0; i < countof(names2); i++)
1473       {
1474         GtkWidget *w = name_to_widget (toplevel, names2[i]);
1475         gtk_widget_set_sensitive (GTK_WIDGET(w), False);
1476       }
1477   }
1478 }
1479
1480
1481 /* Even though we've given these text fields a maximum number of characters,
1482    their default size is still about 30 characters wide -- so measure out
1483    a string in their font, and resize them to just fit that.
1484  */
1485 static void
1486 fix_text_entry_sizes (GtkWidget *toplevel)
1487 {
1488   const char *names[] = { "timeout_text", "cycle_text", "lock_text",
1489                           "dpms_standby_text", "dpms_suspend_text",
1490                           "dpms_off_text", "fade_text" };
1491   int i;
1492   int width = 0;
1493   GtkWidget *w;
1494
1495   for (i = 0; i < countof(names); i++)
1496     {
1497       w = GTK_WIDGET (name_to_widget (toplevel, names[i]));
1498       if (width == 0)
1499         width = gdk_text_width (w->style->font, "00:00:00_", 9);
1500       gtk_widget_set_usize (w, width, -2);
1501     }
1502
1503   /* Now fix the size of the combo box.
1504    */
1505   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "visual_combo"));
1506   w = GTK_COMBO (w)->entry;
1507   width = gdk_text_width (w->style->font, "PseudoColor___", 14);
1508   gtk_widget_set_usize (w, width, -2);
1509
1510 #if 0
1511   /* Now fix the size of the list.
1512    */
1513   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel), "list"));
1514   width = gdk_text_width (w->style->font, "nnnnnnnnnnnnnnnnnnnnnn", 22);
1515   gtk_widget_set_usize (w, width, -2);
1516 #endif
1517 }
1518
1519
1520
1521 \f
1522 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
1523  */
1524
1525 static char *up_arrow_xpm[] = {
1526   "15 15 4 1",
1527   "     c None",
1528   "-    c #FFFFFF",
1529   "+    c #D6D6D6",
1530   "@    c #000000",
1531
1532   "       @       ",
1533   "       @       ",
1534   "      -+@      ",
1535   "      -+@      ",
1536   "     -+++@     ",
1537   "     -+++@     ",
1538   "    -+++++@    ",
1539   "    -+++++@    ",
1540   "   -+++++++@   ",
1541   "   -+++++++@   ",
1542   "  -+++++++++@  ",
1543   "  -+++++++++@  ",
1544   " -+++++++++++@ ",
1545   " @@@@@@@@@@@@@ ",
1546   "               ",
1547
1548   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1549      the end of the array (Gtk 1.2.5.) */
1550   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1551   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1552 };
1553
1554 static char *down_arrow_xpm[] = {
1555   "15 15 4 1",
1556   "     c None",
1557   "-    c #FFFFFF",
1558   "+    c #D6D6D6",
1559   "@    c #000000",
1560
1561   "               ",
1562   " ------------- ",
1563   " -+++++++++++@ ",
1564   "  -+++++++++@  ",
1565   "  -+++++++++@  ",
1566   "   -+++++++@   ",
1567   "   -+++++++@   ",
1568   "    -+++++@    ",
1569   "    -+++++@    ",
1570   "     -+++@     ",
1571   "     -+++@     ",
1572   "      -+@      ",
1573   "      -+@      ",
1574   "       @       ",
1575   "       @       ",
1576
1577   /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
1578      the end of the array (Gtk 1.2.5.) */
1579   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
1580   "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
1581 };
1582
1583 static void
1584 pixmapify_button (GtkWidget *toplevel, int down_p)
1585 {
1586   GdkPixmap *pixmap;
1587   GdkBitmap *mask;
1588   GtkWidget *pixmapwid;
1589   GtkStyle *style;
1590   GtkWidget *w;
1591
1592   w = GTK_WIDGET (name_to_widget (GTK_WIDGET (toplevel),
1593                                   (down_p ? "next" : "prev")));
1594   style = gtk_widget_get_style (w);
1595   mask = 0;
1596   pixmap = gdk_pixmap_create_from_xpm_d (w->window, &mask,
1597                                          &style->bg[GTK_STATE_NORMAL],
1598                                          (down_p
1599                                           ? (gchar **) down_arrow_xpm
1600                                           : (gchar **) up_arrow_xpm));
1601   pixmapwid = gtk_pixmap_new (pixmap, mask);
1602   gtk_widget_show (pixmapwid);
1603   gtk_container_remove (GTK_CONTAINER (w), GTK_BIN (w)->child);
1604   gtk_container_add (GTK_CONTAINER (w), pixmapwid);
1605 }
1606
1607 static void
1608 map_next_button_cb (GtkWidget *w, gpointer user_data)
1609 {
1610   pixmapify_button (w, 1);
1611 }
1612
1613 static void
1614 map_prev_button_cb (GtkWidget *w, gpointer user_data)
1615 {
1616   pixmapify_button (w, 0);
1617 }
1618
1619
1620 \f
1621 /* Work around a Gtk bug that causes label widgets to wrap text too early.
1622  */
1623
1624 static void
1625 you_are_not_a_unique_or_beautiful_snowflake (GtkWidget *label,
1626                                              GtkAllocation *allocation,
1627                                              void *foo)
1628 {
1629   GtkRequisition req;
1630   GtkWidgetAuxInfo *aux_info;
1631
1632   aux_info = gtk_object_get_data (GTK_OBJECT (label), "gtk-aux-info");
1633
1634   aux_info->width = allocation->width;
1635   aux_info->height = -2;
1636   aux_info->x = -1;
1637   aux_info->y = -1;
1638
1639   gtk_widget_size_request (label, &req);
1640 }
1641
1642
1643 /* Feel the love.  Thanks to Nat Friedman for finding this workaround.
1644  */
1645 static void
1646 eschew_gtk_lossage (GtkWidget *toplevel)
1647 {
1648   GtkWidgetAuxInfo *aux_info;
1649   GtkWidget *label = GTK_WIDGET (name_to_widget (toplevel, "doc"));
1650
1651   aux_info = g_new0 (GtkWidgetAuxInfo, 1);
1652   aux_info->width = label->allocation.width;
1653   aux_info->height = -2;
1654   aux_info->x = -1;
1655   aux_info->y = -1;
1656
1657   gtk_object_set_data (GTK_OBJECT (label), "gtk-aux-info", aux_info);
1658
1659   gtk_signal_connect (GTK_OBJECT (label), "size_allocate",
1660                       you_are_not_a_unique_or_beautiful_snowflake, NULL);
1661
1662   gtk_widget_queue_resize (label); 
1663 }
1664
1665
1666 char *
1667 get_hack_blurb (screenhack *hack)
1668 {
1669   char *doc_string;
1670   char *prog_name = strdup (hack->command);
1671   char *pretty_name = (hack->name
1672                        ? strdup (hack->name)
1673                        : make_hack_name (hack->command));
1674   char doc_name[255], doc_class[255];
1675   char *s, *s2;
1676
1677   for (s = prog_name; *s && !isspace(*s); s++)
1678     ;
1679   *s = 0;
1680   s = strrchr (prog_name, '/');
1681   if (s) strcpy (prog_name, s+1);
1682
1683   sprintf (doc_name,  "hacks.%s.documentation", pretty_name);
1684   sprintf (doc_class, "hacks.%s.documentation", prog_name);
1685   free (prog_name);
1686   free (pretty_name);
1687
1688   doc_string = get_string_resource (doc_name, doc_class);
1689   if (doc_string)
1690     {
1691       for (s = doc_string; *s; s++)
1692         {
1693           if (*s == '\n')
1694             {
1695               /* skip over whitespace at beginning of line */
1696               s++;
1697               while (*s && (*s == ' ' || *s == '\t'))
1698                 s++;
1699             }
1700           else if (*s == ' ' || *s == '\t')
1701             {
1702               /* compress all other horizontal whitespace. */
1703               *s = ' ';
1704               s++;
1705               for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++)
1706                 ;
1707               if (s2 > s) strcpy (s, s2);
1708               s--;
1709             }
1710         }
1711
1712       while (*s && isspace (*s))      /* Strip trailing whitespace */
1713         *(--s) = 0;
1714
1715       /* Delete whitespace at end of each line. */
1716       for (; s > doc_string; s--)
1717         if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t'))
1718           {
1719             for (s2 = s-1;
1720                  s2 > doc_string && (*s2 == ' ' || *s2 == '\t');
1721                  s2--)
1722               ;
1723             s2++;
1724             if (s2 < s) strcpy (s2, s);
1725             s = s2;
1726           }
1727       
1728       /* Delete leading blank lines. */
1729       for (s = doc_string; *s == '\n'; s++)
1730         ;
1731       if (s > doc_string) strcpy (doc_string, s);
1732     }
1733   else
1734     {
1735       static int doc_installed = 0;
1736       if (doc_installed == 0)
1737         {
1738           if (get_boolean_resource ("hacks.documentation.isInstalled",
1739                                     "hacks.documentation.isInstalled"))
1740             doc_installed = 1;
1741           else
1742             doc_installed = -1;
1743         }
1744
1745       if (doc_installed < 0)
1746         doc_string =
1747           strdup ("Error:\n\n"
1748                   "The documentation strings do not appear to be "
1749                   "installed.  This is probably because there is "
1750                   "an \"XScreenSaver\" app-defaults file installed "
1751                   "that is from an older version of the program. "
1752                   "To fix this problem, delete that file, or "
1753                   "install a current version (either will work.)");
1754       else
1755         doc_string = strdup ("");
1756     }
1757
1758   return doc_string;
1759 }
1760
1761
1762 static void
1763 populate_demo_window (GtkWidget *toplevel, int which, prefs_pair *pair)
1764 {
1765   saver_preferences *p = pair->a;
1766   screenhack *hack = (which >= 0 && which < p->screenhacks_count
1767                       ? p->screenhacks[which] : 0);
1768   GtkFrame *frame = GTK_FRAME (name_to_widget (toplevel, "frame"));
1769   GtkLabel *doc = GTK_LABEL (name_to_widget (toplevel, "doc"));
1770   GtkEntry *cmd = GTK_ENTRY (name_to_widget (toplevel, "cmd_text"));
1771   GtkToggleButton *enabled =
1772     GTK_TOGGLE_BUTTON (name_to_widget (toplevel, "enabled"));
1773   GtkCombo *vis = GTK_COMBO (name_to_widget (toplevel, "visual_combo"));
1774
1775   char *pretty_name = (hack
1776                        ? (hack->name
1777                           ? strdup (hack->name)
1778                           : make_hack_name (hack->command))
1779                        : 0);
1780   char *doc_string = hack ? get_hack_blurb (hack) : 0;
1781
1782   gtk_frame_set_label (frame, (pretty_name ? pretty_name : ""));
1783   gtk_label_set_text (doc, (doc_string ? doc_string : ""));
1784   gtk_entry_set_text (cmd, (hack ? hack->command : ""));
1785   gtk_entry_set_position (cmd, 0);
1786
1787   updating_enabled_cb++;
1788   gtk_toggle_button_set_active (enabled, (hack ? hack->enabled_p : False));
1789   updating_enabled_cb--;
1790
1791   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry),
1792                       (hack
1793                        ? (hack->visual && *hack->visual
1794                           ? hack->visual
1795                           : "Any")
1796                        : ""));
1797
1798   gtk_container_resize_children (GTK_CONTAINER (GTK_WIDGET (doc)->parent));
1799
1800   sensitize_demo_widgets (toplevel, (hack ? True : False));
1801
1802   if (pretty_name) free (pretty_name);
1803   if (doc_string) free (doc_string);
1804
1805   _selected_hack_number = which;
1806 }
1807
1808
1809 static void
1810 widget_deleter (GtkWidget *widget, gpointer data)
1811 {
1812   /* #### Well, I want to destroy these widgets, but if I do that, they get
1813      referenced again, and eventually I get a SEGV.  So instead of
1814      destroying them, I'll just hide them, and leak a bunch of memory
1815      every time the disk file changes.  Go go go Gtk!
1816
1817      #### Ok, that's a lie, I get a crash even if I just hide the widget
1818      and don't ever delete it.  Fuck!
1819    */
1820 #if 0
1821   gtk_widget_destroy (widget);
1822 #else
1823   gtk_widget_hide (widget);
1824 #endif
1825 }
1826
1827
1828 static int
1829 maybe_reload_init_file (GtkWidget *widget, prefs_pair *pair)
1830 {
1831   int status = 0;
1832   saver_preferences *p =  pair->a;
1833
1834   static Bool reentrant_lock = False;
1835   if (reentrant_lock) return 0;
1836   reentrant_lock = True;
1837
1838   if (init_file_changed_p (p))
1839     {
1840       const char *f = init_file_name();
1841       char *b;
1842       int which;
1843       GtkList *list;
1844
1845       if (!f || !*f) return 0;
1846       b = (char *) malloc (strlen(f) + 1024);
1847       sprintf (b,
1848                "Warning:\n\n"
1849                "file \"%s\" has changed, reloading.\n",
1850                f);
1851       warning_dialog (widget, b, False, 100);
1852       free (b);
1853
1854       load_init_file (p);
1855
1856       which = selected_hack_number (widget);
1857       list = GTK_LIST (name_to_widget (widget, "list"));
1858       gtk_container_foreach (GTK_CONTAINER (list), widget_deleter, NULL);
1859       populate_hack_list (widget, pair);
1860       gtk_list_select_item (list, which);
1861       populate_prefs_page (widget, pair);
1862       populate_demo_window (widget, which, pair);
1863       ensure_selected_item_visible (GTK_WIDGET (list));
1864
1865       status = 1;
1866     }
1867
1868   reentrant_lock = False;
1869   return status;
1870 }
1871
1872
1873 \f
1874 /* Setting window manager icon
1875  */
1876
1877 static void
1878 init_icon (GdkWindow *window)
1879 {
1880   GdkBitmap *mask = 0;
1881   GdkColor transp;
1882   GdkPixmap *pixmap =
1883     gdk_pixmap_create_from_xpm_d (window, &mask, &transp,
1884                                   (gchar **) logo_50_xpm);
1885   if (pixmap)
1886     gdk_window_set_icon (window, 0, pixmap, mask);
1887 }
1888
1889 \f
1890 /* The main demo-mode command loop.
1891  */
1892
1893 #if 0
1894 static Bool
1895 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1896         XrmRepresentation *type, XrmValue *value, XPointer closure)
1897 {
1898   int i;
1899   for (i = 0; quarks[i]; i++)
1900     {
1901       if (bindings[i] == XrmBindTightly)
1902         fprintf (stderr, (i == 0 ? "" : "."));
1903       else if (bindings[i] == XrmBindLoosely)
1904         fprintf (stderr, "*");
1905       else
1906         fprintf (stderr, " ??? ");
1907       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1908     }
1909
1910   fprintf (stderr, ": %s\n", (char *) value->addr);
1911
1912   return False;
1913 }
1914 #endif
1915
1916
1917 static void
1918 the_network_is_not_the_computer (GtkWidget *parent)
1919 {
1920   Display *dpy = gdk_display;
1921   char *rversion, *ruser, *rhost;
1922   char *luser, *lhost;
1923   char *msg = 0;
1924   struct passwd *p = getpwuid (getuid ());
1925   const char *d = DisplayString (dpy);
1926
1927 # if defined(HAVE_UNAME)
1928   struct utsname uts;
1929   if (uname (&uts) < 0)
1930     lhost = "<UNKNOWN>";
1931   else
1932     lhost = uts.nodename;
1933 # elif defined(VMS)
1934   strcpy (lhost, getenv("SYS$NODE"));
1935 # else  /* !HAVE_UNAME && !VMS */
1936   strcat (lhost, "<UNKNOWN>");
1937 # endif /* !HAVE_UNAME && !VMS */
1938
1939   if (p && p->pw_name)
1940     luser = p->pw_name;
1941   else
1942     luser = "???";
1943
1944   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1945
1946   /* Make a buffer that's big enough for a number of copies of all the
1947      strings, plus some. */
1948   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1949                                (ruser ? strlen(ruser) : 0) +
1950                                (rhost ? strlen(rhost) : 0) +
1951                                strlen(lhost) +
1952                                strlen(luser) +
1953                                strlen(d) +
1954                                1024));
1955   *msg = 0;
1956
1957   if (!rversion || !*rversion)
1958     {
1959       sprintf (msg,
1960                "Warning:\n\n"
1961                "The XScreenSaver daemon doesn't seem to be running\n"
1962                "on display \"%s\".  Launch it now?",
1963                d);
1964     }
1965   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1966     {
1967       /* Warn that the two processes are running as different users.
1968        */
1969       sprintf(msg,
1970                "Warning:\n\n"
1971               "%s is running as user \"%s\" on host \"%s\".\n"
1972               "But the xscreensaver managing display \"%s\"\n"
1973               "is running as user \"%s\" on host \"%s\".\n"
1974               "\n"
1975               "Since they are different users, they won't be reading/writing\n"
1976               "the same ~/.xscreensaver file, so %s isn't\n"
1977               "going to work right.\n"
1978               "\n"
1979               "You should either re-run %s as \"%s\", or re-run\n"
1980               "xscreensaver as \"%s\".\n"
1981               "\n"
1982               "Restart the xscreensaver daemon now?\n",
1983               progname, luser, lhost,
1984               d,
1985               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1986               progname,
1987               progname, (ruser ? ruser : "???"),
1988               luser);
1989     }
1990   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1991     {
1992       /* Warn that the two processes are running on different hosts.
1993        */
1994       sprintf (msg,
1995                "Warning:\n\n"
1996                "%s is running as user \"%s\" on host \"%s\".\n"
1997                "But the xscreensaver managing display \"%s\"\n"
1998                "is running as user \"%s\" on host \"%s\".\n"
1999                "\n"
2000                "If those two machines don't share a file system (that is,\n"
2001                "if they don't see the same ~%s/.xscreensaver file) then\n"
2002                "%s won't work right.\n"
2003                "\n"
2004                "Restart the daemon on \"%s\" as \"%s\" now?\n",
2005                progname, luser, lhost,
2006                d,
2007                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
2008                luser,
2009                progname,
2010                lhost, luser);
2011     }
2012   else if (!!strcmp (rversion, short_version))
2013     {
2014       /* Warn that the version numbers don't match.
2015        */
2016       sprintf (msg,
2017                "Warning:\n\n"
2018                "This is %s version %s.\n"
2019                "But the xscreensaver managing display \"%s\"\n"
2020                "is version %s.  This could cause problems.\n"
2021               "\n"
2022               "Restart the xscreensaver daemon now?\n",
2023                progname, short_version,
2024                d,
2025                rversion);
2026     }
2027
2028
2029   if (*msg)
2030     warning_dialog (parent, msg, True, 1);
2031
2032   free (msg);
2033 }
2034
2035
2036 /* We use this error handler so that X errors are preceeded by the name
2037    of the program that generated them.
2038  */
2039 static int
2040 demo_ehandler (Display *dpy, XErrorEvent *error)
2041 {
2042   fprintf (stderr, "\nX error in %s:\n", progname);
2043   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
2044     exit (-1);
2045   else
2046     fprintf (stderr, " (nonfatal.)\n");
2047   return 0;
2048 }
2049
2050
2051 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
2052    of the program that generated them; and also that we can ignore one
2053    particular bogus error message that Gdk madly spews.
2054  */
2055 static void
2056 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
2057                const gchar *message, gpointer user_data)
2058 {
2059   /* Ignore the message "Got event for unknown window: 0x...".
2060      Apparently some events are coming in for the xscreensaver window
2061      (presumably reply events related to the ClientMessage) and Gdk
2062      feels the need to complain about them.  So, just suppress any
2063      messages that look like that one.
2064    */
2065   if (strstr (message, "unknown window"))
2066     return;
2067
2068   fprintf (stderr, "%s: %s-%s: %s%s", blurb(), log_domain,
2069            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
2070             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
2071             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
2072             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
2073             log_level == G_LOG_LEVEL_INFO     ? "info" :
2074             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
2075            message,
2076            ((!*message || message[strlen(message)-1] != '\n')
2077             ? "\n" : ""));
2078 }
2079
2080
2081 static char *defaults[] = {
2082 #include "XScreenSaver_ad.h"
2083  0
2084 };
2085
2086 #if 0
2087 #ifdef HAVE_CRAPPLET
2088 static struct poptOption crapplet_options[] = {
2089   {NULL, '\0', 0, NULL, 0}
2090 };
2091 #endif /* HAVE_CRAPPLET */
2092 #endif /* 0 */
2093
2094 #define USAGE() \
2095   fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n", \
2096            real_progname)
2097
2098
2099 static void
2100 map_window_cb (GtkWidget *w, gpointer user_data)
2101 {
2102   Boolean oi = initializing_p;
2103   initializing_p = True;
2104   eschew_gtk_lossage (w);
2105   ensure_selected_item_visible (GTK_WIDGET(name_to_widget(w, "list")));
2106   initializing_p = oi;
2107 }
2108
2109
2110 int
2111 main (int argc, char **argv)
2112 {
2113   XtAppContext app;
2114   prefs_pair Pair, *pair;
2115   saver_preferences P, P2, *p, *p2;
2116   Bool prefs = False;
2117   int i;
2118   Display *dpy;
2119   Widget toplevel_shell;
2120   GtkWidget *gtk_window;
2121   char *real_progname = argv[0];
2122   char *s;
2123
2124   initializing_p = True;
2125
2126   s = strrchr (real_progname, '/');
2127   if (s) real_progname = s+1;
2128
2129   p = &P;
2130   p2 = &P2;
2131   pair = &Pair;
2132   pair->a = p;
2133   pair->b = p2;
2134   memset (p,  0, sizeof (*p));
2135   memset (p2, 0, sizeof (*p2));
2136
2137   global_prefs_pair = pair;  /* I hate C so much... */
2138
2139   progname = real_progname;
2140
2141   short_version = (char *) malloc (5);
2142   memcpy (short_version, screensaver_id + 17, 4);
2143   short_version [4] = 0;
2144
2145
2146   /* Register our error message logger for every ``log domain'' known.
2147      There's no way to do this globally, so I grepped the Gtk/Gdk sources
2148      for all of the domains that seem to be in use.
2149   */
2150   {
2151     const char * const domains[] = { "Gtk", "Gdk", "GLib", "GModule",
2152                                      "GThread", "Gnome", "GnomeUI", 0 };
2153     for (i = 0; domains[i]; i++)
2154       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
2155   }
2156
2157   /* This is gross, but Gtk understands --display and not -display...
2158    */
2159   for (i = 1; i < argc; i++)
2160     if (argv[i][0] && argv[i][1] && 
2161         !strncmp(argv[i], "-display", strlen(argv[i])))
2162       argv[i] = "--display";
2163
2164
2165   /* We need to parse this arg really early... Sigh. */
2166   for (i = 1; i < argc; i++)
2167     if (argv[i] &&
2168         (!strcmp(argv[i], "--crapplet") ||
2169          !strcmp(argv[i], "--capplet")))
2170       {
2171 # ifdef HAVE_CRAPPLET
2172         int j;
2173         crapplet_p = True;
2174         for (j = i; j < argc; j++)  /* remove it from the list */
2175           argv[j] = argv[j+1];
2176         argc--;
2177
2178 # else  /* !HAVE_CRAPPLET */
2179         fprintf (stderr, "%s: not compiled with --crapplet support\n",
2180                  real_progname[i]);
2181         USAGE ();
2182         exit (1);
2183 # endif /* !HAVE_CRAPPLET */
2184       }
2185
2186   /* Let Gtk open the X connection, then initialize Xt to use that
2187      same connection.  Doctor Frankenstein would be proud.
2188    */
2189 # ifdef HAVE_CRAPPLET
2190   if (crapplet_p)
2191     {
2192       GnomeClient *client;
2193       GnomeClientFlags flags = 0;
2194
2195       int init_results = gnome_capplet_init ("screensaver-properties",
2196                                              short_version,
2197                                              argc, argv, NULL, 0, NULL);
2198       /* init_results is:
2199          0 upon successful initialization;
2200          1 if --init-session-settings was passed on the cmdline;
2201          2 if --ignore was passed on the cmdline;
2202         -1 on error.
2203
2204          So the 1 signifies just to init the settings, and quit, basically.
2205          (Meaning launch the xscreensaver daemon.)
2206        */
2207
2208       if (init_results < 0)
2209         {
2210 #  if 0
2211           g_error ("An initialization error occurred while "
2212                    "starting xscreensaver-capplet.\n");
2213 #  else  /* !0 */
2214           fprintf (stderr, "%s: gnome_capplet_init failed: %d\n",
2215                    real_progname, init_results);
2216           exit (1);
2217 #  endif /* !0 */
2218         }
2219
2220       client = gnome_master_client ();
2221
2222       if (client)
2223         flags = gnome_client_get_flags (client);
2224
2225       if (flags & GNOME_CLIENT_IS_CONNECTED)
2226         {
2227           int token =
2228             gnome_startup_acquire_token ("GNOME_SCREENSAVER_PROPERTIES",
2229                                          gnome_client_get_id (client));
2230           if (token)
2231             {
2232               char *session_args[20];
2233               int i = 0;
2234               session_args[i++] = real_progname;
2235               session_args[i++] = "--capplet";
2236               session_args[i++] = "--init-session-settings";
2237               session_args[i] = 0;
2238               gnome_client_set_priority (client, 20);
2239               gnome_client_set_restart_style (client, GNOME_RESTART_ANYWAY);
2240               gnome_client_set_restart_command (client, i, session_args);
2241             }
2242           else
2243             {
2244               gnome_client_set_restart_style (client, GNOME_RESTART_NEVER);
2245             }
2246
2247           gnome_client_flush (client);
2248         }
2249
2250       if (init_results == 1)
2251         {
2252           system ("xscreensaver -nosplash &");
2253           return 0;
2254         }
2255
2256     }
2257   else
2258 # endif /* HAVE_CRAPPLET */
2259     {
2260       gtk_init (&argc, &argv);
2261     }
2262
2263
2264   /* We must read exactly the same resources as xscreensaver.
2265      That means we must have both the same progclass *and* progname,
2266      at least as far as the resource database is concerned.  So,
2267      put "xscreensaver" in argv[0] while initializing Xt.
2268    */
2269   argv[0] = "xscreensaver";
2270   progname = argv[0];
2271
2272
2273   /* Teach Xt to use the Display that Gtk/Gdk have already opened.
2274    */
2275   XtToolkitInitialize ();
2276   app = XtCreateApplicationContext ();
2277   dpy = gdk_display;
2278   XtAppSetFallbackResources (app, defaults);
2279   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
2280   toplevel_shell = XtAppCreateShell (progname, progclass,
2281                                      applicationShellWidgetClass,
2282                                      dpy, 0, 0);
2283
2284   dpy = XtDisplay (toplevel_shell);
2285   db = XtDatabase (dpy);
2286   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
2287   XSetErrorHandler (demo_ehandler);
2288
2289
2290   /* After doing Xt-style command-line processing, complain about any
2291      unrecognized command-line arguments.
2292    */
2293   for (i = 1; i < argc; i++)
2294     {
2295       char *s = argv[i];
2296       if (s[0] == '-' && s[1] == '-')
2297         s++;
2298       if (!strcmp (s, "-prefs"))
2299         prefs = True;
2300       else if (crapplet_p)
2301         /* There are lots of random args that we don't care about when we're
2302            started as a crapplet, so just ignore unknown args in that case. */
2303         ;
2304       else
2305         {
2306           fprintf (stderr, "%s: unknown option: %s\n", real_progname, argv[i]);
2307           USAGE ();
2308           exit (1);
2309         }
2310     }
2311
2312   /* Load the init file, which may end up consulting the X resource database
2313      and the site-wide app-defaults file.  Note that at this point, it's
2314      important that `progname' be "xscreensaver", rather than whatever
2315      was in argv[0].
2316    */
2317   p->db = db;
2318   load_init_file (p);
2319   *p2 = *p;
2320
2321   /* Now that Xt has been initialized, and the resources have been read,
2322      we can set our `progname' variable to something more in line with
2323      reality.
2324    */
2325   progname = real_progname;
2326
2327
2328 #if 0
2329   /* Print out all the resources we read. */
2330   {
2331     XrmName name = { 0 };
2332     XrmClass class = { 0 };
2333     int count = 0;
2334     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
2335                           (POINTER) &count);
2336   }
2337 #endif
2338
2339
2340   /* Intern the atoms that xscreensaver_command() needs.
2341    */
2342   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
2343   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
2344   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
2345   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
2346   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
2347   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
2348   XA_SELECT = XInternAtom (dpy, "SELECT", False);
2349   XA_DEMO = XInternAtom (dpy, "DEMO", False);
2350   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
2351   XA_BLANK = XInternAtom (dpy, "BLANK", False);
2352   XA_LOCK = XInternAtom (dpy, "LOCK", False);
2353   XA_EXIT = XInternAtom (dpy, "EXIT", False);
2354   XA_RESTART = XInternAtom (dpy, "RESTART", False);
2355
2356
2357   /* Create the window and all its widgets.
2358    */
2359   gtk_window = create_xscreensaver_demo ();
2360   toplevel_widget = gtk_window;
2361
2362   /* Set the window's title. */
2363   {
2364     char title[255];
2365     char *v = (char *) strdup(strchr(screensaver_id, ' '));
2366     char *s1, *s2, *s3, *s4;
2367     s1 = (char *) strchr(v,  ' '); s1++;
2368     s2 = (char *) strchr(s1, ' ');
2369     s3 = (char *) strchr(v,  '('); s3++;
2370     s4 = (char *) strchr(s3, ')');
2371     *s2 = 0;
2372     *s4 = 0;
2373     sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3);
2374     gtk_window_set_title (GTK_WINDOW (gtk_window), title);
2375     free (v);
2376   }
2377
2378   /* Various other widget initializations...
2379    */
2380   gtk_signal_connect (GTK_OBJECT (gtk_window), "delete_event",
2381                       GTK_SIGNAL_FUNC (wm_close_cb), NULL);
2382
2383   populate_hack_list (gtk_window, pair);
2384   populate_prefs_page (gtk_window, pair);
2385   sensitize_demo_widgets (gtk_window, False);
2386   fix_text_entry_sizes (gtk_window);
2387   scroll_to_current_hack (gtk_window, pair);
2388
2389   gtk_signal_connect (
2390               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "list")),
2391               "map", GTK_SIGNAL_FUNC(map_window_cb), 0);
2392   gtk_signal_connect (
2393               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "prev")),
2394               "map", GTK_SIGNAL_FUNC(map_prev_button_cb), 0);
2395   gtk_signal_connect (
2396               GTK_OBJECT (name_to_widget (GTK_WIDGET (gtk_window), "next")),
2397               "map", GTK_SIGNAL_FUNC(map_next_button_cb), 0);
2398
2399
2400   /* Handle the -prefs command-line argument. */
2401   if (prefs)
2402     {
2403       GtkNotebook *notebook =
2404         GTK_NOTEBOOK (name_to_widget (gtk_window, "notebook"));
2405       gtk_notebook_set_page (notebook, 1);
2406     }
2407
2408 # ifdef HAVE_CRAPPLET
2409   if (crapplet_p)
2410     {
2411       GtkWidget *capplet;
2412       GtkWidget *top_vbox;
2413
2414       capplet = capplet_widget_new ();
2415
2416       top_vbox = GTK_BIN (gtk_window)->child;
2417
2418       gtk_widget_ref (top_vbox);
2419       gtk_container_remove (GTK_CONTAINER (gtk_window), top_vbox);
2420       GTK_OBJECT_SET_FLAGS (top_vbox, GTK_FLOATING);
2421
2422       /* In crapplet-mode, take off the menubar. */
2423       gtk_widget_hide (name_to_widget (gtk_window, "menubar"));
2424
2425       gtk_container_add (GTK_CONTAINER (capplet), top_vbox);
2426       gtk_widget_show (capplet);
2427       gtk_widget_hide (gtk_window);
2428
2429       /* Hook up the Control Center's redundant Help button, too. */
2430       gtk_signal_connect (GTK_OBJECT (capplet), "help",
2431                           GTK_SIGNAL_FUNC (doc_menu_cb), 0);
2432
2433       /* Issue any warnings about the running xscreensaver daemon. */
2434       the_network_is_not_the_computer (top_vbox);
2435     }
2436   else
2437 # endif /* HAVE_CRAPPLET */
2438     {
2439       gtk_widget_show (gtk_window);
2440       init_icon (GTK_WIDGET(gtk_window)->window);
2441
2442       /* Issue any warnings about the running xscreensaver daemon. */
2443       the_network_is_not_the_computer (gtk_window);
2444     }
2445
2446   /* Run the Gtk event loop, and not the Xt event loop.  This means that
2447      if there were Xt timers or fds registered, they would never get serviced,
2448      and if there were any Xt widgets, they would never have events delivered.
2449      Fortunately, we're using Gtk for all of the UI, and only initialized
2450      Xt so that we could process the command line and use the X resource
2451      manager.
2452    */
2453   initializing_p = False;
2454
2455 # ifdef HAVE_CRAPPLET
2456   if (crapplet_p)
2457     capplet_gtk_main ();
2458   else
2459 # endif /* HAVE_CRAPPLET */
2460     gtk_main ();
2461
2462   exit (0);
2463 }
2464
2465 #endif /* HAVE_GTK -- whole file */