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