1 /* xscreensaver, Copyright (c) 1992, 1995, 1997, 1998, 2001, 2002, 2003, 2004
2 * Jamie Zawinski <jwz@jwz.org>
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
12 * And remember: X Windows is to graphics hacking as roman numerals are to
13 * the square root of pi.
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.
20 - create a procedure `screenhack(dpy, window)'
22 - create a variable `char *progclass' which names this program's
25 - create a variable `char defaults []' for the default resources, and
28 - create a variable `XrmOptionDescRec options[]' for the command-line,
29 and null-terminate it.
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>
44 # include <X11/SGIScheme.h> /* for SgiUseSchemes() */
49 # include <X11/Xmu/Error.h>
51 # include <Xmu/Error.h>
56 #include "screenhack.h"
60 #ifndef _XSCREENSAVER_VROOT_H_
61 # error Error! You have an old version of vroot.h! Check -I args.
62 #endif /* _XSCREENSAVER_VROOT_H_ */
65 # define isupper(c) ((c) >= 'A' && (c) <= 'Z')
68 # define _tolower(c) ((c) - 'A' + 'a')
77 static XrmOptionDescRec default_options [] = {
78 { "-root", ".root", XrmoptionNoArg, "True" },
79 { "-window", ".root", XrmoptionNoArg, "False" },
80 { "-mono", ".mono", XrmoptionNoArg, "True" },
81 { "-install", ".installColormap", XrmoptionNoArg, "True" },
82 { "-noinstall",".installColormap", XrmoptionNoArg, "False" },
83 { "-visual", ".visualID", XrmoptionSepArg, 0 },
84 { "-window-id", ".windowID", XrmoptionSepArg, 0 },
88 static char *default_defaults[] = {
90 "*geometry: 600x480", /* this should be .geometry, but nooooo... */
92 "*installColormap: false",
95 "*desktopGrabber: xscreensaver-getimage %s",
99 static XrmOptionDescRec *merged_options;
100 static int merged_options_size;
101 static char **merged_defaults;
106 int def_opts_size, opts_size;
107 int def_defaults_size, defaults_size;
109 for (def_opts_size = 0; default_options[def_opts_size].option;
112 for (opts_size = 0; options[opts_size].option; opts_size++)
115 merged_options_size = def_opts_size + opts_size;
116 merged_options = (XrmOptionDescRec *)
117 malloc ((merged_options_size + 1) * sizeof(*default_options));
118 memcpy (merged_options, default_options,
119 (def_opts_size * sizeof(*default_options)));
120 memcpy (merged_options + def_opts_size, options,
121 ((opts_size + 1) * sizeof(*default_options)));
123 for (def_defaults_size = 0; default_defaults[def_defaults_size];
126 for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
128 merged_defaults = (char **)
129 malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
130 memcpy (merged_defaults, default_defaults,
131 def_defaults_size * sizeof(*defaults));
132 memcpy (merged_defaults + def_defaults_size, defaults,
133 (defaults_size + 1) * sizeof(*defaults));
135 /* This totally sucks. Xt should behave like this by default.
136 If the string in `defaults' looks like ".foo", change that
141 for (s = merged_defaults; *s; s++)
144 const char *oldr = *s;
145 char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
146 strcpy (newr, progclass);
154 /* Make the X errors print out the name of this program, so we have some
155 clue which one has a bug when they die under the screensaver.
159 screenhack_ehandler (Display *dpy, XErrorEvent *error)
161 fprintf (stderr, "\nX error in %s:\n", progname);
162 if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
165 fprintf (stderr, " (nonfatal.)\n");
170 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
172 return (event->xany.type == MapNotify &&
173 event->xvisibility.window == (Window) window);
178 extern void pre_merge_options (void);
182 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
184 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
185 Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
188 screenhack_handle_event (Display *dpy, XEvent *event)
190 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
191 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
193 switch (event->xany.type)
199 XLookupString (&event->xkey, &c, 1, &keysym, 0);
205 else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
206 XBell (dpy, 0); /* beep for non-chord keys */
214 if (event->xclient.message_type != XA_WM_PROTOCOLS)
216 char *s = XGetAtomName(dpy, event->xclient.message_type);
217 if (!s) s = "(null)";
218 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
221 else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
223 char *s1 = XGetAtomName(dpy, event->xclient.message_type);
224 char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
225 if (!s1) s1 = "(null)";
226 if (!s2) s2 = "(null)";
227 fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
241 screenhack_handle_events (Display *dpy)
243 while (XPending (dpy))
246 XNextEvent (dpy, &event);
247 screenhack_handle_event (dpy, &event);
253 pick_visual (Screen *screen)
256 /* If we're linking against GL (that is, this is the version of screenhack.o
257 that the GL hacks will use, which is different from the one that the
258 non-GL hacks will use) then try to pick the "best" visual by interrogating
259 the GL library instead of by asking Xlib. GL knows better.
262 char *string = get_string_resource ("visualID", "VisualID");
266 for (s = string; *s; s++)
267 if (isupper (*s)) *s = _tolower (*s);
269 if (!string || !*string ||
270 !strcmp (string, "gl") ||
271 !strcmp (string, "best") ||
272 !strcmp (string, "color") ||
273 !strcmp (string, "default"))
274 v = get_gl_visual (screen); /* from ../utils/visual-gl.c */
282 return get_visual_resource (screen, "visualID", "VisualID", False);
286 /* Notice when the user has requested a different visual or colormap
287 on a pre-existing window (e.g., "-root -visual truecolor" or
288 "-window-id 0x2c00001 -install") and complain, since when drawing
289 on an existing window, we have no choice about these things.
292 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
295 char *visual_string = get_string_resource ("visualID", "VisualID");
296 Visual *desired_visual = pick_visual (screen);
300 if (window == RootWindowOfScreen (screen))
301 strcpy (win, "root window");
303 sprintf (win, "window 0x%lx", (unsigned long) window);
306 sprintf (why, "-window-id 0x%lx", (unsigned long) window);
308 strcpy (why, "-root");
310 if (visual_string && *visual_string)
313 for (s = visual_string; *s; s++)
314 if (isupper (*s)) *s = _tolower (*s);
316 if (!strcmp (visual_string, "default") ||
317 !strcmp (visual_string, "default") ||
318 !strcmp (visual_string, "best"))
319 /* don't warn about these, just silently DWIM. */
321 else if (visual != desired_visual)
323 fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
324 progname, visual_string, why);
325 fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
326 progname, win, XVisualIDFromVisual (visual));
328 free (visual_string);
331 if (visual == DefaultVisualOfScreen (screen) &&
332 has_writable_cells (screen, visual) &&
333 get_boolean_resource ("installColormap", "InstallColormap"))
335 fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
337 fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
338 progname, win, (unsigned long) cmap);
342 if (!validate_gl_visual (stderr, screen, win, visual))
351 /* Bad Things Happen if stdin, stdout, and stderr have been closed
352 (as by the `sh incantation "attraction >&- 2>&-"). When you do
353 that, the X connection gets allocated to one of these fds, and
354 then some random library writes to stderr, and random bits get
355 stuffed down the X pipe, causing "Xlib: sequence lost" errors.
356 So, we cause the first three file descriptors to be open to
357 /dev/null if they aren't open to something else already. This
358 must be done before any other files are opened (or the closing
359 of that other file will again free up one of the "magic" first
362 We do this by opening /dev/null three times, and then closing
363 those fds, *unless* any of them got allocated as #0, #1, or #2,
364 in which case we leave them open. Gag.
366 Really, this crap is technically required of *every* X program,
367 if you want it to be robust in the face of "2>&-".
369 int fd0 = open ("/dev/null", O_RDWR);
370 int fd1 = open ("/dev/null", O_RDWR);
371 int fd2 = open ("/dev/null", O_RDWR);
372 if (fd0 > 2) close (fd0);
373 if (fd1 > 2) close (fd1);
374 if (fd2 > 2) close (fd2);
379 main (int argc, char **argv)
388 Window on_window = 0;
390 Boolean dont_clear /*, dont_map */;
396 pre_merge_options ();
401 /* We have to do this on SGI to prevent the background color from being
402 overridden by the current desktop color scheme (we'd like our backgrounds
403 to be black, thanks.) This should be the same as setting the
404 "*useSchemes: none" resource, but it's not -- if that resource is
405 present in the `default_defaults' above, it doesn't work, though it
406 does work when passed as an -xrm arg on the command line. So screw it,
407 turn them off from C instead.
409 SgiUseSchemes ("none");
412 toplevel = XtAppInitialize (&app, progclass, merged_options,
413 merged_options_size, &argc, argv,
414 merged_defaults, 0, 0);
415 dpy = XtDisplay (toplevel);
416 screen = XtScreen (toplevel);
417 db = XtDatabase (dpy);
419 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
421 /* half-assed way of avoiding buffer-overrun attacks. */
422 if (strlen (progname) >= 100) progname[100] = 0;
424 XSetErrorHandler (screenhack_ehandler);
426 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
427 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
430 char *v = (char *) strdup(strchr(screensaver_id, ' '));
431 char *s1, *s2, *s3, *s4;
432 s1 = (char *) strchr(v, ' '); s1++;
433 s2 = (char *) strchr(s1, ' ');
434 s3 = (char *) strchr(v, '('); s3++;
435 s4 = (char *) strchr(s3, ')');
438 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
449 Bool help_p = (!strcmp(argv[1], "-help") ||
450 !strcmp(argv[1], "--help"));
451 fprintf (stderr, "%s\n", version);
452 for (s = progclass; *s; s++) fprintf(stderr, " ");
453 fprintf (stderr, " http://www.jwz.org/xscreensaver/\n\n");
456 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
457 fprintf (stderr, "Options include: ");
458 for (i = 0; i < merged_options_size; i++)
460 char *sw = merged_options [i].option;
461 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
462 int size = strlen (sw) + (argp ? 6 : 0) + 2;
465 fprintf (stderr, "\n\t\t ");
469 fprintf (stderr, "%s", sw);
470 if (argp) fprintf (stderr, " <arg>");
471 if (i != merged_options_size - 1) fprintf (stderr, ", ");
474 fprintf (stderr, ".\n");
479 fprintf (stderr, "\nResources:\n\n");
480 for (i = 0; i < merged_options_size; i++)
482 const char *opt = merged_options [i].option;
483 const char *res = merged_options [i].specifier + 1;
484 const char *val = merged_options [i].value;
485 char *s = get_string_resource ((char *) res, (char *) res);
490 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
494 fprintf (stderr, " %-16s %-18s ", opt, res);
495 if (merged_options [i].argKind == XrmoptionSepArg)
497 fprintf (stderr, "[%s]", (s ? s : "?"));
501 fprintf (stderr, "%s", (val ? val : "(null)"));
502 if (val && s && !strcasecmp (val, s))
503 fprintf (stderr, " [default]");
505 fprintf (stderr, "\n");
507 fprintf (stderr, "\n");
511 exit (help_p ? 0 : 1);
514 dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
515 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
516 mono_p = get_boolean_resource ("mono", "Boolean");
517 if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
520 root_p = get_boolean_resource ("root", "Boolean");
523 char *s = get_string_resource ("windowID", "WindowID");
525 on_window = get_integer_resource ("windowID", "WindowID");
531 XWindowAttributes xgwa;
532 window = (Window) on_window;
533 XtDestroyWidget (toplevel);
534 XGetWindowAttributes (dpy, window, &xgwa);
535 cmap = xgwa.colormap;
536 visual = xgwa.visual;
537 screen = xgwa.screen;
538 visual_warning (screen, window, visual, cmap, True);
540 /* Select KeyPress and resize events on the external window.
542 xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
543 XSelectInput (dpy, window, xgwa.your_event_mask);
545 /* Select ButtonPress and ButtonRelease events on the external window,
546 if no other app has already selected them (only one app can select
547 ButtonPress at a time: BadAccess results.)
549 if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
550 XSelectInput (dpy, window,
551 (xgwa.your_event_mask |
552 ButtonPressMask | ButtonReleaseMask));
556 XWindowAttributes xgwa;
557 window = VirtualRootWindowOfScreen (XtScreen (toplevel));
558 XtDestroyWidget (toplevel);
559 XGetWindowAttributes (dpy, window, &xgwa);
560 cmap = xgwa.colormap;
561 visual = xgwa.visual;
562 visual_warning (screen, window, visual, cmap, False);
566 Boolean def_visual_p;
567 visual = pick_visual (screen);
570 if (!validate_gl_visual (stderr, screen, "window", visual))
574 if (toplevel->core.width <= 0)
575 toplevel->core.width = 600;
576 if (toplevel->core.height <= 0)
577 toplevel->core.height = 480;
579 def_visual_p = (visual == DefaultVisualOfScreen (screen));
586 cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
588 bg = get_pixel_resource ("background", "Background", dpy, cmap);
589 bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
591 new = XtVaAppCreateShell (progname, progclass,
592 topLevelShellWidgetClass, dpy,
593 XtNmappedWhenManaged, False,
595 XtNdepth, visual_depth (screen, visual),
596 XtNwidth, toplevel->core.width,
597 XtNheight, toplevel->core.height,
599 XtNbackground, (Pixel) bg,
600 XtNborderColor, (Pixel) bd,
601 XtNinput, True, /* for WM_HINTS */
603 XtDestroyWidget (toplevel);
605 XtRealizeWidget (toplevel);
606 window = XtWindow (toplevel);
610 XtVaSetValues (toplevel,
611 XtNmappedWhenManaged, False,
612 XtNinput, True, /* for WM_HINTS */
614 XtRealizeWidget (toplevel);
615 window = XtWindow (toplevel);
617 if (get_boolean_resource ("installColormap", "InstallColormap"))
619 cmap = XCreateColormap (dpy, window,
620 DefaultVisualOfScreen (XtScreen (toplevel)),
622 XSetWindowColormap (dpy, window, cmap);
626 cmap = DefaultColormap (dpy, DefaultScreen (dpy));
633 XtVaSetValues (toplevel, XtNmappedWhenManaged, False, NULL);
634 XtRealizeWidget (toplevel);
639 XtPopup (toplevel, XtGrabNone);
642 XtVaSetValues(toplevel, XtNtitle, version, NULL);
644 /* For screenhack_handle_events(): select KeyPress, and
645 announce that we accept WM_DELETE_WINDOW. */
647 XWindowAttributes xgwa;
648 XGetWindowAttributes (dpy, window, &xgwa);
649 XSelectInput (dpy, window,
650 (xgwa.your_event_mask | KeyPressMask |
651 ButtonPressMask | ButtonReleaseMask));
652 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
654 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
660 XSetWindowBackground (dpy, window,
661 get_pixel_resource ("background", "Background",
663 XClearWindow (dpy, window);
666 if (!root_p && !on_window)
667 /* wait for it to be mapped */
668 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
672 /* This is the one and only place that the random-number generator is
673 seeded in any screenhack. You do not need to seed the RNG again,
674 it is done for you before your code is invoked. */
678 screenhack (dpy, window); /* doesn't return */