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