source: trunk/third/xscreensaver/hacks/screenhack.c @ 20148

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