http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[xscreensaver] / hacks / screenhack.c
1 /* xscreensaver, Copyright (c) 1992-2010 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       else
225         *s = strdup (*s);
226   }
227 }
228
229 \f
230 /* Make the X errors print out the name of this program, so we have some
231    clue which one has a bug when they die under the screensaver.
232  */
233
234 static int
235 screenhack_ehandler (Display *dpy, XErrorEvent *error)
236 {
237   fprintf (stderr, "\nX error in %s:\n", progname);
238   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
239     exit (-1);
240   else
241     fprintf (stderr, " (nonfatal.)\n");
242   return 0;
243 }
244
245 static Bool
246 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
247 {
248   return (event->xany.type == MapNotify &&
249           event->xvisibility.window == (Window) window);
250 }
251
252
253 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
254
255 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
256    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
257    Returns False if the screen saver should now terminate.
258  */
259 static Bool
260 screenhack_handle_event_1 (Display *dpy, XEvent *event)
261 {
262   switch (event->xany.type)
263     {
264     case KeyPress:
265       {
266         KeySym keysym;
267         char c = 0;
268         XLookupString (&event->xkey, &c, 1, &keysym, 0);
269         if (c == 'q' ||
270             c == 'Q' ||
271             c == 3 ||   /* ^C */
272             c == 27)    /* ESC */
273           return False;  /* exit */
274         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
275           XBell (dpy, 0);  /* beep for non-chord keys */
276       }
277       break;
278     case ButtonPress:
279       XBell (dpy, 0);
280       break;
281     case ClientMessage:
282       {
283         if (event->xclient.message_type != XA_WM_PROTOCOLS)
284           {
285             char *s = XGetAtomName(dpy, event->xclient.message_type);
286             if (!s) s = "(null)";
287             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
288                      progname, s);
289           }
290         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
291           {
292             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
293             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
294             if (!s1) s1 = "(null)";
295             if (!s2) s2 = "(null)";
296             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
297                      progname, s1, s2);
298           }
299         else
300           {
301             return False;  /* exit */
302           }
303       }
304       break;
305     }
306   return True;
307 }
308
309
310 static Visual *
311 pick_visual (Screen *screen)
312 {
313   struct xscreensaver_function_table *ft = xscreensaver_function_table;
314
315   if (ft->pick_visual_hook)
316     {
317       Visual *v = ft->pick_visual_hook (screen);
318       if (v) return v;
319     }
320
321   return get_visual_resource (screen, "visualID", "VisualID", False);
322 }
323
324
325 /* Notice when the user has requested a different visual or colormap
326    on a pre-existing window (e.g., "-root -visual truecolor" or
327    "-window-id 0x2c00001 -install") and complain, since when drawing
328    on an existing window, we have no choice about these things.
329  */
330 static void
331 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
332                 Bool window_p)
333 {
334   struct xscreensaver_function_table *ft = xscreensaver_function_table;
335
336   char *visual_string = get_string_resource (DisplayOfScreen (screen),
337                                              "visualID", "VisualID");
338   Visual *desired_visual = pick_visual (screen);
339   char win[100];
340   char why[100];
341
342   if (window == RootWindowOfScreen (screen))
343     strcpy (win, "root window");
344   else
345     sprintf (win, "window 0x%lx", (unsigned long) window);
346
347   if (window_p)
348     sprintf (why, "-window-id 0x%lx", (unsigned long) window);
349   else
350     strcpy (why, "-root");
351
352   if (visual_string && *visual_string)
353     {
354       char *s;
355       for (s = visual_string; *s; s++)
356         if (isupper (*s)) *s = _tolower (*s);
357
358       if (!strcmp (visual_string, "default") ||
359           !strcmp (visual_string, "default") ||
360           !strcmp (visual_string, "best"))
361         /* don't warn about these, just silently DWIM. */
362         ;
363       else if (visual != desired_visual)
364         {
365           fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
366                    progname, visual_string, why);
367           fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
368                    progname, win, XVisualIDFromVisual (visual));
369         }
370       free (visual_string);
371     }
372
373   if (visual == DefaultVisualOfScreen (screen) &&
374       has_writable_cells (screen, visual) &&
375       get_boolean_resource (DisplayOfScreen (screen),
376                             "installColormap", "InstallColormap"))
377     {
378       fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
379                progname, why);
380       fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
381                progname, win, (unsigned long) cmap);
382     }
383
384   if (ft->validate_visual_hook)
385     {
386       if (! ft->validate_visual_hook (screen, win, visual))
387         exit (1);
388     }
389 }
390
391
392 static void
393 fix_fds (void)
394 {
395   /* Bad Things Happen if stdin, stdout, and stderr have been closed
396      (as by the `sh incantation "attraction >&- 2>&-").  When you do
397      that, the X connection gets allocated to one of these fds, and
398      then some random library writes to stderr, and random bits get
399      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
400      So, we cause the first three file descriptors to be open to
401      /dev/null if they aren't open to something else already.  This
402      must be done before any other files are opened (or the closing
403      of that other file will again free up one of the "magic" first
404      three FDs.)
405
406      We do this by opening /dev/null three times, and then closing
407      those fds, *unless* any of them got allocated as #0, #1, or #2,
408      in which case we leave them open.  Gag.
409
410      Really, this crap is technically required of *every* X program,
411      if you want it to be robust in the face of "2>&-".
412    */
413   int fd0 = open ("/dev/null", O_RDWR);
414   int fd1 = open ("/dev/null", O_RDWR);
415   int fd2 = open ("/dev/null", O_RDWR);
416   if (fd0 > 2) close (fd0);
417   if (fd1 > 2) close (fd1);
418   if (fd2 > 2) close (fd2);
419 }
420
421
422 static Boolean
423 screenhack_table_handle_events (Display *dpy,
424                                 const struct xscreensaver_function_table *ft,
425                                 Window window, void *closure
426 #ifdef DEBUG_PAIR
427                                 , Window window2, void *closure2
428 #endif
429                                 )
430 {
431   XtAppContext app = XtDisplayToApplicationContext (dpy);
432
433   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
434     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
435
436   while (XPending (dpy))
437     {
438       XEvent event;
439       XNextEvent (dpy, &event);
440
441       if (event.xany.type == ConfigureNotify)
442         {
443           if (event.xany.window == window)
444             ft->reshape_cb (dpy, window, closure,
445                             event.xconfigure.width, event.xconfigure.height);
446 #ifdef DEBUG_PAIR
447           if (window2 && event.xany.window == window2)
448             ft->reshape_cb (dpy, window2, closure2,
449                             event.xconfigure.width, event.xconfigure.height);
450 #endif
451         }
452       else if (event.xany.type == ClientMessage ||
453                (! (event.xany.window == window
454                    ? ft->event_cb (dpy, window, closure, &event)
455 #ifdef DEBUG_PAIR
456                    : (window2 && event.xany.window == window2)
457                    ? ft->event_cb (dpy, window2, closure2, &event)
458 #endif
459                    : 0)))
460         if (! screenhack_handle_event_1 (dpy, &event))
461           return False;
462
463       if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
464         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
465     }
466   return True;
467 }
468
469
470 static Boolean
471 usleep_and_process_events (Display *dpy,
472                            const struct xscreensaver_function_table *ft,
473                            Window window, fps_state *fpst, void *closure,
474                            unsigned long delay
475 #ifdef DEBUG_PAIR
476                          , Window window2, fps_state *fpst2, void *closure2,
477                            unsigned long delay2
478 #endif
479                            )
480 {
481   do {
482     unsigned long quantum = 100000;  /* 1/10th second */
483     if (quantum > delay) 
484       quantum = delay;
485     delay -= quantum;
486
487     XSync (dpy, False);
488     if (quantum > 0)
489       {
490         usleep (quantum);
491         if (fpst) fps_slept (fpst, quantum);
492 #ifdef DEBUG_PAIR
493         if (fpst2) fps_slept (fpst2, quantum);
494 #endif
495       }
496
497     if (! screenhack_table_handle_events (dpy, ft, window, closure
498 #ifdef DEBUG_PAIR
499                                           , window2, closure2
500 #endif
501                                           ))
502       return False;
503   } while (delay > 0);
504
505   return True;
506 }
507
508
509 static void
510 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
511 {
512   fps_compute (fpst, 0);
513   fps_draw (fpst);
514 }
515
516
517 static void
518 run_screenhack_table (Display *dpy, 
519                       Window window,
520 # ifdef DEBUG_PAIR
521                       Window window2,
522 # endif
523                       const struct xscreensaver_function_table *ft)
524 {
525
526   /* Kludge: even though the init_cb functions are declared to take 2 args,
527      actually call them with 3, for the benefit of xlockmore_init() and
528      xlockmore_setup().
529    */
530   void *(*init_cb) (Display *, Window, void *) = 
531     (void *(*) (Display *, Window, void *)) ft->init_cb;
532
533   void (*fps_cb) (Display *, Window, fps_state *, void *) = ft->fps_cb;
534
535   void *closure = init_cb (dpy, window, ft->setup_arg);
536   fps_state *fpst = fps_init (dpy, window);
537
538 #ifdef DEBUG_PAIR
539   void *closure2 = 0;
540   fps_state *fpst2 = 0;
541   if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
542   if (window2) fpst2 = fps_init (dpy, window2);
543 #endif
544
545   if (! closure)  /* if it returns nothing, it can't possibly be re-entrant. */
546     abort();
547
548   if (! fps_cb) fps_cb = screenhack_do_fps;
549
550   while (1)
551     {
552       unsigned long delay = ft->draw_cb (dpy, window, closure);
553 #ifdef DEBUG_PAIR
554       unsigned long delay2 = 0;
555       if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
556 #endif
557
558       if (fpst) fps_cb (dpy, window, fpst, closure);
559 #ifdef DEBUG_PAIR
560       if (fpst2) fps_cb (dpy, window, fpst2, closure);
561 #endif
562
563       if (! usleep_and_process_events (dpy, ft,
564                                        window, fpst, closure, delay
565 #ifdef DEBUG_PAIR
566                                        , window2, fpst2, closure2, delay2
567 #endif
568                                        ))
569         break;
570     }
571
572   ft->free_cb (dpy, window, closure);
573   if (fpst) fps_free (fpst);
574
575 #ifdef DEBUG_PAIR
576   if (window2) ft->free_cb (dpy, window2, closure2);
577   if (window2) fps_free (fpst2);
578 #endif
579 }
580
581
582 static Widget
583 make_shell (Screen *screen, Widget toplevel, int width, int height)
584 {
585   Display *dpy = DisplayOfScreen (screen);
586   Visual *visual = pick_visual (screen);
587   Boolean def_visual_p = (toplevel && 
588                           visual == DefaultVisualOfScreen (screen));
589
590   if (width  <= 0) width  = 600;
591   if (height <= 0) height = 480;
592
593   if (def_visual_p)
594     {
595       Window window;
596       XtVaSetValues (toplevel,
597                      XtNmappedWhenManaged, False,
598                      XtNwidth, width,
599                      XtNheight, height,
600                      XtNinput, True,  /* for WM_HINTS */
601                      NULL);
602       XtRealizeWidget (toplevel);
603       window = XtWindow (toplevel);
604
605       if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
606         {
607           Colormap cmap = 
608             XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
609                              AllocNone);
610           XSetWindowColormap (dpy, window, cmap);
611         }
612     }
613   else
614     {
615       unsigned int bg, bd;
616       Widget new;
617       Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
618                                        visual, AllocNone);
619       bg = get_pixel_resource (dpy, cmap, "background", "Background");
620       bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
621
622       new = XtVaAppCreateShell (progname, progclass,
623                                 topLevelShellWidgetClass, dpy,
624                                 XtNmappedWhenManaged, False,
625                                 XtNvisual, visual,
626                                 XtNdepth, visual_depth (screen, visual),
627                                 XtNwidth, width,
628                                 XtNheight, height,
629                                 XtNcolormap, cmap,
630                                 XtNbackground, (Pixel) bg,
631                                 XtNborderColor, (Pixel) bd,
632                                 XtNinput, True,  /* for WM_HINTS */
633                                 NULL);
634
635       if (!toplevel)  /* kludge for the second window in -pair mode... */
636         XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
637
638       XtRealizeWidget (new);
639       toplevel = new;
640     }
641
642   return toplevel;
643 }
644
645 static void
646 init_window (Display *dpy, Widget toplevel, const char *title)
647 {
648   Window window;
649   XWindowAttributes xgwa;
650   XtPopup (toplevel, XtGrabNone);
651   XtVaSetValues (toplevel, XtNtitle, title, NULL);
652
653   /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
654    */
655   window = XtWindow (toplevel);
656   XGetWindowAttributes (dpy, window, &xgwa);
657   XSelectInput (dpy, window,
658                 (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
659                  ButtonPressMask | ButtonReleaseMask));
660   XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
661                    PropModeReplace,
662                    (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
663 }
664
665
666 int
667 main (int argc, char **argv)
668 {
669   struct xscreensaver_function_table *ft = xscreensaver_function_table;
670
671   XWindowAttributes xgwa;
672   Widget toplevel;
673   Display *dpy;
674   Window window;
675 # ifdef DEBUG_PAIR
676   Window window2 = 0;
677   Widget toplevel2 = 0;
678 # endif
679   XtAppContext app;
680   Bool root_p;
681   Window on_window = 0;
682   XEvent event;
683   Boolean dont_clear;
684   char version[255];
685
686   fix_fds();
687
688   progname = argv[0];   /* reset later */
689   progclass = ft->progclass;
690
691   if (ft->setup_cb)
692     ft->setup_cb (ft, ft->setup_arg);
693
694   merge_options ();
695
696 #ifdef __sgi
697   /* We have to do this on SGI to prevent the background color from being
698      overridden by the current desktop color scheme (we'd like our backgrounds
699      to be black, thanks.)  This should be the same as setting the
700      "*useSchemes: none" resource, but it's not -- if that resource is
701      present in the `default_defaults' above, it doesn't work, though it
702      does work when passed as an -xrm arg on the command line.  So screw it,
703      turn them off from C instead.
704    */
705   SgiUseSchemes ("none"); 
706 #endif /* __sgi */
707
708   toplevel = XtAppInitialize (&app, progclass, merged_options,
709                               merged_options_size, &argc, argv,
710                               merged_defaults, 0, 0);
711
712   dpy = XtDisplay (toplevel);
713
714   XtGetApplicationNameAndClass (dpy,
715                                 (char **) &progname,
716                                 (char **) &progclass);
717
718   /* half-assed way of avoiding buffer-overrun attacks. */
719   if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
720
721   XSetErrorHandler (screenhack_ehandler);
722
723   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
724   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
725
726   {
727     char *v = (char *) strdup(strchr(screensaver_id, ' '));
728     char *s1, *s2, *s3, *s4;
729     s1 = (char *) strchr(v,  ' '); s1++;
730     s2 = (char *) strchr(s1, ' ');
731     s3 = (char *) strchr(v,  '('); s3++;
732     s4 = (char *) strchr(s3, ')');
733     *s2 = 0;
734     *s4 = 0;
735     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
736              progclass, s1, s3);
737     free(v);
738   }
739
740   if (argc > 1)
741     {
742       const char *s;
743       int i;
744       int x = 18;
745       int end = 78;
746       Bool help_p = (!strcmp(argv[1], "-help") ||
747                      !strcmp(argv[1], "--help"));
748       fprintf (stderr, "%s\n", version);
749       for (s = progclass; *s; s++) fprintf(stderr, " ");
750       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
751
752       if (!help_p)
753         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
754       fprintf (stderr, "Options include: ");
755       for (i = 0; i < merged_options_size; i++)
756         {
757           char *sw = merged_options [i].option;
758           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
759           int size = strlen (sw) + (argp ? 6 : 0) + 2;
760           if (x + size >= end)
761             {
762               fprintf (stderr, "\n\t\t ");
763               x = 18;
764             }
765           x += size;
766           fprintf (stderr, "%s", sw);
767           if (argp) fprintf (stderr, " <arg>");
768           if (i != merged_options_size - 1) fprintf (stderr, ", ");
769         }
770
771       fprintf (stderr, ".\n");
772
773 #if 0
774       if (help_p)
775         {
776           fprintf (stderr, "\nResources:\n\n");
777           for (i = 0; i < merged_options_size; i++)
778             {
779               const char *opt = merged_options [i].option;
780               const char *res = merged_options [i].specifier + 1;
781               const char *val = merged_options [i].value;
782               char *s = get_string_resource (dpy, (char *) res, (char *) res);
783
784               if (s)
785                 {
786                   int L = strlen(s);
787                 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
788                   s[--L] = 0;
789                 }
790
791               fprintf (stderr, "    %-16s %-18s ", opt, res);
792               if (merged_options [i].argKind == XrmoptionSepArg)
793                 {
794                   fprintf (stderr, "[%s]", (s ? s : "?"));
795                 }
796               else
797                 {
798                   fprintf (stderr, "%s", (val ? val : "(null)"));
799                   if (val && s && !strcasecmp (val, s))
800                     fprintf (stderr, " [default]");
801                 }
802               fprintf (stderr, "\n");
803             }
804           fprintf (stderr, "\n");
805         }
806 #endif
807
808       exit (help_p ? 0 : 1);
809     }
810
811   {
812     char **s;
813     for (s = merged_defaults; *s; s++)
814       free(*s);
815   }
816
817   free (merged_options);
818   free (merged_defaults);
819   merged_options = 0;
820   merged_defaults = 0;
821
822   dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
823   mono_p = get_boolean_resource (dpy, "mono", "Boolean");
824   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
825     mono_p = True;
826
827   root_p = get_boolean_resource (dpy, "root", "Boolean");
828
829   {
830     char *s = get_string_resource (dpy, "windowID", "WindowID");
831     if (s && *s)
832       on_window = get_integer_resource (dpy, "windowID", "WindowID");
833     if (s) free (s);
834   }
835
836   if (on_window)
837     {
838       window = (Window) on_window;
839       XtDestroyWidget (toplevel);
840       XGetWindowAttributes (dpy, window, &xgwa);
841       visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
842
843       /* Select KeyPress and resize events on the external window.
844        */
845       xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
846       XSelectInput (dpy, window, xgwa.your_event_mask);
847
848       /* Select ButtonPress and ButtonRelease events on the external window,
849          if no other app has already selected them (only one app can select
850          ButtonPress at a time: BadAccess results.)
851        */
852       if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
853         XSelectInput (dpy, window,
854                       (xgwa.your_event_mask |
855                        ButtonPressMask | ButtonReleaseMask));
856     }
857   else if (root_p)
858     {
859       window = VirtualRootWindowOfScreen (XtScreen (toplevel));
860       XtDestroyWidget (toplevel);
861       XGetWindowAttributes (dpy, window, &xgwa);
862       /* With RANDR, the root window can resize! */
863       XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask);
864       visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
865     }
866   else
867     {
868       Widget new = make_shell (XtScreen (toplevel), toplevel,
869                                toplevel->core.width,
870                                toplevel->core.height);
871       if (new != toplevel)
872         {
873           XtDestroyWidget (toplevel);
874           toplevel = new;
875         }
876
877       init_window (dpy, toplevel, version);
878       window = XtWindow (toplevel);
879       XGetWindowAttributes (dpy, window, &xgwa);
880
881 # ifdef DEBUG_PAIR
882       if (get_boolean_resource (dpy, "pair", "Boolean"))
883         {
884           toplevel2 = make_shell (xgwa.screen, 0,
885                                   toplevel->core.width,
886                                   toplevel->core.height);
887           init_window (dpy, toplevel2, version);
888           window2 = XtWindow (toplevel2);
889         }
890 # endif /* DEBUG_PAIR */
891     }
892
893   if (!dont_clear)
894     {
895       unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
896                                             "background", "Background");
897       XSetWindowBackground (dpy, window, bg);
898       XClearWindow (dpy, window);
899 # ifdef DEBUG_PAIR
900       if (window2)
901         {
902           XSetWindowBackground (dpy, window2, bg);
903           XClearWindow (dpy, window2);
904         }
905 # endif
906     }
907
908   if (!root_p && !on_window)
909     /* wait for it to be mapped */
910     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
911
912   XSync (dpy, False);
913
914   /* This is the one and only place that the random-number generator is
915      seeded in any screenhack.  You do not need to seed the RNG again,
916      it is done for you before your code is invoked. */
917 # undef ya_rand_init
918   ya_rand_init (0);
919
920   run_screenhack_table (dpy, window, 
921 # ifdef DEBUG_PAIR
922                         window2,
923 # endif
924                         ft);
925
926   XtDestroyWidget (toplevel);
927   XtDestroyApplicationContext (app);
928
929   return 0;
930 }