http://ftp.nluug.nl/pub/os/Linux/distr/pardusrepo/sources/xscreensaver-5.02.tar.gz
[xscreensaver] / hacks / screenhack.c
1 /* xscreensaver, Copyright (c) 1992-2006 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * And remember: X Windows is to graphics hacking as roman numerals are to
12  * the square root of pi.
13  */
14
15 /* This file contains simple code to open a window or draw on the root.
16    The idea being that, when writing a graphics hack, you can just link
17    with this .o to get all of the uninteresting junk out of the way.
18
19    Create a few static global procedures and variables:
20
21       static void *YOURNAME_init (Display *, Window);
22
23           Return an opaque structure representing your drawing state.
24
25       static unsigned long YOURNAME_draw (Display *, Window, void *closure);
26
27           Draw one frame.
28           The `closure' arg is your drawing state, that you created in `init'.
29           Return the number of microseconds to wait until the next frame.
30
31           This should return in some small fraction of a second. 
32           Do not call `usleep' or loop excessively.  For long loops, use a
33           finite state machine.
34
35       static void YOURNAME_reshape (Display *, Window, void *closure,
36                                     unsigned int width, unsigned int height);
37
38           Called when the size of the window changes with the new size.
39
40       static Bool YOURNAME_event (Display *, Window, void *closure,
41                                   XEvent *event);
42
43           Called when a keyboard or mouse event arrives.
44           Return True if you handle it in some way, False otherwise.
45
46       static void YOURNAME_event (Display *, Window, void *closure);
47
48            Called when you are done: free everything you've allocated,
49            including your private `state' structure.
50
51       static char YOURNAME_defaults [] = { "...", "...", ... , 0 };
52
53            This variable is an array of strings, your default resources.
54            Null-terminate the list.
55
56       static XrmOptionDescRec YOURNAME_options[] = { { ... }, ... { 0,0,0,0 } }
57
58            This variable describes your command-line options.
59            Null-terminate the list.
60
61       Finally , invoke the XSCREENSAVER_MODULE() macro to tie it all together.
62
63    Additional caveats:
64
65       - Make sure that all functions in your module are static (check this
66         by running "nm -g" on the .o file).
67
68       - Do not use global variables: all such info must be stored in the
69         private `state' structure.
70
71       - Do not static function-local variables, either.  Put it in `state'.
72
73         Assume that there are N independent runs of this code going in the
74         same address space at the same time: they must not affect each other.
75
76       - Don't forget to write an XML file to describe the user interface
77         of your screen saver module.  See .../hacks/config/README for details.
78  */
79
80 #define DEBUG_TIMING
81 #define DEBUG_PAIR
82
83 #include <stdio.h>
84 #include <X11/Intrinsic.h>
85 #include <X11/IntrinsicP.h>
86 #include <X11/CoreP.h>
87 #include <X11/Shell.h>
88 #include <X11/StringDefs.h>
89 #include <X11/keysym.h>
90
91 #ifdef __sgi
92 # include <X11/SGIScheme.h>     /* for SgiUseSchemes() */
93 #endif /* __sgi */
94
95 #ifdef HAVE_XMU
96 # ifndef VMS
97 #  include <X11/Xmu/Error.h>
98 # else /* VMS */
99 #  include <Xmu/Error.h>
100 # endif
101 #else
102 # include "xmu.h"
103 #endif
104
105 #include "screenhackI.h"
106 #include "version.h"
107 #include "vroot.h"
108
109 #ifndef _XSCREENSAVER_VROOT_H_
110 # error Error!  You have an old version of vroot.h!  Check -I args.
111 #endif /* _XSCREENSAVER_VROOT_H_ */
112
113 #ifndef isupper
114 # define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
115 #endif
116 #ifndef _tolower
117 # define _tolower(c)  ((c) - 'A' + 'a')
118 #endif
119
120
121 /* This is defined by the SCREENHACK_MAIN() macro via screenhack.h.
122  */
123 extern struct xscreensaver_function_table *xscreensaver_function_table;
124
125
126 const char *progname;   /* used by hacks in error messages */
127 const char *progclass;  /* used by ../utils/resources.c */
128 Bool mono_p;            /* used by hacks */
129
130
131 static XrmOptionDescRec default_options [] = {
132   { "-root",    ".root",                XrmoptionNoArg, "True" },
133   { "-window",  ".root",                XrmoptionNoArg, "False" },
134   { "-mono",    ".mono",                XrmoptionNoArg, "True" },
135   { "-install", ".installColormap",     XrmoptionNoArg, "True" },
136   { "-noinstall",".installColormap",    XrmoptionNoArg, "False" },
137   { "-visual",  ".visualID",            XrmoptionSepArg, 0 },
138   { "-window-id", ".windowID",          XrmoptionSepArg, 0 },
139
140 # ifdef DEBUG_PAIR
141   { "-pair",    ".pair",                XrmoptionNoArg, "True" },
142 # endif /* DEBUG_PAIR */
143   { 0, 0, 0, 0 }
144 };
145
146 static char *default_defaults[] = {
147   ".root:               false",
148   "*geometry:           600x480", /* this should be .geometry, but nooooo... */
149   "*mono:               false",
150   "*installColormap:    false",
151   "*visualID:           default",
152   "*windowID:           ",
153   "*desktopGrabber:     xscreensaver-getimage %s",
154   0
155 };
156
157 static XrmOptionDescRec *merged_options;
158 static int merged_options_size;
159 static char **merged_defaults;
160
161 static void
162 merge_options (void)
163 {
164   struct xscreensaver_function_table *ft = xscreensaver_function_table;
165
166   const XrmOptionDescRec *options = ft->options;
167   const char * const *defaults    = ft->defaults;
168   const char *progclass           = ft->progclass;
169
170   int def_opts_size, opts_size;
171   int def_defaults_size, defaults_size;
172
173   for (def_opts_size = 0; default_options[def_opts_size].option;
174        def_opts_size++)
175     ;
176   for (opts_size = 0; options[opts_size].option; opts_size++)
177     ;
178
179   merged_options_size = def_opts_size + opts_size;
180   merged_options = (XrmOptionDescRec *)
181     malloc ((merged_options_size + 1) * sizeof(*default_options));
182   memcpy (merged_options, default_options,
183           (def_opts_size * sizeof(*default_options)));
184   memcpy (merged_options + def_opts_size, options,
185           ((opts_size + 1) * sizeof(*default_options)));
186
187   for (def_defaults_size = 0; default_defaults[def_defaults_size];
188        def_defaults_size++)
189     ;
190   for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
191     ;
192   merged_defaults = (char **)
193     malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
194   memcpy (merged_defaults, default_defaults,
195           def_defaults_size * sizeof(*defaults));
196   memcpy (merged_defaults + def_defaults_size, defaults,
197           (defaults_size + 1) * sizeof(*defaults));
198
199   /* This totally sucks.  Xt should behave like this by default.
200      If the string in `defaults' looks like ".foo", change that
201      to "Progclass.foo".
202    */
203   {
204     char **s;
205     for (s = merged_defaults; *s; s++)
206       if (**s == '.')
207         {
208           const char *oldr = *s;
209           char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
210           strcpy (newr, progclass);
211           strcat (newr, oldr);
212           *s = newr;
213         }
214   }
215 }
216
217 \f
218 /* Make the X errors print out the name of this program, so we have some
219    clue which one has a bug when they die under the screensaver.
220  */
221
222 static int
223 screenhack_ehandler (Display *dpy, XErrorEvent *error)
224 {
225   fprintf (stderr, "\nX error in %s:\n", progname);
226   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
227     exit (-1);
228   else
229     fprintf (stderr, " (nonfatal.)\n");
230   return 0;
231 }
232
233 static Bool
234 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
235 {
236   return (event->xany.type == MapNotify &&
237           event->xvisibility.window == (Window) window);
238 }
239
240
241 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
242
243 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
244    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
245    Returns False if the screen saver should now terminate.
246  */
247 static Bool
248 screenhack_handle_event_1 (Display *dpy, XEvent *event)
249 {
250   switch (event->xany.type)
251     {
252     case KeyPress:
253       {
254         KeySym keysym;
255         char c = 0;
256         XLookupString (&event->xkey, &c, 1, &keysym, 0);
257         if (c == 'q' ||
258             c == 'Q' ||
259             c == 3 ||   /* ^C */
260             c == 27)    /* ESC */
261           return False;  /* exit */
262         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
263           XBell (dpy, 0);  /* beep for non-chord keys */
264       }
265       break;
266     case ButtonPress:
267       XBell (dpy, 0);
268       break;
269     case ClientMessage:
270       {
271         if (event->xclient.message_type != XA_WM_PROTOCOLS)
272           {
273             char *s = XGetAtomName(dpy, event->xclient.message_type);
274             if (!s) s = "(null)";
275             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
276                      progname, s);
277           }
278         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
279           {
280             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
281             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
282             if (!s1) s1 = "(null)";
283             if (!s2) s2 = "(null)";
284             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
285                      progname, s1, s2);
286           }
287         else
288           {
289             return False;  /* exit */
290           }
291       }
292       break;
293     }
294   return True;
295 }
296
297
298 static Visual *
299 pick_visual (Screen *screen)
300 {
301   struct xscreensaver_function_table *ft = xscreensaver_function_table;
302
303   if (ft->pick_visual_hook)
304     {
305       Visual *v = ft->pick_visual_hook (screen);
306       if (v) return v;
307     }
308
309   return get_visual_resource (screen, "visualID", "VisualID", False);
310 }
311
312
313 /* Notice when the user has requested a different visual or colormap
314    on a pre-existing window (e.g., "-root -visual truecolor" or
315    "-window-id 0x2c00001 -install") and complain, since when drawing
316    on an existing window, we have no choice about these things.
317  */
318 static void
319 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
320                 Bool window_p)
321 {
322   struct xscreensaver_function_table *ft = xscreensaver_function_table;
323
324   char *visual_string = get_string_resource (DisplayOfScreen (screen),
325                                              "visualID", "VisualID");
326   Visual *desired_visual = pick_visual (screen);
327   char win[100];
328   char why[100];
329
330   if (window == RootWindowOfScreen (screen))
331     strcpy (win, "root window");
332   else
333     sprintf (win, "window 0x%lx", (unsigned long) window);
334
335   if (window_p)
336     sprintf (why, "-window-id 0x%lx", (unsigned long) window);
337   else
338     strcpy (why, "-root");
339
340   if (visual_string && *visual_string)
341     {
342       char *s;
343       for (s = visual_string; *s; s++)
344         if (isupper (*s)) *s = _tolower (*s);
345
346       if (!strcmp (visual_string, "default") ||
347           !strcmp (visual_string, "default") ||
348           !strcmp (visual_string, "best"))
349         /* don't warn about these, just silently DWIM. */
350         ;
351       else if (visual != desired_visual)
352         {
353           fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
354                    progname, visual_string, why);
355           fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
356                    progname, win, XVisualIDFromVisual (visual));
357         }
358       free (visual_string);
359     }
360
361   if (visual == DefaultVisualOfScreen (screen) &&
362       has_writable_cells (screen, visual) &&
363       get_boolean_resource (DisplayOfScreen (screen),
364                             "installColormap", "InstallColormap"))
365     {
366       fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
367                progname, why);
368       fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
369                progname, win, (unsigned long) cmap);
370     }
371
372   if (ft->validate_visual_hook)
373     {
374       if (! ft->validate_visual_hook (screen, win, visual))
375         exit (1);
376     }
377 }
378
379
380 static void
381 fix_fds (void)
382 {
383   /* Bad Things Happen if stdin, stdout, and stderr have been closed
384      (as by the `sh incantation "attraction >&- 2>&-").  When you do
385      that, the X connection gets allocated to one of these fds, and
386      then some random library writes to stderr, and random bits get
387      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
388      So, we cause the first three file descriptors to be open to
389      /dev/null if they aren't open to something else already.  This
390      must be done before any other files are opened (or the closing
391      of that other file will again free up one of the "magic" first
392      three FDs.)
393
394      We do this by opening /dev/null three times, and then closing
395      those fds, *unless* any of them got allocated as #0, #1, or #2,
396      in which case we leave them open.  Gag.
397
398      Really, this crap is technically required of *every* X program,
399      if you want it to be robust in the face of "2>&-".
400    */
401   int fd0 = open ("/dev/null", O_RDWR);
402   int fd1 = open ("/dev/null", O_RDWR);
403   int fd2 = open ("/dev/null", O_RDWR);
404   if (fd0 > 2) close (fd0);
405   if (fd1 > 2) close (fd1);
406   if (fd2 > 2) close (fd2);
407 }
408
409
410 #ifdef DEBUG_TIMING
411
412 static void
413 check_timing (unsigned long delay)
414 {
415   static unsigned long frame_count = 0;
416   static unsigned long delay_sum = 0;
417   static struct timeval prev1 = { 0, };
418   static struct timeval prev2 = { 0, };
419   struct timeval now;
420   double uprev1, uprev2, unow;
421
422 # ifdef GETTIMEOFDAY_TWO_ARGS
423   gettimeofday (&now, 0);
424 # else
425   gettimeofday (&now);
426 # endif
427
428   if (prev1.tv_sec == 0)
429     prev1 = prev2 = now;
430
431   frame_count++;
432
433   uprev1 = prev1.tv_sec + ((double) prev1.tv_usec * 0.000001);
434   uprev2 = prev2.tv_sec + ((double) prev2.tv_usec * 0.000001);
435   unow   =  now.tv_sec  + ((double)   now.tv_usec * 0.000001);
436
437   if (unow >= uprev1 + 1.5)
438     fprintf (stderr,
439              "%s: warning: blocked event processing for %.1f secs!\n",
440              progname, unow - uprev1);
441   prev1 = now;
442
443   if (unow >= uprev2 + 10.0)
444     {
445       double fps   = frame_count / (unow - uprev2);
446       double elapsed = unow - uprev2;
447       double slept = (delay_sum * 0.000001);
448       double sleep_ratio = slept / elapsed;
449
450       if (sleep_ratio < 0.10) {
451         fprintf (stderr, 
452                  "%s: warning: only %.0f%% idle over the"
453                  " last %2.0f secs (at %.1f FPS)\n",
454                  progname, 100 * sleep_ratio, elapsed, fps);
455       }
456
457       prev2 = now;
458       frame_count = 0;
459       delay_sum = 0;
460     }
461
462   delay_sum += delay;
463 }
464
465 #else  /* !DEBUG_TIMING */
466 # define check_timing(delay) /**/
467 #endif /* !DEBUG_TIMING */
468
469 static Boolean
470 screenhack_table_handle_events (Display *dpy,
471                                 const struct xscreensaver_function_table *ft,
472                                 Window window, void *closure
473 #ifdef DEBUG_PAIR
474                                 , Window window2, void *closure2
475 #endif
476                                 )
477 {
478   XtAppContext app = XtDisplayToApplicationContext (dpy);
479
480   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
481     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
482
483   while (XPending (dpy))
484     {
485       XEvent event;
486       XNextEvent (dpy, &event);
487
488       if (event.xany.type == ConfigureNotify)
489         {
490           if (event.xany.window == window)
491             ft->reshape_cb (dpy, window, closure,
492                             event.xconfigure.width, event.xconfigure.height);
493 #ifdef DEBUG_PAIR
494           if (event.xany.window == window2)
495             ft->reshape_cb (dpy, window2, closure2,
496                             event.xconfigure.width, event.xconfigure.height);
497 #endif
498         }
499       else if (event.xany.type == ClientMessage ||
500                (! (event.xany.window == window
501                    ? ft->event_cb (dpy, window, closure, &event)
502 #ifdef DEBUG_PAIR
503                    : event.xany.window == window2
504                    ? ft->event_cb (dpy, window2, closure2, &event)
505 #endif
506                    : 0)))
507         if (! screenhack_handle_event_1 (dpy, &event))
508           return False;
509
510       if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
511         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
512     }
513   return True;
514 }
515
516
517 static Boolean
518 usleep_and_process_events (Display *dpy,
519                            const struct xscreensaver_function_table *ft,
520                            Window window, void *closure, unsigned long delay
521 #ifdef DEBUG_PAIR
522                          , Window window2, void *closure2, unsigned long delay2
523 #endif
524                            )
525 {
526   do {
527     unsigned long quantum = 100000;  /* 1/10th second */
528     if (quantum > delay) 
529       quantum = delay;
530     delay -= quantum;
531
532     XSync (dpy, False);
533     if (quantum > 0)
534       usleep (quantum);
535
536     check_timing (quantum);
537
538     /* The above isn't quite right in pair-mode: we always run both windows
539        with the timing of window 2.  But, it's just a debugging hack, so
540        that doesn't really matter that much... */
541
542     if (! screenhack_table_handle_events (dpy, ft, window, closure
543 #ifdef DEBUG_PAIR
544                                           , window2, closure2
545 #endif
546                                           ))
547       return False;
548   } while (delay > 0);
549
550   return True;
551 }
552
553
554 static void
555 run_screenhack_table (Display *dpy, 
556                       Window window,
557 # ifdef DEBUG_PAIR
558                       Window window2,
559 # endif
560                       const struct xscreensaver_function_table *ft)
561 {
562
563   /* Kludge: even though the init_cb functions are declared to take 2 args,
564      actually call them with 3, for the benefit of xlockmore_init() and
565      xlockmore_setup().
566    */
567   void *(*init_cb) (Display *, Window, void *) = 
568     (void *(*) (Display *, Window, void *)) ft->init_cb;
569
570   void *closure = init_cb (dpy, window, ft->setup_arg);
571
572 #ifdef DEBUG_PAIR
573   void *closure2 = 0;
574   if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
575 #endif
576
577   if (! closure)  /* if it returns nothing, it can't possibly be re-entrant. */
578     abort();
579
580   while (1)
581     {
582       unsigned long delay = ft->draw_cb (dpy, window, closure);
583 #ifdef DEBUG_PAIR
584       unsigned long delay2;
585       if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
586 #endif
587
588       if (! usleep_and_process_events (dpy, ft,
589                                        window, closure, delay
590 #ifdef DEBUG_PAIR
591                                        , window2, closure2, delay2
592 #endif
593                                        ))
594         break;
595     }
596
597   ft->free_cb (dpy, window, closure);
598
599 #ifdef DEBUG_PAIR
600   if (window2)
601     ft->free_cb (dpy, window2, closure2);
602 #endif
603 }
604
605
606 static Widget
607 make_shell (Screen *screen, Widget toplevel, int width, int height)
608 {
609   Display *dpy = DisplayOfScreen (screen);
610   Visual *visual = pick_visual (screen);
611   Boolean def_visual_p = (toplevel && 
612                           visual == DefaultVisualOfScreen (screen));
613
614   if (width  <= 0) width  = 600;
615   if (height <= 0) height = 480;
616
617 # ifdef USE_GL
618   if (!validate_gl_visual (stderr, screen, "window", visual))
619     exit (1);
620 # endif /* USE_GL */
621
622   if (def_visual_p)
623     {
624       Window window;
625       XtVaSetValues (toplevel,
626                      XtNmappedWhenManaged, False,
627                      XtNwidth, width,
628                      XtNheight, height,
629                      XtNinput, True,  /* for WM_HINTS */
630                      NULL);
631       XtRealizeWidget (toplevel);
632       window = XtWindow (toplevel);
633
634       if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
635         {
636           Colormap cmap = 
637             XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
638                              AllocNone);
639           XSetWindowColormap (dpy, window, cmap);
640         }
641     }
642   else
643     {
644       unsigned int bg, bd;
645       Widget new;
646       Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
647                                        visual, AllocNone);
648       bg = get_pixel_resource (dpy, cmap, "background", "Background");
649       bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
650
651       new = XtVaAppCreateShell (progname, progclass,
652                                 topLevelShellWidgetClass, dpy,
653                                 XtNmappedWhenManaged, False,
654                                 XtNvisual, visual,
655                                 XtNdepth, visual_depth (screen, visual),
656                                 XtNwidth, width,
657                                 XtNheight, height,
658                                 XtNcolormap, cmap,
659                                 XtNbackground, (Pixel) bg,
660                                 XtNborderColor, (Pixel) bd,
661                                 XtNinput, True,  /* for WM_HINTS */
662                                 NULL);
663
664       if (!toplevel)  /* kludge for the second window in -pair mode... */
665         XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
666
667       XtRealizeWidget (new);
668       toplevel = new;
669     }
670
671   return toplevel;
672 }
673
674 static void
675 init_window (Display *dpy, Widget toplevel, const char *title)
676 {
677   Window window;
678   XWindowAttributes xgwa;
679   XtPopup (toplevel, XtGrabNone);
680   XtVaSetValues (toplevel, XtNtitle, title, NULL);
681
682   /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
683    */
684   window = XtWindow (toplevel);
685   XGetWindowAttributes (dpy, window, &xgwa);
686   XSelectInput (dpy, window,
687                 (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
688                  ButtonPressMask | ButtonReleaseMask));
689   XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
690                    PropModeReplace,
691                    (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
692 }
693
694
695 int
696 main (int argc, char **argv)
697 {
698   struct xscreensaver_function_table *ft = xscreensaver_function_table;
699
700   XWindowAttributes xgwa;
701   Widget toplevel;
702   Display *dpy;
703   Window window;
704 # ifdef DEBUG_PAIR
705   Window window2 = 0;
706   Widget toplevel2 = 0;
707 # endif
708   XtAppContext app;
709   Bool root_p;
710   Window on_window = 0;
711   XEvent event;
712   Boolean dont_clear;
713   char version[255];
714
715   fix_fds();
716
717   progname = argv[0];   /* reset later */
718   progclass = ft->progclass;
719
720   if (ft->setup_cb)
721     ft->setup_cb (ft, ft->setup_arg);
722
723   merge_options ();
724
725 #ifdef __sgi
726   /* We have to do this on SGI to prevent the background color from being
727      overridden by the current desktop color scheme (we'd like our backgrounds
728      to be black, thanks.)  This should be the same as setting the
729      "*useSchemes: none" resource, but it's not -- if that resource is
730      present in the `default_defaults' above, it doesn't work, though it
731      does work when passed as an -xrm arg on the command line.  So screw it,
732      turn them off from C instead.
733    */
734   SgiUseSchemes ("none"); 
735 #endif /* __sgi */
736
737   toplevel = XtAppInitialize (&app, progclass, merged_options,
738                               merged_options_size, &argc, argv,
739                               merged_defaults, 0, 0);
740
741   dpy = XtDisplay (toplevel);
742
743   XtGetApplicationNameAndClass (dpy,
744                                 (char **) &progname,
745                                 (char **) &progclass);
746
747   /* half-assed way of avoiding buffer-overrun attacks. */
748   if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
749
750   XSetErrorHandler (screenhack_ehandler);
751
752   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
753   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
754
755   {
756     char *v = (char *) strdup(strchr(screensaver_id, ' '));
757     char *s1, *s2, *s3, *s4;
758     s1 = (char *) strchr(v,  ' '); s1++;
759     s2 = (char *) strchr(s1, ' ');
760     s3 = (char *) strchr(v,  '('); s3++;
761     s4 = (char *) strchr(s3, ')');
762     *s2 = 0;
763     *s4 = 0;
764     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
765              progclass, s1, s3);
766     free(v);
767   }
768
769   if (argc > 1)
770     {
771       const char *s;
772       int i;
773       int x = 18;
774       int end = 78;
775       Bool help_p = (!strcmp(argv[1], "-help") ||
776                      !strcmp(argv[1], "--help"));
777       fprintf (stderr, "%s\n", version);
778       for (s = progclass; *s; s++) fprintf(stderr, " ");
779       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
780
781       if (!help_p)
782         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
783       fprintf (stderr, "Options include: ");
784       for (i = 0; i < merged_options_size; i++)
785         {
786           char *sw = merged_options [i].option;
787           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
788           int size = strlen (sw) + (argp ? 6 : 0) + 2;
789           if (x + size >= end)
790             {
791               fprintf (stderr, "\n\t\t ");
792               x = 18;
793             }
794           x += size;
795           fprintf (stderr, "%s", sw);
796           if (argp) fprintf (stderr, " <arg>");
797           if (i != merged_options_size - 1) fprintf (stderr, ", ");
798         }
799
800       fprintf (stderr, ".\n");
801
802 #if 0
803       if (help_p)
804         {
805           fprintf (stderr, "\nResources:\n\n");
806           for (i = 0; i < merged_options_size; i++)
807             {
808               const char *opt = merged_options [i].option;
809               const char *res = merged_options [i].specifier + 1;
810               const char *val = merged_options [i].value;
811               char *s = get_string_resource (dpy, (char *) res, (char *) res);
812
813               if (s)
814                 {
815                   int L = strlen(s);
816                 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
817                   s[--L] = 0;
818                 }
819
820               fprintf (stderr, "    %-16s %-18s ", opt, res);
821               if (merged_options [i].argKind == XrmoptionSepArg)
822                 {
823                   fprintf (stderr, "[%s]", (s ? s : "?"));
824                 }
825               else
826                 {
827                   fprintf (stderr, "%s", (val ? val : "(null)"));
828                   if (val && s && !strcasecmp (val, s))
829                     fprintf (stderr, " [default]");
830                 }
831               fprintf (stderr, "\n");
832             }
833           fprintf (stderr, "\n");
834         }
835 #endif
836
837       exit (help_p ? 0 : 1);
838     }
839
840   free (merged_options);
841   free (merged_defaults);
842   merged_options = 0;
843   merged_defaults = 0;
844
845   dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
846   mono_p = get_boolean_resource (dpy, "mono", "Boolean");
847   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
848     mono_p = True;
849
850   root_p = get_boolean_resource (dpy, "root", "Boolean");
851
852   {
853     char *s = get_string_resource (dpy, "windowID", "WindowID");
854     if (s && *s)
855       on_window = get_integer_resource (dpy, "windowID", "WindowID");
856     if (s) free (s);
857   }
858
859   if (on_window)
860     {
861       window = (Window) on_window;
862       XtDestroyWidget (toplevel);
863       XGetWindowAttributes (dpy, window, &xgwa);
864       visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
865
866       /* Select KeyPress and resize events on the external window.
867        */
868       xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
869       XSelectInput (dpy, window, xgwa.your_event_mask);
870
871       /* Select ButtonPress and ButtonRelease events on the external window,
872          if no other app has already selected them (only one app can select
873          ButtonPress at a time: BadAccess results.)
874        */
875       if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
876         XSelectInput (dpy, window,
877                       (xgwa.your_event_mask |
878                        ButtonPressMask | ButtonReleaseMask));
879     }
880   else if (root_p)
881     {
882       window = VirtualRootWindowOfScreen (XtScreen (toplevel));
883       XtDestroyWidget (toplevel);
884       XGetWindowAttributes (dpy, window, &xgwa);
885       visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
886     }
887   else
888     {
889       Widget new = make_shell (XtScreen (toplevel), toplevel,
890                                toplevel->core.width,
891                                toplevel->core.height);
892       if (new != toplevel)
893         {
894           XtDestroyWidget (toplevel);
895           toplevel = new;
896         }
897
898       init_window (dpy, toplevel, version);
899       window = XtWindow (toplevel);
900       XGetWindowAttributes (dpy, window, &xgwa);
901
902 # ifdef DEBUG_PAIR
903       if (get_boolean_resource (dpy, "pair", "Boolean"))
904         {
905           toplevel2 = make_shell (xgwa.screen, 0,
906                                   toplevel->core.width,
907                                   toplevel->core.height);
908           init_window (dpy, toplevel2, version);
909           window2 = XtWindow (toplevel2);
910         }
911 # endif /* DEBUG_PAIR */
912     }
913
914   if (!dont_clear)
915     {
916       unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
917                                             "background", "Background");
918       XSetWindowBackground (dpy, window, bg);
919       XClearWindow (dpy, window);
920 # ifdef DEBUG_PAIR
921       if (window2)
922         {
923           XSetWindowBackground (dpy, window2, bg);
924           XClearWindow (dpy, window2);
925         }
926 # endif
927     }
928
929   if (!root_p && !on_window)
930     /* wait for it to be mapped */
931     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
932
933   XSync (dpy, False);
934
935   /* This is the one and only place that the random-number generator is
936      seeded in any screenhack.  You do not need to seed the RNG again,
937      it is done for you before your code is invoked. */
938 # undef ya_rand_init
939   ya_rand_init (0);
940
941   run_screenhack_table (dpy, window, 
942 # ifdef DEBUG_PAIR
943                         window2,
944 # endif
945                         ft);
946
947   XtDestroyWidget (toplevel);
948   XtDestroyApplicationContext (app);
949
950   return 0;
951 }