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