ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.tar.gz
[xscreensaver] / driver / demo.c
1 /* demo.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
18 #ifdef FORCE_ATHENA
19 # undef HAVE_MOTIF
20 # undef HAVE_GTK
21 # define HAVE_ATHENA 1
22 #endif
23 #ifdef FORCE_GTK
24 # undef HAVE_MOTIF
25 # undef HAVE_ATHENA
26 # define HAVE_GTK 1
27 #endif
28 #ifdef FORCE_MOTIF
29 # undef HAVE_GTK
30 # undef HAVE_ATHENA
31 # define HAVE_MOTIF 1
32 #endif
33
34 /* Only one, please. */
35 #ifdef HAVE_MOTIF
36 # undef HAVE_ATHENA
37 # undef HAVE_GTK
38 #endif
39 #ifdef HAVE_GTK
40 # undef HAVE_MOTIF
41 # undef HAVE_ATHENA
42 #endif
43 #ifdef HAVE_ATHENA
44 # undef HAVE_MOTIF
45 # undef HAVE_GTK
46 #endif
47
48
49 #include <stdlib.h>
50
51 #ifdef HAVE_UNISTD_H
52 # include <unistd.h>
53 #endif
54
55 #ifndef VMS
56 # include <pwd.h>               /* for getpwuid() */
57 #else /* VMS */
58 # include "vms-pwd.h"
59 #endif /* VMS */
60
61 #ifdef HAVE_UNAME
62 # include <sys/utsname.h>       /* for uname() */
63 #endif /* HAVE_UNAME */
64
65 #include <stdio.h>
66
67 #include <X11/Intrinsic.h>
68 #include <X11/StringDefs.h>
69
70 /* We don't actually use any widget internals, but these are included
71    so that gdb will have debug info for the widgets... */
72 #include <X11/IntrinsicP.h>
73 #include <X11/ShellP.h>
74
75 #ifdef HAVE_XMU
76 # ifndef VMS
77 #  include <X11/Xmu/Error.h>
78 # else /* VMS */
79 #  include <Xmu/Error.h>
80 # endif
81 #else
82 # include "xmu.h"
83 #endif
84
85
86 #ifdef HAVE_MOTIF
87 # include <Xm/Xm.h>
88 # include <Xm/Text.h>
89 # include <Xm/List.h>
90 # include <Xm/ToggleB.h>
91 # include <Xm/MessageB.h>
92 # include <Xm/LabelG.h>
93 # include <Xm/RowColumn.h>
94
95 #elif defined(HAVE_ATHENA)
96   /* Athena demo code contributed by Jon A. Christopher <jac8782@tamu.edu> */
97   /* Copyright 1997, with the same permissions as above. */
98 # include <X11/Shell.h>
99 # include <X11/Xaw/Form.h>
100 # include <X11/Xaw/Box.h>
101 # include <X11/Xaw/List.h>
102 # include <X11/Xaw/Command.h>
103 # include <X11/Xaw/Toggle.h>
104 # include <X11/Xaw/Viewport.h>
105 # include <X11/Xaw/Dialog.h>
106 # include <X11/Xaw/Scrollbar.h>
107 # include <X11/Xaw/Text.h>
108
109 #elif defined(HAVE_GTK)
110 # include <gtk/gtk.h>
111 extern Display *gdk_display;
112 #endif /* HAVE_ATHENA */
113
114 #include "version.h"
115 #include "prefs.h"
116 #include "resources.h"          /* for parse_time() */
117 #include "visual.h"             /* for has_writable_cells() */
118 #include "remote.h"             /* for xscreensaver_command() */
119 #include "usleep.h"
120
121 #include <stdio.h>
122 #include <string.h>
123 #include <ctype.h>
124
125 #ifdef HAVE_GTK
126 # define WIDGET GtkWidget *
127 # define POINTER gpointer
128 #else
129 # define WIDGET Widget
130 # define POINTER XtPointer
131 #endif
132
133
134
135 char *progname = 0;
136 char *progclass = "XScreenSaver";
137 XrmDatabase db;
138
139 typedef struct {
140   saver_preferences *a, *b;
141 } prefs_pair;
142
143
144 char *blurb (void) { return progname; }
145
146 static void run_hack (Display *dpy, int n);
147
148 #ifdef HAVE_ATHENA
149 static saver_preferences *global_prefs_kludge = 0;    /* I hate C so much... */
150 #endif /* HAVE_ATHENA */
151
152 static char *short_version = 0;
153
154 Atom XA_VROOT;
155 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
156 Atom XA_SCREENSAVER_TIME, XA_SCREENSAVER_ID, XA_SELECT, XA_DEMO, XA_RESTART;
157
158 extern void create_demo_dialog (Widget, Visual *, Colormap);
159 extern void create_preferences_dialog (Widget, Visual *, Colormap);
160
161 extern WIDGET demo_dialog;
162 extern WIDGET label1;
163 extern WIDGET text_line;
164 extern WIDGET text_activate;
165 extern WIDGET demo_form;
166 extern WIDGET demo_list;
167 extern WIDGET next;
168 extern WIDGET prev;
169 extern WIDGET done;
170 extern WIDGET restart;
171 extern WIDGET edit;
172
173 extern WIDGET preferences_dialog;
174 extern WIDGET preferences_form;
175 extern WIDGET prefs_done;
176 extern WIDGET prefs_cancel;
177 extern WIDGET timeout_text;
178 extern WIDGET cycle_text;
179 extern WIDGET fade_text;
180 extern WIDGET fade_ticks_text;
181 extern WIDGET lock_timeout_text;
182 extern WIDGET passwd_timeout_text;
183 extern WIDGET verbose_toggle;
184 extern WIDGET install_cmap_toggle;
185 extern WIDGET fade_toggle;
186 extern WIDGET unfade_toggle;
187 extern WIDGET lock_toggle;
188
189
190 #ifdef HAVE_MOTIF
191
192 # define set_toggle_button_state(toggle,state) \
193   XmToggleButtonSetState ((toggle), (state), True)
194 # define set_text_string(text_widget,string) \
195   XmTextSetString ((text_widget), (string))
196 # define add_button_callback(button,cb,arg) \
197   XtAddCallback ((button), XmNactivateCallback, (cb), (arg))
198 # define add_toggle_callback(button,cb,arg) \
199   XtAddCallback ((button), XmNvalueChangedCallback, (cb), (arg))
200 # define add_text_callback add_toggle_callback
201 # define disable_widget(widget) \
202   XtVaSetValues((widget), XtNsensitive, False, 0)
203 # define widget_name(widget) XtName(widget)
204 # define widget_display(widget) XtDisplay(widget)
205 # define widget_screen(widget) XtScreen(widget)
206 # define CB_ARGS(a,b,c) (a,b,c)
207
208 #elif defined(HAVE_ATHENA)
209
210 # define set_toggle_button_state(toggle,state) \
211   XtVaSetValues((toggle), XtNstate, (state),  0)
212 # define set_text_string(text_widget,string) \
213   XtVaSetValues ((text_widget), XtNvalue, (string), 0)
214 # define add_button_callback(button,cb,arg) \
215   XtAddCallback ((button), XtNcallback, (cb), (arg))
216 # define add_toggle_callback add_button_callback
217 # define add_text_callback(b,c,a) ERROR!
218 # define disable_widget(widget) \
219   XtVaSetValues((widget), XtNsensitive, False, 0)
220 # define widget_name(widget) XtName(widget)
221 # define widget_display(widget) XtDisplay(widget)
222 # define widget_screen(widget) XtScreen(widget)
223 # define CB_ARGS(a,b,c) (a,b,c)
224
225 #elif defined(HAVE_GTK)
226
227 # define set_toggle_button_state(toggle,state) \
228   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(toggle),(state))
229 # define set_text_string(text_widget,string) \
230   gtk_entry_set_text (GTK_ENTRY (text_widget), (string))
231 # define add_button_callback(button,cb,arg) \
232   gtk_signal_connect_object (GTK_OBJECT (button), "clicked", \
233                              GTK_SIGNAL_FUNC (cb), (arg))
234 # define add_toggle_callback(button,cb,arg) \
235   gtk_signal_connect_object (GTK_OBJECT (button), "toggled", \
236                              GTK_SIGNAL_FUNC (cb), (arg))
237 # define add_text_callback(button,cb,arg) \
238   gtk_signal_connect_object (GTK_OBJECT (button), "activate", \
239                              GTK_SIGNAL_FUNC (cb), (arg))
240 # define disable_widget(widget) \
241   gtk_widget_set_sensitive (GTK_WIDGET(widget), FALSE)
242 # define widget_name(widget) gtk_widget_get_name(GTK_WIDGET(widget))
243 # define widget_display(widget) (gdk_display)
244 # define widget_screen(widget) (DefaultScreenOfDisplay(widget_display(widget)))
245 # define CB_ARGS(a,b,c) (b,a)
246
247 #endif /* HAVE_GTK */
248
249
250
251
252 static char *
253 get_text_string (WIDGET text_widget)
254 {
255 #ifdef HAVE_MOTIF
256   return XmTextGetString (text_widget);
257 #elif defined(HAVE_ATHENA)
258   char *string = 0;
259   if (XtIsSubclass(text_widget, textWidgetClass))
260     XtVaGetValues (text_widget, XtNstring, &string, 0);
261   else if (XtIsSubclass(text_widget, dialogWidgetClass))
262     XtVaGetValues (text_widget, XtNvalue, &string, 0);
263   else
264     string = 0;
265
266   return string;
267 #elif defined(HAVE_GTK)
268   return gtk_entry_get_text (GTK_ENTRY (text_widget));
269 #endif /* HAVE_GTK */
270 }
271
272
273 static char *
274 get_label_string (WIDGET label_widget)
275 {
276 #ifdef HAVE_MOTIF
277   char *label = 0;
278   XmString xm_label = 0;
279   XtVaGetValues (label_widget, XmNlabelString, &xm_label, 0);
280   if (!xm_label)
281     return 0;
282   XmStringGetLtoR (xm_label, XmSTRING_DEFAULT_CHARSET, &label);
283   return label;
284 #elif defined(HAVE_ATHENA)
285   char *label = 0;
286   XtVaGetValues (label_widget, XtNlabel, &label, 0);
287   return (label ? strdup(label) : 0);
288 #elif defined(HAVE_GTK)
289   char *label = 0;
290   gtk_label_get (GTK_LABEL (label_widget), &label);
291   return label;
292 #endif /* HAVE_GTK */
293 }
294
295
296 static void
297 set_label_string (WIDGET label_widget, char *string)
298 {
299 #ifdef HAVE_MOTIF
300   XmString xm_string = XmStringCreate (string, XmSTRING_DEFAULT_CHARSET);
301   XtVaSetValues (label_widget, XmNlabelString, xm_string, 0);
302   XmStringFree (xm_string);
303 #elif defined(HAVE_ATHENA)
304   XtVaSetValues (label_widget, XtNlabel, string, 0);
305 #elif defined(HAVE_GTK)
306   gtk_label_set_text (GTK_LABEL (label_widget), string);
307 #endif /* HAVE_GTK */
308 }
309
310
311 /* Given a label widget that has a %s in it, do the printf thing.
312    If the label's string is obviously wrong, complain about resource lossage.
313  */
314 static void
315 format_into_label (WIDGET label, const char *arg)
316 {
317   char *text = get_label_string (label);
318   char *buf = (char *) malloc ((text ? strlen(text) : 0) + strlen(arg) + 100);
319
320   if (!text || !*text || !strcmp (text, widget_name (label)))
321       strcpy (buf, "ERROR: RESOURCES ARE NOT INSTALLED CORRECTLY");
322     else
323       sprintf (buf, text, arg);
324
325     set_label_string (label, buf);
326     free (buf);
327
328 # ifndef HAVE_GTK
329     XtFree (text);
330 # endif /* HAVE_GTK */
331 }
332
333
334 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
335  */
336 static void
337 ensure_selected_item_visible (WIDGET list)
338 {
339 #ifdef HAVE_MOTIF
340   int *pos_list = 0;
341   int pos_count = 0;
342   if (XmListGetSelectedPos (list, &pos_list, &pos_count) && pos_count > 0)
343     {
344       int top = -2;
345       int visible = 0;
346       XtVaGetValues (list,
347                      XmNtopItemPosition, &top,
348                      XmNvisibleItemCount, &visible,
349                      0);
350       if (pos_list[0] >= top + visible)
351         {
352           int pos = pos_list[0] - visible + 1;
353           if (pos < 0) pos = 0;
354           XmListSetPos (list, pos);
355         }
356       else if (pos_list[0] < top)
357         {
358           XmListSetPos (list, pos_list[0]);
359         }
360     }
361   if (pos_list)
362     XtFree ((char *) pos_list);
363
364 #elif defined(HAVE_ATHENA)
365 # ifdef HAVE_XawViewportSetCoordinates
366
367   int margin = 16;      /* should be line height or something. */
368   int count = 0;
369   int pos;
370   Dimension list_h = 0, vp_h = 0;
371   Dimension top_margin = 4;  /* I don't know where this value comes from */
372   Position vp_x = 0, vp_y = 0, current_y;
373   double cratio;
374   Widget viewport = XtParent(demo_list);
375   Widget sb = (viewport ? XtNameToWidget(viewport, "*vertical") : 0);
376   float sb_top = 0, sb_size = 0;
377   XawListReturnStruct *current = XawListShowCurrent(demo_list);
378   if (!current || !sb) return;
379
380   XtVaGetValues(demo_list,
381                 XtNnumberStrings, &count,
382                 XtNheight, &list_h,
383                 0);
384   if (count < 2 || list_h < 10) return;
385
386   XtVaGetValues(viewport, XtNheight, &vp_h, XtNx, &vp_x, XtNy, &vp_y, 0);
387   if (vp_h < 10) return;
388
389   XtVaGetValues(sb, XtNtopOfThumb, &sb_top, XtNshown, &sb_size, 0);
390   if (sb_size <= 0) return;
391
392   pos = current->list_index;
393   cratio = ((double) pos)  / ((double) count);
394   current_y = (cratio * list_h);
395
396   if (cratio < sb_top ||
397       cratio > sb_top + sb_size)
398     {
399       if (cratio < sb_top)
400         current_y -= (vp_h - margin - margin);
401       else
402         current_y -= margin;
403
404       if ((long)current_y >= (long) list_h)
405         current_y = (Position) ((long)list_h - (long)vp_h);
406
407       if ((long)current_y < (long)top_margin)
408         current_y = (Position)top_margin;
409
410       XawViewportSetCoordinates (viewport, vp_x, current_y);
411     }
412 # endif /* HAVE_XawViewportSetCoordinates */
413 #elif defined(HAVE_GTK)
414
415   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (list);
416   GtkViewport *vp = GTK_VIEWPORT (GTK_BIN(scroller)->child);
417   GtkList *list_widget = GTK_LIST (GTK_BIN(vp)->child);
418   GList *kids;
419   int nkids = 0;
420   GtkWidget *selected = 0;
421   int which = -1;
422   GtkAdjustment *adj;
423   gint parent_h, child_y, child_h, children_h, ignore;
424   double ratio_t, ratio_b;
425
426   GList *slist = list_widget->selection;
427   selected = (slist ? GTK_WIDGET (slist->data) : 0);
428   if (!selected)
429     return;
430
431   which = gtk_list_child_position (list_widget, GTK_WIDGET (selected));
432
433   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
434        kids; kids = kids->next)
435     nkids++;
436
437   adj = gtk_scrolled_window_get_vadjustment (scroller);                        
438
439   gdk_window_get_geometry (GTK_WIDGET(vp)->window,
440                            &ignore, &ignore, &ignore, &parent_h, &ignore);
441   gdk_window_get_geometry (GTK_WIDGET(selected)->window,
442                            &ignore, &child_y, &ignore, &child_h, &ignore);
443   children_h = nkids * child_h;
444
445   ratio_t = ((double) child_y) / ((double) children_h);
446   ratio_b = ((double) child_y + child_h) / ((double) children_h);
447
448   if (ratio_t < (adj->value / adj->upper) ||
449       ratio_b > ((adj->value + adj->page_size) / adj->upper))
450     {
451       double target;
452
453       if (ratio_t < (adj->value / adj->upper))
454         {
455           double ratio_w = ((double) parent_h) / ((double) children_h);
456           double ratio_l = (ratio_b - ratio_t);
457           target = ((ratio_t - ratio_w + ratio_l) * adj->upper);
458         }
459       else /* if (ratio_b > ((adj->value + adj->page_size) / adj->upper))*/
460         {
461           target = ratio_t * adj->upper;
462         }
463
464       if (target > adj->upper - adj->page_size)
465         target = adj->upper - adj->page_size;
466       if (target < 0)
467         target = 0;
468
469       gtk_adjustment_set_value (adj, target);
470     }
471
472
473 #endif /* HAVE_GTK */
474 }
475
476
477 /* Callback for the text area:
478    - note the text the user has entered;
479    - change the corresponding element in `screenhacks';
480    - write the .xscreensaver file;
481    - tell the xscreensaver daemon to run that hack.
482
483    (Note: in GTK, this one has a different arg list than the other callbacks.)
484  */
485 static void
486 #ifdef HAVE_GTK
487 text_cb (WIDGET text_widget, POINTER client_data)
488 #else  /* !HAVE_GTK */
489 text_cb (WIDGET text_widget, POINTER client_data, POINTER call_data)
490 #endif /* !HAVE_GTK */
491 {
492   saver_preferences *p = (saver_preferences *) client_data;
493   char *new_text = get_text_string (text_widget);
494   Display *dpy = widget_display (text_widget);
495   Bool save = TRUE;
496
497   int hack_number = -1;         /* 0-based */
498
499 #ifdef HAVE_ATHENA
500   XawListReturnStruct *current = XawListShowCurrent(demo_list);
501   hack_number = current->list_index;
502 #elif defined(HAVE_MOTIF)
503   int *pos_list = 0;
504   int pos_count = 0;
505   if (XmListGetSelectedPos (demo_list, &pos_list, &pos_count))
506     hack_number = pos_list[0] - 1;
507   if (pos_list)
508     XtFree ((char *) pos_list);
509 #elif defined(HAVE_GTK)
510   GList *slist =
511     GTK_LIST (GTK_BIN(GTK_BIN(demo_list)->child)->child)->selection;
512   GtkWidget *selected = (slist ? GTK_WIDGET (slist->data) : 0);
513   if (selected)
514     hack_number =
515       gtk_list_child_position (
516                          GTK_LIST (GTK_BIN(GTK_BIN(demo_list)->child)->child),
517                          GTK_WIDGET (selected));
518 #endif /* HAVE_GTK */
519
520   ensure_selected_item_visible (demo_list);
521
522   if (hack_number < 0 || hack_number >= p->screenhacks_count)
523     {
524       set_text_string (text_widget, "");
525 #ifdef HAVE_GTK
526       gdk_beep();
527 #else  /* !HAVE_GTK */
528       XBell (XtDisplay (text_widget), 0);
529 #endif /* !HAVE_GTK */
530     }
531   else
532     {
533       if (p->screenhacks [hack_number])
534         free (p->screenhacks [hack_number]);
535       p->screenhacks [hack_number] = strdup (new_text);
536
537 #ifdef HAVE_MOTIF
538
539       XmListDeselectAllItems (demo_list);
540       {
541         XmString xmstr = XmStringCreate (new_text, XmSTRING_DEFAULT_CHARSET);
542         XmListReplaceItemsPos (demo_list, &xmstr, 1, hack_number+1);
543         XmStringFree (xmstr);
544       }
545       XmListSelectPos (demo_list, hack_number+1, True);
546
547 #elif defined(HAVE_ATHENA)
548
549       {
550         Widget vp = XtParent(demo_list);
551         Widget sb = (vp ? XtNameToWidget(vp, "*vertical") : 0);
552         Dimension list_h = 0;
553         Position vp_x = 0, vp_y = 0;
554         float sb_top = 0;
555
556         XawListUnhighlight (demo_list);
557
558         XtVaGetValues (vp, XtNx, &vp_x, 0);
559         XtVaGetValues (sb, XtNtopOfThumb, &sb_top, 0);
560         XtVaGetValues (demo_list, XtNheight, &list_h, 0);
561         vp_y = (sb_top * list_h);
562         XtVaSetValues (demo_list,
563                        XtNlist, p->screenhacks,
564                        XtNnumberStrings, p->screenhacks_count,
565                        0);
566         XawViewportSetCoordinates (vp, vp_x, vp_y);
567         XawListHighlight (demo_list, hack_number);
568       }
569
570 #elif defined(HAVE_GTK)
571       {
572         GtkList *list_widget =
573           GTK_LIST (GTK_BIN(GTK_BIN(demo_list)->child)->child);
574         GList *slist = list_widget->selection;
575         GtkWidget *selected = (slist ? GTK_WIDGET (slist->data) : 0);
576         GtkLabel *label = (selected
577                            ? GTK_LABEL (GTK_BIN (selected)->child) : 0);
578         char *old_text = 0;
579         gtk_label_get (label, &old_text);
580         save = !!strcmp (new_text, old_text);
581         if (label)
582           gtk_label_set_text (label, new_text);
583       }
584 #endif /* HAVE_GTK */
585
586       if (save)
587         write_init_file (p, short_version);
588
589       XSync (dpy, False);
590       usleep (500000);          /* give the disk time to settle down */
591
592       run_hack (dpy, hack_number+1);
593     }
594 }
595
596
597 #ifdef HAVE_ATHENA
598 /* Bend over backwards to make hitting Return in the text field do the
599    right thing. 
600    */
601 static void text_enter (Widget w, XEvent *event, String *av, Cardinal *ac)
602 {
603   text_cb (w, global_prefs_kludge, 0);    /* I hate C so much... */
604 }
605
606 static XtActionsRec actions[] = {{"done",      text_enter}
607                                 };
608 static char translations[] = ("<Key>Return:     done()\n"
609                               "<Key>Linefeed:   done()\n"
610                               "Ctrl<Key>M:      done()\n"
611                               "Ctrl<Key>J:      done()\n");
612 #endif /* HAVE_ATHENA */
613
614
615 #ifdef HAVE_GTK
616 /* Helper for the Gtk versions of the Run Next and Run Previous buttons.
617  */
618 static void
619 next_internal (GtkEntry *entry, gboolean next_p)
620 {
621   GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (demo_list);
622   GtkList *list_widget = GTK_LIST(GTK_BIN(GTK_BIN(scroller)->child)->child);
623   GtkWidget *target = 0;
624   GList *kids;
625   int nkids = 0;
626   int n;
627
628   GList *slist = list_widget->selection;
629   target = (slist ? GTK_WIDGET (slist->data) : 0);
630
631   for (kids = gtk_container_children (GTK_CONTAINER (list_widget));
632        kids; kids = kids->next)
633     nkids++;
634
635   if (target)
636     {
637       n = gtk_list_child_position (GTK_LIST (list_widget), target);
638       n += (next_p ? 1 : -1);
639       if (n >= nkids) n = 0;
640       if (n < 0) n = nkids-1;
641     }
642   else if (next_p)
643     n = 0;
644   else
645     n = nkids-1;
646
647   gtk_list_select_item (GTK_LIST (list_widget), n);
648
649   ensure_selected_item_visible ((WIDGET) scroller);
650
651   run_hack (widget_display (scroller), n + 1);
652 }
653
654 #endif /* HAVE_GTK */
655
656
657
658 /* Callback for the Run Next button.
659  */
660 static void
661 next_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
662 {
663 #ifdef HAVE_ATHENA
664   XawListReturnStruct *current = XawListShowCurrent(demo_list);
665   int cnt;
666   XtVaGetValues (demo_list, XtNnumberStrings, &cnt, 0);
667   if (current->list_index == XAW_LIST_NONE ||
668       current->list_index + 1 >= cnt)
669     current->list_index = 0;
670   else
671     current->list_index++;
672   XawListHighlight(demo_list, current->list_index);
673
674   ensure_selected_item_visible (demo_list);
675   current = XawListShowCurrent(demo_list);
676   XtVaSetValues(text_line, XtNstring, current->string, 0);
677
678   run_hack (XtDisplay (button), current->list_index + 1);
679
680 #elif defined(HAVE_MOTIF)
681
682   saver_preferences *p = (saver_preferences *) client_data;
683   int *pos_list = 0;
684   int pos_count = 0;
685   int pos;
686   if (! XmListGetSelectedPos (demo_list, &pos_list, &pos_count))
687     {
688       pos = 1;
689       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
690       XmListSelectPos (demo_list, pos, True);
691     }
692   else
693     {
694       pos = pos_list[0] + 1;
695       if (pos > p->screenhacks_count)
696         pos = 1;
697       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
698       XmListSelectPos (demo_list, pos, True);
699     }
700      
701   ensure_selected_item_visible (demo_list);
702   run_hack (XtDisplay (button), pos);
703   if (pos_list)
704     XtFree ((char *) pos_list);
705
706 #elif defined(HAVE_GTK)
707   next_internal (GTK_ENTRY (text_line), TRUE);
708 #endif /* HAVE_GTK */
709 }
710
711
712 /* Callback for the Run Previous button.
713  */
714 static void
715 prev_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
716 {
717 #ifdef HAVE_ATHENA
718   XawListReturnStruct *current = XawListShowCurrent(demo_list);
719   int cnt;
720   XtVaGetValues (demo_list, XtNnumberStrings, &cnt, 0);
721   if (current->list_index == XAW_LIST_NONE ||
722       current->list_index <= 0)
723     current->list_index = cnt-1;
724   else
725     current->list_index--;
726   XawListHighlight(demo_list, current->list_index);
727
728   ensure_selected_item_visible (demo_list);
729   current = XawListShowCurrent(demo_list);
730   XtVaSetValues(text_line, XtNstring, current->string, 0);
731
732   run_hack (XtDisplay (button), current->list_index + 1);
733
734 #elif defined(HAVE_MOTIF)
735
736   saver_preferences *p = (saver_preferences *) client_data;
737   int *pos_list = 0;
738   int pos_count = 0;
739   int pos;
740   if (! XmListGetSelectedPos (demo_list, &pos_list, &pos_count))
741     {
742       pos = p->screenhacks_count;
743       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
744       XmListSelectPos (demo_list, pos, True);
745     }
746   else
747     {
748       pos = pos_list[0] - 1;
749       if (pos == 0)
750         pos = p->screenhacks_count;
751       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
752       XmListSelectPos (demo_list, pos, True);
753     }
754      
755   ensure_selected_item_visible (demo_list);
756   run_hack (XtDisplay (button), pos);
757   if (pos_list)
758     XtFree ((char *) pos_list);
759
760 #elif defined(HAVE_GTK)
761   next_internal (GTK_ENTRY (text_line), FALSE);
762 #endif /* HAVE_GTK */
763 }
764
765
766 /* Callback run when a list element is double-clicked.
767    (Note: in GTK, this one has a different arg list than the other callbacks.)
768  */
769 #ifdef HAVE_GTK
770 static gint
771 select_cb (GtkWidget *button, GdkEventButton *event, gpointer client_data)
772 #else  /* !HAVE_GTK */
773 static void
774 select_cb (WIDGET button, POINTER client_data, POINTER call_data)
775 #endif /* !HAVE_GTK */
776 {
777 /*  saver_preferences *p = (saver_preferences *) client_data; */
778
779 #ifdef HAVE_ATHENA
780   XawListReturnStruct *item = (XawListReturnStruct*)call_data;
781   XtVaSetValues(text_line, XtNstring, item->string, 0);
782   run_hack (XtDisplay (button), item->list_index + 1);
783
784 #elif defined(HAVE_MOTIF)
785   XmListCallbackStruct *lcb = (XmListCallbackStruct *) call_data;
786   char *string = 0;
787   if (lcb->item)
788     XmStringGetLtoR (lcb->item, XmSTRING_DEFAULT_CHARSET, &string);
789   set_text_string (text_line, (string ? string : ""));
790
791   if (lcb->reason == XmCR_DEFAULT_ACTION && string)
792     run_hack (XtDisplay (button), lcb->item_position);
793
794   if (string)
795     XtFree (string);
796
797 #elif defined(HAVE_GTK)
798   char *string = 0;
799   gtk_label_get (GTK_LABEL (GTK_BIN(button)->child), &string);
800   set_text_string (text_line, (string ? string : ""));
801
802   if (event->type == GDK_2BUTTON_PRESS)
803     {
804       GtkViewport *vp = GTK_VIEWPORT (GTK_BIN(demo_list)->child);
805       GtkList *lw = GTK_LIST (GTK_BIN(vp)->child);
806       int which = gtk_list_child_position (lw, GTK_WIDGET (button));
807       run_hack (gdk_display, which + 1);
808     }
809
810   return FALSE;
811 #endif /* HAVE_GTK */
812 }
813
814
815 static void pop_preferences_dialog (prefs_pair *pair);
816 static void make_preferences_dialog (prefs_pair *pair, Widget parent);
817
818 /* Callback for the Preferences button.
819  */
820 static void
821 preferences_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
822 {
823   prefs_pair *pair = (prefs_pair *) client_data;
824 #ifdef HAVE_GTK
825   Widget parent = 0;
826 #else /* !HAVE_GTK */
827   Widget parent = button;
828
829   do {
830     parent = XtParent(parent);
831   } while (XtParent(parent));
832 #endif /* !HAVE_GTK */
833
834   if (! preferences_dialog)
835     make_preferences_dialog (pair, parent);
836   *pair->b = *pair->a;
837   pop_preferences_dialog (pair);
838 }
839
840
841 /* Callback for the Quit button.
842  */
843 static void
844 quit_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
845 {
846   /* Save here?  Right now we don't need to, because we save every time
847      the text field is edited, or the Preferences OK button is pressed.
848   */
849   exit (0);
850 }
851
852
853 /* Callback for the (now unused) Restart button.
854  */
855 static void
856 restart_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
857 {
858   xscreensaver_command (widget_display (button), XA_RESTART, 0, False);
859 }
860
861
862 static void
863 pop_up_dialog_box (WIDGET dialog, WIDGET form)
864 {
865 #ifdef HAVE_ATHENA
866   XtRealizeWidget (dialog);
867   XtPopup (dialog, XtGrabNone);
868 #elif defined(HAVE_MOTIF)
869   XtRealizeWidget (form);
870   XtManageChild (form);
871 #endif /* HAVE_MOTIF */
872
873 #ifdef HAVE_GTK
874   gtk_widget_show (dialog);
875   gdk_window_show (GTK_WIDGET (dialog)->window);
876   gdk_window_raise (GTK_WIDGET (dialog)->window);
877 #else /* !HAVE_GTK */
878   XMapRaised (XtDisplay (dialog), XtWindow (dialog));
879 #endif /* !HAVE_GTK */
880 }
881
882
883 #ifdef HAVE_GTK
884 /* Callback for WM_DELETE_WINDOW on the main demo window.
885  */
886 static void
887 destroy (GtkWidget *widget, gpointer data)
888 {
889   gtk_main_quit ();
890 }
891
892 /* Callback for the "Run" button to the right of the text entry line.
893  */
894 static void
895 select_button_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
896 {
897   gtk_signal_emit_by_name (GTK_OBJECT (text_line), "activate");
898 }
899 #endif /* HAVE_GTK */
900
901
902 static void
903 make_demo_dialog (Widget toplevel_shell, prefs_pair *pair)
904 {
905   saver_preferences *p =  pair->a;
906   /* saver_preferences *p2 = pair->b; */
907   Widget parent = toplevel_shell;
908   char **hacks = p->screenhacks;
909
910   create_demo_dialog (parent,
911                       DefaultVisualOfScreen (widget_screen (parent)),
912                       DefaultColormapOfScreen (widget_screen (parent)));
913
914 #ifdef HAVE_GTK
915   gtk_window_set_title (GTK_WINDOW (demo_dialog), progclass);
916   gtk_signal_connect (GTK_OBJECT (demo_dialog), "delete_event",
917                       GTK_SIGNAL_FUNC (destroy), NULL);
918   gtk_signal_connect (GTK_OBJECT (demo_dialog), "destroy",
919                       GTK_SIGNAL_FUNC (destroy), NULL);
920 #endif /* HAVE_GTK */
921
922   format_into_label (label1, short_version);
923   add_button_callback (next,    next_cb,        (POINTER) p);
924   add_button_callback (prev,    prev_cb,        (POINTER) p);
925   add_button_callback (done,    quit_cb,        (POINTER) p);
926   if (restart)
927     add_button_callback(restart,restart_cb,     (POINTER) p);
928   add_button_callback (edit,    preferences_cb, (POINTER) pair);
929
930 #ifdef HAVE_MOTIF
931   XtAddCallback (demo_list, XmNbrowseSelectionCallback,
932                  select_cb, (POINTER) p);
933   XtAddCallback (demo_list, XmNdefaultActionCallback,
934                  select_cb, (POINTER) p);
935   XtAddCallback (text_line, XmNactivateCallback, text_cb, (POINTER) p);
936
937   if (hacks)
938     for (; *hacks; hacks++)
939       {
940         XmString xmstr = XmStringCreate (*hacks, XmSTRING_DEFAULT_CHARSET);
941         XmListAddItem (demo_list, xmstr, 0);
942         XmStringFree (xmstr);
943       }
944
945 #elif defined(HAVE_ATHENA)
946
947   /* Hook up the text line. */
948
949   XtAppAddActions(XtWidgetToApplicationContext(text_line),
950                   actions, XtNumber(actions));
951   XtOverrideTranslations(text_line, XtParseTranslationTable(translations));
952
953
954   /* Must realize the widget before populating the list, or the dialog
955      will be as wide as the longest string.
956   */
957   XtRealizeWidget (demo_dialog);
958
959   XtVaSetValues (demo_list,
960                  XtNlist, hacks,
961                  XtNnumberStrings, p->screenhacks_count,
962                  0);
963   XtAddCallback (demo_list, XtNcallback, select_cb, p);
964
965   /* Now that we've populated the list, make sure that the list is as
966      wide as the dialog itself.
967   */
968   {
969     Widget viewport = XtParent(demo_list);
970     Widget subform = XtParent(viewport);
971     Widget box = XtNameToWidget(demo_dialog, "*box");
972     Widget label1 = XtNameToWidget(demo_dialog, "*label1");
973     Widget label2 = XtNameToWidget(demo_dialog, "*label2");
974     Dimension x=0, y=0, w=0, h=0, bw=0, w2=0;
975     XtVaGetValues(subform,
976                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
977     XtVaGetValues(box, XtNwidth, &w2, 0);
978     if (w2 != w)
979       XtResizeWidget(subform, w2, h, bw);
980
981     /* Why isn't the viewport getting centered? */
982     XtVaGetValues(viewport,
983                   XtNx, &x, XtNy, &y, XtNheight, &h, XtNborderWidth, &bw, 0);
984     XtConfigureWidget(viewport, x, y, w2-x-x, h, bw);
985
986     /* And the text line, too. */
987     XtVaGetValues(text_line,
988                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
989     XtVaGetValues(viewport, XtNwidth, &w2, 0);
990     if (w2 != w)
991       XtResizeWidget(text_line, w2, h, bw);
992
993     /* And the labels too. */
994     XtVaGetValues(label1,
995                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
996     if (w2 != w)
997       XtResizeWidget(label1, w2, h, bw);
998
999     XtVaGetValues(label2,
1000                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
1001     if (w2 != w)
1002       XtResizeWidget(label2, w2, h, bw);
1003
1004   }
1005
1006 #elif defined(HAVE_GTK)
1007   {
1008     GtkList *list = GTK_LIST(GTK_BIN(GTK_BIN(demo_list)->child)->child);
1009     char **s;
1010     for (s = hacks; *s; s++)
1011       {
1012         GtkWidget *line = gtk_list_item_new_with_label (*s);
1013         gtk_container_add (GTK_CONTAINER (list), line);
1014         gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
1015                             GTK_SIGNAL_FUNC (select_cb),
1016                             (POINTER) line);
1017         GTK_WIDGET (GTK_BIN(line)->child)->style =
1018           gtk_style_copy (GTK_WIDGET (text_line)->style);
1019         gtk_widget_show (line);
1020       }
1021     gtk_signal_connect (GTK_OBJECT (text_line), "activate",
1022                         GTK_SIGNAL_FUNC (text_cb),
1023                         (POINTER) p);
1024     gtk_signal_connect (GTK_OBJECT (text_activate), "clicked",
1025                         GTK_SIGNAL_FUNC (select_button_cb),
1026                         (POINTER) p);
1027   }
1028 #endif /* HAVE_GTK */
1029
1030   pop_up_dialog_box(demo_dialog, demo_form);
1031
1032 #ifdef HAVE_ATHENA
1033   /* For Athena, have to do this after the dialog is managed. */
1034   ensure_selected_item_visible (demo_list);
1035 #endif /* HAVE_ATHENA */
1036 }
1037
1038 \f
1039 /* the Preferences dialog
1040  */
1041
1042 /* Helper for the text fields that contain time specifications:
1043    this parses the text, and does error checking.
1044  */
1045 static void 
1046 hack_time_text (Display *dpy, char *line, Time *store, Bool sec_p)
1047 {
1048   if (*line)
1049     {
1050       int value;
1051       value = parse_time (line, sec_p, True);
1052       value *= 1000;    /* Time measures in microseconds */
1053       if (value < 0)
1054         /*XBell (dpy, 0)*/;
1055       else
1056         *store = value;
1057     }
1058 }
1059
1060
1061 /* Callback for text fields that hold a time that default to seconds,
1062    when not fully spelled out.  client_data is a Time* where the value goes.
1063  */
1064 static void
1065 prefs_sec_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
1066 {
1067   hack_time_text (widget_display (button), get_text_string (button),
1068                   (Time *) client_data, True);
1069 }
1070
1071
1072 /* Callback for text fields that hold a time that default to minutes,
1073    when not fully spelled out.  client_data is an Time* where the value goes.
1074  */
1075 static void
1076 prefs_min_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
1077 {
1078   hack_time_text (widget_display (button), get_text_string (button),
1079                   (Time *) client_data, False);
1080 }
1081
1082
1083 /* Callback for text fields that hold an integer value.
1084    client_data is an int* where the value goes.
1085  */
1086 static void
1087 prefs_int_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
1088 {
1089   char *line = get_text_string (button);
1090   int *store = (int *) client_data;
1091   unsigned int value;
1092   char c;
1093   if (! *line)
1094     ;
1095   else if (sscanf (line, "%u%c", &value, &c) != 1)
1096 #ifdef HAVE_GTK
1097     gdk_beep();
1098 #else  /* !HAVE_GTK */
1099     XBell (XtDisplay (button), 0);
1100 #endif /* !HAVE_GTK */
1101   else
1102     *store = value;
1103 }
1104
1105
1106 /* Callback for toggle buttons.  client_data is a Bool* where the value goes.
1107  */
1108 static void
1109 prefs_bool_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER call_data)
1110 {
1111   Bool *store = (Bool *) client_data;
1112 #ifdef HAVE_MOTIF
1113   *store = ((XmToggleButtonCallbackStruct *) call_data)->set;
1114 #elif defined(HAVE_ATHENA)
1115   Boolean state = FALSE;
1116   XtVaGetValues (button, XtNstate, &state, 0);
1117   *store = state;
1118 #elif defined(HAVE_GTK)
1119   *store = GTK_TOGGLE_BUTTON (button)->active;
1120 #endif /* HAVE_GTK */
1121 }
1122
1123
1124 /* Callback for the Cancel button on the Preferences dialog.
1125  */
1126 static void
1127 prefs_cancel_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER ignored)
1128 {
1129 #ifdef HAVE_GTK
1130   gdk_window_hide (GTK_WIDGET (preferences_dialog)->window);
1131   gtk_widget_show (demo_dialog);
1132   gdk_window_show (GTK_WIDGET (demo_dialog)->window);
1133   gdk_window_raise (GTK_WIDGET (demo_dialog)->window);
1134 #else  /* !HAVE_GTK */
1135   XtDestroyWidget (preferences_dialog);
1136   preferences_dialog = 0;
1137   XMapRaised (XtDisplay (demo_dialog), XtWindow (demo_dialog));
1138 #endif /* !HAVE_GTK */
1139 }
1140
1141
1142 /* Callback for the OK button on the Preferences dialog.
1143  */
1144 static void
1145 prefs_ok_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER call_data)
1146 {
1147   prefs_pair *pair = (prefs_pair *) client_data;
1148   saver_preferences *p =  pair->a;
1149   saver_preferences *p2 = pair->b;
1150
1151   prefs_cancel_cb CB_ARGS(button, client_data, call_data);
1152
1153 #ifdef HAVE_ATHENA
1154   /* Athena doesn't let us put callbacks on these widgets, so run
1155      all the callbacks by hand when OK is pressed. */
1156   prefs_min_cb (timeout_text,        (POINTER) &p2->timeout,        0);
1157   prefs_min_cb (cycle_text,          (POINTER) &p2->cycle,          0);
1158   prefs_sec_cb (fade_text,           (POINTER) &p2->fade_seconds,   0);
1159   prefs_int_cb (fade_ticks_text,     (POINTER) &p2->fade_ticks,     0);
1160   prefs_min_cb (lock_timeout_text,   (POINTER) &p2->lock_timeout,   0);
1161   prefs_sec_cb (passwd_timeout_text, (POINTER) &p2->passwd_timeout, 0);
1162 #elif defined(HAVE_GTK)
1163   /* Do it again anyway for GTK. */
1164   prefs_min_cb ((POINTER) &p2->timeout,        timeout_text);
1165   prefs_min_cb ((POINTER) &p2->cycle,          cycle_text);
1166   prefs_sec_cb ((POINTER) &p2->fade_seconds,   fade_text);
1167   prefs_int_cb ((POINTER) &p2->fade_ticks,     fade_ticks_text);
1168   prefs_min_cb ((POINTER) &p2->lock_timeout,   lock_timeout_text);
1169   prefs_sec_cb ((POINTER) &p2->passwd_timeout, passwd_timeout_text);
1170 #endif /* HAVE_GTK */
1171
1172   p->timeout        = p2->timeout;
1173   p->cycle          = p2->cycle;
1174   p->lock_timeout   = p2->lock_timeout;
1175   p->passwd_timeout = p2->passwd_timeout;
1176   p->fade_seconds   = p2->fade_seconds;
1177   p->fade_ticks     = p2->fade_ticks;
1178   p->verbose_p      = p2->verbose_p;
1179   p->install_cmap_p = p2->install_cmap_p;
1180   p->fade_p         = p2->fade_p;
1181   p->unfade_p       = p2->unfade_p;
1182   p->lock_p         = p2->lock_p;
1183
1184   write_init_file (p, short_version);
1185 }
1186
1187
1188 #ifdef HAVE_GTK
1189 static void
1190 close_prefs_cb CB_ARGS(WIDGET button, POINTER client_data, POINTER call_data)
1191 {
1192   prefs_cancel_cb CB_ARGS(button, client_data, call_data);
1193 }
1194 #endif /* HAVE_GTK */
1195
1196
1197 static void
1198 make_preferences_dialog (prefs_pair *pair, Widget parent)
1199 {
1200   saver_preferences *p =  pair->a;
1201   saver_preferences *p2 = pair->b;
1202
1203   Screen *screen = widget_screen (parent);
1204   Display *dpy = widget_display (parent);
1205
1206   *p2 = *p;     /* copy all slots of p into p2. */
1207
1208   create_preferences_dialog (parent,
1209                              DefaultVisualOfScreen (screen),
1210                              DefaultColormapOfScreen (screen));
1211
1212 #ifdef HAVE_GTK
1213   gtk_window_set_title (GTK_WINDOW (preferences_dialog), progclass);
1214   gtk_signal_connect (GTK_OBJECT (preferences_dialog), "delete_event",
1215                       GTK_SIGNAL_FUNC (close_prefs_cb), NULL);
1216   gtk_signal_connect (GTK_OBJECT (preferences_dialog), "destroy",
1217                       GTK_SIGNAL_FUNC (close_prefs_cb), NULL);
1218 #endif /* HAVE_GTK */
1219
1220   add_button_callback (prefs_done,   prefs_ok_cb,     (POINTER) pair);
1221   add_button_callback (prefs_cancel, prefs_cancel_cb, 0);
1222
1223 #define CB(widget,type,slot) \
1224         add_text_callback ((widget), (type), (POINTER) (slot))
1225 #define CBT(widget,type,slot) \
1226         add_toggle_callback ((widget), (type), (POINTER) (slot))
1227
1228 #ifndef HAVE_ATHENA
1229   /* When using Athena widgets, we can't set callbacks for these,
1230      so in that case, we run them by hand when "OK" is pressed. */
1231   CB (timeout_text,             prefs_min_cb,  &p2->timeout);
1232   CB (cycle_text,               prefs_min_cb,  &p2->cycle);
1233   CB (fade_text,                prefs_sec_cb,  &p2->fade_seconds);
1234   CB (fade_ticks_text,          prefs_int_cb,  &p2->fade_ticks);
1235   CB (lock_timeout_text,        prefs_min_cb,  &p2->lock_timeout);
1236   CB (passwd_timeout_text,      prefs_sec_cb,  &p2->passwd_timeout);
1237
1238 #endif /* !HAVE_ATHENA */
1239
1240   CBT (verbose_toggle,          prefs_bool_cb, &p2->verbose_p);
1241   CBT (install_cmap_toggle,     prefs_bool_cb, &p2->install_cmap_p);
1242   CBT (fade_toggle,             prefs_bool_cb, &p2->fade_p);
1243   CBT (unfade_toggle,           prefs_bool_cb, &p2->unfade_p);
1244   CBT (lock_toggle,             prefs_bool_cb, &p2->lock_p);
1245 #undef CB
1246 #undef CBT
1247
1248   {
1249     Bool found_any_writable_cells = False;
1250     int nscreens = ScreenCount(dpy);
1251     int i;
1252     for (i = 0; i < nscreens; i++)
1253       {
1254         Screen *s = ScreenOfDisplay (dpy, i);
1255         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
1256           {
1257             found_any_writable_cells = True;
1258             break;
1259           }
1260       }
1261
1262     if (! found_any_writable_cells)     /* fading isn't possible */
1263       {
1264         disable_widget (fade_text);
1265         disable_widget (fade_ticks_text);
1266         disable_widget (install_cmap_toggle);
1267         disable_widget (fade_toggle);
1268         disable_widget (unfade_toggle);
1269       }
1270   }
1271 }
1272
1273
1274 /* Formats a `Time' into "H:MM:SS".  (Time is microseconds.)
1275  */
1276 static void
1277 format_time (char *buf, Time time)
1278 {
1279   int s = time / 1000;
1280   unsigned int h = 0, m = 0;
1281   if (s >= 60)
1282     {
1283       m += (s / 60);
1284       s %= 60;
1285     }
1286   if (m >= 60)
1287     {
1288       h += (m / 60);
1289       m %= 60;
1290     }
1291   sprintf (buf, "%u:%02u:%02u", h, m, s);
1292 }
1293
1294
1295 static void
1296 pop_preferences_dialog (prefs_pair *pair)
1297 {
1298   /* saver_preferences *p =  pair->a; */
1299   saver_preferences *p2 = pair->b;
1300   char s[100];
1301
1302   format_time (s, p2->timeout);        set_text_string(timeout_text, s);
1303   format_time (s, p2->cycle);          set_text_string(cycle_text, s);
1304   format_time (s, p2->lock_timeout);   set_text_string(lock_timeout_text, s);
1305   format_time (s, p2->passwd_timeout); set_text_string(passwd_timeout_text, s);
1306   format_time (s, p2->fade_seconds);   set_text_string(fade_text, s);
1307   sprintf (s, "%u", p2->fade_ticks);   set_text_string(fade_ticks_text, s);
1308
1309   set_toggle_button_state (verbose_toggle,      p2->verbose_p);
1310   set_toggle_button_state (install_cmap_toggle, p2->install_cmap_p);
1311   set_toggle_button_state (fade_toggle,         p2->fade_p);
1312   set_toggle_button_state (unfade_toggle,       p2->unfade_p);
1313   set_toggle_button_state (lock_toggle,         p2->lock_p);
1314
1315   pop_up_dialog_box (preferences_dialog, preferences_form);
1316 }
1317
1318
1319 static void
1320 run_hack (Display *dpy, int n)
1321 {
1322   if (n <= 0) abort();
1323   xscreensaver_command (dpy, XA_DEMO, n, False);
1324 }
1325
1326
1327 static void
1328 warning_dialog_dismiss_cb CB_ARGS(WIDGET button, POINTER client_data,
1329                                   POINTER ignored)
1330 {
1331   WIDGET shell = (WIDGET) client_data;
1332 #ifdef HAVE_GTK
1333   gdk_window_hide (GTK_WIDGET (shell)->window);
1334 #else  /* !HAVE_GTK */
1335   XtDestroyWidget (shell);
1336 #endif /* !HAVE_GTK */
1337 }
1338
1339
1340 static void
1341 warning_dialog (WIDGET parent, const char *message)
1342 {
1343   char *msg = strdup (message);
1344   char *head;
1345
1346   WIDGET dialog = 0;
1347   WIDGET label = 0;
1348   WIDGET ok = 0;
1349   int i = 0;
1350
1351 #ifdef HAVE_MOTIF
1352
1353   Widget w;
1354   Widget container;
1355   XmString xmstr;
1356   Arg av[10];
1357   int ac = 0;
1358
1359   ac = 0;
1360   dialog = XmCreateWarningDialog (parent, "versionWarning", av, ac);
1361
1362   w = XmMessageBoxGetChild (dialog, XmDIALOG_MESSAGE_LABEL);
1363   if (w) XtUnmanageChild (w);
1364   w = XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON);
1365   if (w) XtUnmanageChild (w);
1366   w = XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON);
1367   if (w) XtUnmanageChild (w);
1368
1369   ok = XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON);
1370
1371   ac = 0;
1372   XtSetArg (av[ac], XmNnumColumns, 1); ac++;
1373   XtSetArg (av[ac], XmNorientation, XmVERTICAL); ac++;
1374   XtSetArg (av[ac], XmNpacking, XmPACK_COLUMN); ac++;
1375   XtSetArg (av[ac], XmNrowColumnType, XmWORK_AREA); ac++;
1376   XtSetArg (av[ac], XmNspacing, 0); ac++;
1377   container = XmCreateRowColumn (dialog, "container", av, ac);
1378
1379 #elif defined(HAVE_ATHENA)
1380
1381   Widget form;
1382   dialog = XtVaCreatePopupShell("warning_dialog", transientShellWidgetClass,
1383                                 parent, 0);
1384   form = XtVaCreateManagedWidget("warning_form", formWidgetClass, dialog, 0);
1385
1386 #elif defined(HAVE_GTK)
1387   dialog = gtk_dialog_new ();
1388 #endif /* HAVE_GTK */
1389
1390   head = msg;
1391   while (head)
1392     {
1393       char name[20];
1394       char *s = strchr (head, '\n');
1395       if (s) *s = 0;
1396
1397       sprintf (name, "label%d", i++);
1398
1399 #ifdef HAVE_MOTIF
1400       xmstr = XmStringCreate (head, XmSTRING_DEFAULT_CHARSET);
1401       ac = 0;
1402       XtSetArg (av[ac], XmNlabelString, xmstr); ac++;
1403       label = XmCreateLabelGadget (container, name, av, ac);
1404       XtManageChild (label);
1405       XmStringFree (xmstr);
1406 #elif defined(HAVE_ATHENA)
1407       
1408       label = XtVaCreateManagedWidget (name, labelWidgetClass,
1409                                        form,
1410                                        XtNleft, XtChainLeft,
1411                                        XtNright, XtChainRight,
1412                                        XtNlabel, head,
1413                                        (label ? XtNfromVert : XtNtop),
1414                                        (label ? label : XtChainTop),
1415                                        0);
1416
1417 #elif defined(HAVE_GTK)
1418       {
1419         char buf[255];
1420         label = gtk_label_new (head);
1421         sprintf (buf, "warning_dialog.%s.font", name);
1422         GTK_WIDGET (label)->style = gtk_style_copy (GTK_WIDGET (label)->style);
1423         GTK_WIDGET (label)->style->font =
1424           gdk_font_load (get_string_resource (buf, "Dialog.Label.Font"));
1425         /* gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); */
1426         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1427                             label, TRUE, TRUE, 0);
1428         gtk_widget_show (label);
1429       }
1430 #endif /* HAVE_GTK */
1431
1432       if (s)
1433         head = s+1;
1434       else
1435         head = 0;
1436     }
1437
1438 #ifdef HAVE_MOTIF
1439
1440   XtManageChild (container);
1441   XtRealizeWidget (dialog);
1442   XtManageChild (dialog);
1443
1444 #elif defined(HAVE_ATHENA)
1445
1446   ok = XtVaCreateManagedWidget ("ok", commandWidgetClass, form,
1447                                 XtNleft, XtChainLeft,
1448                                 XtNbottom, XtChainBottom,
1449                                 XtNfromVert, label,
1450                                 0);
1451
1452   XtRealizeWidget (dialog);
1453   XtPopup (dialog, XtGrabNone);
1454
1455 #elif defined(HAVE_GTK)
1456   label = gtk_label_new ("");
1457   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1458                       label, TRUE, TRUE, 0);
1459   gtk_widget_show (label);
1460
1461   label = gtk_hbutton_box_new ();
1462   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1463                       label, TRUE, TRUE, 0);
1464
1465   ok = gtk_button_new_with_label (
1466                           get_string_resource ("warning_dialog.ok.label",
1467                                                "warning_dialog.Button.Label"));
1468   gtk_box_pack_start (GTK_BOX (label), ok, TRUE, FALSE, 0);
1469   gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
1470   gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
1471   gtk_widget_show (ok);
1472   gtk_widget_show (label);
1473   gtk_widget_show (dialog);
1474 /*  gtk_window_set_default (GTK_WINDOW (dialog), ok);*/
1475
1476   gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
1477                                 GTK_WIDGET (preferences_dialog
1478                                             ? preferences_dialog
1479                                             : demo_dialog)->window);
1480
1481   gdk_window_show (GTK_WIDGET (dialog)->window);
1482   gdk_window_raise (GTK_WIDGET (dialog)->window);
1483 #endif /* HAVE_GTK */
1484
1485   add_button_callback (ok, warning_dialog_dismiss_cb, (POINTER) dialog);
1486
1487   free (msg);
1488 }
1489
1490
1491 \f
1492 /* The main demo-mode command loop.
1493  */
1494
1495 #if 0
1496 static Bool
1497 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1498         XrmRepresentation *type, XrmValue *value, XPointer closure)
1499 {
1500   int i;
1501   for (i = 0; quarks[i]; i++)
1502     {
1503       if (bindings[i] == XrmBindTightly)
1504         fprintf (stderr, (i == 0 ? "" : "."));
1505       else if (bindings[i] == XrmBindLoosely)
1506         fprintf (stderr, "*");
1507       else
1508         fprintf (stderr, " ??? ");
1509       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1510     }
1511
1512   fprintf (stderr, ": %s\n", (char *) value->addr);
1513
1514   return False;
1515 }
1516 #endif
1517
1518
1519 static void
1520 the_network_is_not_the_computer (WIDGET parent)
1521 {
1522   Display *dpy = widget_display (parent);
1523   char *rversion, *ruser, *rhost;
1524   char *luser, *lhost;
1525   char *msg = 0;
1526   struct passwd *p = getpwuid (getuid ());
1527   const char *d = DisplayString (dpy);
1528
1529 # if defined(HAVE_UNAME)
1530   struct utsname uts;
1531   if (uname (&uts) < 0)
1532     lhost = "<UNKNOWN>";
1533   else
1534     lhost = uts.nodename;
1535 # elif defined(VMS)
1536   strcpy (lhost, getenv("SYS$NODE"));
1537 # else  /* !HAVE_UNAME && !VMS */
1538   strcat (lhost, "<UNKNOWN>");
1539 # endif /* !HAVE_UNAME && !VMS */
1540
1541   if (p && p->pw_name)
1542     luser = p->pw_name;
1543   else
1544     luser = "???";
1545
1546   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1547
1548   /* Make a buffer that's big enough for a number of copies of all the
1549      strings, plus some. */
1550   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1551                                (ruser ? strlen(ruser) : 0) +
1552                                (rhost ? strlen(rhost) : 0) +
1553                                strlen(lhost) +
1554                                strlen(luser) +
1555                                strlen(d) +
1556                                30));
1557   *msg = 0;
1558
1559   if (!rversion || !*rversion)
1560     {
1561       sprintf (msg,
1562                "Warning:\n\n"
1563                "xscreensaver doesn't seem to be running on display \"%s\".",
1564                d);
1565     }
1566   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1567     {
1568       /* Warn that the two processes are running as different users.
1569        */
1570       sprintf(msg,
1571                "Warning:\n\n"
1572               "%s is running as user \"%s\" on host \"%s\".\n"
1573               "But the xscreensaver managing display \"%s\"\n"
1574               "is running as user \"%s\" on host \"%s\".\n"
1575               "\n"
1576               "Since they are different users, they won't be reading/writing\n"
1577               "the same ~/.xscreensaver file, so %s isn't\n"
1578               "going to work right.\n"
1579               "\n"
1580               "Either re-run %s as \"%s\", or re-run\n"
1581               "xscreensaver as \"%s\".\n",
1582               progname, luser, lhost,
1583               d,
1584               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1585               progname,
1586               progname, (ruser ? ruser : "???"),
1587               luser);
1588     }
1589   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1590     {
1591       /* Warn that the two processes are running on different hosts.
1592        */
1593       sprintf (msg,
1594                "Warning:\n\n"
1595                "%s is running as user \"%s\" on host \"%s\".\n"
1596                "But the xscreensaver managing display \"%s\"\n"
1597                "is running as user \"%s\" on host \"%s\".\n"
1598                "\n"
1599                "If those two machines don't share a file system (that is,\n"
1600                "if they don't see the same ~%s/.xscreensaver file) then\n"
1601                "%s won't work right.",
1602                progname, luser, lhost,
1603                d,
1604                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1605                luser,
1606                progname);
1607     }
1608   else if (!!strcmp (rversion, short_version))
1609     {
1610       /* Warn that the version numbers don't match.
1611        */
1612       sprintf (msg,
1613                "Warning:\n\n"
1614                "This is %s version %s.\n"
1615                "But the xscreensaver managing display \"%s\"\n"
1616                "is version %s.  This could cause problems.",
1617                progname, short_version,
1618                d,
1619                rversion);
1620     }
1621
1622
1623   if (*msg)
1624     warning_dialog (parent, msg);
1625
1626   free (msg);
1627 }
1628
1629
1630 /* We use this error handler so that X errors are preceeded by the name
1631    of the program that generated them.
1632  */
1633 static int
1634 demo_ehandler (Display *dpy, XErrorEvent *error)
1635 {
1636   fprintf (stderr, "\nX error in %s:\n", progname);
1637   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
1638     exit (-1);
1639   else
1640     fprintf (stderr, " (nonfatal.)\n");
1641   return 0;
1642 }
1643
1644
1645 #ifdef HAVE_GTK
1646
1647 /* We use this error handler so that Gtk/Gdk errors are preceeded by the name
1648    of the program that generated them; and also that we can ignore one
1649    particular bogus error message that Gdk madly spews.
1650  */
1651 static void
1652 g_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
1653                const gchar *message, gpointer user_data)
1654 {
1655   /* Ignore the message "Got event for unknown window: 0x...".
1656      Apparently some events are coming in for the xscreensaver window
1657      (presumably reply events related to the ClientMessage) and Gdk
1658      feels the need to complain about them.  So, just suppress any
1659      messages that look like that one.
1660    */
1661   if (strstr (message, "unknown window"))
1662     return;
1663
1664   fprintf (stderr, "%s: %s-%s: %s%s", blurb(), log_domain,
1665            (log_level == G_LOG_LEVEL_ERROR    ? "error" :
1666             log_level == G_LOG_LEVEL_CRITICAL ? "critical" :
1667             log_level == G_LOG_LEVEL_WARNING  ? "warning" :
1668             log_level == G_LOG_LEVEL_MESSAGE  ? "message" :
1669             log_level == G_LOG_LEVEL_INFO     ? "info" :
1670             log_level == G_LOG_LEVEL_DEBUG    ? "debug" : "???"),
1671            message,
1672            ((!*message || message[strlen(message)-1] != '\n')
1673             ? "\n" : ""));
1674 }
1675 #endif /* HAVE_GTK */
1676
1677
1678
1679 static char *defaults[] = {
1680 #include "XScreenSaver_ad.h"
1681  0
1682 };
1683
1684 int
1685 main (int argc, char **argv)
1686 {
1687   XtAppContext app;
1688   prefs_pair Pair, *pair;
1689   saver_preferences P, P2, *p, *p2;
1690   Bool prefs = False;
1691   int i;
1692   Display *dpy;
1693   Widget toplevel_shell;
1694   char *real_progname = argv[0];
1695   char *s;
1696
1697   s = strrchr (real_progname, '/');
1698   if (s) real_progname = s+1;
1699
1700   p = &P;
1701   p2 = &P2;
1702   pair = &Pair;
1703   pair->a = p;
1704   pair->b = p2;
1705   memset (p,  0, sizeof (*p));
1706   memset (p2, 0, sizeof (*p2));
1707
1708   progname = real_progname;
1709
1710 #ifdef HAVE_GTK
1711   /* Register our error message logger for every ``log domain'' known.
1712      There's no way to do this globally, so I grepped the Gtk/Gdk sources
1713      for all of the domains that seem to be in use.
1714   */
1715   {
1716     const char * const domains[] = { "Gtk", "Gdk", "GLib", "GModule",
1717                                      "GThread", "Gnome", "GnomeUI", 0 };
1718     for (i = 0; domains[i]; i++)
1719       g_log_set_handler (domains[i], G_LOG_LEVEL_MASK, g_log_handler, 0);
1720   }
1721
1722   /* This is gross, but Gtk understands --display and not -display... */
1723   for (i = 1; i < argc; i++)
1724     if (argv[i][0] && argv[i][1] && 
1725         !strncmp(argv[i], "-display", strlen(argv[i])))
1726       argv[i] = "--display";
1727
1728   /* Let Gtk open the X connection, then initialize Xt to use that
1729      same connection.  Doctor Frankenstein would be proud. */   
1730   gtk_init (&argc, &argv);
1731 #endif /* HAVE_GTK */
1732
1733
1734   /* We must read exactly the same resources as xscreensaver.
1735      That means we must have both the same progclass *and* progname,
1736      at least as far as the resource database is concerned.  So,
1737      put "xscreensaver" in argv[0] while initializing Xt.
1738    */
1739   argv[0] = "xscreensaver";
1740   progname = argv[0];
1741
1742
1743 #ifdef HAVE_GTK
1744   /* If we're using Gtk, the X connection is already open.
1745      Now teach Xt about it.
1746    */
1747   XtToolkitInitialize ();
1748   app = XtCreateApplicationContext ();
1749   dpy = gdk_display;
1750   XtAppSetFallbackResources (app, defaults);
1751   XtDisplayInitialize (app, dpy, progname, progclass, 0, 0, &argc, argv);
1752   toplevel_shell = XtAppCreateShell (progname, progclass,
1753                                      applicationShellWidgetClass,
1754                                      dpy, 0, 0);
1755
1756 #else  /* !HAVE_GTK */
1757   /* No Gtk -- open the X connection here. */
1758   toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
1759                                     defaults, 0, 0);
1760 #endif /* !HAVE_GTK */
1761
1762   dpy = XtDisplay (toplevel_shell);
1763   db = XtDatabase (dpy);
1764   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
1765   XSetErrorHandler (demo_ehandler);
1766
1767   /* Complain about unrecognized command-line arguments.
1768    */
1769   for (i = 1; i < argc; i++)
1770     {
1771       char *s = argv[i];
1772       if (s[0] == '-' && s[1] == '-')
1773         s++;
1774       if (!strcmp (s, "-prefs"))
1775         prefs = True;
1776       else
1777         {
1778           fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n",
1779                    real_progname);
1780           exit (1);
1781         }
1782     }
1783
1784   short_version = (char *) malloc (5);
1785   memcpy (short_version, screensaver_id + 17, 4);
1786   short_version [4] = 0;
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 #ifdef HAVE_ATHENA
1805   global_prefs_kludge = p;      /* I hate C so much... */
1806 #endif /* HAVE_ATHENA */
1807
1808 #if 0
1809   {
1810     XrmName name = { 0 };
1811     XrmClass class = { 0 };
1812     int count = 0;
1813     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
1814                           (POINTER) &count);
1815   }
1816 #endif
1817
1818
1819   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
1820   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
1821   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
1822   XA_SCREENSAVER_TIME = XInternAtom (dpy, "_SCREENSAVER_TIME", False);
1823   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
1824   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
1825   XA_SELECT = XInternAtom (dpy, "SELECT", False);
1826   XA_DEMO = XInternAtom (dpy, "DEMO", False);
1827   XA_RESTART = XInternAtom (dpy, "RESTART", False);
1828
1829   make_demo_dialog (toplevel_shell, pair);
1830
1831   if (prefs)
1832     {
1833       make_preferences_dialog (pair, toplevel_shell);
1834       pop_preferences_dialog (pair);
1835     }
1836
1837   the_network_is_not_the_computer (preferences_dialog
1838                                    ? preferences_dialog
1839                                    : demo_dialog);
1840
1841 #ifdef HAVE_GTK
1842
1843   /* Run the Gtk event loop, and not the Xt event loop.  This means that
1844      if there were Xt timers or fds registered, they would never get serviced,
1845      and if there were any Xt widgets, they would never have events delivered.
1846      Fortunately, we're using Gtk for all of the UI, and only initialized
1847      Xt so that we could process the command line and use the X resource
1848      manager.
1849    */
1850   gtk_main ();
1851
1852 #else  /* !HAVE_GTK */
1853
1854   XtAppMainLoop (app);
1855
1856 #endif /* !HAVE_GTK */
1857
1858   exit (0);
1859 }