7c11b35b04be6f5b9209b3ff63e806582a007498
[xscreensaver] / hacks / screenhack.c
1 /* xscreensaver, Copyright (c) 1992, 1995, 1997, 1998, 2001
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 #ifndef isupper
61 # define isupper(c)  ((c) >= 'A' && (c) <= 'Z')
62 #endif
63 #ifndef _tolower
64 # define _tolower(c)  ((c) - 'A' + 'a')
65 #endif
66
67
68 char *progname;
69 XrmDatabase db;
70 XtAppContext app;
71 Bool mono_p;
72
73 static XrmOptionDescRec default_options [] = {
74   { "-root",    ".root",                XrmoptionNoArg, "True" },
75   { "-window",  ".root",                XrmoptionNoArg, "False" },
76   { "-mono",    ".mono",                XrmoptionNoArg, "True" },
77   { "-install", ".installColormap",     XrmoptionNoArg, "True" },
78   { "-noinstall",".installColormap",    XrmoptionNoArg, "False" },
79   { "-visual",  ".visualID",            XrmoptionSepArg, 0 },
80   { "-window-id", ".windowID",          XrmoptionSepArg, 0 },
81   { 0, 0, 0, 0 }
82 };
83
84 static char *default_defaults[] = {
85   ".root:               false",
86   "*geometry:           600x480", /* this should be .geometry, but nooooo... */
87   "*mono:               false",
88   "*installColormap:    false",
89   "*visualID:           default",
90   "*windowID:           ",
91   "*desktopGrabber:     xscreensaver-getimage %s",
92   0
93 };
94
95 static XrmOptionDescRec *merged_options;
96 static int merged_options_size;
97 static char **merged_defaults;
98
99 static void
100 merge_options (void)
101 {
102   int def_opts_size, opts_size;
103   int def_defaults_size, defaults_size;
104
105   for (def_opts_size = 0; default_options[def_opts_size].option;
106        def_opts_size++)
107     ;
108   for (opts_size = 0; options[opts_size].option; opts_size++)
109     ;
110
111   merged_options_size = def_opts_size + opts_size;
112   merged_options = (XrmOptionDescRec *)
113     malloc ((merged_options_size + 1) * sizeof(*default_options));
114   memcpy (merged_options, default_options,
115           (def_opts_size * sizeof(*default_options)));
116   memcpy (merged_options + def_opts_size, options,
117           ((opts_size + 1) * sizeof(*default_options)));
118
119   for (def_defaults_size = 0; default_defaults[def_defaults_size];
120        def_defaults_size++)
121     ;
122   for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
123     ;
124   merged_defaults = (char **)
125     malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
126   memcpy (merged_defaults, default_defaults,
127           def_defaults_size * sizeof(*defaults));
128   memcpy (merged_defaults + def_defaults_size, defaults,
129           (defaults_size + 1) * sizeof(*defaults));
130
131   /* This totally sucks.  Xt should behave like this by default.
132      If the string in `defaults' looks like ".foo", change that
133      to "Progclass.foo".
134    */
135   {
136     char **s;
137     for (s = merged_defaults; *s; s++)
138       if (**s == '.')
139         {
140           const char *oldr = *s;
141           char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
142           strcpy (newr, progclass);
143           strcat (newr, oldr);
144           *s = newr;
145         }
146   }
147 }
148
149 \f
150 /* Make the X errors print out the name of this program, so we have some
151    clue which one has a bug when they die under the screensaver.
152  */
153
154 static int
155 screenhack_ehandler (Display *dpy, XErrorEvent *error)
156 {
157   fprintf (stderr, "\nX error in %s:\n", progname);
158   if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
159     exit (-1);
160   else
161     fprintf (stderr, " (nonfatal.)\n");
162   return 0;
163 }
164
165 static Bool
166 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
167 {
168   return (event->xany.type == MapNotify &&
169           event->xvisibility.window == (Window) window);
170 }
171
172
173 #ifdef XLOCKMORE
174 extern void pre_merge_options (void);
175 #endif
176
177
178 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
179
180 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
181    Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
182  */
183 void
184 screenhack_handle_event (Display *dpy, XEvent *event)
185 {
186   switch (event->xany.type)
187     {
188     case KeyPress:
189       {
190         KeySym keysym;
191         char c = 0;
192         XLookupString (&event->xkey, &c, 1, &keysym, 0);
193         if (c == 'q' ||
194             c == 'Q' ||
195             c == 3 ||   /* ^C */
196             c == 27)    /* ESC */
197           exit (0);
198         else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
199           XBell (dpy, 0);  /* beep for non-chord keys */
200       }
201       break;
202     case ButtonPress:
203       XBell (dpy, 0);
204       break;
205     case ClientMessage:
206       {
207         if (event->xclient.message_type != XA_WM_PROTOCOLS)
208           {
209             char *s = XGetAtomName(dpy, event->xclient.message_type);
210             if (!s) s = "(null)";
211             fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
212                      progname, s);
213           }
214         else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
215           {
216             char *s1 = XGetAtomName(dpy, event->xclient.message_type);
217             char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
218             if (!s1) s1 = "(null)";
219             if (!s2) s2 = "(null)";
220             fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
221                      progname, s1, s2);
222           }
223         else
224           {
225             exit (0);
226           }
227       }
228       break;
229     }
230 }
231
232
233 void
234 screenhack_handle_events (Display *dpy)
235 {
236   while (XPending (dpy))
237     {
238       XEvent event;
239       XNextEvent (dpy, &event);
240       screenhack_handle_event (dpy, &event);
241     }
242 }
243
244
245 static Visual *
246 pick_visual (Screen *screen)
247 {
248 #ifdef USE_GL
249   /* If we're linking against GL (that is, this is the version of screenhack.o
250      that the GL hacks will use, which is different from the one that the
251      non-GL hacks will use) then try to pick the "best" visual by interrogating
252      the GL library instead of by asking Xlib.  GL knows better.
253    */
254   Visual *v = 0;
255   char *string = get_string_resource ("visualID", "VisualID");
256   char *s;
257
258   if (string)
259     for (s = string; *s; s++)
260       if (isupper (*s)) *s = _tolower (*s);
261
262   if (!string || !*string ||
263       !strcmp (string, "gl") ||
264       !strcmp (string, "best") ||
265       !strcmp (string, "color") ||
266       !strcmp (string, "default"))
267     v = get_gl_visual (screen);         /* from ../utils/visual-gl.c */
268
269   if (string)
270     free (string);
271   if (v)
272     return v;
273 #endif /* USE_GL */
274
275   return get_visual_resource (screen, "visualID", "VisualID", False);
276 }
277
278
279 /* Notice when the user has requested a different visual or colormap
280    on a pre-existing window (e.g., "-root -visual truecolor" or
281    "-window-id 0x2c00001 -install") and complain, since when drawing
282    on an existing window, we have no choice about these things.
283  */
284 static void
285 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
286                 Bool window_p)
287 {
288   char *visual_string = get_string_resource ("visualID", "VisualID");
289   Visual *desired_visual = pick_visual (screen);
290   char win[100];
291   char why[100];
292
293   if (window == RootWindowOfScreen (screen))
294     strcpy (win, "root window");
295   else
296     sprintf (win, "window 0x%x", (unsigned long) window);
297
298   if (window_p)
299     sprintf (why, "-window-id 0x%x", (unsigned long) window);
300   else
301     strcpy (why, "-root");
302
303   if (visual_string && *visual_string)
304     {
305       char *s;
306       for (s = visual_string; *s; s++)
307         if (isupper (*s)) *s = _tolower (*s);
308
309       if (!strcmp (visual_string, "default") ||
310           !strcmp (visual_string, "default") ||
311           !strcmp (visual_string, "best"))
312         /* don't warn about these, just silently DWIM. */
313         ;
314       else if (visual != desired_visual)
315         {
316           fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
317                    progname, visual_string, why);
318           fprintf (stderr, "%s: using %s's visual 0x%x.\n",
319                    progname, win, XVisualIDFromVisual (visual));
320         }
321       free (visual_string);
322     }
323
324   if (visual == DefaultVisualOfScreen (screen) &&
325       has_writable_cells (screen, visual) &&
326       get_boolean_resource ("installColormap", "InstallColormap"))
327     {
328       fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
329                progname, why);
330       fprintf (stderr, "%s: using %s's colormap 0x%x.\n",
331                progname, win, (unsigned long) cmap);
332     }
333
334 # ifdef USE_GL
335   if (!validate_gl_visual (stderr, screen, win, visual))
336     exit (1);
337 # endif /* USE_GL */
338 }
339
340
341 int
342 main (int argc, char **argv)
343 {
344   Widget toplevel;
345   Display *dpy;
346   Window window;
347   Screen *screen;
348   Visual *visual;
349   Colormap cmap;
350   Bool root_p;
351   Window on_window = 0;
352   XEvent event;
353   Boolean dont_clear /*, dont_map */;
354   char version[255];
355
356 #ifdef XLOCKMORE
357   pre_merge_options ();
358 #endif
359   merge_options ();
360
361 #ifdef __sgi
362   /* We have to do this on SGI to prevent the background color from being
363      overridden by the current desktop color scheme (we'd like our backgrounds
364      to be black, thanks.)  This should be the same as setting the
365      "*useSchemes: none" resource, but it's not -- if that resource is
366      present in the `default_defaults' above, it doesn't work, though it
367      does work when passed as an -xrm arg on the command line.  So screw it,
368      turn them off from C instead.
369    */
370   SgiUseSchemes ("none"); 
371 #endif /* __sgi */
372
373   toplevel = XtAppInitialize (&app, progclass, merged_options,
374                               merged_options_size, &argc, argv,
375                               merged_defaults, 0, 0);
376   dpy = XtDisplay (toplevel);
377   screen = XtScreen (toplevel);
378   db = XtDatabase (dpy);
379
380   XtGetApplicationNameAndClass (dpy, &progname, &progclass);
381
382   /* half-assed way of avoiding buffer-overrun attacks. */
383   if (strlen (progname) >= 100) progname[100] = 0;
384
385   XSetErrorHandler (screenhack_ehandler);
386
387   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
388   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
389
390   {
391     char *v = (char *) strdup(strchr(screensaver_id, ' '));
392     char *s1, *s2, *s3, *s4;
393     s1 = (char *) strchr(v,  ' '); s1++;
394     s2 = (char *) strchr(s1, ' ');
395     s3 = (char *) strchr(v,  '('); s3++;
396     s4 = (char *) strchr(s3, ')');
397     *s2 = 0;
398     *s4 = 0;
399     sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
400              progclass, s1, s3);
401     free(v);
402   }
403
404   if (argc > 1)
405     {
406       const char *s;
407       int i;
408       int x = 18;
409       int end = 78;
410       Bool help_p = (!strcmp(argv[1], "-help") ||
411                      !strcmp(argv[1], "--help"));
412       fprintf (stderr, "%s\n", version);
413       for (s = progclass; *s; s++) fprintf(stderr, " ");
414       fprintf (stderr, "  http://www.jwz.org/xscreensaver/\n\n");
415
416       if (!help_p)
417         fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
418       fprintf (stderr, "Options include: ");
419       for (i = 0; i < merged_options_size; i++)
420         {
421           char *sw = merged_options [i].option;
422           Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
423           int size = strlen (sw) + (argp ? 6 : 0) + 2;
424           if (x + size >= end)
425             {
426               fprintf (stderr, "\n\t\t ");
427               x = 18;
428             }
429           x += size;
430           fprintf (stderr, "%s", sw);
431           if (argp) fprintf (stderr, " <arg>");
432           if (i != merged_options_size - 1) fprintf (stderr, ", ");
433         }
434
435       fprintf (stderr, ".\n");
436
437 #if 0
438       if (help_p)
439         {
440           fprintf (stderr, "\nResources:\n\n");
441           for (i = 0; i < merged_options_size; i++)
442             {
443               const char *opt = merged_options [i].option;
444               const char *res = merged_options [i].specifier + 1;
445               const char *val = merged_options [i].value;
446               char *s = get_string_resource ((char *) res, (char *) res);
447
448               if (s)
449                 {
450                   int L = strlen(s);
451                 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
452                   s[--L] = 0;
453                 }
454
455               fprintf (stderr, "    %-16s %-18s ", opt, res);
456               if (merged_options [i].argKind == XrmoptionSepArg)
457                 {
458                   fprintf (stderr, "[%s]", (s ? s : "?"));
459                 }
460               else
461                 {
462                   fprintf (stderr, "%s", (val ? val : "(null)"));
463                   if (val && s && !strcasecmp (val, s))
464                     fprintf (stderr, " [default]");
465                 }
466               fprintf (stderr, "\n");
467             }
468           fprintf (stderr, "\n");
469         }
470 #endif
471
472       exit (help_p ? 0 : 1);
473     }
474
475   dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
476 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
477   mono_p = get_boolean_resource ("mono", "Boolean");
478   if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
479     mono_p = True;
480
481   root_p = get_boolean_resource ("root", "Boolean");
482
483   {
484     char *s = get_string_resource ("windowID", "WindowID");
485     if (s && *s)
486       on_window = get_integer_resource ("windowID", "WindowID");
487     if (s) free (s);
488   }
489
490   if (on_window)
491     {
492       XWindowAttributes xgwa;
493       window = (Window) on_window;
494       XtDestroyWidget (toplevel);
495       XGetWindowAttributes (dpy, window, &xgwa);
496       cmap = xgwa.colormap;
497       visual = xgwa.visual;
498       visual_warning (screen, window, visual, cmap, True);
499     }
500   else if (root_p)
501     {
502       XWindowAttributes xgwa;
503       window = RootWindowOfScreen (XtScreen (toplevel));
504       XtDestroyWidget (toplevel);
505       XGetWindowAttributes (dpy, window, &xgwa);
506       cmap = xgwa.colormap;
507       visual = xgwa.visual;
508       visual_warning (screen, window, visual, cmap, False);
509     }
510   else
511     {
512       Boolean def_visual_p;
513       visual = pick_visual (screen);
514
515 # ifdef USE_GL
516       if (!validate_gl_visual (stderr, screen, "window", visual))
517         exit (1);
518 # endif /* USE_GL */
519
520       if (toplevel->core.width <= 0)
521         toplevel->core.width = 600;
522       if (toplevel->core.height <= 0)
523         toplevel->core.height = 480;
524
525       def_visual_p = (visual == DefaultVisualOfScreen (screen));
526
527       if (!def_visual_p)
528         {
529           unsigned int bg, bd;
530           Widget new;
531
532           cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
533                                   visual, AllocNone);
534           bg = get_pixel_resource ("background", "Background", dpy, cmap);
535           bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
536
537           new = XtVaAppCreateShell (progname, progclass,
538                                     topLevelShellWidgetClass, dpy,
539                                     XtNmappedWhenManaged, False,
540                                     XtNvisual, visual,
541                                     XtNdepth, visual_depth (screen, visual),
542                                     XtNwidth, toplevel->core.width,
543                                     XtNheight, toplevel->core.height,
544                                     XtNcolormap, cmap,
545                                     XtNbackground, (Pixel) bg,
546                                     XtNborderColor, (Pixel) bd,
547                                     XtNinput, True,  /* for WM_HINTS */
548                                     0);
549           XtDestroyWidget (toplevel);
550           toplevel = new;
551           XtRealizeWidget (toplevel);
552           window = XtWindow (toplevel);
553         }
554       else
555         {
556           XtVaSetValues (toplevel,
557                          XtNmappedWhenManaged, False,
558                          XtNinput, True,  /* for WM_HINTS */
559                          0);
560           XtRealizeWidget (toplevel);
561           window = XtWindow (toplevel);
562
563           if (get_boolean_resource ("installColormap", "InstallColormap"))
564             {
565               cmap = XCreateColormap (dpy, window,
566                                    DefaultVisualOfScreen (XtScreen (toplevel)),
567                                       AllocNone);
568               XSetWindowColormap (dpy, window, cmap);
569             }
570           else
571             {
572               cmap = DefaultColormap (dpy, DefaultScreen (dpy));
573             }
574         }
575
576 /*
577       if (dont_map)
578         {
579           XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
580           XtRealizeWidget (toplevel);
581         }
582       else
583 */
584         {
585           XtPopup (toplevel, XtGrabNone);
586         }
587
588       XtVaSetValues(toplevel, XtNtitle, version, 0);
589
590       /* For screenhack_handle_events(): select KeyPress, and
591          announce that we accept WM_DELETE_WINDOW. */
592       {
593         XWindowAttributes xgwa;
594         XGetWindowAttributes (dpy, window, &xgwa);
595         XSelectInput (dpy, window,
596                       xgwa.your_event_mask | KeyPressMask | ButtonPressMask);
597         XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
598                          PropModeReplace,
599                          (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
600       }
601     }
602
603   if (!dont_clear)
604     {
605       XSetWindowBackground (dpy, window,
606                             get_pixel_resource ("background", "Background",
607                                                 dpy, cmap));
608       XClearWindow (dpy, window);
609     }
610
611   if (!root_p && !on_window)
612     /* wait for it to be mapped */
613     XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
614
615   XSync (dpy, False);
616
617   /* This is the one and only place that the random-number generator is
618      seeded in any screenhack.  You do not need to seed the RNG again,
619      it is done for you before your code is invoked. */
620 # undef ya_rand_init
621   ya_rand_init (0);
622
623   screenhack (dpy, window); /* doesn't return */
624   return 0;
625 }