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 switch (event->xany.type)
196 XLookupString (&event->xkey, &c, 1, &keysym, 0);
202 else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
203 XBell (dpy, 0); /* beep for non-chord keys */
211 if (event->xclient.message_type != XA_WM_PROTOCOLS)
213 char *s = XGetAtomName(dpy, event->xclient.message_type);
214 if (!s) s = "(null)";
215 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
218 else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
220 char *s1 = XGetAtomName(dpy, event->xclient.message_type);
221 char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
222 if (!s1) s1 = "(null)";
223 if (!s2) s2 = "(null)";
224 fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
238 screenhack_handle_events (Display *dpy)
240 while (XPending (dpy))
243 XNextEvent (dpy, &event);
244 screenhack_handle_event (dpy, &event);
250 pick_visual (Screen *screen)
253 /* If we're linking against GL (that is, this is the version of screenhack.o
254 that the GL hacks will use, which is different from the one that the
255 non-GL hacks will use) then try to pick the "best" visual by interrogating
256 the GL library instead of by asking Xlib. GL knows better.
259 char *string = get_string_resource ("visualID", "VisualID");
263 for (s = string; *s; s++)
264 if (isupper (*s)) *s = _tolower (*s);
266 if (!string || !*string ||
267 !strcmp (string, "gl") ||
268 !strcmp (string, "best") ||
269 !strcmp (string, "color") ||
270 !strcmp (string, "default"))
271 v = get_gl_visual (screen); /* from ../utils/visual-gl.c */
279 return get_visual_resource (screen, "visualID", "VisualID", False);
283 /* Notice when the user has requested a different visual or colormap
284 on a pre-existing window (e.g., "-root -visual truecolor" or
285 "-window-id 0x2c00001 -install") and complain, since when drawing
286 on an existing window, we have no choice about these things.
289 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
292 char *visual_string = get_string_resource ("visualID", "VisualID");
293 Visual *desired_visual = pick_visual (screen);
297 if (window == RootWindowOfScreen (screen))
298 strcpy (win, "root window");
300 sprintf (win, "window 0x%lx", (unsigned long) window);
303 sprintf (why, "-window-id 0x%lx", (unsigned long) window);
305 strcpy (why, "-root");
307 if (visual_string && *visual_string)
310 for (s = visual_string; *s; s++)
311 if (isupper (*s)) *s = _tolower (*s);
313 if (!strcmp (visual_string, "default") ||
314 !strcmp (visual_string, "default") ||
315 !strcmp (visual_string, "best"))
316 /* don't warn about these, just silently DWIM. */
318 else if (visual != desired_visual)
320 fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
321 progname, visual_string, why);
322 fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
323 progname, win, XVisualIDFromVisual (visual));
325 free (visual_string);
328 if (visual == DefaultVisualOfScreen (screen) &&
329 has_writable_cells (screen, visual) &&
330 get_boolean_resource ("installColormap", "InstallColormap"))
332 fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
334 fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
335 progname, win, (unsigned long) cmap);
339 if (!validate_gl_visual (stderr, screen, win, visual))
348 /* Bad Things Happen if stdin, stdout, and stderr have been closed
349 (as by the `sh incantation "attraction >&- 2>&-"). When you do
350 that, the X connection gets allocated to one of these fds, and
351 then some random library writes to stderr, and random bits get
352 stuffed down the X pipe, causing "Xlib: sequence lost" errors.
353 So, we cause the first three file descriptors to be open to
354 /dev/null if they aren't open to something else already. This
355 must be done before any other files are opened (or the closing
356 of that other file will again free up one of the "magic" first
359 We do this by opening /dev/null three times, and then closing
360 those fds, *unless* any of them got allocated as #0, #1, or #2,
361 in which case we leave them open. Gag.
363 Really, this crap is technically required of *every* X program,
364 if you want it to be robust in the face of "2>&-".
366 int fd0 = open ("/dev/null", O_RDWR);
367 int fd1 = open ("/dev/null", O_RDWR);
368 int fd2 = open ("/dev/null", O_RDWR);
369 if (fd0 > 2) close (fd0);
370 if (fd1 > 2) close (fd1);
371 if (fd2 > 2) close (fd2);
376 main (int argc, char **argv)
385 Window on_window = 0;
387 Boolean dont_clear /*, dont_map */;
393 pre_merge_options ();
398 /* We have to do this on SGI to prevent the background color from being
399 overridden by the current desktop color scheme (we'd like our backgrounds
400 to be black, thanks.) This should be the same as setting the
401 "*useSchemes: none" resource, but it's not -- if that resource is
402 present in the `default_defaults' above, it doesn't work, though it
403 does work when passed as an -xrm arg on the command line. So screw it,
404 turn them off from C instead.
406 SgiUseSchemes ("none");
409 toplevel = XtAppInitialize (&app, progclass, merged_options,
410 merged_options_size, &argc, argv,
411 merged_defaults, 0, 0);
412 dpy = XtDisplay (toplevel);
413 screen = XtScreen (toplevel);
414 db = XtDatabase (dpy);
416 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
418 /* half-assed way of avoiding buffer-overrun attacks. */
419 if (strlen (progname) >= 100) progname[100] = 0;
421 XSetErrorHandler (screenhack_ehandler);
423 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
424 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
427 char *v = (char *) strdup(strchr(screensaver_id, ' '));
428 char *s1, *s2, *s3, *s4;
429 s1 = (char *) strchr(v, ' '); s1++;
430 s2 = (char *) strchr(s1, ' ');
431 s3 = (char *) strchr(v, '('); s3++;
432 s4 = (char *) strchr(s3, ')');
435 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
446 Bool help_p = (!strcmp(argv[1], "-help") ||
447 !strcmp(argv[1], "--help"));
448 fprintf (stderr, "%s\n", version);
449 for (s = progclass; *s; s++) fprintf(stderr, " ");
450 fprintf (stderr, " http://www.jwz.org/xscreensaver/\n\n");
453 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
454 fprintf (stderr, "Options include: ");
455 for (i = 0; i < merged_options_size; i++)
457 char *sw = merged_options [i].option;
458 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
459 int size = strlen (sw) + (argp ? 6 : 0) + 2;
462 fprintf (stderr, "\n\t\t ");
466 fprintf (stderr, "%s", sw);
467 if (argp) fprintf (stderr, " <arg>");
468 if (i != merged_options_size - 1) fprintf (stderr, ", ");
471 fprintf (stderr, ".\n");
476 fprintf (stderr, "\nResources:\n\n");
477 for (i = 0; i < merged_options_size; i++)
479 const char *opt = merged_options [i].option;
480 const char *res = merged_options [i].specifier + 1;
481 const char *val = merged_options [i].value;
482 char *s = get_string_resource ((char *) res, (char *) res);
487 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
491 fprintf (stderr, " %-16s %-18s ", opt, res);
492 if (merged_options [i].argKind == XrmoptionSepArg)
494 fprintf (stderr, "[%s]", (s ? s : "?"));
498 fprintf (stderr, "%s", (val ? val : "(null)"));
499 if (val && s && !strcasecmp (val, s))
500 fprintf (stderr, " [default]");
502 fprintf (stderr, "\n");
504 fprintf (stderr, "\n");
508 exit (help_p ? 0 : 1);
511 dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
512 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
513 mono_p = get_boolean_resource ("mono", "Boolean");
514 if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
517 root_p = get_boolean_resource ("root", "Boolean");
520 char *s = get_string_resource ("windowID", "WindowID");
522 on_window = get_integer_resource ("windowID", "WindowID");
528 XWindowAttributes xgwa;
529 window = (Window) on_window;
530 XtDestroyWidget (toplevel);
531 XGetWindowAttributes (dpy, window, &xgwa);
532 cmap = xgwa.colormap;
533 visual = xgwa.visual;
534 screen = xgwa.screen;
535 visual_warning (screen, window, visual, cmap, True);
537 /* Select KeyPress and resize events on the external window.
539 xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
540 XSelectInput (dpy, window, xgwa.your_event_mask);
542 /* Select ButtonPress and ButtonRelease events on the external window,
543 if no other app has already selected them (only one app can select
544 ButtonPress at a time: BadAccess results.)
546 if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
547 XSelectInput (dpy, window,
548 (xgwa.your_event_mask |
549 ButtonPressMask | ButtonReleaseMask));
553 XWindowAttributes xgwa;
554 window = VirtualRootWindowOfScreen (XtScreen (toplevel));
555 XtDestroyWidget (toplevel);
556 XGetWindowAttributes (dpy, window, &xgwa);
557 cmap = xgwa.colormap;
558 visual = xgwa.visual;
559 visual_warning (screen, window, visual, cmap, False);
563 Boolean def_visual_p;
564 visual = pick_visual (screen);
567 if (!validate_gl_visual (stderr, screen, "window", visual))
571 if (toplevel->core.width <= 0)
572 toplevel->core.width = 600;
573 if (toplevel->core.height <= 0)
574 toplevel->core.height = 480;
576 def_visual_p = (visual == DefaultVisualOfScreen (screen));
583 cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
585 bg = get_pixel_resource ("background", "Background", dpy, cmap);
586 bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
588 new = XtVaAppCreateShell (progname, progclass,
589 topLevelShellWidgetClass, dpy,
590 XtNmappedWhenManaged, False,
592 XtNdepth, visual_depth (screen, visual),
593 XtNwidth, toplevel->core.width,
594 XtNheight, toplevel->core.height,
596 XtNbackground, (Pixel) bg,
597 XtNborderColor, (Pixel) bd,
598 XtNinput, True, /* for WM_HINTS */
600 XtDestroyWidget (toplevel);
602 XtRealizeWidget (toplevel);
603 window = XtWindow (toplevel);
607 XtVaSetValues (toplevel,
608 XtNmappedWhenManaged, False,
609 XtNinput, True, /* for WM_HINTS */
611 XtRealizeWidget (toplevel);
612 window = XtWindow (toplevel);
614 if (get_boolean_resource ("installColormap", "InstallColormap"))
616 cmap = XCreateColormap (dpy, window,
617 DefaultVisualOfScreen (XtScreen (toplevel)),
619 XSetWindowColormap (dpy, window, cmap);
623 cmap = DefaultColormap (dpy, DefaultScreen (dpy));
630 XtVaSetValues (toplevel, XtNmappedWhenManaged, False, NULL);
631 XtRealizeWidget (toplevel);
636 XtPopup (toplevel, XtGrabNone);
639 XtVaSetValues(toplevel, XtNtitle, version, NULL);
641 /* For screenhack_handle_events(): select KeyPress, and
642 announce that we accept WM_DELETE_WINDOW. */
644 XWindowAttributes xgwa;
645 XGetWindowAttributes (dpy, window, &xgwa);
646 XSelectInput (dpy, window,
647 (xgwa.your_event_mask | KeyPressMask |
648 ButtonPressMask | ButtonReleaseMask));
649 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
651 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
657 XSetWindowBackground (dpy, window,
658 get_pixel_resource ("background", "Background",
660 XClearWindow (dpy, window);
663 if (!root_p && !on_window)
664 /* wait for it to be mapped */
665 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
669 /* This is the one and only place that the random-number generator is
670 seeded in any screenhack. You do not need to seed the RNG again,
671 it is done for you before your code is invoked. */
675 screenhack (dpy, window); /* doesn't return */