http://ftp.x.org/contrib/applications/xscreensaver-3.10.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 XtAppContext app;
63 Bool mono_p;
64
65 static XrmOptionDescRec default_options [] = {
66   { "-root",    ".root",                XrmoptionNoArg, "True" },
67   { "-window",  ".root",                XrmoptionNoArg, "False" },
68   { "-mono",    ".mono",                XrmoptionNoArg, "True" },
69   { "-install", ".installColormap",     XrmoptionNoArg, "True" },
70   { "-noinstall",".installColormap",    XrmoptionNoArg, "False" },
71   { "-visual",  ".visualID",            XrmoptionSepArg, 0 },
72   { "-window-id", ".windowID",          XrmoptionSepArg, 0 },
73   { 0, 0, 0, 0 }
74 };
75
76 static char *default_defaults[] = {
77   ".root:               false",
78   "*geometry:           600x480", /* this should be .geometry, but nooooo... */
79   "*mono:               false",
80   "*installColormap:    false",
81   "*visualID:           default",
82   "*windowID:           ",
83   0
84 };
85
86 static XrmOptionDescRec *merged_options;
87 static int merged_options_size;
88 static char **merged_defaults;
89
90 static void
91 merge_options (void)
92 {
93   int def_opts_size, opts_size;
94   int def_defaults_size, defaults_size;
95
96   for (def_opts_size = 0; default_options[def_opts_size].option;
97        def_opts_size++)
98     ;
99   for (opts_size = 0; options[opts_size].option; opts_size++)
100     ;
101
102   merged_options_size = def_opts_size + opts_size;
103   merged_options = (XrmOptionDescRec *)
104     malloc ((merged_options_size + 1) * sizeof(*default_options));
105   memcpy (merged_options, default_options,
106           (def_opts_size * sizeof(*default_options)));
107   memcpy (merged_options + def_opts_size, options,
108           ((opts_size + 1) * sizeof(*default_options)));
109
110   for (def_defaults_size = 0; default_defaults[def_defaults_size];
111        def_defaults_size++)
112     ;
113   for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
114     ;
115   merged_defaults = (char **)
116     malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
117   memcpy (merged_defaults, default_defaults,
118           def_defaults_size * sizeof(*defaults));
119   memcpy (merged_defaults + def_defaults_size, defaults,
120           (defaults_size + 1) * sizeof(*defaults));
121
122   /* This totally sucks.  Xt should behave like this by default.
123      If the string in `defaults' looks like ".foo", change that
124      to "Progclass.foo".
125    */
126   {
127     char **s;
128     for (s = merged_defaults; *s; s++)
129       if (**s == '.')
130         {
131           const char *oldr = *s;
132           char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
133           strcpy (newr, progclass);
134           strcat (newr, oldr);
135           *s = newr;
136         }
137   }
138 }
139
140 \f
141 /* Make the X errors print out the name of this program, so we have some
142    clue which one has a bug when they die under the screensaver.
143  */
144
145 static int
146 screenhack_ehandler (Display *dpy, XErrorEvent *error)
147 {
148   fprintf (stderr, "\nX error in %s:\n", progname);
149   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
150     exit (-1);
151   else
152     fprintf (stderr, " (nonfatal.)\n");
153   return 0;
154 }
155
156 static Bool
157 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
158 {
159   return (event->xany.type == MapNotify &&
160           event->xvisibility.window == (Window) window);
161 }
162
163
164 #ifdef USE_GL
165 extern Visual *get_gl_visual (Screen *, const char *, const char *);
166 #endif
167
168 #ifdef XLOCKMORE
169 extern void pre_merge_options (void);
170 #endif
171
172
173 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
174
175 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
176    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
177  */
178 void
179 screenhack_handle_event (Display *dpy, XEvent *event)
180 {
181   switch (event->xany.type)
182     {
183     case KeyPress:
184       {
185         KeySym keysym;
186         char c = 0;
187         XLookupString (&event->xkey, &c, 1, &keysym, 0);
188         if (c == 'q' ||
189             c == 'Q' ||
190             c == 3 ||   /* ^C */
191             c == 27)    /* ESC */
192           exit (0);
193         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
194           XBell (dpy, 0);  /* beep for non-chord keys */
195       }
196       break;
197     case ButtonPress:
198       XBell (dpy, 0);
199       break;
200     case ClientMessage:
201       {
202         if (event->xclient.message_type != XA_WM_PROTOCOLS)
203           {
204             char *s = XGetAtomName(dpy, event->xclient.message_type);
205             if (!s) s = "(null)";
206             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
207                      progname, s);
208           }
209         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
210           {
211             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
212             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
213             if (!s1) s1 = "(null)";
214             if (!s2) s2 = "(null)";
215             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
216                      progname, s1, s2);
217           }
218         else
219           {
220             exit (0);
221           }
222       }
223       break;
224     }
225 }
226
227
228 void
229 screenhack_handle_events (Display *dpy)
230 {
231   while (XPending (dpy))
232     {
233       XEvent event;
234       XNextEvent (dpy, &event);
235       screenhack_handle_event (dpy, &event);
236     }
237 }
238
239
240
241 int
242 main (int argc, char **argv)
243 {
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 }