76e8917d6843797325700804f0cfa9d6884f7fe9
[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 #ifdef HAVE_ATHENA_KLUDGE       /* don't ask */
18 # undef HAVE_MOTIF
19 # define HAVE_ATHENA 1
20 #endif
21
22 #include <stdlib.h>
23
24 #ifdef HAVE_UNISTD_H
25 # include <unistd.h>
26 #endif
27
28 #ifndef VMS
29 # include <pwd.h>               /* for getpwuid() */
30 #else /* VMS */
31 # include "vms-pwd.h"
32 #endif /* VMS */
33
34 #ifdef HAVE_UNAME
35 # include <sys/utsname.h>       /* for uname() */
36 #endif /* HAVE_UNAME */
37
38 #include <stdio.h>
39
40 #include <X11/Intrinsic.h>
41 #include <X11/StringDefs.h>
42
43 /* We don't actually use any widget internals, but these are included
44    so that gdb will have debug info for the widgets... */
45 #include <X11/IntrinsicP.h>
46 #include <X11/ShellP.h>
47
48 #ifdef HAVE_MOTIF
49 # include <Xm/Xm.h>
50 # include <Xm/Text.h>
51 # include <Xm/List.h>
52 # include <Xm/ToggleB.h>
53 # include <Xm/MessageB.h>
54 # include <Xm/LabelG.h>
55 # include <Xm/RowColumn.h>
56
57 #else  /* HAVE_ATHENA */
58   /* Athena demo code contributed by Jon A. Christopher <jac8782@tamu.edu> */
59   /* Copyright 1997, with the same permissions as above. */
60 # include <X11/Shell.h>
61 # include <X11/Xaw/Form.h>
62 # include <X11/Xaw/Box.h>
63 # include <X11/Xaw/List.h>
64 # include <X11/Xaw/Command.h>
65 # include <X11/Xaw/Toggle.h>
66 # include <X11/Xaw/Viewport.h>
67 # include <X11/Xaw/Dialog.h>
68 # include <X11/Xaw/Scrollbar.h>
69 # include <X11/Xaw/Text.h>
70 #endif /* HAVE_ATHENA */
71
72 #include "version.h"
73 #include "prefs.h"
74 #include "resources.h"          /* for parse_time() */
75 #include "visual.h"             /* for has_writable_cells() */
76 #include "remote.h"             /* for xscreensaver_command() */
77 #include "usleep.h"
78
79 #include <stdio.h>
80 #include <string.h>
81 #include <ctype.h>
82
83
84 char *progname = 0;
85 char *progclass = "XScreenSaver";
86 XrmDatabase db;
87
88 typedef struct {
89   saver_preferences *a, *b;
90 } prefs_pair;
91
92
93 char *blurb (void) { return progname; }
94
95 static void run_hack (Display *dpy, int n);
96
97 #ifdef HAVE_ATHENA
98 static saver_preferences *global_prefs_kludge = 0;    /* I hate C so much... */
99 #endif /* HAVE_ATHENA */
100
101 static char *short_version = 0;
102
103 Atom XA_VROOT;
104 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
105 Atom XA_SCREENSAVER_TIME, XA_SCREENSAVER_ID, XA_SELECT, XA_DEMO, XA_RESTART;
106
107 extern void create_demo_dialog (Widget, Visual *, Colormap);
108 extern void create_preferences_dialog (Widget, Visual *, Colormap);
109
110 extern Widget demo_dialog;
111 extern Widget label1;
112 extern Widget text_line;
113 extern Widget demo_form;
114 extern Widget demo_list;
115 extern Widget next, prev, done, restart, edit;
116
117 extern Widget preferences_dialog;
118 extern Widget preferences_form;
119 extern Widget prefs_done, prefs_cancel;
120 extern Widget timeout_text, cycle_text, fade_text, fade_ticks_text;
121 extern Widget lock_timeout_text, passwd_timeout_text;
122 extern Widget verbose_toggle, install_cmap_toggle, fade_toggle, unfade_toggle,
123   lock_toggle;
124
125
126 #ifdef HAVE_MOTIF
127
128 # define set_toggle_button_state(toggle,state) \
129   XmToggleButtonSetState ((toggle), (state), True)
130 # define set_text_string(text_widget,string) \
131   XmTextSetString ((text_widget), (string))
132 # define add_button_callback(button,cb,arg) \
133   XtAddCallback ((button), XmNactivateCallback, (cb), (arg))
134 # define add_toggle_callback(button,cb,arg) \
135   XtAddCallback ((button), XmNvalueChangedCallback, (cb), (arg))
136 # define add_text_callback add_toggle_callback
137
138 #else  /* HAVE_ATHENA */
139
140 # define set_toggle_button_state(toggle,state) \
141   XtVaSetValues((toggle), XtNstate, (state),  0)
142 # define set_text_string(text_widget,string) \
143   XtVaSetValues ((text_widget), XtNvalue, (string), 0)
144 # define add_button_callback(button,cb,arg) \
145   XtAddCallback ((button), XtNcallback, (cb), (arg))
146 # define add_toggle_callback add_button_callback
147 # define add_text_callback(b,c,a) ERROR!
148
149 #endif /* HAVE_ATHENA */
150
151
152 #define disable_widget(widget) \
153   XtVaSetValues((widget), XtNsensitive, False, 0)
154
155
156 static char *
157 get_text_string (Widget text_widget)
158 {
159 #ifdef HAVE_MOTIF
160   return XmTextGetString (text_widget);
161 #else  /* HAVE_ATHENA */
162   char *string = 0;
163   if (XtIsSubclass(text_widget, textWidgetClass))
164     XtVaGetValues (text_widget, XtNstring, &string, 0);
165   else if (XtIsSubclass(text_widget, dialogWidgetClass))
166     XtVaGetValues (text_widget, XtNvalue, &string, 0);
167   else
168     string = 0;
169
170   return string;
171 #endif /* HAVE_ATHENA */
172 }
173
174 static char *
175 get_label_string (Widget label_widget)
176 {
177 #ifdef HAVE_MOTIF
178   char *label = 0;
179   XmString xm_label = 0;
180   XtVaGetValues (label_widget, XmNlabelString, &xm_label, 0);
181   if (!xm_label)
182     return 0;
183   XmStringGetLtoR (xm_label, XmSTRING_DEFAULT_CHARSET, &label);
184   return label;
185 #else  /* HAVE_ATHENA */
186   char *label = 0;
187   XtVaGetValues (label_widget, XtNlabel, &label, 0);
188   return (label ? strdup(label) : 0);
189 #endif /* HAVE_ATHENA */
190 }
191
192
193 static void
194 set_label_string (Widget label_widget, char *string)
195 {
196 #ifdef HAVE_MOTIF
197   XmString xm_string = XmStringCreate (string, XmSTRING_DEFAULT_CHARSET);
198   XtVaSetValues (label_widget, XmNlabelString, xm_string, 0);
199   XmStringFree (xm_string);
200 #else  /* HAVE_ATHENA */
201   XtVaSetValues (label_widget, XtNlabel, string, 0);
202 #endif /* HAVE_ATHENA */
203 }
204
205
206 static void
207 format_into_label (Widget label, const char *arg)
208 {
209   char *text = get_label_string (label);
210   char *buf = (char *) malloc ((text ? strlen(text) : 0) + strlen(arg) + 100);
211
212   if (!text || !strcmp (text, XtName (label)))
213       strcpy (buf, "ERROR: RESOURCES ARE NOT INSTALLED CORRECTLY");
214     else
215       sprintf (buf, text, arg);
216
217     set_label_string (label, buf);
218     free (buf);
219     XtFree (text);
220 }
221
222
223 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
224  */
225 static void
226 ensure_selected_item_visible (Widget list)
227 {
228 #ifdef HAVE_MOTIF
229   int *pos_list = 0;
230   int pos_count = 0;
231   if (XmListGetSelectedPos (list, &pos_list, &pos_count) && pos_count > 0)
232     {
233       int top = -2;
234       int visible = 0;
235       XtVaGetValues (list,
236                      XmNtopItemPosition, &top,
237                      XmNvisibleItemCount, &visible,
238                      0);
239       if (pos_list[0] >= top + visible)
240         {
241           int pos = pos_list[0] - visible + 1;
242           if (pos < 0) pos = 0;
243           XmListSetPos (list, pos);
244         }
245       else if (pos_list[0] < top)
246         {
247           XmListSetPos (list, pos_list[0]);
248         }
249     }
250   if (pos_list)
251     XtFree ((char *) pos_list);
252
253 #else  /* HAVE_ATHENA */
254 # ifdef HAVE_XawViewportSetCoordinates
255
256   int margin = 16;      /* should be line height or something. */
257   int count = 0;
258   int pos;
259   Dimension list_h = 0, vp_h = 0;
260   Dimension top_margin = 4;  /* I don't know where this value comes from */
261   Position vp_x = 0, vp_y = 0, current_y;
262   double cratio;
263   Widget viewport = XtParent(demo_list);
264   Widget sb = (viewport ? XtNameToWidget(viewport, "*vertical") : 0);
265   float sb_top = 0, sb_size = 0;
266   XawListReturnStruct *current = XawListShowCurrent(demo_list);
267   if (!current || !sb) return;
268
269   XtVaGetValues(demo_list,
270                 XtNnumberStrings, &count,
271                 XtNheight, &list_h,
272                 0);
273   if (count < 2 || list_h < 10) return;
274
275   XtVaGetValues(viewport, XtNheight, &vp_h, XtNx, &vp_x, XtNy, &vp_y, 0);
276   if (vp_h < 10) return;
277
278   XtVaGetValues(sb, XtNtopOfThumb, &sb_top, XtNshown, &sb_size, 0);
279   if (sb_size <= 0) return;
280
281   pos = current->list_index;
282   cratio = ((double) pos)  / ((double) count);
283   current_y = (cratio * list_h);
284
285   if (cratio < sb_top ||
286       cratio > sb_top + sb_size)
287     {
288       if (cratio < sb_top)
289         current_y -= (vp_h - margin - margin);
290       else
291         current_y -= margin;
292
293       if ((long)current_y >= (long) list_h)
294         current_y = (Position) ((long)list_h - (long)vp_h);
295
296       if ((long)current_y < (long)top_margin)
297         current_y = (Position)top_margin;
298
299       XawViewportSetCoordinates (viewport, vp_x, current_y);
300     }
301 # endif /* HAVE_XawViewportSetCoordinates */
302 #endif /* HAVE_ATHENA */
303 }
304
305
306 /* Callback for the text area:
307    - note the text the user has entered;
308    - change the corresponding element in `screenhacks';
309    - write the .xscreensaver file;
310    - tell the xscreensaver daemon to run that hack.
311  */
312 static void
313 text_cb (Widget text_widget, XtPointer client_data, XtPointer call_data)
314 {
315   Display *dpy = XtDisplay (text_widget);
316   saver_preferences *p = (saver_preferences *) client_data;
317   char *new_text = get_text_string (text_widget);
318
319   int hack_number = -1;         /* 0-based */
320
321 #ifdef HAVE_ATHENA
322   XawListReturnStruct *current = XawListShowCurrent(demo_list);
323   hack_number = current->list_index;
324 #else  /* HAVE_MOTIF */
325   int *pos_list = 0;
326   int pos_count = 0;
327   if (XmListGetSelectedPos (demo_list, &pos_list, &pos_count))
328     hack_number = pos_list[0] - 1;
329   if (pos_list)
330     XtFree ((char *) pos_list);
331 #endif /* HAVE_MOTIF */
332
333   ensure_selected_item_visible (demo_list);
334
335   if (hack_number < 0 || hack_number >= p->screenhacks_count)
336     {
337       set_text_string (text_widget, "");
338       XBell (dpy, 0);
339     }
340   else
341     {
342 fprintf(stderr, "%d:\nold: %s\nnew: %s\n",
343         hack_number, p->screenhacks [hack_number], new_text);
344
345       if (p->screenhacks [hack_number])
346         free (p->screenhacks [hack_number]);
347       p->screenhacks [hack_number] = strdup (new_text);
348
349 #ifdef HAVE_MOTIF
350
351       XmListDeselectAllItems (demo_list);
352       {
353         XmString xmstr = XmStringCreate (new_text, XmSTRING_DEFAULT_CHARSET);
354         XmListReplaceItemsPos (demo_list, &xmstr, 1, hack_number+1);
355         XmStringFree (xmstr);
356       }
357       XmListSelectPos (demo_list, hack_number+1, True);
358
359 #else  /* HAVE_ATHENA */
360
361       {
362         Widget vp = XtParent(demo_list);
363         Widget sb = (vp ? XtNameToWidget(vp, "*vertical") : 0);
364         Dimension list_h = 0;
365         Position vp_x = 0, vp_y = 0;
366         float sb_top = 0;
367
368         XawListUnhighlight (demo_list);
369
370         XtVaGetValues (vp, XtNx, &vp_x, 0);
371         XtVaGetValues (sb, XtNtopOfThumb, &sb_top, 0);
372         XtVaGetValues (demo_list, XtNheight, &list_h, 0);
373         vp_y = (sb_top * list_h);
374         XtVaSetValues (demo_list,
375                        XtNlist, p->screenhacks,
376                        XtNnumberStrings, p->screenhacks_count,
377                        0);
378         XawViewportSetCoordinates (vp, vp_x, vp_y);
379         XawListHighlight (demo_list, hack_number);
380       }
381
382 #endif /* HAVE_ATHENA */
383
384       write_init_file (p, short_version);
385       XSync (dpy, False);
386       usleep (500000);          /* give the disk time to settle down */
387
388       run_hack (dpy, hack_number+1);
389     }
390 }
391
392
393 #ifdef HAVE_ATHENA
394 /* Bend over backwards to make hitting Return in the text field do the
395    right thing. 
396    */
397 static void text_enter (Widget w, XEvent *event, String *av, Cardinal *ac)
398 {
399   text_cb (w, global_prefs_kludge, 0);    /* I hate C so much... */
400 }
401
402 static XtActionsRec actions[] = {{"done",      text_enter}
403                                 };
404 static char translations[] = ("<Key>Return:     done()\n"
405                               "<Key>Linefeed:   done()\n"
406                               "Ctrl<Key>M:      done()\n"
407                               "Ctrl<Key>J:      done()\n");
408 #endif /* HAVE_ATHENA */
409
410
411 /* Callback for the Run Next button.
412  */
413 static void
414 next_cb (Widget button, XtPointer client_data, XtPointer call_data)
415 {
416 #ifdef HAVE_ATHENA
417   XawListReturnStruct *current = XawListShowCurrent(demo_list);
418   int cnt;
419   XtVaGetValues (demo_list, XtNnumberStrings, &cnt, 0);
420   if (current->list_index == XAW_LIST_NONE ||
421       current->list_index + 1 >= cnt)
422     current->list_index = 0;
423   else
424     current->list_index++;
425   XawListHighlight(demo_list, current->list_index);
426
427   ensure_selected_item_visible (demo_list);
428   current = XawListShowCurrent(demo_list);
429   XtVaSetValues(text_line, XtNstring, current->string, 0);
430
431   run_hack (XtDisplay (button), current->list_index + 1);
432
433 #else  /* HAVE_MOTIF */
434
435   saver_preferences *p = (saver_preferences *) client_data;
436   int *pos_list = 0;
437   int pos_count = 0;
438   int pos;
439   if (! XmListGetSelectedPos (demo_list, &pos_list, &pos_count))
440     {
441       pos = 1;
442       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
443       XmListSelectPos (demo_list, pos, True);
444     }
445   else
446     {
447       pos = pos_list[0] + 1;
448       if (pos > p->screenhacks_count)
449         pos = 1;
450       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
451       XmListSelectPos (demo_list, pos, True);
452     }
453      
454   ensure_selected_item_visible (demo_list);
455   run_hack (XtDisplay (button), pos);
456   if (pos_list)
457     XtFree ((char *) pos_list);
458
459 #endif /* HAVE_MOTIF */
460 }
461
462
463 /* Callback for the Run Previous button.
464  */
465 static void
466 prev_cb (Widget button, XtPointer client_data, XtPointer call_data)
467 {
468 #ifdef HAVE_ATHENA
469   XawListReturnStruct *current = XawListShowCurrent(demo_list);
470   int cnt;
471   XtVaGetValues (demo_list, XtNnumberStrings, &cnt, 0);
472   if (current->list_index == XAW_LIST_NONE ||
473       current->list_index <= 0)
474     current->list_index = cnt-1;
475   else
476     current->list_index--;
477   XawListHighlight(demo_list, current->list_index);
478
479   ensure_selected_item_visible (demo_list);
480   current = XawListShowCurrent(demo_list);
481   XtVaSetValues(text_line, XtNstring, current->string, 0);
482
483   run_hack (XtDisplay (button), current->list_index + 1);
484
485 #else  /* HAVE_MOTIF */
486
487   saver_preferences *p = (saver_preferences *) client_data;
488   int *pos_list = 0;
489   int pos_count = 0;
490   int pos;
491   if (! XmListGetSelectedPos (demo_list, &pos_list, &pos_count))
492     {
493       pos = p->screenhacks_count;
494       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
495       XmListSelectPos (demo_list, pos, True);
496     }
497   else
498     {
499       pos = pos_list[0] - 1;
500       if (pos == 0)
501         pos = p->screenhacks_count;
502       XmListDeselectAllItems (demo_list);       /* LessTif lossage */
503       XmListSelectPos (demo_list, pos, True);
504     }
505      
506   ensure_selected_item_visible (demo_list);
507   run_hack (XtDisplay (button), pos);
508   if (pos_list)
509     XtFree ((char *) pos_list);
510
511 #endif /* HAVE_MOTIF */
512 }
513
514
515 /* Callback run when a list element is double-clicked.
516  */
517 static void
518 select_cb (Widget button, XtPointer client_data, XtPointer call_data)
519 {
520 /*  saver_preferences *p = (saver_preferences *) client_data; */
521
522 #ifdef HAVE_ATHENA
523   XawListReturnStruct *item = (XawListReturnStruct*)call_data;
524   XtVaSetValues(text_line, XtNstring, item->string, 0);
525   run_hack (XtDisplay (button), item->list_index + 1);
526
527 #else  /* HAVE_MOTIF */
528   XmListCallbackStruct *lcb = (XmListCallbackStruct *) call_data;
529   char *string = 0;
530   if (lcb->item)
531     XmStringGetLtoR (lcb->item, XmSTRING_DEFAULT_CHARSET, &string);
532   set_text_string (text_line, (string ? string : ""));
533
534   if (lcb->reason == XmCR_DEFAULT_ACTION && string)
535     run_hack (XtDisplay (button), lcb->item_position);
536
537   if (string)
538     XtFree (string);
539 #endif /* HAVE_MOTIF */
540 }
541
542
543
544 static void pop_preferences_dialog (prefs_pair *pair);
545 static void make_preferences_dialog (prefs_pair *pair, Widget parent);
546
547 /* Callback for the Preferences button.
548  */
549 static void
550 preferences_cb (Widget button, XtPointer client_data, XtPointer call_data)
551 {
552   prefs_pair *pair = (prefs_pair *) client_data;
553   Widget parent = button;
554
555   do {
556     parent = XtParent(parent);
557   } while (XtParent(parent));
558
559   if (! preferences_dialog)
560     make_preferences_dialog (pair, parent);
561   pop_preferences_dialog (pair);
562 }
563
564 /* Callback for the Quit button.
565  */
566 static void
567 quit_cb (Widget button, XtPointer client_data, XtPointer call_data)
568 {
569   /* Save here?  Right now we don't need to, because we save every time
570      the text field is edited, or the Preferences OK button is pressed.
571   */
572   exit (0);
573 }
574
575
576 /* Callback for the (now unused) Restart button.
577  */
578 static void
579 restart_cb (Widget button, XtPointer client_data, XtPointer call_data)
580 {
581   xscreensaver_command (XtDisplay (button), XA_RESTART, 0, False);
582 }
583
584
585 static void
586 pop_up_dialog_box (Widget dialog, Widget form)
587 {
588 #ifdef HAVE_ATHENA
589   XtRealizeWidget (dialog);
590   XtPopup (dialog, XtGrabNone);
591 #else  /* HAVE_MOTIF */
592   XtRealizeWidget (form);
593   XtManageChild (form);
594 #endif /* HAVE_MOTIF */
595   XMapRaised (XtDisplay (dialog), XtWindow (dialog));
596 }
597
598
599 static void
600 make_demo_dialog (Widget toplevel_shell, prefs_pair *pair)
601 {
602   saver_preferences *p =  pair->a;
603   /* saver_preferences *p2 = pair->b; */
604
605   Widget parent = toplevel_shell;
606   char **hacks = p->screenhacks;
607
608   create_demo_dialog (parent,
609                       DefaultVisualOfScreen (XtScreen (parent)),
610                       DefaultColormapOfScreen (XtScreen (parent)));
611   format_into_label (label1, short_version);
612
613   add_button_callback (next,    next_cb,        (XtPointer) p);
614   add_button_callback (prev,    prev_cb,        (XtPointer) p);
615   add_button_callback (done,    quit_cb,        (XtPointer) p);
616   if (restart)
617     add_button_callback(restart,restart_cb,     (XtPointer) p);
618   add_button_callback (edit,    preferences_cb, (XtPointer) pair);
619
620 #ifdef HAVE_MOTIF
621   XtAddCallback (demo_list, XmNbrowseSelectionCallback,
622                  select_cb, (XtPointer) p);
623   XtAddCallback (demo_list, XmNdefaultActionCallback,
624                  select_cb, (XtPointer) p);
625   XtAddCallback (text_line, XmNactivateCallback, text_cb, (XtPointer) p);
626
627   if (hacks)
628     for (; *hacks; hacks++)
629       {
630         XmString xmstr = XmStringCreate (*hacks, XmSTRING_DEFAULT_CHARSET);
631         XmListAddItem (demo_list, xmstr, 0);
632         XmStringFree (xmstr);
633       }
634
635 #else  /* HAVE_ATHENA */
636
637   /* Hook up the text line. */
638
639   XtAppAddActions(XtWidgetToApplicationContext(text_line),
640                   actions, XtNumber(actions));
641   XtOverrideTranslations(text_line, XtParseTranslationTable(translations));
642
643
644   /* Must realize the widget before populating the list, or the dialog
645      will be as wide as the longest string.
646   */
647   XtRealizeWidget (demo_dialog);
648
649   XtVaSetValues (demo_list,
650                  XtNlist, hacks,
651                  XtNnumberStrings, p->screenhacks_count,
652                  0);
653   XtAddCallback (demo_list, XtNcallback, select_cb, p);
654
655   /* Now that we've populated the list, make sure that the list is as
656      wide as the dialog itself.
657   */
658   {
659     Widget viewport = XtParent(demo_list);
660     Widget subform = XtParent(viewport);
661     Widget box = XtNameToWidget(demo_dialog, "*box");
662     Widget label1 = XtNameToWidget(demo_dialog, "*label1");
663     Widget label2 = XtNameToWidget(demo_dialog, "*label2");
664     Dimension x=0, y=0, w=0, h=0, bw=0, w2=0;
665     XtVaGetValues(subform,
666                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
667     XtVaGetValues(box, XtNwidth, &w2, 0);
668     if (w2 != w)
669       XtResizeWidget(subform, w2, h, bw);
670
671     /* Why isn't the viewport getting centered? */
672     XtVaGetValues(viewport,
673                   XtNx, &x, XtNy, &y, XtNheight, &h, XtNborderWidth, &bw, 0);
674     XtConfigureWidget(viewport, x, y, w2-x-x, h, bw);
675
676     /* And the text line, too. */
677     XtVaGetValues(text_line,
678                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
679     XtVaGetValues(viewport, XtNwidth, &w2, 0);
680     if (w2 != w)
681       XtResizeWidget(text_line, w2, h, bw);
682
683     /* And the labels too. */
684     XtVaGetValues(label1,
685                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
686     if (w2 != w)
687       XtResizeWidget(label1, w2, h, bw);
688
689     XtVaGetValues(label2,
690                   XtNwidth, &w, XtNheight, &h, XtNborderWidth, &bw, 0);
691     if (w2 != w)
692       XtResizeWidget(label2, w2, h, bw);
693
694   }
695
696 #endif /* HAVE_ATHENA */
697
698   pop_up_dialog_box(demo_dialog, demo_form);
699
700 #ifdef HAVE_ATHENA
701   /* For Athena, have to do this after the dialog is managed. */
702   ensure_selected_item_visible (demo_list);
703 #endif /* HAVE_ATHENA */
704 }
705
706 \f
707 /* the Preferences dialog
708  */
709
710 /* Helper for the text fields that contain time specifications:
711    this parses the text, and does error checking.
712  */
713 static void 
714 hack_time_text (Display *dpy, char *line, Time *store, Bool sec_p)
715 {
716   if (*line)
717     {
718       int value;
719       value = parse_time (line, sec_p, True);
720       value *= 1000;    /* Time measures in microseconds */
721       if (value < 0)
722         /*XBell (dpy, 0)*/;
723       else
724         *store = value;
725     }
726 }
727
728
729 /* Callback for text fields that hold a time that default to seconds,
730    when not fully spelled out.  client_data is an Time* where the value goes.
731  */
732 static void
733 prefs_sec_cb (Widget button, XtPointer client_data, XtPointer call_data)
734 {
735   hack_time_text (XtDisplay (button), get_text_string (button),
736                 (Time *) client_data, True);
737 }
738
739
740 /* Callback for text fields that hold a time that default to minutes,
741    when not fully spelled out.  client_data is an Time* where the value goes.
742  */
743 static void
744 prefs_min_cb (Widget button, XtPointer client_data, XtPointer call_data)
745 {
746   hack_time_text (XtDisplay (button), get_text_string (button),
747                 (Time *) client_data, False);
748 }
749
750
751 /* Callback for text fields that hold an integer value.
752    client_data is an int* where the value goes.
753  */
754 static void
755 prefs_int_cb (Widget button, XtPointer client_data, XtPointer call_data)
756 {
757   char *line = get_text_string (button);
758   int *store = (int *) client_data;
759   unsigned int value;
760   char c;
761   if (! *line)
762     ;
763   else if (sscanf (line, "%u%c", &value, &c) != 1)
764     XBell (XtDisplay (button), 0);
765   else
766     *store = value;
767 }
768
769 /* Callback for toggle buttons.  client_data is an Bool* where the value goes.
770  */
771 static void
772 prefs_bool_cb (Widget button, XtPointer client_data, XtPointer call_data)
773 {
774   Bool *store = (Bool *) client_data;
775 #ifdef HAVE_MOTIF
776   *store = ((XmToggleButtonCallbackStruct *) call_data)->set;
777 #else /* HAVE_ATHENA */
778   Boolean state = FALSE;
779   XtVaGetValues (button, XtNstate, &state, 0);
780   *store = state;
781 #endif /* HAVE_ATHENA */
782 }
783
784
785 /* Callback for the Cancel button on the Preferences dialog.
786  */
787 static void
788 prefs_cancel_cb (Widget button, XtPointer client_data, XtPointer call_data)
789 {
790   XtDestroyWidget (preferences_dialog);
791   preferences_dialog = 0;
792   XMapRaised (XtDisplay (demo_dialog), XtWindow (demo_dialog));
793 }
794
795
796 /* Callback for the OK button on the Preferences dialog.
797  */
798 static void
799 prefs_ok_cb (Widget button, XtPointer client_data, XtPointer call_data)
800 {
801   prefs_pair *pair = (prefs_pair *) client_data;
802   saver_preferences *p =  pair->a;
803   saver_preferences *p2 = pair->b;
804
805   prefs_cancel_cb (button, 0, call_data);
806
807 #ifdef HAVE_ATHENA
808   /* Athena doesn't let us put callbacks on these widgets, so run
809      all the callbacks by hand when OK is pressed. */
810   prefs_min_cb (timeout_text,        (XtPointer) &p2->timeout,        0);
811   prefs_min_cb (cycle_text,          (XtPointer) &p2->cycle,          0);
812   prefs_sec_cb (fade_text,           (XtPointer) &p2->fade_seconds,   0);
813   prefs_int_cb (fade_ticks_text,     (XtPointer) &p2->fade_ticks,     0);
814   prefs_min_cb (lock_timeout_text,   (XtPointer) &p2->lock_timeout,   0);
815   prefs_sec_cb (passwd_timeout_text, (XtPointer) &p2->passwd_timeout, 0);
816 #endif /* HAVE_ATHENA */
817
818   p->timeout        = p2->timeout;
819   p->cycle          = p2->cycle;
820   p->lock_timeout   = p2->lock_timeout;
821   p->passwd_timeout = p2->passwd_timeout;
822   p->fade_seconds   = p2->fade_seconds;
823   p->fade_ticks     = p2->fade_ticks;
824   p->verbose_p      = p2->verbose_p;
825   p->install_cmap_p = p2->install_cmap_p;
826   p->fade_p         = p2->fade_p;
827   p->unfade_p       = p2->unfade_p;
828   p->lock_p         = p2->lock_p;
829
830   write_init_file (p, short_version);
831 }
832
833
834 static void
835 make_preferences_dialog (prefs_pair *pair, Widget parent)
836 {
837   saver_preferences *p =  pair->a;
838   saver_preferences *p2 = pair->b;
839
840   Screen *screen = XtScreen (parent);
841   Display *dpy = XtDisplay (parent);
842
843   *p2 = *p;     /* copy all slots of p into p2. */
844
845   create_preferences_dialog (parent,
846                              DefaultVisualOfScreen (screen),
847                              DefaultColormapOfScreen (screen));
848
849   add_button_callback (prefs_done,   prefs_ok_cb,     (XtPointer) pair);
850   add_button_callback (prefs_cancel, prefs_cancel_cb, 0);
851
852 #define CB(widget,type,slot) \
853         add_text_callback ((widget), (type), (XtPointer) (slot))
854 #define CBT(widget,type,slot) \
855         add_toggle_callback ((widget), (type), (XtPointer) (slot))
856
857 #ifdef HAVE_MOTIF
858   /* When using Athena widgets, we can't set callbacks for these,
859      so in that case, we run them by hand when "OK" is pressed. */
860   CB (timeout_text,             prefs_min_cb,  &p2->timeout);
861   CB (cycle_text,               prefs_min_cb,  &p2->cycle);
862   CB (fade_text,                prefs_sec_cb,  &p2->fade_seconds);
863   CB (fade_ticks_text,          prefs_int_cb,  &p2->fade_ticks);
864   CB (lock_timeout_text,        prefs_min_cb,  &p2->lock_timeout);
865   CB (passwd_timeout_text,      prefs_sec_cb,  &p2->passwd_timeout);
866 #endif /* HAVE_MOTIF */
867
868   CBT (verbose_toggle,          prefs_bool_cb, &p2->verbose_p);
869   CBT (install_cmap_toggle,     prefs_bool_cb, &p2->install_cmap_p);
870   CBT (fade_toggle,             prefs_bool_cb, &p2->fade_p);
871   CBT (unfade_toggle,           prefs_bool_cb, &p2->unfade_p);
872   CBT (lock_toggle,             prefs_bool_cb, &p2->lock_p);
873 #undef CB
874 #undef CBT
875
876   {
877     Bool found_any_writable_cells = False;
878     int nscreens = ScreenCount(dpy);
879     int i;
880     for (i = 0; i < nscreens; i++)
881       {
882         Screen *s = ScreenOfDisplay (dpy, i);
883         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
884           {
885             found_any_writable_cells = True;
886             break;
887           }
888       }
889
890     if (! found_any_writable_cells)     /* fading isn't possible */
891       {
892         disable_widget (fade_text);
893         disable_widget (fade_ticks_text);
894         disable_widget (install_cmap_toggle);
895         disable_widget (fade_toggle);
896         disable_widget (unfade_toggle);
897       }
898   }
899 }
900
901
902 /* Formats a `Time' into "H:MM:SS".  (Time is microseconds.)
903  */
904 static void
905 format_time (char *buf, Time time)
906 {
907   int s = time / 1000;
908   unsigned int h = 0, m = 0;
909   if (s >= 60)
910     {
911       m += (s / 60);
912       s %= 60;
913     }
914   if (m >= 60)
915     {
916       h += (m / 60);
917       m %= 60;
918     }
919   sprintf (buf, "%u:%02u:%02u", h, m, s);
920 }
921
922
923 static void
924 pop_preferences_dialog (prefs_pair *pair)
925 {
926   /* saver_preferences *p =  pair->a; */
927   saver_preferences *p2 = pair->b;
928   char s[100];
929
930   format_time (s, p2->timeout);        set_text_string(timeout_text, s);
931   format_time (s, p2->cycle);          set_text_string(cycle_text, s);
932   format_time (s, p2->lock_timeout);   set_text_string(lock_timeout_text, s);
933   format_time (s, p2->passwd_timeout); set_text_string(passwd_timeout_text, s);
934   format_time (s, p2->fade_seconds);   set_text_string(fade_text, s);
935   sprintf (s, "%u", p2->fade_ticks);   set_text_string(fade_ticks_text, s);
936
937   set_toggle_button_state (verbose_toggle,      p2->verbose_p);
938   set_toggle_button_state (install_cmap_toggle, p2->install_cmap_p);
939   set_toggle_button_state (fade_toggle,         p2->fade_p);
940   set_toggle_button_state (unfade_toggle,       p2->unfade_p);
941   set_toggle_button_state (lock_toggle,         p2->lock_p);
942
943   pop_up_dialog_box (preferences_dialog, preferences_form);
944 }
945
946
947 static void
948 run_hack (Display *dpy, int n)
949 {
950   if (n <= 0) abort();
951   xscreensaver_command (dpy, XA_DEMO, n, False);
952 }
953
954
955 static void
956 warning_dialog_dismiss_cb (Widget button, XtPointer client_data,
957                            XtPointer call_data)
958 {
959   Widget shell = (Widget) client_data;
960   XtDestroyWidget (shell);
961 }
962
963 static void
964 warning_dialog (Widget parent, const char *message)
965 {
966   char *msg = strdup (message);
967   char *head;
968
969   Widget dialog = 0;
970   Widget label = 0;
971   Widget ok = 0;
972   int i = 0;
973
974 #ifdef HAVE_MOTIF
975
976   Widget w;
977   Widget container;
978   XmString xmstr;
979   Arg av[10];
980   int ac = 0;
981
982   ac = 0;
983   dialog = XmCreateWarningDialog (parent, "versionWarning", av, ac);
984
985   w = XmMessageBoxGetChild (dialog, XmDIALOG_MESSAGE_LABEL);
986   if (w) XtUnmanageChild (w);
987   w = XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON);
988   if (w) XtUnmanageChild (w);
989   w = XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON);
990   if (w) XtUnmanageChild (w);
991
992   ok = XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON);
993
994   ac = 0;
995   XtSetArg (av[ac], XmNnumColumns, 1); ac++;
996   XtSetArg (av[ac], XmNorientation, XmVERTICAL); ac++;
997   XtSetArg (av[ac], XmNpacking, XmPACK_COLUMN); ac++;
998   XtSetArg (av[ac], XmNrowColumnType, XmWORK_AREA); ac++;
999   XtSetArg (av[ac], XmNspacing, 0); ac++;
1000   container = XmCreateRowColumn (dialog, "container", av, ac);
1001
1002 #else  /* HAVE_ATHENA */
1003
1004   Widget form;
1005   dialog = XtVaCreatePopupShell("warning_dialog", transientShellWidgetClass,
1006                                 parent, 0);
1007   form = XtVaCreateManagedWidget("warning_form", formWidgetClass, dialog, 0);
1008
1009 #endif /* HAVE_ATHENA */
1010
1011   head = msg;
1012   while (head)
1013     {
1014       char name[20];
1015       char *s = strchr (head, '\n');
1016       if (s) *s = 0;
1017
1018       sprintf (name, "label%d", i++);
1019
1020 #ifdef HAVE_MOTIF
1021       xmstr = XmStringCreate (head, XmSTRING_DEFAULT_CHARSET);
1022       ac = 0;
1023       XtSetArg (av[ac], XmNlabelString, xmstr); ac++;
1024       label = XmCreateLabelGadget (container, name, av, ac);
1025       XtManageChild (label);
1026       XmStringFree (xmstr);
1027 #else  /* HAVE_ATHENA */
1028       
1029       label = XtVaCreateManagedWidget (name, labelWidgetClass,
1030                                        form,
1031                                        XtNleft, XtChainLeft,
1032                                        XtNright, XtChainRight,
1033                                        XtNlabel, head,
1034                                        (label ? XtNfromVert : XtNtop),
1035                                        (label ? label : XtChainTop),
1036                                        0);
1037
1038 #endif /* HAVE_ATHENA */
1039
1040       if (s)
1041         head = s+1;
1042       else
1043         head = 0;
1044     }
1045
1046 #ifdef HAVE_MOTIF
1047
1048   XtManageChild (container);
1049   XtRealizeWidget (dialog);
1050   XtManageChild (dialog);
1051
1052 #else  /* HAVE_ATHENA */
1053
1054   ok = XtVaCreateManagedWidget ("ok", commandWidgetClass, form,
1055                                 XtNleft, XtChainLeft,
1056                                 XtNbottom, XtChainBottom,
1057                                 XtNfromVert, label,
1058                                 0);
1059
1060   XtRealizeWidget (dialog);
1061   XtPopup (dialog, XtGrabNone);
1062
1063 #endif /* HAVE_ATHENA */
1064
1065   add_button_callback (ok, warning_dialog_dismiss_cb, dialog);
1066
1067   free (msg);
1068 }
1069
1070
1071 \f
1072 /* The main demo-mode command loop.
1073  */
1074
1075 #if 0
1076 static Bool
1077 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1078         XrmRepresentation *type, XrmValue *value, XPointer closure)
1079 {
1080   int i;
1081   for (i = 0; quarks[i]; i++)
1082     {
1083       if (bindings[i] == XrmBindTightly)
1084         fprintf (stderr, (i == 0 ? "" : "."));
1085       else if (bindings[i] == XrmBindLoosely)
1086         fprintf (stderr, "*");
1087       else
1088         fprintf (stderr, " ??? ");
1089       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1090     }
1091
1092   fprintf (stderr, ": %s\n", (char *) value->addr);
1093
1094   return False;
1095 }
1096 #endif
1097
1098 static void
1099 the_network_is_not_the_computer (Widget parent)
1100 {
1101   Display *dpy = XtDisplay (parent);
1102   char *rversion, *ruser, *rhost;
1103   char *luser, *lhost;
1104   char *msg = 0;
1105   struct passwd *p = getpwuid (getuid ());
1106   const char *d = DisplayString (dpy);
1107
1108 # if defined(HAVE_UNAME)
1109   struct utsname uts;
1110   if (uname (&uts) < 0)
1111     lhost = "<UNKNOWN>";
1112   else
1113     lhost = uts.nodename;
1114 # elif defined(VMS)
1115   strcpy (lhost, getenv("SYS$NODE"));
1116 # else  /* !HAVE_UNAME && !VMS */
1117   strcat (lhost, "<UNKNOWN>");
1118 # endif /* !HAVE_UNAME && !VMS */
1119
1120   if (p && p->pw_name)
1121     luser = p->pw_name;
1122   else
1123     luser = "???";
1124
1125   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1126
1127   /* Make a buffer that's big enough for a number of copies of all the
1128      strings, plus some. */
1129   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1130                                (ruser ? strlen(ruser) : 0) +
1131                                (rhost ? strlen(rhost) : 0) +
1132                                strlen(lhost) +
1133                                strlen(luser) +
1134                                strlen(d) +
1135                                30));
1136   *msg = 0;
1137
1138   if (!rversion || !*rversion)
1139     {
1140       sprintf (msg,
1141                "Warning:\n\n"
1142                "xscreensaver doesn't seem to be running on display \"%s\".",
1143                d);
1144     }
1145   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1146     {
1147       /* Warn that the two processes are running as different users.
1148        */
1149       sprintf(msg,
1150                "Warning:\n\n"
1151               "%s is running as user \"%s\" on host \"%s\".\n"
1152               "But the xscreensaver managing display \"%s\"\n"
1153               "is running as user \"%s\" on host \"%s\".\n"
1154               "\n"
1155               "Since they are different users, they won't be reading/writing\n"
1156               "the same ~/.xscreensaver file, so %s isn't\n"
1157               "going to work right.\n"
1158               "\n"
1159               "Either re-run %s as \"%s\", or re-run\n"
1160               "xscreensaver as \"%s\".\n",
1161               progname, luser, lhost,
1162               d,
1163               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1164               progname,
1165               progname, (ruser ? ruser : "???"),
1166               luser);
1167     }
1168   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1169     {
1170       /* Warn that the two processes are running on different hosts.
1171        */
1172       sprintf (msg,
1173                "Warning:\n\n"
1174                "%s is running as user \"%s\" on host \"%s\".\n"
1175                "But the xscreensaver managing display \"%s\"\n"
1176                "is running as user \"%s\" on host \"%s\".\n"
1177                "\n"
1178                "If those two machines don't share a file system (that is,\n"
1179                "if they don't see the same ~%s/.xscreensaver file) then\n"
1180                "%s won't work right.",
1181                progname, luser, lhost,
1182                d,
1183                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1184                luser,
1185                progname);
1186     }
1187   else if (!!strcmp (rversion, short_version))
1188     {
1189       /* Warn that the version numbers don't match.
1190        */
1191       sprintf (msg,
1192                "Warning:\n\n"
1193                "This is %s version %s.\n"
1194                "But the xscreensaver managing display \"%s\"\n"
1195                "is version %s.  This could cause problems.",
1196                progname, short_version,
1197                d,
1198                rversion);
1199     }
1200
1201
1202   if (*msg)
1203     warning_dialog (parent, msg);
1204
1205   free (msg);
1206 }
1207
1208
1209 static char *defaults[] = {
1210 #include "XScreenSaver_ad.h"
1211  0
1212 };
1213
1214 int
1215 main (int argc, char **argv)
1216 {
1217   XtAppContext app;
1218   prefs_pair Pair, *pair;
1219   saver_preferences P, P2, *p, *p2;
1220   Bool prefs = False;
1221   int i;
1222   Display *dpy;
1223   Widget toplevel_shell;
1224   char *real_progname = argv[0];
1225   char *s;
1226
1227   s = strrchr (real_progname, '/');
1228   if (s) real_progname = s+1;
1229
1230   p = &P;
1231   p2 = &P2;
1232   pair = &Pair;
1233   pair->a = p;
1234   pair->b = p2;
1235   memset (p,  0, sizeof (*p));
1236   memset (p2, 0, sizeof (*p2));
1237
1238   /* We must read exactly the same resources as xscreensaver.
1239      That means we must have both the same progclass *and* progname,
1240      at least as far as the resource database is concerned.  So,
1241      put "xscreensaver" in argv[0] while initializing Xt.
1242    */
1243   argv[0] = "xscreensaver";
1244
1245   toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
1246                                     defaults, 0, 0);
1247   dpy = XtDisplay (toplevel_shell);
1248   db = XtDatabase (dpy);
1249   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
1250
1251   for (i = 1; i < argc; i++)
1252     {
1253       char *s = argv[i];
1254       if (s[0] == '-' && s[1] == '-')
1255         s++;
1256       if (!strcmp (s, "-prefs"))
1257         prefs = True;
1258       else
1259         {
1260           fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n",
1261                    real_progname);
1262           exit (1);
1263         }
1264     }
1265
1266   short_version = (char *) malloc (5);
1267   memcpy (short_version, screensaver_id + 17, 4);
1268   short_version [4] = 0;
1269
1270   p->db = db;
1271   p->fading_possible_p = True;
1272   load_init_file (p);
1273   *p2 = *p;
1274
1275
1276   /* Now that Xt has been initialized, we can set our `progname' variable
1277      to something that makes more sense (like our "real" argv[0].)
1278    */
1279   progname = real_progname;
1280
1281
1282 #ifdef HAVE_ATHENA
1283   global_prefs_kludge = p;      /* I hate C so much... */
1284 #endif /* HAVE_ATHENA */
1285
1286 #if 0
1287   {
1288     XrmName name = { 0 };
1289     XrmClass class = { 0 };
1290     int count = 0;
1291     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
1292                           (XtPointer) &count);
1293   }
1294 #endif
1295
1296
1297   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
1298   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
1299   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
1300   XA_SCREENSAVER_TIME = XInternAtom (dpy, "_SCREENSAVER_TIME", False);
1301   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
1302   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
1303   XA_SELECT = XInternAtom (dpy, "SELECT", False);
1304   XA_DEMO = XInternAtom (dpy, "DEMO", False);
1305   XA_RESTART = XInternAtom (dpy, "RESTART", False);
1306
1307   make_demo_dialog (toplevel_shell, pair);
1308
1309   if (prefs)
1310     {
1311       make_preferences_dialog (pair, toplevel_shell);
1312       pop_preferences_dialog (pair);
1313     }
1314
1315   the_network_is_not_the_computer (preferences_dialog
1316                                    ? preferences_dialog
1317                                    : demo_dialog);
1318
1319   XtAppMainLoop(app);
1320   exit (0);
1321 }