http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.30.tar.gz
[xscreensaver] / hacks / screenhack.c
1 /* xscreensaver, Copyright (c) 1992, 1995, 1997, 1998, 2001
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   { 0, 0, 0, 0 }
81 };
82
83 static char *default_defaults[] = {
84   ".root:               false",
85   "*geometry:           600x480", /* this should be .geometry, but nooooo... */
86   "*mono:               false",
87   "*installColormap:    false",
88   "*visualID:           default",
89   0
90 };
91
92 static XrmOptionDescRec *merged_options;
93 static int merged_options_size;
94 static char **merged_defaults;
95
96 static void
97 merge_options (void)
98 {
99   int def_opts_size, opts_size;
100   int def_defaults_size, defaults_size;
101
102   for (def_opts_size = 0; default_options[def_opts_size].option;
103        def_opts_size++)
104     ;
105   for (opts_size = 0; options[opts_size].option; opts_size++)
106     ;
107
108   merged_options_size = def_opts_size + opts_size;
109   merged_options = (XrmOptionDescRec *)
110     malloc ((merged_options_size + 1) * sizeof(*default_options));
111   memcpy (merged_options, default_options,
112           (def_opts_size * sizeof(*default_options)));
113   memcpy (merged_options + def_opts_size, options,
114           ((opts_size + 1) * sizeof(*default_options)));
115
116   for (def_defaults_size = 0; default_defaults[def_defaults_size];
117        def_defaults_size++)
118     ;
119   for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
120     ;
121   merged_defaults = (char **)
122     malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
123   memcpy (merged_defaults, default_defaults,
124           def_defaults_size * sizeof(*defaults));
125   memcpy (merged_defaults + def_defaults_size, defaults,
126           (defaults_size + 1) * sizeof(*defaults));
127
128   /* This totally sucks.  Xt should behave like this by default.
129      If the string in `defaults' looks like ".foo", change that
130      to "Progclass.foo".
131    */
132   {
133     char **s;
134     for (s = merged_defaults; *s; s++)
135       if (**s == '.')
136         {
137           const char *oldr = *s;
138           char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
139           strcpy (newr, progclass);
140           strcat (newr, oldr);
141           *s = newr;
142         }
143   }
144 }
145
146 \f
147 /* Make the X errors print out the name of this program, so we have some
148    clue which one has a bug when they die under the screensaver.
149  */
150
151 static int
152 screenhack_ehandler (Display *dpy, XErrorEvent *error)
153 {
154   fprintf (stderr, "\nX error in %s:\n", progname);
155   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
156     exit (-1);
157   else
158     fprintf (stderr, " (nonfatal.)\n");
159   return 0;
160 }
161
162 static Bool
163 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
164 {
165   return (event->xany.type == MapNotify &&
166           event->xvisibility.window == (Window) window);
167 }
168
169
170 #ifdef XLOCKMORE
171 extern void pre_merge_options (void);
172 #endif
173
174
175 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
176
177 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
178    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
179  */
180 void
181 screenhack_handle_event (Display *dpy, XEvent *event)
182 {
183   switch (event->xany.type)
184     {
185     case KeyPress:
186       {
187         KeySym keysym;
188         char c = 0;
189         XLookupString (&event->xkey, &c, 1, &keysym, 0);
190         if (c == 'q' ||
191             c == 'Q' ||
192             c == 3 ||   /* ^C */
193             c == 27)    /* ESC */
194           exit (0);
195         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
196           XBell (dpy, 0);  /* beep for non-chord keys */
197       }
198       break;
199     case ButtonPress:
200       XBell (dpy, 0);
201       break;
202     case ClientMessage:
203       {
204         if (event->xclient.message_type != XA_WM_PROTOCOLS)
205           {
206             char *s = XGetAtomName(dpy, event->xclient.message_type);
207             if (!s) s = "(null)";
208             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
209                      progname, s);
210           }
211         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
212           {
213             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
214             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
215             if (!s1) s1 = "(null)";
216             if (!s2) s2 = "(null)";
217             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
218                      progname, s1, s2);
219           }
220         else
221           {
222             exit (0);
223           }
224       }
225       break;
226     }
227 }
228
229
230 void
231 screenhack_handle_events (Display *dpy)
232 {
233   while (XPending (dpy))
234     {
235       XEvent event;
236       XNextEvent (dpy, &event);
237       screenhack_handle_event (dpy, &event);
238     }
239 }
240
241
242 static Visual *
243 pick_visual (Screen *screen)
244 {
245 #ifdef USE_GL
246   /* If we're linking against GL (that is, this is the version of screenhack.o
247      that the GL hacks will use, which is different from the one that the
248      non-GL hacks will use) then try to pick the "best" visual by interrogating
249      the GL library instead of by asking Xlib.  GL knows better.
250    */
251   Visual *v = 0;
252   char *string = get_string_resource ("visualID", "VisualID");
253   char *s;
254
255   if (string)
256     for (s = string; *s; s++)
257       if (isupper (*s)) *s = _tolower (*s);
258
259   if (!string || !*string ||
260       !strcmp (string, "gl") ||
261       !strcmp (string, "best") ||
262       !strcmp (string, "color") ||
263       !strcmp (string, "default"))
264     v = get_gl_visual (screen);         /* from ../utils/visual-gl.c */
265
266   if (string)
267     free (string);
268   if (v)
269     return v;
270 #endif /* USE_GL */
271
272   return get_visual_resource (screen, "visualID", "VisualID", False);
273 }
274
275
276 /* Notice when the user has requested a different visual or colormap
277    on a pre-existing window (e.g., "-root -visual truecolor") and 
278    complain, since when drawing on an existing window, we have no 
279    choice about these things.
280  */
281 static void
282 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
283                 Bool window_p)
284 {
285   char *visual_string = get_string_resource ("visualID", "VisualID");
286   Visual *desired_visual = pick_visual (screen);
287   char win[100];
288   char why[100];
289
290   if (window == RootWindowOfScreen (screen))
291     strcpy (win, "root window");
292   else
293     sprintf (win, "window 0x%x", (unsigned long) window);
294
295   if (window_p)
296     sprintf (why, "-window-id 0x%x", (unsigned long) window);
297   else
298     strcpy (why, "-root");
299
300   if (visual_string && *visual_string)
301     {
302       char *s;
303       for (s = visual_string; *s; s++)
304         if (isupper (*s)) *s = _tolower (*s);
305
306       if (!strcmp (visual_string, "default") ||
307           !strcmp (visual_string, "default") ||
308           !strcmp (visual_string, "best"))
309         /* don't warn about these, just silently DWIM. */
310         ;
311       else if (visual != desired_visual)
312         {
313           fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
314                    progname, visual_string, why);
315           fprintf (stderr, "%s: using %s's visual 0x%x.\n",
316                    progname, win, XVisualIDFromVisual (visual));
317         }
318       free (visual_string);
319     }
320
321   if (visual == DefaultVisualOfScreen (screen) &&
322       has_writable_cells (screen, visual) &&
323       get_boolean_resource ("installColormap", "InstallColormap"))
324     {
325       fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
326                progname, why);
327       fprintf (stderr, "%s: using %s's colormap 0x%x.\n",
328                progname, win, (unsigned long) cmap);
329     }
330
331 # ifdef USE_GL
332   if (!validate_gl_visual (stderr, screen, win, visual))
333     exit (1);
334 # endif /* USE_GL */
335 }
336
337
338 int
339 main (int argc, char **argv)
340 {
341   Widget toplevel;
342   Display *dpy;
343   Window window;
344   Screen *screen;
345   Visual *visual;
346   Colormap cmap;
347   Bool root_p;
348   XEvent event;
349   Boolean dont_clear /*, dont_map */;
350   char version[255];
351
352 #ifdef XLOCKMORE
353   pre_merge_options ();
354 #endif
355   merge_options ();
356
357 #ifdef __sgi
358   /* We have to do this on SGI to prevent the background color from being
359      overridden by the current desktop color scheme (we'd like our backgrounds
360      to be black, thanks.)  This should be the same as setting the
361      "*useSchemes: none" resource, but it's not -- if that resource is
362      present in the `default_defaults' above, it doesn't work, though it
363      does work when passed as an -xrm arg on the command line.  So screw it,
364      turn them off from C instead.
365    */
366   SgiUseSchemes ("none"); 
367 #endif /* __sgi */
368
369   toplevel = XtAppInitialize (&app, progclass, merged_options,
370                               merged_options_size, &argc, argv,
371                               merged_defaults, 0, 0);
372   dpy = XtDisplay (toplevel);
373   screen = XtScreen (toplevel);
374   db = XtDatabase (dpy);
375
376   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
377
378   /* half-assed way of avoiding buffer-overrun attacks. */
379   if (strlen (progname) >= 100) progname[100] = 0;
380
381   XSetErrorHandler (screenhack_ehandler);
382
383   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
384   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
385
386   {
387     char *v = (char *) strdup(strchr(screensaver_id, ' '));
388     char *s1, *s2, *s3, *s4;
389     s1 = (char *) strchr(v,  ' '); s1++;
390     s2 = (char *) strchr(s1, ' ');
391     s3 = (char *) strchr(v,  '('); s3++;
392     s4 = (char *) strchr(s3, ')');
393     *s2 = 0;
394     *s4 = 0;
395     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
396              progclass, s1, s3);
397     free(v);
398   }
399
400   if (argc > 1)
401     {
402       const char *s;
403       int i;
404       int x = 18;
405       int end = 78;
406       Bool help_p = !strcmp(argv[1], "-help");
407       fprintf (stderr, "%s\n", version);
408       for (s = progclass; *s; s++) fprintf(stderr, " ");
409       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
410
411       if (!help_p)
412         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
413       fprintf (stderr, "Options include: ");
414       for (i = 0; i < merged_options_size; i++)
415         {
416           char *sw = merged_options [i].option;
417           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
418           int size = strlen (sw) + (argp ? 6 : 0) + 2;
419           if (x + size >= end)
420             {
421               fprintf (stderr, "\n\t\t ");
422               x = 18;
423             }
424           x += size;
425           fprintf (stderr, "%s", sw);
426           if (argp) fprintf (stderr, " <arg>");
427           if (i != merged_options_size - 1) fprintf (stderr, ", ");
428         }
429       fprintf (stderr, ".\n");
430       exit (help_p ? 0 : 1);
431     }
432
433   dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
434 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
435   mono_p = get_boolean_resource ("mono", "Boolean");
436   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
437     mono_p = True;
438
439   root_p = get_boolean_resource ("root", "Boolean");
440
441   if (root_p)
442     {
443       XWindowAttributes xgwa;
444       window = RootWindowOfScreen (XtScreen (toplevel));
445       XtDestroyWidget (toplevel);
446       XGetWindowAttributes (dpy, window, &xgwa);
447       cmap = xgwa.colormap;
448       visual = xgwa.visual;
449       visual_warning (screen, window, visual, cmap, False);
450     }
451   else
452     {
453       Boolean def_visual_p;
454       visual = pick_visual (screen);
455
456 # ifdef USE_GL
457       if (!validate_gl_visual (stderr, screen, "window", visual))
458         exit (1);
459 # endif /* USE_GL */
460
461       if (toplevel->core.width <= 0)
462         toplevel->core.width = 600;
463       if (toplevel->core.height <= 0)
464         toplevel->core.height = 480;
465
466       def_visual_p = (visual == DefaultVisualOfScreen (screen));
467
468       if (!def_visual_p)
469         {
470           unsigned int bg, bd;
471           Widget new;
472
473           cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
474                                   visual, AllocNone);
475           bg = get_pixel_resource ("background", "Background", dpy, cmap);
476           bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
477
478           new = XtVaAppCreateShell (progname, progclass,
479                                     topLevelShellWidgetClass, dpy,
480                                     XtNmappedWhenManaged, False,
481                                     XtNvisual, visual,
482                                     XtNdepth, visual_depth (screen, visual),
483                                     XtNwidth, toplevel->core.width,
484                                     XtNheight, toplevel->core.height,
485                                     XtNcolormap, cmap,
486                                     XtNbackground, (Pixel) bg,
487                                     XtNborderColor, (Pixel) bd,
488                                     XtNinput, True,  /* for WM_HINTS */
489                                     0);
490           XtDestroyWidget (toplevel);
491           toplevel = new;
492           XtRealizeWidget (toplevel);
493           window = XtWindow (toplevel);
494         }
495       else
496         {
497           XtVaSetValues (toplevel,
498                          XtNmappedWhenManaged, False,
499                          XtNinput, True,  /* for WM_HINTS */
500                          0);
501           XtRealizeWidget (toplevel);
502           window = XtWindow (toplevel);
503
504           if (get_boolean_resource ("installColormap", "InstallColormap"))
505             {
506               cmap = XCreateColormap (dpy, window,
507                                    DefaultVisualOfScreen (XtScreen (toplevel)),
508                                       AllocNone);
509               XSetWindowColormap (dpy, window, cmap);
510             }
511           else
512             {
513               cmap = DefaultColormap (dpy, DefaultScreen (dpy));
514             }
515         }
516
517 /*
518       if (dont_map)
519         {
520           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
521           XtRealizeWidget (toplevel);
522         }
523       else
524 */
525         {
526           XtPopup (toplevel, XtGrabNone);
527         }
528
529       XtVaSetValues(toplevel, XtNtitle, version, 0);
530
531       /* For screenhack_handle_events(): select KeyPress, and
532          announce that we accept WM_DELETE_WINDOW. */
533       {
534         XWindowAttributes xgwa;
535         XGetWindowAttributes (dpy, window, &xgwa);
536         XSelectInput (dpy, window,
537                       xgwa.your_event_mask | KeyPressMask | ButtonPressMask);
538         XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
539                          PropModeReplace,
540                          (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
541       }
542     }
543
544   if (!dont_clear)
545     {
546       XSetWindowBackground (dpy, window,
547                             get_pixel_resource ("background", "Background",
548                                                 dpy, cmap));
549       XClearWindow (dpy, window);
550     }
551
552   if (!root_p)
553     /* wait for it to be mapped */
554     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
555
556   XSync (dpy, False);
557
558   /* This is the one and only place that the random-number generator is
559      seeded in any screenhack.  You do not need to seed the RNG again,
560      it is done for you before your code is invoked. */
561 # undef ya_rand_init
562   ya_rand_init (0);
563
564   screenhack (dpy, window); /* doesn't return */
565   return 0;
566 }