5432711d3112f8f75558b7b4f9df1dc63bad3fc6
[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   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
237 void
238 screenhack_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
249 static Visual *
250 pick_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  */
288 static void
289 visual_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
345 static void
346 fix_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
375 int
376 main (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 and resize events on the external window.
538        */
539       xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
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 }