1 /* xscreensaver, Copyright (c) 1992, 1995, 1997, 1998, 2001, 2002
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%x", (unsigned long) window);
299 sprintf (why, "-window-id 0x%x", (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%x.\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%x.\n",
331 progname, win, (unsigned long) cmap);
335 if (!validate_gl_visual (stderr, screen, win, visual))
342 main (int argc, char **argv)
351 Window on_window = 0;
353 Boolean dont_clear /*, dont_map */;
357 pre_merge_options ();
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.
370 SgiUseSchemes ("none");
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);
380 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
382 /* half-assed way of avoiding buffer-overrun attacks. */
383 if (strlen (progname) >= 100) progname[100] = 0;
385 XSetErrorHandler (screenhack_ehandler);
387 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
388 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
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, ')');
399 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
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");
417 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
418 fprintf (stderr, "Options include: ");
419 for (i = 0; i < merged_options_size; i++)
421 char *sw = merged_options [i].option;
422 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
423 int size = strlen (sw) + (argp ? 6 : 0) + 2;
426 fprintf (stderr, "\n\t\t ");
430 fprintf (stderr, "%s", sw);
431 if (argp) fprintf (stderr, " <arg>");
432 if (i != merged_options_size - 1) fprintf (stderr, ", ");
435 fprintf (stderr, ".\n");
440 fprintf (stderr, "\nResources:\n\n");
441 for (i = 0; i < merged_options_size; i++)
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);
451 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
455 fprintf (stderr, " %-16s %-18s ", opt, res);
456 if (merged_options [i].argKind == XrmoptionSepArg)
458 fprintf (stderr, "[%s]", (s ? s : "?"));
462 fprintf (stderr, "%s", (val ? val : "(null)"));
463 if (val && s && !strcasecmp (val, s))
464 fprintf (stderr, " [default]");
466 fprintf (stderr, "\n");
468 fprintf (stderr, "\n");
472 exit (help_p ? 0 : 1);
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)
481 root_p = get_boolean_resource ("root", "Boolean");
484 char *s = get_string_resource ("windowID", "WindowID");
486 on_window = get_integer_resource ("windowID", "WindowID");
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);
500 /* Select KeyPress events on the external window.
502 xgwa.your_event_mask |= KeyPressMask;
503 XSelectInput (dpy, window, xgwa.your_event_mask);
505 /* Select ButtonPress events on the external window, if no other
506 app has already selected it (only one app can select ButtonPress
507 at a time: BadAccess results.)
509 if (! (xgwa.all_event_masks & ButtonPressMask))
510 XSelectInput (dpy, window, xgwa.your_event_mask | ButtonPressMask);
514 XWindowAttributes xgwa;
515 window = RootWindowOfScreen (XtScreen (toplevel));
516 XtDestroyWidget (toplevel);
517 XGetWindowAttributes (dpy, window, &xgwa);
518 cmap = xgwa.colormap;
519 visual = xgwa.visual;
520 visual_warning (screen, window, visual, cmap, False);
524 Boolean def_visual_p;
525 visual = pick_visual (screen);
528 if (!validate_gl_visual (stderr, screen, "window", visual))
532 if (toplevel->core.width <= 0)
533 toplevel->core.width = 600;
534 if (toplevel->core.height <= 0)
535 toplevel->core.height = 480;
537 def_visual_p = (visual == DefaultVisualOfScreen (screen));
544 cmap = XCreateColormap (dpy, RootWindowOfScreen(screen),
546 bg = get_pixel_resource ("background", "Background", dpy, cmap);
547 bd = get_pixel_resource ("borderColor", "Foreground", dpy, cmap);
549 new = XtVaAppCreateShell (progname, progclass,
550 topLevelShellWidgetClass, dpy,
551 XtNmappedWhenManaged, False,
553 XtNdepth, visual_depth (screen, visual),
554 XtNwidth, toplevel->core.width,
555 XtNheight, toplevel->core.height,
557 XtNbackground, (Pixel) bg,
558 XtNborderColor, (Pixel) bd,
559 XtNinput, True, /* for WM_HINTS */
561 XtDestroyWidget (toplevel);
563 XtRealizeWidget (toplevel);
564 window = XtWindow (toplevel);
568 XtVaSetValues (toplevel,
569 XtNmappedWhenManaged, False,
570 XtNinput, True, /* for WM_HINTS */
572 XtRealizeWidget (toplevel);
573 window = XtWindow (toplevel);
575 if (get_boolean_resource ("installColormap", "InstallColormap"))
577 cmap = XCreateColormap (dpy, window,
578 DefaultVisualOfScreen (XtScreen (toplevel)),
580 XSetWindowColormap (dpy, window, cmap);
584 cmap = DefaultColormap (dpy, DefaultScreen (dpy));
591 XtVaSetValues (toplevel, XtNmappedWhenManaged, False, 0);
592 XtRealizeWidget (toplevel);
597 XtPopup (toplevel, XtGrabNone);
600 XtVaSetValues(toplevel, XtNtitle, version, 0);
602 /* For screenhack_handle_events(): select KeyPress, and
603 announce that we accept WM_DELETE_WINDOW. */
605 XWindowAttributes xgwa;
606 XGetWindowAttributes (dpy, window, &xgwa);
607 XSelectInput (dpy, window,
608 xgwa.your_event_mask | KeyPressMask | ButtonPressMask);
609 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
611 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
617 XSetWindowBackground (dpy, window,
618 get_pixel_resource ("background", "Background",
620 XClearWindow (dpy, window);
623 if (!root_p && !on_window)
624 /* wait for it to be mapped */
625 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
629 /* This is the one and only place that the random-number generator is
630 seeded in any screenhack. You do not need to seed the RNG again,
631 it is done for you before your code is invoked. */
635 screenhack (dpy, window); /* doesn't return */