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