From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / driver / demo-Xm.c
1 /* demo-Xm.c --- implements the interactive demo-mode and options dialogs.
2  * xscreensaver, Copyright (c) 1993-2003, 2005 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_MOTIF /* whole file */
18
19 #include <stdlib.h>
20
21 #ifdef HAVE_UNISTD_H
22 # include <unistd.h>
23 #endif
24
25 #ifndef VMS
26 # include <pwd.h>               /* for getpwuid() */
27 #else /* VMS */
28 # include "vms-pwd.h"
29 #endif /* VMS */
30
31 #ifdef HAVE_UNAME
32 # include <sys/utsname.h>       /* for uname() */
33 #endif /* HAVE_UNAME */
34
35 #include <stdio.h>
36
37 #include <X11/Xproto.h>         /* for CARD32 */
38 #include <X11/Xatom.h>          /* for XA_INTEGER */
39 #include <X11/Intrinsic.h>
40 #include <X11/StringDefs.h>
41
42 /* We don't actually use any widget internals, but these are included
43    so that gdb will have debug info for the widgets... */
44 #include <X11/IntrinsicP.h>
45 #include <X11/ShellP.h>
46
47 #ifdef HAVE_XPM
48 # include <X11/xpm.h>
49 #endif /* HAVE_XPM */
50
51 #ifdef HAVE_XMU
52 # ifndef VMS
53 #  include <X11/Xmu/Error.h>
54 # else /* VMS */
55 #  include <Xmu/Error.h>
56 # endif
57 #else
58 # include "xmu.h"
59 #endif
60
61
62
63 #include <Xm/Xm.h>
64 #include <Xm/List.h>
65 #include <Xm/PushB.h>
66 #include <Xm/LabelG.h>
67 #include <Xm/RowColumn.h>
68 #include <Xm/MessageB.h>
69
70 #ifdef HAVE_XMCOMBOBOX          /* a Motif 2.0 widget */
71 # include <Xm/ComboBox.h>
72 # ifndef XmNtextField           /* Lesstif 0.89.4 bug */
73 #  undef HAVE_XMCOMBOBOX
74 # endif
75 # if (XmVersion < 2001)         /* Lesstif has two personalities these days */
76 #  undef HAVE_XMCOMBOBOX
77 # endif
78 #endif /* HAVE_XMCOMBOBOX */
79
80 #include "version.h"
81 #include "prefs.h"
82 #include "resources.h"          /* for parse_time() */
83 #include "visual.h"             /* for has_writable_cells() */
84 #include "remote.h"             /* for xscreensaver_command() */
85 #include "usleep.h"
86
87 #include <stdio.h>
88 #include <string.h>
89 #include <ctype.h>
90
91 #undef countof
92 #define countof(x) (sizeof((x))/sizeof((*x)))
93
94
95 char *progname = 0;
96 char *progclass = "XScreenSaver";
97 XrmDatabase db;
98
99 typedef struct {
100   saver_preferences *a, *b;
101 } prefs_pair;
102
103 static void *global_prefs_pair;  /* I hate C so much... */
104
105 char *blurb (void) { return progname; }
106
107 extern Widget create_xscreensaver_demo (Widget parent);
108 extern const char *visual_menu[];
109
110
111 static char *short_version = 0;
112
113 Atom XA_VROOT;
114 Atom XA_SCREENSAVER, XA_SCREENSAVER_RESPONSE, XA_SCREENSAVER_VERSION;
115 Atom XA_SCREENSAVER_ID, XA_SCREENSAVER_STATUS, XA_SELECT, XA_DEMO;
116 Atom XA_ACTIVATE, XA_BLANK, XA_LOCK, XA_RESTART, XA_EXIT;
117
118
119 static void populate_demo_window (Widget toplevel,
120                                   int which, prefs_pair *pair);
121 static void populate_prefs_page (Widget top, prefs_pair *pair);
122 static int apply_changes_and_save (Widget widget);
123 static int maybe_reload_init_file (Widget widget, prefs_pair *pair);
124 static void await_xscreensaver (Widget widget);
125
126 \f
127 /* Some random utility functions
128  */
129
130 static Widget 
131 name_to_widget (Widget widget, const char *name)
132 {
133   Widget parent;
134   char name2[255];
135   name2[0] = '*';
136   strcpy (name2+1, name);
137   
138   while ((parent = XtParent (widget)))
139     widget = parent;
140   return XtNameToWidget (widget, name2);
141 }
142
143
144
145 /* Why this behavior isn't automatic in *either* toolkit, I'll never know.
146    Takes a scroller, viewport, or list as an argument.
147  */
148 static void
149 ensure_selected_item_visible (Widget list)
150 {
151   int *pos_list = 0;
152   int pos_count = 0;
153   if (XmListGetSelectedPos (list, &pos_list, &pos_count) && pos_count > 0)
154     {
155       int top = -2;
156       int visible = 0;
157       XtVaGetValues (list,
158                      XmNtopItemPosition, &top,
159                      XmNvisibleItemCount, &visible,
160                      NULL);
161       if (pos_list[0] >= top + visible)
162         {
163           int pos = pos_list[0] - visible + 1;
164           if (pos < 0) pos = 0;
165           XmListSetPos (list, pos);
166         }
167       else if (pos_list[0] < top)
168         {
169           XmListSetPos (list, pos_list[0]);
170         }
171     }
172   if (pos_list)
173     XtFree ((char *) pos_list);
174 }
175
176
177 static void
178 warning_dialog_dismiss_cb  (Widget button, XtPointer client_data,
179                             XtPointer user_data)
180 {
181   Widget shell = (Widget) client_data;
182   XtDestroyWidget (shell);
183 }
184
185
186 static void
187 warning_dialog (Widget parent, const char *message, int center)
188 {
189   char *msg = strdup (message);
190   char *head;
191
192   Widget dialog = 0;
193   Widget label = 0;
194   Widget ok = 0;
195   int i = 0;
196
197   Widget w;
198   Widget container;
199   XmString xmstr;
200   Arg av[10];
201   int ac = 0;
202
203   ac = 0;
204   dialog = XmCreateWarningDialog (parent, "warning", av, ac);
205
206   w = XmMessageBoxGetChild (dialog, XmDIALOG_MESSAGE_LABEL);
207   if (w) XtUnmanageChild (w);
208   w = XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON);
209   if (w) XtUnmanageChild (w);
210   w = XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON);
211   if (w) XtUnmanageChild (w);
212
213   ok = XmMessageBoxGetChild (dialog, XmDIALOG_OK_BUTTON);
214
215   ac = 0;
216   XtSetArg (av[ac], XmNnumColumns, 1); ac++;
217   XtSetArg (av[ac], XmNorientation, XmVERTICAL); ac++;
218   XtSetArg (av[ac], XmNpacking, XmPACK_COLUMN); ac++;
219   XtSetArg (av[ac], XmNrowColumnType, XmWORK_AREA); ac++;
220   XtSetArg (av[ac], XmNspacing, 0); ac++;
221   container = XmCreateRowColumn (dialog, "container", av, ac);
222
223   head = msg;
224   while (head)
225     {
226       char name[20];
227       char *s = strchr (head, '\n');
228       if (s) *s = 0;
229
230       sprintf (name, "label%d", i++);
231
232       xmstr = XmStringCreate (head, XmSTRING_DEFAULT_CHARSET);
233       ac = 0;
234       XtSetArg (av[ac], XmNlabelString, xmstr); ac++;
235       XtSetArg (av[ac], XmNmarginHeight, 0); ac++;
236       label = XmCreateLabelGadget (container, name, av, ac);
237       XtManageChild (label);
238       XmStringFree (xmstr);
239
240       if (s)
241         head = s+1;
242       else
243         head = 0;
244
245       center--;
246     }
247
248   XtManageChild (container);
249   XtRealizeWidget (dialog);
250   XtManageChild (dialog);
251
252   XtAddCallback (ok, XmNactivateCallback, warning_dialog_dismiss_cb, dialog);
253
254   free (msg);
255 }
256
257
258 static void
259 run_cmd (Widget widget, Atom command, int arg)
260 {
261   char *err = 0;
262   int status;
263
264   apply_changes_and_save (widget);
265   status = xscreensaver_command (XtDisplay (widget),
266                                  command, arg, False, &err);
267   if (status < 0)
268     {
269       char buf [255];
270       if (err)
271         sprintf (buf, "Error:\n\n%s", err);
272       else
273         strcpy (buf, "Unknown error!");
274       warning_dialog (widget, buf, 100);
275     }
276   if (err) free (err);
277 }
278
279
280 static void
281 run_hack (Widget widget, int which, Bool report_errors_p)
282 {
283   if (which < 0) return;
284   apply_changes_and_save (widget);
285   if (report_errors_p)
286     run_cmd (widget, XA_DEMO, which + 1);
287   else
288     {
289       char *s = 0;
290       xscreensaver_command (XtDisplay (widget), XA_DEMO, which + 1, False, &s);
291       if (s) free (s);
292     }
293 }
294
295
296 \f
297 /* Button callbacks
298  */
299
300 void
301 exit_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
302 {
303   apply_changes_and_save (XtParent (button));
304   exit (0);
305 }
306
307 #if 0
308 static void
309 wm_close_cb (Widget widget, GdkEvent *event, XtPointer data)
310 {
311   apply_changes_and_save (XtParent (button));
312   exit (0);
313 }
314 #endif
315
316 void
317 cut_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
318 {
319   /* #### */
320   warning_dialog (XtParent (button),
321                   "Error:\n\n"
322                   "cut unimplemented\n", 1);
323 }
324
325
326 void
327 copy_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
328 {
329   /* #### */
330   warning_dialog (XtParent (button),
331                   "Error:\n\n"
332                   "copy unimplemented\n", 1);
333 }
334
335
336 void
337 paste_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
338 {
339   /* #### */
340   warning_dialog (XtParent (button),
341                   "Error:\n\n"
342                   "paste unimplemented\n", 1);
343 }
344
345
346 void
347 about_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
348 {
349   char buf [2048];
350   char *s = strdup (screensaver_id + 4);
351   char *s2;
352
353   s2 = strchr (s, ',');
354   *s2 = 0;
355   s2 += 2;
356
357   sprintf (buf, "%s\n%s\n"
358            "\n"
359            "This is the Motif version of \"xscreensaver-demo\".  The Motif\n"
360            "version is no longer maintained.  Please use the GTK version\n"
361            "instead, which has many more features.\n"
362            "\n"
363            "For xscreensaver updates, check http://www.jwz.org/xscreensaver/",
364            s, s2);
365   free (s);
366
367   warning_dialog (XtParent (button), buf, 100);
368 }
369
370
371 void
372 doc_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
373 {
374   prefs_pair *pair = (prefs_pair *) client_data;
375
376   saver_preferences *p =  pair->a;
377   char *help_command;
378
379   if (!p->help_url || !*p->help_url)
380     {
381       warning_dialog (XtParent (button),
382                       "Error:\n\n"
383                       "No Help URL has been specified.\n", 100);
384       return;
385     }
386
387   help_command = (char *) malloc (strlen (p->load_url_command) +
388                                   (strlen (p->help_url) * 4) + 20);
389   strcpy (help_command, "( ");
390   sprintf (help_command + strlen(help_command),
391            p->load_url_command,
392            p->help_url, p->help_url, p->help_url, p->help_url);
393   strcat (help_command, " ) &");
394   system (help_command);
395   free (help_command);
396 }
397
398
399 void
400 activate_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
401 {
402   run_cmd (XtParent (button), XA_ACTIVATE, 0);
403 }
404
405
406 void
407 lock_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
408 {
409   run_cmd (XtParent (button), XA_LOCK, 0);
410 }
411
412
413 void
414 kill_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
415 {
416   run_cmd (XtParent (button), XA_EXIT, 0);
417 }
418
419
420 void
421 restart_menu_cb (Widget button, XtPointer client_data, XtPointer ignored)
422 {
423 #if 0
424   run_cmd (XtParent (button), XA_RESTART, 0);
425 #else
426   button = XtParent (button);
427   apply_changes_and_save (button);
428   xscreensaver_command (XtDisplay (button), XA_EXIT, 0, False, NULL);
429   sleep (1);
430   system ("xscreensaver -nosplash &");
431 #endif
432
433   await_xscreensaver (button);
434 }
435
436 static void
437 await_xscreensaver (Widget widget)
438 {
439   int countdown = 5;
440
441   Display *dpy = XtDisplay (widget);
442   char *rversion = 0;
443
444   while (!rversion && (--countdown > 0))
445     {
446       /* Check for the version of the running xscreensaver... */
447       server_xscreensaver_version (dpy, &rversion, 0, 0);
448
449       /* If it's not there yet, wait a second... */
450       sleep (1);
451     }
452
453   if (rversion)
454     {
455       /* Got it. */
456       free (rversion);
457     }
458   else
459     {
460       /* Timed out, no screensaver running. */
461
462       char buf [1024];
463       Bool root_p = (geteuid () == 0);
464       
465       strcpy (buf, 
466               "Error:\n\n"
467               "The xscreensaver daemon did not start up properly.\n"
468               "\n");
469
470       if (root_p)
471 # ifdef __GNUC__
472         __extension__     /* don't warn about "string length is greater than
473                              the length ISO C89 compilers are required to
474                              support" in the following expression... */
475 # endif
476         strcat (buf,
477             "You are running as root.  This usually means that xscreensaver\n"
478             "was unable to contact your X server because access control is\n"
479             "turned on.  Try running this command:\n"
480             "\n"
481             "                        xhost +localhost\n"
482             "\n"
483             "and then selecting `File / Restart Daemon'.\n"
484             "\n"
485             "Note that turning off access control will allow anyone logged\n"
486             "on to this machine to access your screen, which might be\n"
487             "considered a security problem.  Please read the xscreensaver\n"
488             "manual and FAQ for more information.\n"
489             "\n"
490             "You shouldn't run X as root. Instead, you should log in as a\n"
491             "normal user, and `su' as necessary.");
492       else
493         strcat (buf, "Please check your $PATH and permissions.");
494
495       warning_dialog (XtParent (widget), buf, 1);
496     }
497 }
498
499
500 static int _selected_hack_number = -1;
501
502 static int
503 selected_hack_number (Widget toplevel)
504 {
505   return _selected_hack_number;
506 }
507
508
509 static int
510 demo_write_init_file (Widget widget, saver_preferences *p)
511 {
512   if (!write_init_file (XtDisplay (widget), p, short_version, False))
513     return 0;
514   else
515     {
516       const char *f = init_file_name();
517       if (!f || !*f)
518         warning_dialog (widget,
519                         "Error:\n\nCouldn't determine init file name!\n",
520                         100);
521       else
522         {
523           char *b = (char *) malloc (strlen(f) + 1024);
524           sprintf (b, "Error:\n\nCouldn't write %s\n", f);
525           warning_dialog (widget, b, 100);
526           free (b);
527         }
528       return -1;
529     }
530 }
531
532
533 static int
534 apply_changes_and_save (Widget widget)
535 {
536   prefs_pair *pair = global_prefs_pair;
537   saver_preferences *p =  pair->a;
538   Widget list_widget = name_to_widget (widget, "list");
539   int which = selected_hack_number (widget);
540
541   Widget cmd = name_to_widget (widget, "cmdText");
542   Widget enabled = name_to_widget (widget, "enabled");
543
544   Widget vis = name_to_widget (widget, "combo");
545 # ifdef HAVE_XMCOMBOBOX
546   Widget text = 0;
547 # else /* !HAVE_XMCOMBOBOX */
548   Widget menu = 0, *kids = 0, selected_item = 0;
549   Cardinal nkids = 0;
550   int i = 0;
551 # endif /* !HAVE_XMCOMBOBOX */
552
553   Bool enabled_p = False;
554   const char *visual = 0;
555   const char *command = 0;
556   
557   char c;
558   unsigned long id;
559
560   if (which < 0) return -1;
561
562 # ifdef HAVE_XMCOMBOBOX
563   XtVaGetValues (vis, XmNtextField, &text, NULL);
564   if (!text)
565     /* If we can't get at the text field of this combo box, we're screwed. */
566     abort();
567   XtVaGetValues (text, XmNvalue, &visual, NULL);
568
569 # else /* !HAVE_XMCOMBOBOX */
570   XtVaGetValues (vis, XmNsubMenuId, &menu, NULL);
571   XtVaGetValues (menu, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
572   XtVaGetValues (menu, XmNmenuHistory, &selected_item, NULL);
573   if (selected_item)
574     for (i = 0; i < nkids; i++)
575       if (kids[i] == selected_item)
576         break;
577
578   visual = visual_menu[i];
579 # endif /* !HAVE_XMCOMBOBOX */
580
581   XtVaGetValues (enabled, XmNset, &enabled_p, NULL);
582   XtVaGetValues (cmd, XtNvalue, &command, NULL);
583
584   if (maybe_reload_init_file (widget, pair) != 0)
585     return 1;
586
587   /* Sanity-check and canonicalize whatever the user typed into the combo box.
588    */
589   if      (!strcasecmp (visual, ""))                   visual = "";
590   else if (!strcasecmp (visual, "any"))                visual = "";
591   else if (!strcasecmp (visual, "default"))            visual = "Default";
592   else if (!strcasecmp (visual, "default-n"))          visual = "Default-N";
593   else if (!strcasecmp (visual, "default-i"))          visual = "Default-I";
594   else if (!strcasecmp (visual, "best"))               visual = "Best";
595   else if (!strcasecmp (visual, "mono"))               visual = "Mono";
596   else if (!strcasecmp (visual, "monochrome"))         visual = "Mono";
597   else if (!strcasecmp (visual, "gray"))               visual = "Gray";
598   else if (!strcasecmp (visual, "grey"))               visual = "Gray";
599   else if (!strcasecmp (visual, "color"))              visual = "Color";
600   else if (!strcasecmp (visual, "gl"))                 visual = "GL";
601   else if (!strcasecmp (visual, "staticgray"))         visual = "StaticGray";
602   else if (!strcasecmp (visual, "staticcolor"))        visual = "StaticColor";
603   else if (!strcasecmp (visual, "truecolor"))          visual = "TrueColor";
604   else if (!strcasecmp (visual, "grayscale"))          visual = "GrayScale";
605   else if (!strcasecmp (visual, "greyscale"))          visual = "GrayScale";
606   else if (!strcasecmp (visual, "pseudocolor"))        visual = "PseudoColor";
607   else if (!strcasecmp (visual, "directcolor"))        visual = "DirectColor";
608   else if (1 == sscanf (visual, " %lu %c", &id, &c))   ;
609   else if (1 == sscanf (visual, " 0x%lx %c", &id, &c)) ;
610   else
611     {
612       XBell (XtDisplay (widget), 0);                      /* unparsable */
613       visual = "";
614       /* #### gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (vis)->entry), "Any");*/
615     }
616
617   ensure_selected_item_visible (list_widget);
618
619   if (!p->screenhacks[which]->visual)
620     p->screenhacks[which]->visual = strdup ("");
621   if (!p->screenhacks[which]->command)
622     p->screenhacks[which]->command = strdup ("");
623
624   if (p->screenhacks[which]->enabled_p != enabled_p ||
625       !!strcasecmp (p->screenhacks[which]->visual, visual) ||
626       !!strcasecmp (p->screenhacks[which]->command, command))
627     {
628       /* Something was changed -- store results into the struct,
629          and write the file.
630        */
631       free (p->screenhacks[which]->visual);
632       free (p->screenhacks[which]->command);
633       p->screenhacks[which]->visual = strdup (visual);
634       p->screenhacks[which]->command = strdup (command);
635       p->screenhacks[which]->enabled_p = enabled_p;
636
637       return demo_write_init_file (widget, p);
638     }
639
640   /* No changes made */
641   return 0;
642 }
643
644 void
645 run_this_cb (Widget button, XtPointer client_data, XtPointer ignored)
646 {
647   int which = selected_hack_number (XtParent (button));
648   if (which < 0) return;
649   if (0 == apply_changes_and_save (XtParent (button)))
650     run_hack (XtParent (button), which, True);
651 }
652
653
654 void
655 manual_cb (Widget button, XtPointer client_data, XtPointer ignored)
656 {
657   prefs_pair *pair = (prefs_pair *) client_data;
658   saver_preferences *p =  pair->a;
659   Widget list_widget = name_to_widget (button, "list");
660   int which = selected_hack_number (button);
661   char *name, *name2, *cmd, *s;
662   if (which < 0) return;
663   apply_changes_and_save (button);
664   ensure_selected_item_visible (list_widget);
665
666   name = strdup (p->screenhacks[which]->command);
667   name2 = name;
668   while (isspace (*name2)) name2++;
669   s = name2;
670   while (*s && !isspace (*s)) s++;
671   *s = 0;
672   s = strrchr (name2, '/');
673   if (s) name = s+1;
674
675   cmd = get_string_resource (XtDisplay (button), "manualCommand", "ManualCommand");
676   if (cmd)
677     {
678       char *cmd2 = (char *) malloc (strlen (cmd) + (strlen (name2) * 4) + 100);
679       strcpy (cmd2, "( ");
680       sprintf (cmd2 + strlen (cmd2),
681                cmd,
682                name2, name2, name2, name2);
683       strcat (cmd2, " ) &");
684       system (cmd2);
685       free (cmd2);
686     }
687   else
688     {
689       warning_dialog (XtParent (button),
690                       "Error:\n\nno `manualCommand' resource set.",
691                       100);
692     }
693
694   free (name);
695 }
696
697
698 void
699 run_next_cb (Widget button, XtPointer client_data, XtPointer ignored)
700 {
701   prefs_pair *pair = (prefs_pair *) client_data;
702   saver_preferences *p =  pair->a;
703
704   Widget list_widget = name_to_widget (button, "list");
705   int which = selected_hack_number (button);
706
707   button = XtParent (button);
708
709   if (which < 0)
710     which = 0;
711   else
712     which++;
713
714   if (which >= p->screenhacks_count)
715     which = 0;
716
717   apply_changes_and_save (button);
718
719   XmListDeselectAllItems (list_widget); /* LessTif lossage */
720   XmListSelectPos (list_widget, which+1, True);
721
722   ensure_selected_item_visible (list_widget);
723   populate_demo_window (button, which, pair);
724   run_hack (button, which, False);
725 }
726
727
728 void
729 run_prev_cb (Widget button, XtPointer client_data, XtPointer ignored)
730 {
731   prefs_pair *pair = (prefs_pair *) client_data;
732   saver_preferences *p =  pair->a;
733
734   Widget list_widget = name_to_widget (button, "list");
735   int which = selected_hack_number (button);
736
737   button = XtParent (button);
738
739   if (which < 0)
740     which = p->screenhacks_count - 1;
741   else
742     which--;
743
744   if (which < 0)
745     which = p->screenhacks_count - 1;
746
747   apply_changes_and_save (button);
748
749   XmListDeselectAllItems (list_widget); /* LessTif lossage */
750   XmListSelectPos (list_widget, which+1, True);
751
752   ensure_selected_item_visible (list_widget);
753   populate_demo_window (button, which, pair);
754   run_hack (button, which, False);
755 }
756
757
758 /* Helper for the text fields that contain time specifications:
759    this parses the text, and does error checking.
760  */
761 static void 
762 hack_time_text (Widget button, const char *line, Time *store, Bool sec_p)
763 {
764   if (*line)
765     {
766       int value;
767       value = parse_time ((char *) line, sec_p, True);
768       value *= 1000;    /* Time measures in microseconds */
769       if (value < 0)
770         {
771           char b[255];
772           sprintf (b,
773                    "Error:\n\n"
774                    "Unparsable time format: \"%s\"\n",
775                    line);
776           warning_dialog (XtParent (button), b, 100);
777         }
778       else
779         *store = value;
780     }
781 }
782
783
784 void
785 prefs_ok_cb (Widget button, XtPointer client_data, XtPointer ignored)
786 {
787   prefs_pair *pair = (prefs_pair *) client_data;
788
789   saver_preferences *p =  pair->a;
790   saver_preferences *p2 = pair->b;
791   Bool changed = False;
792   char *v = 0;
793
794   button = XtParent (button);
795
796 # define SECONDS(field, name) \
797   v = 0; \
798   XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \
799   hack_time_text (button, v, (field), True)
800
801 # define MINUTES(field, name) \
802   v = 0; \
803   XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \
804   hack_time_text (button, v, (field), False)
805
806 # define INTEGER(field, name) do { \
807     unsigned int value; \
808     char c; \
809     XtVaGetValues (name_to_widget (button, (name)), XtNvalue, &v, NULL); \
810     if (! *v) \
811       ; \
812     else if (sscanf (v, "%u%c", &value, &c) != 1) \
813       { \
814         char b[255]; \
815         sprintf (b, "Error:\n\n" "Not an integer: \"%s\"\n", v); \
816         warning_dialog (XtParent (button), b, 100); \
817       } \
818    else \
819      *(field) = value; \
820   } while(0)
821
822 # define CHECKBOX(field, name) \
823   XtVaGetValues (name_to_widget (button, (name)), XmNset, &field, NULL)
824
825   MINUTES (&p2->timeout,        "timeoutText");
826   MINUTES (&p2->cycle,          "cycleText");
827   SECONDS (&p2->fade_seconds,   "fadeSecondsText");
828   INTEGER (&p2->fade_ticks,     "fadeTicksText");
829   MINUTES (&p2->lock_timeout,   "lockText");
830   SECONDS (&p2->passwd_timeout, "passwdText");
831   CHECKBOX (p2->verbose_p,      "verboseToggle");
832   CHECKBOX (p2->install_cmap_p, "cmapToggle");
833   CHECKBOX (p2->fade_p,         "fadeToggle");
834   CHECKBOX (p2->unfade_p,       "unfadeToggle");
835   CHECKBOX (p2->lock_p,         "lockToggle");
836
837 # undef SECONDS
838 # undef MINUTES
839 # undef INTEGER
840 # undef CHECKBOX
841
842 # define COPY(field) \
843   if (p->field != p2->field) changed = True; \
844   p->field = p2->field
845
846   COPY(timeout);
847   COPY(cycle);
848   COPY(lock_timeout);
849   COPY(passwd_timeout);
850   COPY(fade_seconds);
851   COPY(fade_ticks);
852   COPY(verbose_p);
853   COPY(install_cmap_p);
854   COPY(fade_p);
855   COPY(unfade_p);
856   COPY(lock_p);
857 # undef COPY
858
859   populate_prefs_page (button, pair);
860
861   if (changed)
862     demo_write_init_file (button, p);
863 }
864
865
866 void
867 prefs_cancel_cb (Widget button, XtPointer client_data, XtPointer ignored)
868 {
869   prefs_pair *pair = (prefs_pair *) client_data;
870
871   *pair->b = *pair->a;
872   populate_prefs_page (XtParent (button), pair);
873 }
874
875
876 static void
877 list_select_cb (Widget list, XtPointer client_data, XtPointer call_data)
878 {
879   prefs_pair *pair = (prefs_pair *) client_data;
880
881   XmListCallbackStruct *lcb = (XmListCallbackStruct *) call_data;
882   int which = lcb->item_position - 1;
883
884   apply_changes_and_save (list);
885   populate_demo_window (list, which, pair);
886
887   if (lcb->reason == XmCR_DEFAULT_ACTION && which >= 0)
888     run_hack (list, which, True);
889 }
890
891 \f
892 /* Populating the various widgets
893  */
894
895
896 /* Formats a `Time' into "H:MM:SS".  (Time is microseconds.)
897  */
898 static void
899 format_time (char *buf, Time time)
900 {
901   int s = time / 1000;
902   unsigned int h = 0, m = 0;
903   if (s >= 60)
904     {
905       m += (s / 60);
906       s %= 60;
907     }
908   if (m >= 60)
909     {
910       h += (m / 60);
911       m %= 60;
912     }
913   sprintf (buf, "%u:%02u:%02u", h, m, s);
914 }
915
916
917 /* Finds the number of the last hack to run, and makes that item be
918    selected by default.
919  */
920 static void
921 scroll_to_current_hack (Widget toplevel, prefs_pair *pair)
922 {
923   Atom type;
924   int format;
925   unsigned long nitems, bytesafter;
926   unsigned char *data = 0;
927   Display *dpy = XtDisplay (toplevel);
928   int which = 0;
929   Widget list;
930
931   if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
932                           XA_SCREENSAVER_STATUS,
933                           0, 3, False, XA_INTEGER,
934                           &type, &format, &nitems, &bytesafter,
935                           &data)
936       == Success
937       && type == XA_INTEGER
938       && nitems >= 3
939       && data)
940     which = (int) data[2] - 1;
941
942   if (data) free (data);
943
944   if (which < 0)
945     return;
946
947   list = name_to_widget (toplevel, "list");
948   apply_changes_and_save (toplevel);
949
950   XmListDeselectAllItems (list);        /* LessTif lossage */
951   XmListSelectPos (list, which+1, True);
952
953   ensure_selected_item_visible (list);
954   populate_demo_window (toplevel, which, pair);
955 }
956
957
958
959 static void
960 populate_hack_list (Widget toplevel, prefs_pair *pair)
961 {
962   saver_preferences *p =  pair->a;
963   Widget list = name_to_widget (toplevel, "list");
964   screenhack **hacks = p->screenhacks;
965   screenhack **h;
966
967   for (h = hacks; *h; h++)
968     {
969       char *pretty_name = (h[0]->name
970                            ? strdup (h[0]->name)
971                            : make_hack_name (XtDisplay (toplevel), h[0]->command));
972
973       XmString xmstr = XmStringCreate (pretty_name, XmSTRING_DEFAULT_CHARSET);
974       XmListAddItem (list, xmstr, 0);
975       XmStringFree (xmstr);
976     }
977
978   XtAddCallback (list, XmNbrowseSelectionCallback, list_select_cb, pair);
979   XtAddCallback (list, XmNdefaultActionCallback,   list_select_cb, pair);
980 }
981
982
983 static void
984 populate_prefs_page (Widget top, prefs_pair *pair)
985 {
986   saver_preferences *p =  pair->a;
987   char s[100];
988
989   format_time (s, p->timeout);
990   XtVaSetValues (name_to_widget (top, "timeoutText"),     XmNvalue, s, NULL);
991   format_time (s, p->cycle);
992   XtVaSetValues (name_to_widget (top, "cycleText"),       XmNvalue, s, NULL);
993   format_time (s, p->lock_timeout);
994   XtVaSetValues (name_to_widget (top, "lockText"),        XmNvalue, s, NULL);
995   format_time (s, p->passwd_timeout);
996   XtVaSetValues (name_to_widget (top, "passwdText"),      XmNvalue, s, NULL);
997   format_time (s, p->fade_seconds);
998   XtVaSetValues (name_to_widget (top, "fadeSecondsText"), XmNvalue, s, NULL);
999   sprintf (s, "%u", p->fade_ticks);
1000   XtVaSetValues (name_to_widget (top, "fadeTicksText"),   XmNvalue, s, NULL);
1001
1002   XtVaSetValues (name_to_widget (top, "verboseToggle"),
1003                  XmNset, p->verbose_p, NULL);
1004   XtVaSetValues (name_to_widget (top, "cmapToggle"),
1005                  XmNset, p->install_cmap_p, NULL);
1006   XtVaSetValues (name_to_widget (top, "fadeToggle"),
1007                  XmNset, p->fade_p, NULL);
1008   XtVaSetValues (name_to_widget (top, "unfadeToggle"),
1009                  XmNset, p->unfade_p, NULL);
1010   XtVaSetValues (name_to_widget (top, "lockToggle"),
1011                  XmNset, p->lock_p, NULL);
1012
1013
1014   {
1015     Bool found_any_writable_cells = False;
1016     Display *dpy = XtDisplay (top);
1017     int nscreens = ScreenCount(dpy);
1018     int i;
1019     for (i = 0; i < nscreens; i++)
1020       {
1021         Screen *s = ScreenOfDisplay (dpy, i);
1022         if (has_writable_cells (s, DefaultVisualOfScreen (s)))
1023           {
1024             found_any_writable_cells = True;
1025             break;
1026           }
1027       }
1028
1029 #ifdef HAVE_XF86VMODE_GAMMA
1030     found_any_writable_cells = True;  /* if we can gamma fade, go for it */
1031 #endif
1032
1033     XtVaSetValues (name_to_widget (top, "fadeSecondsLabel"), XtNsensitive,
1034                            found_any_writable_cells, NULL);
1035     XtVaSetValues (name_to_widget (top, "fadeTicksLabel"), XtNsensitive,
1036                            found_any_writable_cells, NULL);
1037     XtVaSetValues (name_to_widget (top, "fadeSecondsText"), XtNsensitive,
1038                            found_any_writable_cells, NULL);
1039     XtVaSetValues (name_to_widget (top, "fadeTicksText"), XtNsensitive,
1040                            found_any_writable_cells, NULL);
1041     XtVaSetValues (name_to_widget (top, "cmapToggle"), XtNsensitive,
1042                            found_any_writable_cells, NULL);
1043     XtVaSetValues (name_to_widget (top, "fadeToggle"), XtNsensitive,
1044                            found_any_writable_cells, NULL);
1045     XtVaSetValues (name_to_widget (top, "unfadeToggle"), XtNsensitive,
1046                            found_any_writable_cells, NULL);
1047   }
1048 }
1049
1050
1051 static void
1052 sensitize_demo_widgets (Widget toplevel, Bool sensitive_p)
1053 {
1054   const char *names[] = { "cmdLabel", "cmdText", "enabled",
1055                           "visLabel", "combo", "demo", "man" };
1056   int i;
1057   for (i = 0; i < sizeof(names)/countof(*names); i++)
1058     {
1059       Widget w = name_to_widget (toplevel, names[i]);
1060       XtVaSetValues (w, XtNsensitive, sensitive_p, NULL);
1061     }
1062
1063   /* I don't know how to handle these yet... */
1064   {
1065     const char *names2[] = { "cut", "copy", "paste" };
1066     for (i = 0; i < sizeof(names2)/countof(*names2); i++)
1067       {
1068         Widget w = name_to_widget (toplevel, names2[i]);
1069         XtVaSetValues (w, XtNsensitive, FALSE, NULL);
1070       }
1071   }
1072 }
1073
1074
1075 \f
1076 /* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
1077  */
1078
1079 #ifdef HAVE_XPM
1080
1081 static char *up_arrow_xpm[] = {
1082   "15 15 4 1",
1083   "     c None s background",
1084   "-    c #FFFFFF",
1085   "+    c #D6D6D6",
1086   "@    c #000000",
1087
1088   "       @       ",
1089   "       @       ",
1090   "      -+@      ",
1091   "      -+@      ",
1092   "     -+++@     ",
1093   "     -+++@     ",
1094   "    -+++++@    ",
1095   "    -+++++@    ",
1096   "   -+++++++@   ",
1097   "   -+++++++@   ",
1098   "  -+++++++++@  ",
1099   "  -+++++++++@  ",
1100   " -+++++++++++@ ",
1101   " @@@@@@@@@@@@@ ",
1102   "               "
1103 };
1104
1105 static char *down_arrow_xpm[] = {
1106   "15 15 4 1",
1107   "     c None s background",
1108   "-    c #FFFFFF",
1109   "+    c #D6D6D6",
1110   "@    c #000000",
1111
1112   "               ",
1113   " ------------- ",
1114   " -+++++++++++@ ",
1115   "  -+++++++++@  ",
1116   "  -+++++++++@  ",
1117   "   -+++++++@   ",
1118   "   -+++++++@   ",
1119   "    -+++++@    ",
1120   "    -+++++@    ",
1121   "     -+++@     ",
1122   "     -+++@     ",
1123   "      -+@      ",
1124   "      -+@      ",
1125   "       @       ",
1126   "       @       "
1127 };
1128
1129 #endif /* HAVE_XPM */
1130
1131
1132 static void
1133 pixmapify_buttons (Widget toplevel)
1134 {
1135 #ifdef HAVE_XPM
1136
1137   Display *dpy = XtDisplay (toplevel);
1138   Window window = XtWindow (toplevel);
1139   XWindowAttributes xgwa;
1140   XpmAttributes xpmattrs;
1141   Pixmap up_pixmap = 0, down_pixmap = 0;
1142   int result;
1143   Widget up = name_to_widget (toplevel, "up");
1144   Widget dn = name_to_widget (toplevel, "down");
1145 # ifdef XpmColorSymbols
1146   XColor xc;
1147   XpmColorSymbol symbols[2];
1148   char color[20];
1149 # endif
1150
1151   XGetWindowAttributes (dpy, window, &xgwa);
1152
1153   xpmattrs.valuemask = 0;
1154
1155 # ifdef XpmColorSymbols
1156   symbols[0].name = "background";
1157   symbols[0].pixel = 0;
1158   symbols[1].name = 0;
1159   XtVaGetValues (up, XmNbackground, &xc, NULL);
1160   XQueryColor (dpy, xgwa.colormap, &xc);
1161   sprintf (color, "#%04X%04X%04X", xc.red, xc.green, xc.blue);
1162   symbols[0].value = color;
1163   symbols[0].pixel = xc.pixel;
1164
1165   xpmattrs.valuemask |= XpmColorSymbols;
1166   xpmattrs.colorsymbols = symbols;
1167   xpmattrs.numsymbols = 1;
1168 # endif
1169
1170 # ifdef XpmCloseness
1171   xpmattrs.valuemask |= XpmCloseness;
1172   xpmattrs.closeness = 40000;
1173 # endif
1174 # ifdef XpmVisual
1175   xpmattrs.valuemask |= XpmVisual;
1176   xpmattrs.visual = xgwa.visual;
1177 # endif
1178 # ifdef XpmDepth
1179   xpmattrs.valuemask |= XpmDepth;
1180   xpmattrs.depth = xgwa.depth;
1181 # endif
1182 # ifdef XpmColormap
1183   xpmattrs.valuemask |= XpmColormap;
1184   xpmattrs.colormap = xgwa.colormap;
1185 # endif
1186
1187   result = XpmCreatePixmapFromData(dpy, window, up_arrow_xpm,
1188                                    &up_pixmap, 0 /* mask */, &xpmattrs);
1189   if (!up_pixmap || (result != XpmSuccess && result != XpmColorError))
1190     {
1191       fprintf (stderr, "%s: Can't load pixmaps\n", progname);
1192       return;
1193     }
1194
1195   result = XpmCreatePixmapFromData(dpy, window, down_arrow_xpm,
1196                                    &down_pixmap, 0 /* mask */, &xpmattrs);
1197   if (!down_pixmap || (result != XpmSuccess && result != XpmColorError))
1198     {
1199       fprintf (stderr, "%s: Can't load pixmaps\n", progname);
1200       return;
1201     }
1202
1203   XtVaSetValues (up, XmNlabelType, XmPIXMAP, XmNlabelPixmap, up_pixmap, NULL);
1204   XtVaSetValues (dn, XmNlabelType, XmPIXMAP, XmNlabelPixmap, down_pixmap,NULL);
1205
1206 #endif /* HAVE_XPM */
1207 }
1208
1209
1210
1211 char *
1212 get_hack_blurb (Display *dpy, screenhack *hack)
1213 {
1214   char *doc_string;
1215   char *prog_name = strdup (hack->command);
1216   char *pretty_name = (hack->name
1217                        ? strdup (hack->name)
1218                        : make_hack_name (dpy, hack->command));
1219   char doc_name[255], doc_class[255];
1220   char *s, *s2;
1221
1222   for (s = prog_name; *s && !isspace(*s); s++)
1223     ;
1224   *s = 0;
1225   s = strrchr (prog_name, '/');
1226   if (s) strcpy (prog_name, s+1);
1227
1228   sprintf (doc_name,  "hacks.%s.documentation", pretty_name);
1229   sprintf (doc_class, "hacks.%s.documentation", prog_name);
1230   free (prog_name);
1231   free (pretty_name);
1232
1233   doc_string = get_string_resource (dpy, doc_name, doc_class);
1234   if (doc_string)
1235     {
1236       for (s = doc_string; *s; s++)
1237         {
1238           if (*s == '\n')
1239             {
1240               /* skip over whitespace at beginning of line */
1241               s++;
1242               while (*s && (*s == ' ' || *s == '\t'))
1243                 s++;
1244             }
1245           else if (*s == ' ' || *s == '\t')
1246             {
1247               /* compress all other horizontal whitespace. */
1248               *s = ' ';
1249               s++;
1250               for (s2 = s; *s2 && (*s2 == ' ' || *s2 == '\t'); s2++)
1251                 ;
1252               if (s2 > s) strcpy (s, s2);
1253               s--;
1254             }
1255         }
1256
1257       while (*s && isspace (*s))      /* Strip trailing whitespace */
1258         *(--s) = 0;
1259
1260       /* Delete whitespace at end of each line. */
1261       for (; s > doc_string; s--)
1262         if (*s == '\n' && (s[-1] == ' ' || s[-1] == '\t'))
1263           {
1264             for (s2 = s-1;
1265                  s2 > doc_string && (*s2 == ' ' || *s2 == '\t');
1266                  s2--)
1267               ;
1268             s2++;
1269             if (s2 < s) strcpy (s2, s);
1270             s = s2;
1271           }
1272       
1273       /* Delete leading blank lines. */
1274       for (s = doc_string; *s == '\n'; s++)
1275         ;
1276       if (s > doc_string) strcpy (doc_string, s);
1277     }
1278   else
1279     {
1280 # if 0
1281       static int doc_installed = 0;
1282       if (doc_installed == 0)
1283         {
1284           if (get_boolean_resource ("hacks.documentation.isInstalled",
1285                                     "hacks.documentation.isInstalled"))
1286             doc_installed = 1;
1287           else
1288             doc_installed = -1;
1289         }
1290
1291       if (doc_installed < 0)
1292         doc_string =
1293           strdup ("Error:\n\n"
1294                   "The documentation strings do not appear to be "
1295                   "installed.  This is probably because there is "
1296                   "an \"XScreenSaver\" app-defaults file installed "
1297                   "that is from an older version of the program. "
1298                   "To fix this problem, delete that file, or "
1299                   "install a current version (either will work.)");
1300       else
1301 # endif /* 0 */
1302         doc_string = strdup (
1303            "\n"
1304            "This is the Motif version of \"xscreensaver-demo\".  The Motif "
1305            "version is no longer maintained.  Please use the GTK version "
1306            "instead, which has many more features."
1307            "\n\n"
1308            "If you were running the GTK version, there would be a preview "
1309            "of this screen saver mode displayed here, along with graphical "
1310            "configuration options.");
1311     }
1312
1313   return doc_string;
1314 }
1315
1316
1317 static void
1318 populate_demo_window (Widget toplevel, int which, prefs_pair *pair)
1319 {
1320   saver_preferences *p = pair->a;
1321   screenhack *hack = (which >= 0 ? p->screenhacks[which] : 0);
1322   Widget frameL = name_to_widget (toplevel, "frameLabel");
1323   Widget doc = name_to_widget (toplevel, "doc");
1324   Widget cmd = name_to_widget (toplevel, "cmdText");
1325   Widget enabled = name_to_widget (toplevel, "enabled");
1326   Widget vis = name_to_widget (toplevel, "combo");
1327   int i = 0;
1328
1329   char *pretty_name = (hack
1330                        ? (hack->name
1331                           ? strdup (hack->name)
1332                           : make_hack_name (XtDisplay (toplevel), hack->command))
1333                        : 0);
1334   char *doc_string = hack ? get_hack_blurb (XtDisplay (toplevel), hack) : 0;
1335
1336   XmString xmstr;
1337
1338   xmstr = XmStringCreate (pretty_name, XmSTRING_DEFAULT_CHARSET);
1339   XtVaSetValues (frameL, XmNlabelString, xmstr, NULL);
1340   XmStringFree (xmstr);
1341
1342   XtVaSetValues (doc, XmNvalue, doc_string, NULL);
1343   XtVaSetValues (cmd, XmNvalue, (hack ? hack->command : ""), NULL);
1344
1345   XtVaSetValues (enabled, XmNset, (hack ? hack->enabled_p : False), NULL);
1346
1347   i = 0;
1348   if (hack && hack->visual && *hack->visual)
1349     for (i = 0; visual_menu[i]; i++)
1350       if (!strcasecmp (hack->visual, visual_menu[i]))
1351         break;
1352   if (!visual_menu[i]) i = -1;
1353
1354   {
1355 # ifdef HAVE_XMCOMBOBOX
1356     Widget text = 0;
1357     XtVaGetValues (vis, XmNtextField, &text, NULL);
1358     XtVaSetValues (vis, XmNselectedPosition, i, NULL);
1359     if (i < 0)
1360       XtVaSetValues (text, XmNvalue, hack->visual, NULL);
1361 # else /* !HAVE_XMCOMBOBOX */
1362     Cardinal nkids;
1363     Widget *kids;
1364     Widget menu;
1365
1366     XtVaGetValues (vis, XmNsubMenuId, &menu, NULL);
1367     if (!menu) abort ();
1368     XtVaGetValues (menu, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
1369     if (!kids) abort();
1370     if (i < nkids)
1371       XtVaSetValues (vis, XmNmenuHistory, kids[i], NULL);
1372 # endif /* !HAVE_XMCOMBOBOX */
1373   }
1374
1375   sensitize_demo_widgets (toplevel, (hack ? True : False));
1376
1377   if (pretty_name) free (pretty_name);
1378   if (doc_string) free (doc_string);
1379
1380   _selected_hack_number = which;
1381 }
1382
1383
1384
1385 static int
1386 maybe_reload_init_file (Widget widget, prefs_pair *pair)
1387 {
1388   int status = 0;
1389   saver_preferences *p =  pair->a;
1390
1391   static Bool reentrant_lock = False;
1392   if (reentrant_lock) return 0;
1393   reentrant_lock = True;
1394
1395   if (init_file_changed_p (p))
1396     {
1397       const char *f = init_file_name();
1398       char *b;
1399       int which;
1400       Widget list;
1401
1402       if (!f || !*f) return 0;
1403       b = (char *) malloc (strlen(f) + 1024);
1404       sprintf (b,
1405                "Warning:\n\n"
1406                "file \"%s\" has changed, reloading.\n",
1407                f);
1408       warning_dialog (widget, b, 100);
1409       free (b);
1410
1411       load_init_file (XtDisplay (widget), p);
1412
1413       which = selected_hack_number (widget);
1414       list = name_to_widget (widget, "list");
1415
1416       XtVaSetValues (list, XmNitemCount, 0, NULL);
1417
1418       populate_hack_list (widget, pair);
1419
1420       XmListDeselectAllItems (list);    /* LessTif lossage */
1421       XmListSelectPos (list, which+1, True);
1422
1423       populate_prefs_page (widget, pair);
1424       populate_demo_window (widget, which, pair);
1425       ensure_selected_item_visible (list);
1426
1427       status = 1;
1428     }
1429
1430   reentrant_lock = False;
1431   return status;
1432 }
1433
1434
1435 \f
1436 /* Attach all callback functions to widgets
1437  */
1438
1439 static void
1440 add_callbacks (Widget toplevel, prefs_pair *pair)
1441 {
1442   Widget w;
1443
1444 # define CB(NAME,FN) \
1445   w = name_to_widget (toplevel, (NAME)); \
1446   XtAddCallback (w, XmNactivateCallback, (FN), pair)
1447
1448   CB ("blank",   activate_menu_cb);
1449   CB ("lock",    lock_menu_cb);
1450   CB ("kill",    kill_menu_cb);
1451   CB ("restart", restart_menu_cb);
1452   CB ("exit",    exit_menu_cb);
1453
1454   CB ("cut",     cut_menu_cb);
1455   CB ("copy",    copy_menu_cb);
1456   CB ("paste",   paste_menu_cb);
1457
1458   CB ("about",   about_menu_cb);
1459   CB ("docMenu", doc_menu_cb);
1460
1461   CB ("down",    run_next_cb);
1462   CB ("up",      run_prev_cb);
1463   CB ("demo",    run_this_cb);
1464   CB ("man",     manual_cb);
1465
1466   CB ("preferencesForm.Cancel",  prefs_cancel_cb);
1467   CB ("preferencesForm.OK",      prefs_ok_cb);
1468
1469 # undef CB
1470 }
1471
1472
1473 static void
1474 sanity_check_resources (Widget toplevel)
1475 {
1476   const char *names[] = { "demoTab", "optionsTab", "cmdLabel", "visLabel",
1477                           "enabled", "demo", "man", "timeoutLabel",
1478                           "cycleLabel", "fadeSecondsLabel", "fadeTicksLabel",
1479                           "lockLabel", "passwdLabel" };
1480   int i;
1481   for (i = 0; i < sizeof(names)/countof(*names); i++)
1482     {
1483       Widget w = name_to_widget (toplevel, names[i]);
1484       const char *name = XtName(w);
1485       XmString xm = 0;
1486       char *label = 0;
1487       XtVaGetValues (w, XmNlabelString, &xm, NULL);
1488       if (xm) XmStringGetLtoR (xm, XmSTRING_DEFAULT_CHARSET, &label);
1489       if (w && (!label || !strcmp (name, label)))
1490         {
1491           xm = XmStringCreate ("ERROR", XmSTRING_DEFAULT_CHARSET);
1492           XtVaSetValues (w, XmNlabelString, xm, NULL);
1493         }
1494     }
1495 }
1496
1497 /* Set certain buttons to be the same size (the max of the set.)
1498  */
1499 static void
1500 hack_button_sizes (Widget toplevel)
1501 {
1502   Widget demo = name_to_widget (toplevel, "demo");
1503   Widget man  = name_to_widget (toplevel, "man");
1504   Widget ok   = name_to_widget (toplevel, "OK");
1505   Widget can  = name_to_widget (toplevel, "Cancel");
1506   Widget up   = name_to_widget (toplevel, "up");
1507   Widget down = name_to_widget (toplevel, "down");
1508   Dimension w1, w2;
1509
1510   XtVaGetValues (demo, XmNwidth, &w1, NULL);
1511   XtVaGetValues (man,  XmNwidth, &w2, NULL);
1512   XtVaSetValues ((w1 > w2 ? man : demo), XmNwidth, (w1 > w2 ? w1 : w2), NULL);
1513
1514   XtVaGetValues (ok,   XmNwidth, &w1, NULL);
1515   XtVaGetValues (can,  XmNwidth, &w2, NULL);
1516   XtVaSetValues ((w1 > w2 ? can : ok), XmNwidth, (w1 > w2 ? w1 : w2), NULL);
1517
1518   XtVaGetValues (up,   XmNwidth, &w1, NULL);
1519   XtVaGetValues (down, XmNwidth, &w2, NULL);
1520   XtVaSetValues ((w1 > w2 ? down : up), XmNwidth, (w1 > w2 ? w1 : w2), NULL);
1521 }
1522
1523
1524
1525 \f
1526 /* The main demo-mode command loop.
1527  */
1528
1529 #if 0
1530 static Bool
1531 mapper (XrmDatabase *db, XrmBindingList bindings, XrmQuarkList quarks,
1532         XrmRepresentation *type, XrmValue *value, XPointer closure)
1533 {
1534   int i;
1535   for (i = 0; quarks[i]; i++)
1536     {
1537       if (bindings[i] == XrmBindTightly)
1538         fprintf (stderr, (i == 0 ? "" : "."));
1539       else if (bindings[i] == XrmBindLoosely)
1540         fprintf (stderr, "*");
1541       else
1542         fprintf (stderr, " ??? ");
1543       fprintf(stderr, "%s", XrmQuarkToString (quarks[i]));
1544     }
1545
1546   fprintf (stderr, ": %s\n", (char *) value->addr);
1547
1548   return False;
1549 }
1550 #endif
1551
1552
1553 static void
1554 the_network_is_not_the_computer (Widget parent)
1555 {
1556   Display *dpy = XtDisplay (parent);
1557   char *rversion, *ruser, *rhost;
1558   char *luser, *lhost;
1559   char *msg = 0;
1560   struct passwd *p = getpwuid (getuid ());
1561   const char *d = DisplayString (dpy);
1562
1563 # if defined(HAVE_UNAME)
1564   struct utsname uts;
1565   if (uname (&uts) < 0)
1566     lhost = "<UNKNOWN>";
1567   else
1568     lhost = uts.nodename;
1569 # elif defined(VMS)
1570   strcpy (lhost, getenv("SYS$NODE"));
1571 # else  /* !HAVE_UNAME && !VMS */
1572   strcat (lhost, "<UNKNOWN>");
1573 # endif /* !HAVE_UNAME && !VMS */
1574
1575   if (p && p->pw_name)
1576     luser = p->pw_name;
1577   else
1578     luser = "???";
1579
1580   server_xscreensaver_version (dpy, &rversion, &ruser, &rhost);
1581
1582   /* Make a buffer that's big enough for a number of copies of all the
1583      strings, plus some. */
1584   msg = (char *) malloc (10 * ((rversion ? strlen(rversion) : 0) +
1585                                (ruser ? strlen(ruser) : 0) +
1586                                (rhost ? strlen(rhost) : 0) +
1587                                strlen(lhost) +
1588                                strlen(luser) +
1589                                strlen(d) +
1590                                1024));
1591   *msg = 0;
1592
1593   if (!rversion || !*rversion)
1594     {
1595       sprintf (msg,
1596                "Warning:\n\n"
1597                "The XScreenSaver daemon doesn't seem to be running\n"
1598                "on display \"%s\".  You can launch it by selecting\n"
1599                "`Restart Daemon' from the File menu, or by typing\n"
1600                "\"xscreensaver &\" in a shell.",
1601                d);
1602     }
1603   else if (p && ruser && *ruser && !!strcmp (ruser, p->pw_name))
1604     {
1605       /* Warn that the two processes are running as different users.
1606        */
1607       sprintf(msg,
1608                "Warning:\n\n"
1609               "%s is running as user \"%s\" on host \"%s\".\n"
1610               "But the xscreensaver managing display \"%s\"\n"
1611               "is running as user \"%s\" on host \"%s\".\n"
1612               "\n"
1613               "Since they are different users, they won't be reading/writing\n"
1614               "the same ~/.xscreensaver file, so %s isn't\n"
1615               "going to work right.\n"
1616               "\n"
1617               "Either re-run %s as \"%s\", or re-run\n"
1618               "xscreensaver as \"%s\" (which you can do by\n"
1619               "selecting `Restart Daemon' from the File menu.)\n",
1620               progname, luser, lhost,
1621               d,
1622               (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1623               progname,
1624               progname, (ruser ? ruser : "???"),
1625               luser);
1626     }
1627   else if (rhost && *rhost && !!strcmp (rhost, lhost))
1628     {
1629       /* Warn that the two processes are running on different hosts.
1630        */
1631       sprintf (msg,
1632                "Warning:\n\n"
1633                "%s is running as user \"%s\" on host \"%s\".\n"
1634                "But the xscreensaver managing display \"%s\"\n"
1635                "is running as user \"%s\" on host \"%s\".\n"
1636                "\n"
1637                "If those two machines don't share a file system (that is,\n"
1638                "if they don't see the same ~%s/.xscreensaver file) then\n"
1639                "%s won't work right.\n"
1640                "\n"
1641                "You can restart the daemon on \"%s\" as \"%s\" by\n"
1642                "selecting `Restart Daemon' from the File menu.)",
1643                progname, luser, lhost,
1644                d,
1645                (ruser ? ruser : "???"), (rhost ? rhost : "???"),
1646                luser,
1647                progname,
1648                lhost, luser);
1649     }
1650   else if (!!strcmp (rversion, short_version))
1651     {
1652       /* Warn that the version numbers don't match.
1653        */
1654       sprintf (msg,
1655                "Warning:\n\n"
1656                "This is %s version %s.\n"
1657                "But the xscreensaver managing display \"%s\"\n"
1658                "is version %s.  This could cause problems.",
1659                progname, short_version,
1660                d,
1661                rversion);
1662     }
1663
1664
1665   if (*msg)
1666     warning_dialog (parent, msg, 1);
1667
1668   free (msg);
1669 }
1670
1671
1672 /* We use this error handler so that X errors are preceeded by the name
1673    of the program that generated them.
1674  */
1675 static int
1676 demo_ehandler (Display *dpy, XErrorEvent *error)
1677 {
1678   fprintf (stderr, "\nX error in %s:\n", progname);
1679   XmuPrintDefaultErrorMessage (dpy, error, stderr);
1680   exit (-1);
1681   return 0;
1682 }
1683
1684
1685
1686 #ifdef __GNUC__
1687  __extension__     /* shut up about "string length is greater than the length
1688                       ISO C89 compilers are required to support" when including
1689                       the .ad file... */
1690 #endif
1691
1692 static char *defaults[] = {
1693 #include "XScreenSaver_ad.h"
1694 #include "XScreenSaver_Xm_ad.h"
1695  0
1696 };
1697
1698
1699 int
1700 main (int argc, char **argv)
1701 {
1702   XtAppContext app;
1703   prefs_pair Pair, *pair;
1704   saver_preferences P, P2, *p, *p2;
1705   Bool prefs = False;
1706   int i;
1707   Display *dpy;
1708   Widget toplevel_shell, dialog;
1709   char *real_progname = argv[0];
1710   char *s;
1711
1712   s = strrchr (real_progname, '/');
1713   if (s) real_progname = s+1;
1714
1715   p = &P;
1716   p2 = &P2;
1717   pair = &Pair;
1718   pair->a = p;
1719   pair->b = p2;
1720   memset (p,  0, sizeof (*p));
1721   memset (p2, 0, sizeof (*p2));
1722
1723   global_prefs_pair = pair;
1724
1725   progname = real_progname;
1726
1727   /* We must read exactly the same resources as xscreensaver.
1728      That means we must have both the same progclass *and* progname,
1729      at least as far as the resource database is concerned.  So,
1730      put "xscreensaver" in argv[0] while initializing Xt.
1731    */
1732   argv[0] = "xscreensaver";
1733   progname = argv[0];
1734
1735
1736   toplevel_shell = XtAppInitialize (&app, progclass, 0, 0, &argc, argv,
1737                                     defaults, 0, 0);
1738
1739   dpy = XtDisplay (toplevel_shell);
1740   db = XtDatabase (dpy);
1741   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
1742   XSetErrorHandler (demo_ehandler);
1743
1744   /* Complain about unrecognized command-line arguments.
1745    */
1746   for (i = 1; i < argc; i++)
1747     {
1748       char *s = argv[i];
1749       if (s[0] == '-' && s[1] == '-')
1750         s++;
1751       if (!strcmp (s, "-prefs"))
1752         prefs = True;
1753       else
1754         {
1755           fprintf (stderr, "usage: %s [ -display dpy-string ] [ -prefs ]\n",
1756                    real_progname);
1757           exit (1);
1758         }
1759     }
1760
1761   short_version = (char *) malloc (5);
1762   memcpy (short_version, screensaver_id + 17, 4);
1763   short_version [4] = 0;
1764
1765   /* Load the init file, which may end up consulting the X resource database
1766      and the site-wide app-defaults file.  Note that at this point, it's
1767      important that `progname' be "xscreensaver", rather than whatever
1768      was in argv[0].
1769    */
1770   p->db = db;
1771   load_init_file (dpy, p);
1772   *p2 = *p;
1773
1774   /* Now that Xt has been initialized, and the resources have been read,
1775      we can set our `progname' variable to something more in line with
1776      reality.
1777    */
1778   progname = real_progname;
1779
1780
1781 #if 0
1782   {
1783     XrmName name = { 0 };
1784     XrmClass class = { 0 };
1785     int count = 0;
1786     XrmEnumerateDatabase (db, &name, &class, XrmEnumAllLevels, mapper,
1787                           (POINTER) &count);
1788   }
1789 #endif
1790
1791
1792   /* Intern the atoms that xscreensaver_command() needs.
1793    */
1794   XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
1795   XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
1796   XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION",False);
1797   XA_SCREENSAVER_STATUS = XInternAtom (dpy, "_SCREENSAVER_STATUS", False);
1798   XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
1799   XA_SCREENSAVER_RESPONSE = XInternAtom (dpy, "_SCREENSAVER_RESPONSE", False);
1800   XA_SELECT = XInternAtom (dpy, "SELECT", False);
1801   XA_DEMO = XInternAtom (dpy, "DEMO", False);
1802   XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
1803   XA_BLANK = XInternAtom (dpy, "BLANK", False);
1804   XA_LOCK = XInternAtom (dpy, "LOCK", False);
1805   XA_EXIT = XInternAtom (dpy, "EXIT", False);
1806   XA_RESTART = XInternAtom (dpy, "RESTART", False);
1807
1808   /* Create the window and all its widgets.
1809    */
1810   dialog = create_xscreensaver_demo (toplevel_shell);
1811
1812   /* Set the window's title. */
1813   {
1814     char title[255];
1815     char *v = (char *) strdup(strchr(screensaver_id, ' '));
1816     char *s1, *s2, *s3, *s4;
1817     s1 = (char *) strchr(v,  ' '); s1++;
1818     s2 = (char *) strchr(s1, ' ');
1819     s3 = (char *) strchr(v,  '('); s3++;
1820     s4 = (char *) strchr(s3, ')');
1821     *s2 = 0;
1822     *s4 = 0;
1823     sprintf (title, "%.50s %.50s, %.50s", progclass, s1, s3);
1824     XtVaSetValues (toplevel_shell, XtNtitle, title, NULL);
1825     free (v);
1826   }
1827
1828   sanity_check_resources (toplevel_shell);
1829   add_callbacks (toplevel_shell, pair);
1830   populate_hack_list (toplevel_shell, pair);
1831   populate_prefs_page (toplevel_shell, pair);
1832   sensitize_demo_widgets (toplevel_shell, False);
1833   scroll_to_current_hack (toplevel_shell, pair);
1834
1835   XtManageChild (dialog);
1836   XtRealizeWidget(toplevel_shell);
1837
1838   /* The next few calls must come after XtRealizeWidget(). */
1839   pixmapify_buttons (toplevel_shell);
1840   hack_button_sizes (toplevel_shell);
1841   ensure_selected_item_visible (name_to_widget (toplevel_shell, "list"));
1842
1843   XSync (dpy, False);
1844   XtVaSetValues (toplevel_shell, XmNallowShellResize, False, NULL);
1845
1846
1847   /* Handle the -prefs command-line argument. */
1848   if (prefs)
1849     {
1850       Widget tabber = name_to_widget (toplevel_shell, "folder");
1851       Widget this_tab = name_to_widget (toplevel_shell, "optionsTab");
1852       Widget this_page = name_to_widget (toplevel_shell, "preferencesForm");
1853       Widget *kids = 0;
1854       Cardinal nkids = 0;
1855       if (!tabber) abort();
1856   
1857       XtVaGetValues (tabber, XmNnumChildren, &nkids, XmNchildren, &kids, NULL);
1858       if (!kids) abort();
1859       if (nkids > 0)
1860         XtUnmanageChildren (kids, nkids);
1861
1862       XtManageChild (this_page);
1863
1864       XmProcessTraversal (this_tab, XmTRAVERSE_CURRENT);
1865     }
1866
1867   /* Issue any warnings about the running xscreensaver daemon. */
1868   the_network_is_not_the_computer (toplevel_shell);
1869
1870
1871   XtAppMainLoop (app);
1872   exit (0);
1873 }
1874
1875 #endif /* HAVE_MOTIF -- whole file */