ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-4.21.tar.gz
[xscreensaver] / hacks / screenhack.c
1 /* xscreensaver, Copyright (c) 1992-2005 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 procedure `screenhack(dpy, window)'
20
21    -  create a variable `char *progclass' which names this program's
22       resource class.
23
24    -  create a variable `char defaults []' for the default resources, and
25       null-terminate it.
26
27    -  create a variable `XrmOptionDescRec options[]' for the command-line,
28       and null-terminate it.
29
30    And that's it...
31  */
32
33 #include <stdio.h>
34 #include <X11/Intrinsic.h>
35 #include <X11/IntrinsicP.h>
36 #include <X11/CoreP.h>
37 #include <X11/Shell.h>
38 #include <X11/StringDefs.h>
39 #include <X11/Xutil.h>
40 #include <X11/keysym.h>
41
42 #ifdef __sgi
43 # include <X11/SGIScheme.h>     /* for SgiUseSchemes() */
44 #endif /* __sgi */
45
46 #ifdef HAVE_XMU
47 # ifndef VMS
48 #  include <X11/Xmu/Error.h>
49 # else /* VMS */
50 #  include <Xmu/Error.h>
51 # endif
52 #else
53 # include "xmu.h"
54 #endif
55 #include "screenhack.h"
56 #include "version.h"
57 #include "vroot.h"
58
59 #ifndef _XSCREENSAVER_VROOT_H_
60 # error Error!  You have an old version of vroot.h!  Check -I args.
61 #endif /* _XSCREENSAVER_VROOT_H_ */
62
63 #ifndef isupper
64 # define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
65 #endif
66 #ifndef _tolower
67 # define _tolower(c)  ((c) - 'A' + 'a')
68 #endif
69
70
71 char *progname;
72 XrmDatabase db;
73 XtAppContext app;
74 Bool mono_p;
75
76 static XrmOptionDescRec default_options [] = {
77   { "-root",    ".root",                XrmoptionNoArg, "True" },
78   { "-window",  ".root",                XrmoptionNoArg, "False" },
79   { "-mono",    ".mono",                XrmoptionNoArg, "True" },
80   { "-install", ".installColormap",     XrmoptionNoArg, "True" },
81   { "-noinstall",".installColormap",    XrmoptionNoArg, "False" },
82   { "-visual",  ".visualID",            XrmoptionSepArg, 0 },
83   { "-window-id", ".windowID",          XrmoptionSepArg, 0 },
84   { 0, 0, 0, 0 }
85 };
86
87 static char *default_defaults[] = {
88   ".root:               false",
89   "*geometry:           600x480", /* this should be .geometry, but nooooo... */
90   "*mono:               false",
91   "*installColormap:    false",
92   "*visualID:           default",
93   "*windowID:           ",
94   "*desktopGrabber:     xscreensaver-getimage %s",
95   0
96 };
97
98 static XrmOptionDescRec *merged_options;
99 static int merged_options_size;
100 static char **merged_defaults;
101
102 static void
103 merge_options (void)
104 {
105   int def_opts_size, opts_size;
106   int def_defaults_size, defaults_size;
107
108   for (def_opts_size = 0; default_options[def_opts_size].option;
109        def_opts_size++)
110     ;
111   for (opts_size = 0; options[opts_size].option; opts_size++)
112     ;
113
114   merged_options_size = def_opts_size + opts_size;
115   merged_options = (XrmOptionDescRec *)
116     malloc ((merged_options_size + 1) * sizeof(*default_options));
117   memcpy (merged_options, default_options,
118           (def_opts_size * sizeof(*default_options)));
119   memcpy (merged_options + def_opts_size, options,
120           ((opts_size + 1) * sizeof(*default_options)));
121
122   for (def_defaults_size = 0; default_defaults[def_defaults_size];
123        def_defaults_size++)
124     ;
125   for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
126     ;
127   merged_defaults = (char **)
128     malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
129   memcpy (merged_defaults, default_defaults,
130           def_defaults_size * sizeof(*defaults));
131   memcpy (merged_defaults + def_defaults_size, defaults,
132           (defaults_size + 1) * sizeof(*defaults));
133
134   /* This totally sucks.  Xt should behave like this by default.
135      If the string in `defaults' looks like ".foo", change that
136      to "Progclass.foo".
137    */
138   {
139     char **s;
140     for (s = merged_defaults; *s; s++)
141       if (**s == '.')
142         {
143           const char *oldr = *s;
144           char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
145           strcpy (newr, progclass);
146           strcat (newr, oldr);
147           *s = newr;
148         }
149   }
150 }
151
152 \f
153 /* Make the X errors print out the name of this program, so we have some
154    clue which one has a bug when they die under the screensaver.
155  */
156
157 static int
158 screenhack_ehandler (Display *dpy, XErrorEvent *error)
159 {
160   fprintf (stderr, "\nX error in %s:\n", progname);
161   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
162     exit (-1);
163   else
164     fprintf (stderr, " (nonfatal.)\n");
165   return 0;
166 }
167
168 static Bool
169 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
170 {
171   return (event->xany.type == MapNotify &&
172           event->xvisibility.window == (Window) window);
173 }
174
175
176 #ifdef XLOCKMORE
177 extern void pre_merge_options (void);
178 #endif
179
180
181 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
182
183 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
184    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
185  */
186 void
187 screenhack_handle_event (Display *dpy, XEvent *event)
188 {
189   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
190     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
191
192   switch (event->xany.type)
193     {
194     case KeyPress:
195       {
196         KeySym keysym;
197         char c = 0;
198         XLookupString (&event->xkey, &c, 1, &keysym, 0);
199         if (c == 'q' ||
200             c == 'Q' ||
201             c == 3 ||   /* ^C */
202             c == 27)    /* ESC */
203           exit (0);
204         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
205           XBell (dpy, 0);  /* beep for non-chord keys */
206       }
207       break;
208     case ButtonPress:
209       XBell (dpy, 0);
210       break;
211     case ClientMessage:
212       {
213         if (event->xclient.message_type != XA_WM_PROTOCOLS)
214           {
215             char *s = XGetAtomName(dpy, event->xclient.message_type);
216             if (!s) s = "(null)";
217             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
218                      progname, s);
219           }
220         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
221           {
222             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
223             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
224             if (!s1) s1 = "(null)";
225             if (!s2) s2 = "(null)";
226             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
227                      progname, s1, s2);
228           }
229         else
230           {
231             exit (0);
232           }
233       }
234       break;
235     }
236 }
237
238
239 void
240 screenhack_handle_events (Display *dpy)
241 {
242   while (XPending (dpy))
243     {
244       XEvent event;
245       XNextEvent (dpy, &event);
246       screenhack_handle_event (dpy, &event);
247     }
248 }
249
250
251 static Visual *
252 pick_visual (Screen *screen)
253 {
254 #ifdef USE_GL
255   /* If we're linking against GL (that is, this is the version of screenhack.o
256      that the GL hacks will use, which is different from the one that the
257      non-GL hacks will use) then try to pick the "best" visual by interrogating
258      the GL library instead of by asking Xlib.  GL knows better.
259    */
260   Visual *v = 0;
261   char *string = get_string_resource ("visualID", "VisualID");
262   char *s;
263
264   if (string)
265     for (s = string; *s; s++)
266       if (isupper (*s)) *s = _tolower (*s);
267
268   if (!string || !*string ||
269       !strcmp (string, "gl") ||
270       !strcmp (string, "best") ||
271       !strcmp (string, "color") ||
272       !strcmp (string, "default"))
273     v = get_gl_visual (screen);         /* from ../utils/visual-gl.c */
274
275   if (string)
276     free (string);
277   if (v)
278     return v;
279 #endif /* USE_GL */
280
281   return get_visual_resource (screen, "visualID", "VisualID", False);
282 }
283
284
285 /* Notice when the user has requested a different visual or colormap
286    on a pre-existing window (e.g., "-root -visual truecolor" or
287    "-window-id 0x2c00001 -install") and complain, since when drawing
288    on an existing window, we have no choice about these things.
289  */
290 static void
291 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
292                 Bool window_p)
293 {
294   char *visual_string = get_string_resource ("visualID", "VisualID");
295   Visual *desired_visual = pick_visual (screen);
296   char win[100];
297   char why[100];
298
299   if (window == RootWindowOfScreen (screen))
300     strcpy (win, "root window");
301   else
302     sprintf (win, "window 0x%lx", (unsigned long) window);
303
304   if (window_p)
305     sprintf (why, "-window-id 0x%lx", (unsigned long) window);
306   else
307     strcpy (why, "-root");
308
309   if (visual_string && *visual_string)
310     {
311       char *s;
312       for (s = visual_string; *s; s++)
313         if (isupper (*s)) *s = _tolower (*s);
314
315       if (!strcmp (visual_string, "default") ||
316           !strcmp (visual_string, "default") ||
317           !strcmp (visual_string, "best"))
318         /* don't warn about these, just silently DWIM. */
319         ;
320       else if (visual != desired_visual)
321         {
322           fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
323                    progname, visual_string, why);
324           fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
325                    progname, win, XVisualIDFromVisual (visual));
326         }
327       free (visual_string);
328     }
329
330   if (visual == DefaultVisualOfScreen (screen) &&
331       has_writable_cells (screen, visual) &&
332       get_boolean_resource ("installColormap", "InstallColormap"))
333     {
334       fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
335                progname, why);
336       fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
337                progname, win, (unsigned long) cmap);
338     }
339
340 # ifdef USE_GL
341   if (!validate_gl_visual (stderr, screen, win, visual))
342     exit (1);
343 # endif /* USE_GL */
344 }
345
346
347 static void
348 fix_fds (void)
349 {
350   /* Bad Things Happen if stdin, stdout, and stderr have been closed
351      (as by the `sh incantation "attraction >&- 2>&-").  When you do
352      that, the X connection gets allocated to one of these fds, and
353      then some random library writes to stderr, and random bits get
354      stuffed down the X pipe, causing "Xlib: sequence lost" errors.
355      So, we cause the first three file descriptors to be open to
356      /dev/null if they aren't open to something else already.  This
357      must be done before any other files are opened (or the closing
358      of that other file will again free up one of the "magic" first
359      three FDs.)
360
361      We do this by opening /dev/null three times, and then closing
362      those fds, *unless* any of them got allocated as #0, #1, or #2,
363      in which case we leave them open.  Gag.
364
365      Really, this crap is technically required of *every* X program,
366      if you want it to be robust in the face of "2>&-".
367    */
368   int fd0 = open ("/dev/null", O_RDWR);
369   int fd1 = open ("/dev/null", O_RDWR);
370   int fd2 = open ("/dev/null", O_RDWR);
371   if (fd0 > 2) close (fd0);
372   if (fd1 > 2) close (fd1);
373   if (fd2 > 2) close (fd2);
374 }
375
376
377 int
378 main (int argc, char **argv)
379 {
380   Widget toplevel;
381   Display *dpy;
382   Window window;
383   Screen *screen;
384   Visual *visual;
385   Colormap cmap;
386   Bool root_p;
387   Window on_window = 0;
388   XEvent event;
389   Boolean dont_clear /*, dont_map */;
390   char version[255];
391
392   fix_fds();
393
394   progname = argv[0];   /* reset later */
395
396 #ifdef XLOCKMORE
397   pre_merge_options ();
398 #endif
399   merge_options ();
400
401 #ifdef __sgi
402   /* We have to do this on SGI to prevent the background color from being
403      overridden by the current desktop color scheme (we'd like our backgrounds
404      to be black, thanks.)  This should be the same as setting the
405      "*useSchemes: none" resource, but it's not -- if that resource is
406      present in the `default_defaults' above, it doesn't work, though it
407      does work when passed as an -xrm arg on the command line.  So screw it,
408      turn them off from C instead.
409    */
410   SgiUseSchemes ("none"); 
411 #endif /* __sgi */
412
413   toplevel = XtAppInitialize (&app, progclass, merged_options,
414                               merged_options_size, &argc, argv,
415                               merged_defaults, 0, 0);
416   dpy = XtDisplay (toplevel);
417   screen = XtScreen (toplevel);
418   db = XtDatabase (dpy);
419
420   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
421
422   /* half-assed way of avoiding buffer-overrun attacks. */
423   if (strlen (progname) >= 100) progname[100] = 0;
424
425   XSetErrorHandler (screenhack_ehandler);
426
427   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
428   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
429
430   {
431     char *v = (char *) strdup(strchr(screensaver_id, ' '));
432     char *s1, *s2, *s3, *s4;
433     s1 = (char *) strchr(v,  ' '); s1++;
434     s2 = (char *) strchr(s1, ' ');
435     s3 = (char *) strchr(v,  '('); s3++;
436     s4 = (char *) strchr(s3, ')');
437     *s2 = 0;
438     *s4 = 0;
439     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
440              progclass, s1, s3);
441     free(v);
442   }
443
444   if (argc > 1)
445     {
446       const char *s;
447       int i;
448       int x = 18;
449       int end = 78;
450       Bool help_p = (!strcmp(argv[1], "-help") ||
451                      !strcmp(argv[1], "--help"));
452       fprintf (stderr, "%s\n", version);
453       for (s = progclass; *s; s++) fprintf(stderr, " ");
454       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
455
456       if (!help_p)
457         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
458       fprintf (stderr, "Options include: ");
459       for (i = 0; i < merged_options_size; i++)
460         {
461           char *sw = merged_options [i].option;
462           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
463           int size = strlen (sw) + (argp ? 6 : 0) + 2;
464           if (x + size >= end)
465             {
466               fprintf (stderr, "\n\t\t ");
467               x = 18;
468             }
469           x += size;
470           fprintf (stderr, "%s", sw);
471           if (argp) fprintf (stderr, " <arg>");
472           if (i != merged_options_size - 1) fprintf (stderr, ", ");
473         }
474
475       fprintf (stderr, ".\n");
476
477 #if 0
478       if (help_p)
479         {
480           fprintf (stderr, "\nResources:\n\n");
481           for (i = 0; i < merged_options_size; i++)
482             {
483               const char *opt = merged_options [i].option;
484               const char *res = merged_options [i].specifier + 1;
485               const char *val = merged_options [i].value;
486               char *s = get_string_resource ((char *) res, (char *) res);
487
488               if (s)
489                 {
490                   int L = strlen(s);
491                 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
492                   s[--L] = 0;
493                 }
494
495               fprintf (stderr, "    %-16s %-18s ", opt, res);
496               if (merged_options [i].argKind == XrmoptionSepArg)
497                 {
498                   fprintf (stderr, "[%s]", (s ? s : "?"));
499                 }
500               else
501                 {
502                   fprintf (stderr, "%s", (val ? val : "(null)"));
503                   if (val && s && !strcasecmp (val, s))
504                     fprintf (stderr, " [default]");
505                 }
506               fprintf (stderr, "\n");
507             }
508           fprintf (stderr, "\n");
509         }
510 #endif
511
512       exit (help_p ? 0 : 1);
513     }
514
515   dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
516 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
517   mono_p = get_boolean_resource ("mono", "Boolean");
518   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
519     mono_p = True;
520
521   root_p = get_boolean_resource ("root", "Boolean");
522
523   {
524     char *s = get_string_resource ("windowID", "WindowID");
525     if (s && *s)
526       on_window = get_integer_resource ("windowID", "WindowID");
527     if (s) free (s);
528   }
529
530   if (on_window)
531     {
532       XWindowAttributes xgwa;
533       window = (Window) on_window;
534       XtDestroyWidget (toplevel);
535       XGetWindowAttributes (dpy, window, &xgwa);
536       cmap = xgwa.colormap;
537       visual = xgwa.visual;
538       screen = xgwa.screen;
539       visual_warning (screen, window, visual, cmap, True);
540
541       /* Select KeyPress and resize events on the external window.
542        */
543       xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
544       XSelectInput (dpy, window, xgwa.your_event_mask);
545
546       /* Select ButtonPress and ButtonRelease events on the external window,
547          if no other app has already selected them (only one app can select
548          ButtonPress at a time: BadAccess results.)
549        */
550       if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
551         XSelectInput (dpy, window,
552                       (xgwa.your_event_mask |
553                        ButtonPressMask | ButtonReleaseMask));
554     }
555   else if (root_p)
556     {
557       XWindowAttributes xgwa;
558       window = VirtualRootWindowOfScreen (XtScreen (toplevel));
559       XtDestroyWidget (toplevel);
560       XGetWindowAttributes (dpy, window, &xgwa);
561       cmap = xgwa.colormap;
562       visual = xgwa.visual;
563       visual_warning (screen, window, visual, cmap, False);
564     }
565   else
566     {
567       Boolean def_visual_p;
568       visual = pick_visual (screen);
569
570 # ifdef USE_GL
571       if (!validate_gl_visual (stderr, screen, "window", visual))
572         exit (1);
573 # endif /* USE_GL */
574
575       if (toplevel->core.width <= 0)
576         toplevel->core.width = 600;
577       if (toplevel->core.height <= 0)
578         toplevel->core.height = 480;
579
580       def_visual_p = (visual == DefaultVisualOfScreen (screen));
581
582       if (!def_visual_p)
583         {
584           unsigned int bg, bd;
585           Widget new;
586
587           cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
588                                   visual, AllocNone);
589           bg = get_pixel_resource ("background", "Background", dpy, cmap);
590           bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
591
592           new = XtVaAppCreateShell (progname, progclass,
593                                     topLevelShellWidgetClass, dpy,
594                                     XtNmappedWhenManaged, False,
595                                     XtNvisual, visual,
596                                     XtNdepth, visual_depth (screen, visual),
597                                     XtNwidth, toplevel->core.width,
598                                     XtNheight, toplevel->core.height,
599                                     XtNcolormap, cmap,
600                                     XtNbackground, (Pixel) bg,
601                                     XtNborderColor, (Pixel) bd,
602                                     XtNinput, True,  /* for WM_HINTS */
603                                     NULL);
604           XtDestroyWidget (toplevel);
605           toplevel = new;
606           XtRealizeWidget (toplevel);
607           window = XtWindow (toplevel);
608         }
609       else
610         {
611           XtVaSetValues (toplevel,
612                          XtNmappedWhenManaged, False,
613                          XtNinput, True,  /* for WM_HINTS */
614                          NULL);
615           XtRealizeWidget (toplevel);
616           window = XtWindow (toplevel);
617
618           if (get_boolean_resource ("installColormap", "InstallColormap"))
619             {
620               cmap = XCreateColormap (dpy, window,
621                                    DefaultVisualOfScreen (XtScreen (toplevel)),
622                                       AllocNone);
623               XSetWindowColormap (dpy, window, cmap);
624             }
625           else
626             {
627               cmap = DefaultColormap (dpy, DefaultScreen (dpy));
628             }
629         }
630
631 /*
632       if (dont_map)
633         {
634           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, NULL);
635           XtRealizeWidget (toplevel);
636         }
637       else
638 */
639         {
640           XtPopup (toplevel, XtGrabNone);
641         }
642
643       XtVaSetValues(toplevel, XtNtitle, version, NULL);
644
645       /* For screenhack_handle_events(): select KeyPress, and
646          announce that we accept WM_DELETE_WINDOW. */
647       {
648         XWindowAttributes xgwa;
649         XGetWindowAttributes (dpy, window, &xgwa);
650         XSelectInput (dpy, window,
651                       (xgwa.your_event_mask | KeyPressMask |
652                        ButtonPressMask | ButtonReleaseMask));
653         XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
654                          PropModeReplace,
655                          (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
656       }
657     }
658
659   if (!dont_clear)
660     {
661       XSetWindowBackground (dpy, window,
662                             get_pixel_resource ("background", "Background",
663                                                 dpy, cmap));
664       XClearWindow (dpy, window);
665     }
666
667   if (!root_p && !on_window)
668     /* wait for it to be mapped */
669     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
670
671   XSync (dpy, False);
672
673   /* This is the one and only place that the random-number generator is
674      seeded in any screenhack.  You do not need to seed the RNG again,
675      it is done for you before your code is invoked. */
676 # undef ya_rand_init
677   ya_rand_init (0);
678
679   screenhack (dpy, window); /* doesn't return */
680   return 0;
681 }