From http://www.jwz.org/xscreensaver/xscreensaver-5.37.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
559 #ifdef DEBUG_PAIR
560   void *closure2 = 0;
561   fps_state *fpst2 = 0;
562   if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
563   if (window2) fpst2 = fps_init (dpy, window2);
564 #endif
565
566   if (! closure)  /* if it returns nothing, it can't possibly be re-entrant. */
567     abort();
568
569   if (! fps_cb) fps_cb = screenhack_do_fps;
570
571   while (1)
572     {
573       unsigned long delay = ft->draw_cb (dpy, window, closure);
574 #ifdef DEBUG_PAIR
575       unsigned long delay2 = 0;
576       if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
577 #endif
578
579       if (fpst) fps_cb (dpy, window, fpst, closure);
580 #ifdef DEBUG_PAIR
581       if (fpst2) fps_cb (dpy, window2, fpst2, closure2);
582 #endif
583
584       if (! usleep_and_process_events (dpy, ft,
585                                        window, fpst, closure, delay
586 #ifdef DEBUG_PAIR
587                                        , window2, fpst2, closure2, delay2
588 #endif
589 #ifdef HAVE_RECORD_ANIM
590                                        , anim_state
591 #endif
592                                        ))
593         break;
594     }
595
596 #ifdef HAVE_RECORD_ANIM
597   /* Exiting before target frames hit: write the video anyway. */
598   if (anim_state) screenhack_record_anim_free (anim_state);
599 #endif
600
601   ft->free_cb (dpy, window, closure);
602   if (fpst) fps_free (fpst);
603
604 #ifdef DEBUG_PAIR
605   if (window2) ft->free_cb (dpy, window2, closure2);
606   if (fpst2) fps_free (fpst2);
607 #endif
608 }
609
610
611 static Widget
612 make_shell (Screen *screen, Widget toplevel, int width, int height)
613 {
614   Display *dpy = DisplayOfScreen (screen);
615   Visual *visual = pick_visual (screen);
616   Boolean def_visual_p = (toplevel && 
617                           visual == DefaultVisualOfScreen (screen));
618
619   if (width  <= 0) width  = 600;
620   if (height <= 0) height = 480;
621
622   if (def_visual_p)
623     {
624       Window window;
625       XtVaSetValues (toplevel,
626                      XtNmappedWhenManaged, False,
627                      XtNwidth, width,
628                      XtNheight, height,
629                      XtNinput, True,  /* for WM_HINTS */
630                      NULL);
631       XtRealizeWidget (toplevel);
632       window = XtWindow (toplevel);
633
634       if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
635         {
636           Colormap cmap = 
637             XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
638                              AllocNone);
639           XSetWindowColormap (dpy, window, cmap);
640         }
641     }
642   else
643     {
644       unsigned int bg, bd;
645       Widget new;
646       Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
647                                        visual, AllocNone);
648       bg = get_pixel_resource (dpy, cmap, "background", "Background");
649       bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
650
651       new = XtVaAppCreateShell (progname, progclass,
652                                 topLevelShellWidgetClass, dpy,
653                                 XtNmappedWhenManaged, False,
654                                 XtNvisual, visual,
655                                 XtNdepth, visual_depth (screen, visual),
656                                 XtNwidth, width,
657                                 XtNheight, height,
658                                 XtNcolormap, cmap,
659                                 XtNbackground, (Pixel) bg,
660                                 XtNborderColor, (Pixel) bd,
661                                 XtNinput, True,  /* for WM_HINTS */
662                                 NULL);
663
664       if (!toplevel)  /* kludge for the second window in -pair mode... */
665         XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
666
667       XtRealizeWidget (new);
668       toplevel = new;
669     }
670
671   return toplevel;
672 }
673
674 static void
675 init_window (Display *dpy, Widget toplevel, const char *title)
676 {
677   Window window;
678   XWindowAttributes xgwa;
679   XtPopup (toplevel, XtGrabNone);
680   XtVaSetValues (toplevel, XtNtitle, title, NULL);
681
682   /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
683    */
684   window = XtWindow (toplevel);
685   XGetWindowAttributes (dpy, window, &xgwa);
686   XSelectInput (dpy, window,
687                 (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
688                  ButtonPressMask | ButtonReleaseMask));
689   XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
690                    PropModeReplace,
691                    (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
692 }
693
694
695 int
696 main (int argc, char **argv)
697 {
698   struct xscreensaver_function_table *ft = xscreensaver_function_table;
699
700   XWindowAttributes xgwa;
701   Widget toplevel;
702   Display *dpy;
703   Window window;
704 # ifdef DEBUG_PAIR
705   Window window2 = 0;
706   Widget toplevel2 = 0;
707 # endif
708 #ifdef HAVE_RECORD_ANIM
709   record_anim_state *anim_state = 0;
710 #endif
711   XtAppContext app;
712   Bool root_p;
713   Window on_window = 0;
714   XEvent event;
715   Boolean dont_clear;
716   char version[255];
717
718   fix_fds();
719
720   progname = argv[0];   /* reset later */
721   progclass = ft->progclass;
722
723   if (ft->setup_cb)
724     ft->setup_cb (ft, ft->setup_arg);
725
726   merge_options ();
727
728 #ifdef __sgi
729   /* We have to do this on SGI to prevent the background color from being
730      overridden by the current desktop color scheme (we'd like our backgrounds
731      to be black, thanks.)  This should be the same as setting the
732      "*useSchemes: none" resource, but it's not -- if that resource is
733      present in the `default_defaults' above, it doesn't work, though it
734      does work when passed as an -xrm arg on the command line.  So screw it,
735      turn them off from C instead.
736    */
737   SgiUseSchemes ("none"); 
738 #endif /* __sgi */
739
740   toplevel = XtAppInitialize (&app, progclass, merged_options,
741                               merged_options_size, &argc, argv,
742                               merged_defaults, 0, 0);
743
744   dpy = XtDisplay (toplevel);
745
746   XtGetApplicationNameAndClass (dpy,
747                                 (char **) &progname,
748                                 (char **) &progclass);
749
750   /* half-assed way of avoiding buffer-overrun attacks. */
751   if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
752
753   XSetErrorHandler (screenhack_ehandler);
754
755   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
756   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
757
758   {
759     char *v = (char *) strdup(strchr(screensaver_id, ' '));
760     char *s1, *s2, *s3, *s4;
761     const char *ot = get_string_resource (dpy, "title", "Title");
762     s1 = (char *) strchr(v,  ' '); s1++;
763     s2 = (char *) strchr(s1, ' ');
764     s3 = (char *) strchr(v,  '('); s3++;
765     s4 = (char *) strchr(s3, ')');
766     *s2 = 0;
767     *s4 = 0;
768     if (ot && !*ot) ot = 0;
769     sprintf (version, "%.50s%s%s: from the XScreenSaver %s distribution (%s)",
770              (ot ? ot : ""),
771              (ot ? ": " : ""),
772              progclass, s1, s3);
773     free(v);
774   }
775
776   if (argc > 1)
777     {
778       const char *s;
779       int i;
780       int x = 18;
781       int end = 78;
782       Bool help_p = (!strcmp(argv[1], "-help") ||
783                      !strcmp(argv[1], "--help"));
784       fprintf (stderr, "%s\n", version);
785       for (s = progclass; *s; s++) fprintf(stderr, " ");
786       fprintf (stderr, "  https://www.jwz.org/xscreensaver/\n\n");
787
788       if (!help_p)
789         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
790       fprintf (stderr, "Options include: ");
791       for (i = 0; i < merged_options_size; i++)
792         {
793           char *sw = merged_options [i].option;
794           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
795           int size = strlen (sw) + (argp ? 6 : 0) + 2;
796           if (x + size >= end)
797             {
798               fprintf (stderr, "\n\t\t ");
799               x = 18;
800             }
801           x += size;
802           fprintf (stderr, "%s", sw);
803           if (argp) fprintf (stderr, " <arg>");
804           if (i != merged_options_size - 1) fprintf (stderr, ", ");
805         }
806
807       fprintf (stderr, ".\n");
808
809 #if 0
810       if (help_p)
811         {
812           fprintf (stderr, "\nResources:\n\n");
813           for (i = 0; i < merged_options_size; i++)
814             {
815               const char *opt = merged_options [i].option;
816               const char *res = merged_options [i].specifier + 1;
817               const char *val = merged_options [i].value;
818               char *s = get_string_resource (dpy, (char *) res, (char *) res);
819
820               if (s)
821                 {
822                   int L = strlen(s);
823                 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
824                   s[--L] = 0;
825                 }
826
827               fprintf (stderr, "    %-16s %-18s ", opt, res);
828               if (merged_options [i].argKind == XrmoptionSepArg)
829                 {
830                   fprintf (stderr, "[%s]", (s ? s : "?"));
831                 }
832               else
833                 {
834                   fprintf (stderr, "%s", (val ? val : "(null)"));
835                   if (val && s && !strcasecmp (val, s))
836                     fprintf (stderr, " [default]");
837                 }
838               fprintf (stderr, "\n");
839             }
840           fprintf (stderr, "\n");
841         }
842 #endif
843
844       exit (help_p ? 0 : 1);
845     }
846
847   {
848     char **s;
849     for (s = merged_defaults; *s; s++)
850       free(*s);
851   }
852
853   free (merged_options);
854   free (merged_defaults);
855   merged_options = 0;
856   merged_defaults = 0;
857
858   dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
859   mono_p = get_boolean_resource (dpy, "mono", "Boolean");
860   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
861     mono_p = True;
862
863   root_p = get_boolean_resource (dpy, "root", "Boolean");
864
865   {
866     char *s = get_string_resource (dpy, "windowID", "WindowID");
867     if (s && *s)
868       on_window = get_integer_resource (dpy, "windowID", "WindowID");
869     if (s) free (s);
870   }
871
872   if (on_window)
873     {
874       window = (Window) on_window;
875       XtDestroyWidget (toplevel);
876       XGetWindowAttributes (dpy, window, &xgwa);
877       visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
878
879       /* Select KeyPress and resize events on the external window.
880        */
881       xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
882       XSelectInput (dpy, window, xgwa.your_event_mask);
883
884       /* Select ButtonPress and ButtonRelease events on the external window,
885          if no other app has already selected them (only one app can select
886          ButtonPress at a time: BadAccess results.)
887        */
888       if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
889         XSelectInput (dpy, window,
890                       (xgwa.your_event_mask |
891                        ButtonPressMask | ButtonReleaseMask));
892     }
893   else if (root_p)
894     {
895       window = VirtualRootWindowOfScreen (XtScreen (toplevel));
896       XtDestroyWidget (toplevel);
897       XGetWindowAttributes (dpy, window, &xgwa);
898       /* With RANDR, the root window can resize! */
899       XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask);
900       visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
901     }
902   else
903     {
904       Widget new = make_shell (XtScreen (toplevel), toplevel,
905                                toplevel->core.width,
906                                toplevel->core.height);
907       if (new != toplevel)
908         {
909           XtDestroyWidget (toplevel);
910           toplevel = new;
911         }
912
913       init_window (dpy, toplevel, version);
914       window = XtWindow (toplevel);
915       XGetWindowAttributes (dpy, window, &xgwa);
916
917 # ifdef DEBUG_PAIR
918       if (get_boolean_resource (dpy, "pair", "Boolean"))
919         {
920           toplevel2 = make_shell (xgwa.screen, 0,
921                                   toplevel->core.width,
922                                   toplevel->core.height);
923           init_window (dpy, toplevel2, version);
924           window2 = XtWindow (toplevel2);
925         }
926 # endif /* DEBUG_PAIR */
927     }
928
929   if (!dont_clear)
930     {
931       unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
932                                             "background", "Background");
933       XSetWindowBackground (dpy, window, bg);
934       XClearWindow (dpy, window);
935 # ifdef DEBUG_PAIR
936       if (window2)
937         {
938           XSetWindowBackground (dpy, window2, bg);
939           XClearWindow (dpy, window2);
940         }
941 # endif
942     }
943
944   if (!root_p && !on_window)
945     /* wait for it to be mapped */
946     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
947
948   XSync (dpy, False);
949
950   /* This is the one and only place that the random-number generator is
951      seeded in any screenhack.  You do not need to seed the RNG again,
952      it is done for you before your code is invoked. */
953 # undef ya_rand_init
954   ya_rand_init (0);
955
956
957 #ifdef HAVE_RECORD_ANIM
958   {
959     int frames = get_integer_resource (dpy, "recordAnim", "Integer");
960     if (frames > 0)
961       anim_state = screenhack_record_anim_init (xgwa.screen, window, frames);
962   }
963 #endif
964
965   run_screenhack_table (dpy, window, 
966 # ifdef DEBUG_PAIR
967                         window2,
968 # endif
969 # ifdef HAVE_RECORD_ANIM
970                         anim_state,
971 # endif
972                         ft);
973
974 #ifdef HAVE_RECORD_ANIM
975   if (anim_state) screenhack_record_anim_free (anim_state);
976 #endif
977
978   XtDestroyWidget (toplevel);
979   XtDestroyApplicationContext (app);
980
981   return 0;
982 }