ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.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 XLOCKMORE
165 extern void pre_merge_options (void);
166 #endif
167
168
169 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
170
171 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
172    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
173  */
174 void
175 screenhack_handle_event (Display *dpy, XEvent *event)
176 {
177   switch (event->xany.type)
178     {
179     case KeyPress:
180       {
181         KeySym keysym;
182         char c = 0;
183         XLookupString (&event->xkey, &c, 1, &keysym, 0);
184         if (c == 'q' ||
185             c == 'Q' ||
186             c == 3 ||   /* ^C */
187             c == 27)    /* ESC */
188           exit (0);
189         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
190           XBell (dpy, 0);  /* beep for non-chord keys */
191       }
192       break;
193     case ButtonPress:
194       XBell (dpy, 0);
195       break;
196     case ClientMessage:
197       {
198         if (event->xclient.message_type != XA_WM_PROTOCOLS)
199           {
200             char *s = XGetAtomName(dpy, event->xclient.message_type);
201             if (!s) s = "(null)";
202             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
203                      progname, s);
204           }
205         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
206           {
207             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
208             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
209             if (!s1) s1 = "(null)";
210             if (!s2) s2 = "(null)";
211             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
212                      progname, s1, s2);
213           }
214         else
215           {
216             exit (0);
217           }
218       }
219       break;
220     }
221 }
222
223
224 void
225 screenhack_handle_events (Display *dpy)
226 {
227   while (XPending (dpy))
228     {
229       XEvent event;
230       XNextEvent (dpy, &event);
231       screenhack_handle_event (dpy, &event);
232     }
233 }
234
235
236 static Visual *
237 pick_visual (Screen *screen)
238 {
239 #ifdef USE_GL
240   /* If we're linking against GL (that is, this is the version of screenhack.o
241      that the GL hacks will use, which is different from the one that the
242      non-GL hacks will use) then try to pick the "best" visual by interrogating
243      the GL library instead of by asking Xlib.  GL knows better.
244    */
245   Visual *v = 0;
246   char *string = get_string_resource ("visualID", "VisualID");
247
248   if (!string || !*string ||
249       !strcmp (string, "gl") ||
250       !strcmp (string, "best") ||
251       !strcmp (string, "color") ||
252       !strcmp (string, "default"))
253     v = get_gl_visual (screen);         /* from ../utils/visual-gl.c */
254
255   if (string)
256     free (string);
257   if (v)
258     return v;
259 #endif /* USE_GL */
260
261   return get_visual_resource (screen, "visualID", "VisualID", False);
262 }
263
264
265 int
266 main (int argc, char **argv)
267 {
268   Widget toplevel;
269   Display *dpy;
270   Window window;
271   Visual *visual;
272   Colormap cmap;
273   Bool root_p;
274   Window on_window = 0;
275   XEvent event;
276   Boolean dont_clear /*, dont_map */;
277   char version[255];
278
279 #ifdef XLOCKMORE
280   pre_merge_options ();
281 #endif
282   merge_options ();
283
284 #ifdef __sgi
285   /* We have to do this on SGI to prevent the background color from being
286      overridden by the current desktop color scheme (we'd like our backgrounds
287      to be black, thanks.)  This should be the same as setting the
288      "*useSchemes: none" resource, but it's not -- if that resource is
289      present in the `default_defaults' above, it doesn't work, though it
290      does work when passed as an -xrm arg on the command line.  So screw it,
291      turn them off from C instead.
292    */
293   SgiUseSchemes ("none"); 
294 #endif /* __sgi */
295
296   toplevel = XtAppInitialize (&app, progclass, merged_options,
297                               merged_options_size, &argc, argv,
298                               merged_defaults, 0, 0);
299   dpy = XtDisplay (toplevel);
300   db = XtDatabase (dpy);
301   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
302   XSetErrorHandler (screenhack_ehandler);
303
304   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
305   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
306
307   {
308     char *v = (char *) strdup(strchr(screensaver_id, ' '));
309     char *s1, *s2, *s3, *s4;
310     s1 = (char *) strchr(v,  ' '); s1++;
311     s2 = (char *) strchr(s1, ' ');
312     s3 = (char *) strchr(v,  '('); s3++;
313     s4 = (char *) strchr(s3, ')');
314     *s2 = 0;
315     *s4 = 0;
316     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
317              progclass, s1, s3);
318     free(v);
319   }
320
321   if (argc > 1)
322     {
323       const char *s;
324       int i;
325       int x = 18;
326       int end = 78;
327       Bool help_p = !strcmp(argv[1], "-help");
328       fprintf (stderr, "%s\n", version);
329       for (s = progclass; *s; s++) fprintf(stderr, " ");
330       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
331
332       if (!help_p)
333         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
334       fprintf (stderr, "Options include: ");
335       for (i = 0; i < merged_options_size; i++)
336         {
337           char *sw = merged_options [i].option;
338           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
339           int size = strlen (sw) + (argp ? 6 : 0) + 2;
340           if (x + size >= end)
341             {
342               fprintf (stderr, "\n\t\t ");
343               x = 18;
344             }
345           x += size;
346           fprintf (stderr, "%s", sw);
347           if (argp) fprintf (stderr, " <arg>");
348           if (i != merged_options_size - 1) fprintf (stderr, ", ");
349         }
350       fprintf (stderr, ".\n");
351       exit (help_p ? 0 : 1);
352     }
353
354   dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
355 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
356   mono_p = get_boolean_resource ("mono", "Boolean");
357   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
358     mono_p = True;
359
360   root_p = get_boolean_resource ("root", "Boolean");
361
362   {
363     char *s = get_string_resource ("windowID", "WindowID");
364     if (s && *s)
365       on_window = get_integer_resource ("windowID", "WindowID");
366     if (s) free (s);
367   }
368
369   if (on_window)
370     {
371       XWindowAttributes xgwa;
372       window = (Window) on_window;
373       XtDestroyWidget (toplevel);
374       XGetWindowAttributes (dpy, window, &xgwa);
375       cmap = xgwa.colormap;
376       visual = xgwa.visual;
377     }
378   else if (root_p)
379     {
380       XWindowAttributes xgwa;
381       window = RootWindowOfScreen (XtScreen (toplevel));
382       XtDestroyWidget (toplevel);
383       XGetWindowAttributes (dpy, window, &xgwa);
384       cmap = xgwa.colormap;
385       visual = xgwa.visual;
386     }
387   else
388     {
389       Boolean def_visual_p;
390       Screen *screen = XtScreen (toplevel);
391       visual = pick_visual (screen);
392
393       if (toplevel->core.width <= 0)
394         toplevel->core.width = 600;
395       if (toplevel->core.height <= 0)
396         toplevel->core.height = 480;
397
398       def_visual_p = (visual == DefaultVisualOfScreen (screen));
399
400       if (!def_visual_p)
401         {
402           unsigned int bg, bd;
403           Widget new;
404
405           cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
406                                   visual, AllocNone);
407           bg = get_pixel_resource ("background", "Background", dpy, cmap);
408           bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
409
410           new = XtVaAppCreateShell (progname, progclass,
411                                     topLevelShellWidgetClass, dpy,
412                                     XtNmappedWhenManaged, False,
413                                     XtNvisual, visual,
414                                     XtNdepth, visual_depth (screen, visual),
415                                     XtNwidth, toplevel->core.width,
416                                     XtNheight, toplevel->core.height,
417                                     XtNcolormap, cmap,
418                                     XtNbackground, (Pixel) bg,
419                                     XtNborderColor, (Pixel) bd,
420                                     0);
421           XtDestroyWidget (toplevel);
422           toplevel = new;
423           XtRealizeWidget (toplevel);
424           window = XtWindow (toplevel);
425         }
426       else
427         {
428           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
429           XtRealizeWidget (toplevel);
430           window = XtWindow (toplevel);
431
432           if (get_boolean_resource ("installColormap", "InstallColormap"))
433             {
434               cmap = XCreateColormap (dpy, window,
435                                    DefaultVisualOfScreen (XtScreen (toplevel)),
436                                       AllocNone);
437               XSetWindowColormap (dpy, window, cmap);
438             }
439           else
440             {
441               cmap = DefaultColormap (dpy, DefaultScreen (dpy));
442             }
443         }
444
445 /*
446       if (dont_map)
447         {
448           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
449           XtRealizeWidget (toplevel);
450         }
451       else
452 */
453         {
454           XtPopup (toplevel, XtGrabNone);
455         }
456
457       XtVaSetValues(toplevel, XtNtitle, version, 0);
458
459       /* For screenhack_handle_events(): select KeyPress, and
460          announce that we accept WM_DELETE_WINDOW. */
461       {
462         XWindowAttributes xgwa;
463         XGetWindowAttributes (dpy, window, &xgwa);
464         XSelectInput (dpy, window,
465                       xgwa.your_event_mask | KeyPressMask | ButtonPressMask);
466         XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
467                          PropModeReplace,
468                          (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
469       }
470     }
471
472   if (!dont_clear)
473     {
474       XSetWindowBackground (dpy, window,
475                             get_pixel_resource ("background", "Background",
476                                                 dpy, cmap));
477       XClearWindow (dpy, window);
478     }
479
480   if (!root_p && !on_window)
481     /* wait for it to be mapped */
482     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
483
484   XSync (dpy, False);
485   srandom ((int) time ((time_t *) 0));
486   screenhack (dpy, window); /* doesn't return */
487   return 0;
488 }