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