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