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