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