1 /* xscreensaver, Copyright (c) 1992, 1995, 1997, 1998, 2001, 2002, 2003
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"
61 # define isupper(c) ((c) >= 'A' && (c) <= 'Z')
64 # define _tolower(c) ((c) - 'A' + 'a')
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 },
84 static char *default_defaults[] = {
86 "*geometry: 600x480", /* this should be .geometry, but nooooo... */
88 "*installColormap: false",
91 "*desktopGrabber: xscreensaver-getimage %s",
95 static XrmOptionDescRec *merged_options;
96 static int merged_options_size;
97 static char **merged_defaults;
102 int def_opts_size, opts_size;
103 int def_defaults_size, defaults_size;
105 for (def_opts_size = 0; default_options[def_opts_size].option;
108 for (opts_size = 0; options[opts_size].option; opts_size++)
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)));
119 for (def_defaults_size = 0; default_defaults[def_defaults_size];
122 for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
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));
131 /* This totally sucks. Xt should behave like this by default.
132 If the string in `defaults' looks like ".foo", change that
137 for (s = merged_defaults; *s; s++)
140 const char *oldr = *s;
141 char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
142 strcpy (newr, progclass);
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.
155 screenhack_ehandler (Display *dpy, XErrorEvent *error)
157 fprintf (stderr, "\nX error in %s:\n", progname);
158 if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
161 fprintf (stderr, " (nonfatal.)\n");
166 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
168 return (event->xany.type == MapNotify &&
169 event->xvisibility.window == (Window) window);
174 extern void pre_merge_options (void);
178 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
180 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
181 Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
184 screenhack_handle_event (Display *dpy, XEvent *event)
186 switch (event->xany.type)
192 XLookupString (&event->xkey, &c, 1, &keysym, 0);
198 else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
199 XBell (dpy, 0); /* beep for non-chord keys */
207 if (event->xclient.message_type != XA_WM_PROTOCOLS)
209 char *s = XGetAtomName(dpy, event->xclient.message_type);
210 if (!s) s = "(null)";
211 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
214 else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
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",
234 screenhack_handle_events (Display *dpy)
236 while (XPending (dpy))
239 XNextEvent (dpy, &event);
240 screenhack_handle_event (dpy, &event);
246 pick_visual (Screen *screen)
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.
255 char *string = get_string_resource ("visualID", "VisualID");
259 for (s = string; *s; s++)
260 if (isupper (*s)) *s = _tolower (*s);
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 */
275 return get_visual_resource (screen, "visualID", "VisualID", False);
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.
285 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
288 char *visual_string = get_string_resource ("visualID", "VisualID");
289 Visual *desired_visual = pick_visual (screen);
293 if (window == RootWindowOfScreen (screen))
294 strcpy (win, "root window");
296 sprintf (win, "window 0x%lx", (unsigned long) window);
299 sprintf (why, "-window-id 0x%lx", (unsigned long) window);
301 strcpy (why, "-root");
303 if (visual_string && *visual_string)
306 for (s = visual_string; *s; s++)
307 if (isupper (*s)) *s = _tolower (*s);
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. */
314 else if (visual != desired_visual)
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%lx.\n",
319 progname, win, XVisualIDFromVisual (visual));
321 free (visual_string);
324 if (visual == DefaultVisualOfScreen (screen) &&
325 has_writable_cells (screen, visual) &&
326 get_boolean_resource ("installColormap", "InstallColormap"))
328 fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
330 fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
331 progname, win, (unsigned long) cmap);
335 if (!validate_gl_visual (stderr, screen, win, visual))
344 /* Bad Things Happen if stdin, stdout, and stderr have been closed
345 (as by the `sh incantation "attraction >&- 2>&-"). When you do
346 that, the X connection gets allocated to one of these fds, and
347 then some random library writes to stderr, and random bits get
348 stuffed down the X pipe, causing "Xlib: sequence lost" errors.
349 So, we cause the first three file descriptors to be open to
350 /dev/null if they aren't open to something else already. This
351 must be done before any other files are opened (or the closing
352 of that other file will again free up one of the "magic" first
355 We do this by opening /dev/null three times, and then closing
356 those fds, *unless* any of them got allocated as #0, #1, or #2,
357 in which case we leave them open. Gag.
359 Really, this crap is technically required of *every* X program,
360 if you want it to be robust in the face of "2>&-".
362 int fd0 = open ("/dev/null", O_RDWR);
363 int fd1 = open ("/dev/null", O_RDWR);
364 int fd2 = open ("/dev/null", O_RDWR);
365 if (fd0 > 2) close (fd0);
366 if (fd1 > 2) close (fd1);
367 if (fd2 > 2) close (fd2);
372 main (int argc, char **argv)
381 Window on_window = 0;
383 Boolean dont_clear /*, dont_map */;
389 pre_merge_options ();
394 /* We have to do this on SGI to prevent the background color from being
395 overridden by the current desktop color scheme (we'd like our backgrounds
396 to be black, thanks.) This should be the same as setting the
397 "*useSchemes: none" resource, but it's not -- if that resource is
398 present in the `default_defaults' above, it doesn't work, though it
399 does work when passed as an -xrm arg on the command line. So screw it,
400 turn them off from C instead.
402 SgiUseSchemes ("none");
405 toplevel = XtAppInitialize (&app, progclass, merged_options,
406 merged_options_size, &argc, argv,
407 merged_defaults, 0, 0);
408 dpy = XtDisplay (toplevel);
409 screen = XtScreen (toplevel);
410 db = XtDatabase (dpy);
412 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
414 /* half-assed way of avoiding buffer-overrun attacks. */
415 if (strlen (progname) >= 100) progname[100] = 0;
417 XSetErrorHandler (screenhack_ehandler);
419 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
420 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
423 char *v = (char *) strdup(strchr(screensaver_id, ' '));
424 char *s1, *s2, *s3, *s4;
425 s1 = (char *) strchr(v, ' '); s1++;
426 s2 = (char *) strchr(s1, ' ');
427 s3 = (char *) strchr(v, '('); s3++;
428 s4 = (char *) strchr(s3, ')');
431 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
442 Bool help_p = (!strcmp(argv[1], "-help") ||
443 !strcmp(argv[1], "--help"));
444 fprintf (stderr, "%s\n", version);
445 for (s = progclass; *s; s++) fprintf(stderr, " ");
446 fprintf (stderr, " http://www.jwz.org/xscreensaver/\n\n");
449 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
450 fprintf (stderr, "Options include: ");
451 for (i = 0; i < merged_options_size; i++)
453 char *sw = merged_options [i].option;
454 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
455 int size = strlen (sw) + (argp ? 6 : 0) + 2;
458 fprintf (stderr, "\n\t\t ");
462 fprintf (stderr, "%s", sw);
463 if (argp) fprintf (stderr, " <arg>");
464 if (i != merged_options_size - 1) fprintf (stderr, ", ");
467 fprintf (stderr, ".\n");
472 fprintf (stderr, "\nResources:\n\n");
473 for (i = 0; i < merged_options_size; i++)
475 const char *opt = merged_options [i].option;
476 const char *res = merged_options [i].specifier + 1;
477 const char *val = merged_options [i].value;
478 char *s = get_string_resource ((char *) res, (char *) res);
483 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
487 fprintf (stderr, " %-16s %-18s ", opt, res);
488 if (merged_options [i].argKind == XrmoptionSepArg)
490 fprintf (stderr, "[%s]", (s ? s : "?"));
494 fprintf (stderr, "%s", (val ? val : "(null)"));
495 if (val && s && !strcasecmp (val, s))
496 fprintf (stderr, " [default]");
498 fprintf (stderr, "\n");
500 fprintf (stderr, "\n");
504 exit (help_p ? 0 : 1);
507 dont_clear = get_boolean_resource ("dontClearRoot", "Boolean");
508 /*dont_map = get_boolean_resource ("dontMapWindow", "Boolean"); */
509 mono_p = get_boolean_resource ("mono", "Boolean");
510 if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
513 root_p = get_boolean_resource ("root", "Boolean");
516 char *s = get_string_resource ("windowID", "WindowID");
518 on_window = get_integer_resource ("windowID", "WindowID");
524 XWindowAttributes xgwa;
525 window = (Window) on_window;
526 XtDestroyWidget (toplevel);
527 XGetWindowAttributes (dpy, window, &xgwa);
528 cmap = xgwa.colormap;
529 visual = xgwa.visual;
530 screen = xgwa.screen;
531 visual_warning (screen, window, visual, cmap, True);
533 /* Select KeyPress events on the external window.
535 xgwa.your_event_mask |= KeyPressMask;
536 XSelectInput (dpy, window, xgwa.your_event_mask);
538 /* Select ButtonPress and ButtonRelease events on the external window,
539 if no other app has already selected them (only one app can select
540 ButtonPress at a time: BadAccess results.)
542 if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
543 XSelectInput (dpy, window,
544 (xgwa.your_event_mask |
545 ButtonPressMask | ButtonReleaseMask));
549 XWindowAttributes xgwa;
550 window = RootWindowOfScreen (XtScreen (toplevel));
551 XtDestroyWidget (toplevel);
552 XGetWindowAttributes (dpy, window, &xgwa);
553 cmap = xgwa.colormap;
554 visual = xgwa.visual;
555 visual_warning (screen, window, visual, cmap, False);
559 Boolean def_visual_p;
560 visual = pick_visual (screen);
563 if (!validate_gl_visual (stderr, screen, "window", visual))
567 if (toplevel->core.width <= 0)
568 toplevel->core.width = 600;
569 if (toplevel->core.height <= 0)
570 toplevel->core.height = 480;
572 def_visual_p = (visual == DefaultVisualOfScreen (screen));
579 cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
581 bg = get_pixel_resource ("background", "Background", dpy, cmap);
582 bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
584 new = XtVaAppCreateShell (progname, progclass,
585 topLevelShellWidgetClass, dpy,
586 XtNmappedWhenManaged, False,
588 XtNdepth, visual_depth (screen, visual),
589 XtNwidth, toplevel->core.width,
590 XtNheight, toplevel->core.height,
592 XtNbackground, (Pixel) bg,
593 XtNborderColor, (Pixel) bd,
594 XtNinput, True, /* for WM_HINTS */
596 XtDestroyWidget (toplevel);
598 XtRealizeWidget (toplevel);
599 window = XtWindow (toplevel);
603 XtVaSetValues (toplevel,
604 XtNmappedWhenManaged, False,
605 XtNinput, True, /* for WM_HINTS */
607 XtRealizeWidget (toplevel);
608 window = XtWindow (toplevel);
610 if (get_boolean_resource ("installColormap", "InstallColormap"))
612 cmap = XCreateColormap (dpy, window,
613 DefaultVisualOfScreen (XtScreen (toplevel)),
615 XSetWindowColormap (dpy, window, cmap);
619 cmap = DefaultColormap (dpy, DefaultScreen (dpy));
626 XtVaSetValues (toplevel, XtNmappedWhenManaged, False, NULL);
627 XtRealizeWidget (toplevel);
632 XtPopup (toplevel, XtGrabNone);
635 XtVaSetValues(toplevel, XtNtitle, version, NULL);
637 /* For screenhack_handle_events(): select KeyPress, and
638 announce that we accept WM_DELETE_WINDOW. */
640 XWindowAttributes xgwa;
641 XGetWindowAttributes (dpy, window, &xgwa);
642 XSelectInput (dpy, window,
643 (xgwa.your_event_mask | KeyPressMask |
644 ButtonPressMask | ButtonReleaseMask));
645 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
647 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
653 XSetWindowBackground (dpy, window,
654 get_pixel_resource ("background", "Background",
656 XClearWindow (dpy, window);
659 if (!root_p && !on_window)
660 /* wait for it to be mapped */
661 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
665 /* This is the one and only place that the random-number generator is
666 seeded in any screenhack. You do not need to seed the RNG again,
667 it is done for you before your code is invoked. */
671 screenhack (dpy, window); /* doesn't return */