http://ftp.x.org/contrib/applications/xscreensaver-3.18.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
303   /* half-assed way of avoiding buffer-overrun attacks. */
304   if (strlen (progname) >= 100) progname[100] = 0;
305
306   XSetErrorHandler (screenhack_ehandler);
307
308   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
309   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
310
311   {
312     char *v = (char *) strdup(strchr(screensaver_id, ' '));
313     char *s1, *s2, *s3, *s4;
314     s1 = (char *) strchr(v,  ' '); s1++;
315     s2 = (char *) strchr(s1, ' ');
316     s3 = (char *) strchr(v,  '('); s3++;
317     s4 = (char *) strchr(s3, ')');
318     *s2 = 0;
319     *s4 = 0;
320     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
321              progclass, s1, s3);
322     free(v);
323   }
324
325   if (argc > 1)
326     {
327       const char *s;
328       int i;
329       int x = 18;
330       int end = 78;
331       Bool help_p = !strcmp(argv[1], "-help");
332       fprintf (stderr, "%s\n", version);
333       for (s = progclass; *s; s++) fprintf(stderr, " ");
334       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
335
336       if (!help_p)
337         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
338       fprintf (stderr, "Options include: ");
339       for (i = 0; i < merged_options_size; i++)
340         {
341           char *sw = merged_options [i].option;
342           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
343           int size = strlen (sw) + (argp ? 6 : 0) + 2;
344           if (x + size >= end)
345             {
346               fprintf (stderr, "\n\t\t ");
347               x = 18;
348             }
349           x += size;
350           fprintf (stderr, "%s", sw);
351           if (argp) fprintf (stderr, " <arg>");
352           if (i != merged_options_size - 1) fprintf (stderr, ", ");
353         }
354       fprintf (stderr, ".\n");
355       exit (help_p ? 0 : 1);
356     }
357
358   dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
359 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
360   mono_p = get_boolean_resource ("mono", "Boolean");
361   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
362     mono_p = True;
363
364   root_p = get_boolean_resource ("root", "Boolean");
365
366   {
367     char *s = get_string_resource ("windowID", "WindowID");
368     if (s && *s)
369       on_window = get_integer_resource ("windowID", "WindowID");
370     if (s) free (s);
371   }
372
373   if (on_window)
374     {
375       XWindowAttributes xgwa;
376       window = (Window) on_window;
377       XtDestroyWidget (toplevel);
378       XGetWindowAttributes (dpy, window, &xgwa);
379       cmap = xgwa.colormap;
380       visual = xgwa.visual;
381     }
382   else if (root_p)
383     {
384       XWindowAttributes xgwa;
385       window = RootWindowOfScreen (XtScreen (toplevel));
386       XtDestroyWidget (toplevel);
387       XGetWindowAttributes (dpy, window, &xgwa);
388       cmap = xgwa.colormap;
389       visual = xgwa.visual;
390     }
391   else
392     {
393       Boolean def_visual_p;
394       Screen *screen = XtScreen (toplevel);
395       visual = pick_visual (screen);
396
397       if (toplevel->core.width <= 0)
398         toplevel->core.width = 600;
399       if (toplevel->core.height <= 0)
400         toplevel->core.height = 480;
401
402       def_visual_p = (visual == DefaultVisualOfScreen (screen));
403
404       if (!def_visual_p)
405         {
406           unsigned int bg, bd;
407           Widget new;
408
409           cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
410                                   visual, AllocNone);
411           bg = get_pixel_resource ("background", "Background", dpy, cmap);
412           bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
413
414           new = XtVaAppCreateShell (progname, progclass,
415                                     topLevelShellWidgetClass, dpy,
416                                     XtNmappedWhenManaged, False,
417                                     XtNvisual, visual,
418                                     XtNdepth, visual_depth (screen, visual),
419                                     XtNwidth, toplevel->core.width,
420                                     XtNheight, toplevel->core.height,
421                                     XtNcolormap, cmap,
422                                     XtNbackground, (Pixel) bg,
423                                     XtNborderColor, (Pixel) bd,
424                                     0);
425           XtDestroyWidget (toplevel);
426           toplevel = new;
427           XtRealizeWidget (toplevel);
428           window = XtWindow (toplevel);
429         }
430       else
431         {
432           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
433           XtRealizeWidget (toplevel);
434           window = XtWindow (toplevel);
435
436           if (get_boolean_resource ("installColormap", "InstallColormap"))
437             {
438               cmap = XCreateColormap (dpy, window,
439                                    DefaultVisualOfScreen (XtScreen (toplevel)),
440                                       AllocNone);
441               XSetWindowColormap (dpy, window, cmap);
442             }
443           else
444             {
445               cmap = DefaultColormap (dpy, DefaultScreen (dpy));
446             }
447         }
448
449 /*
450       if (dont_map)
451         {
452           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
453           XtRealizeWidget (toplevel);
454         }
455       else
456 */
457         {
458           XtPopup (toplevel, XtGrabNone);
459         }
460
461       XtVaSetValues(toplevel, XtNtitle, version, 0);
462
463       /* For screenhack_handle_events(): select KeyPress, and
464          announce that we accept WM_DELETE_WINDOW. */
465       {
466         XWindowAttributes xgwa;
467         XGetWindowAttributes (dpy, window, &xgwa);
468         XSelectInput (dpy, window,
469                       xgwa.your_event_mask | KeyPressMask | ButtonPressMask);
470         XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
471                          PropModeReplace,
472                          (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
473       }
474     }
475
476   if (!dont_clear)
477     {
478       XSetWindowBackground (dpy, window,
479                             get_pixel_resource ("background", "Background",
480                                                 dpy, cmap));
481       XClearWindow (dpy, window);
482     }
483
484   if (!root_p && !on_window)
485     /* wait for it to be mapped */
486     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
487
488   XSync (dpy, False);
489   srandom ((int) time ((time_t *) 0));
490   screenhack (dpy, window); /* doesn't return */
491   return 0;
492 }