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