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