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