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