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