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