1 /* xscreensaver, Copyright (c) 1992-2008 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * And remember: X Windows is to graphics hacking as roman numerals are to
12 * the square root of pi.
15 /* This file contains simple code to open a window or draw on the root.
16 The idea being that, when writing a graphics hack, you can just link
17 with this .o to get all of the uninteresting junk out of the way.
19 Create a few static global procedures and variables:
21 static void *YOURNAME_init (Display *, Window);
23 Return an opaque structure representing your drawing state.
25 static unsigned long YOURNAME_draw (Display *, Window, void *closure);
28 The `closure' arg is your drawing state, that you created in `init'.
29 Return the number of microseconds to wait until the next frame.
31 This should return in some small fraction of a second.
32 Do not call `usleep' or loop excessively. For long loops, use a
35 static void YOURNAME_reshape (Display *, Window, void *closure,
36 unsigned int width, unsigned int height);
38 Called when the size of the window changes with the new size.
40 static Bool YOURNAME_event (Display *, Window, void *closure,
43 Called when a keyboard or mouse event arrives.
44 Return True if you handle it in some way, False otherwise.
46 static void YOURNAME_free (Display *, Window, void *closure);
48 Called when you are done: free everything you've allocated,
49 including your private `state' structure.
51 NOTE: this is called in windowed-mode when the user typed
52 'q' or clicks on the window's close box; but when
53 xscreensaver terminates this screenhack, it does so by
54 killing the process with SIGSTOP. So this callback is
57 static char YOURNAME_defaults [] = { "...", "...", ... , 0 };
59 This variable is an array of strings, your default resources.
60 Null-terminate the list.
62 static XrmOptionDescRec YOURNAME_options[] = { { ... }, ... { 0,0,0,0 } }
64 This variable describes your command-line options.
65 Null-terminate the list.
67 Finally , invoke the XSCREENSAVER_MODULE() macro to tie it all together.
71 - Make sure that all functions in your module are static (check this
72 by running "nm -g" on the .o file).
74 - Do not use global variables: all such info must be stored in the
75 private `state' structure.
77 - Do not use static function-local variables, either. Put it in `state'.
79 Assume that there are N independent runs of this code going in the
80 same address space at the same time: they must not affect each other.
82 - Don't forget to write an XML file to describe the user interface
83 of your screen saver module. See .../hacks/config/README for details.
89 #include <X11/Intrinsic.h>
90 #include <X11/IntrinsicP.h>
91 #include <X11/CoreP.h>
92 #include <X11/Shell.h>
93 #include <X11/StringDefs.h>
94 #include <X11/keysym.h>
97 # include <X11/SGIScheme.h> /* for SgiUseSchemes() */
102 # include <X11/Xmu/Error.h>
104 # include <Xmu/Error.h>
110 #include "screenhackI.h"
115 #ifndef _XSCREENSAVER_VROOT_H_
116 # error Error! You have an old version of vroot.h! Check -I args.
117 #endif /* _XSCREENSAVER_VROOT_H_ */
120 # define isupper(c) ((c) >= 'A' && (c) <= 'Z')
123 # define _tolower(c) ((c) - 'A' + 'a')
127 /* This is defined by the SCREENHACK_MAIN() macro via screenhack.h.
129 extern struct xscreensaver_function_table *xscreensaver_function_table;
132 const char *progname; /* used by hacks in error messages */
133 const char *progclass; /* used by ../utils/resources.c */
134 Bool mono_p; /* used by hacks */
137 static XrmOptionDescRec default_options [] = {
138 { "-root", ".root", XrmoptionNoArg, "True" },
139 { "-window", ".root", XrmoptionNoArg, "False" },
140 { "-mono", ".mono", XrmoptionNoArg, "True" },
141 { "-install", ".installColormap", XrmoptionNoArg, "True" },
142 { "-noinstall",".installColormap", XrmoptionNoArg, "False" },
143 { "-visual", ".visualID", XrmoptionSepArg, 0 },
144 { "-window-id", ".windowID", XrmoptionSepArg, 0 },
145 { "-fps", ".doFPS", XrmoptionNoArg, "True" },
146 { "-no-fps", ".doFPS", XrmoptionNoArg, "False" },
149 { "-pair", ".pair", XrmoptionNoArg, "True" },
150 # endif /* DEBUG_PAIR */
154 static char *default_defaults[] = {
156 "*geometry: 600x480", /* this should be .geometry, but nooooo... */
158 "*installColormap: false",
160 "*visualID: default",
162 "*desktopGrabber: xscreensaver-getimage %s",
166 static XrmOptionDescRec *merged_options;
167 static int merged_options_size;
168 static char **merged_defaults;
174 struct xscreensaver_function_table *ft = xscreensaver_function_table;
176 const XrmOptionDescRec *options = ft->options;
177 const char * const *defaults = ft->defaults;
178 const char *progclass = ft->progclass;
180 int def_opts_size, opts_size;
181 int def_defaults_size, defaults_size;
183 for (def_opts_size = 0; default_options[def_opts_size].option;
186 for (opts_size = 0; options[opts_size].option; opts_size++)
189 merged_options_size = def_opts_size + opts_size;
190 merged_options = (XrmOptionDescRec *)
191 malloc ((merged_options_size + 1) * sizeof(*default_options));
192 memcpy (merged_options, default_options,
193 (def_opts_size * sizeof(*default_options)));
194 memcpy (merged_options + def_opts_size, options,
195 ((opts_size + 1) * sizeof(*default_options)));
197 for (def_defaults_size = 0; default_defaults[def_defaults_size];
200 for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
202 merged_defaults = (char **)
203 malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
204 memcpy (merged_defaults, default_defaults,
205 def_defaults_size * sizeof(*defaults));
206 memcpy (merged_defaults + def_defaults_size, defaults,
207 (defaults_size + 1) * sizeof(*defaults));
209 /* This totally sucks. Xt should behave like this by default.
210 If the string in `defaults' looks like ".foo", change that
215 for (s = merged_defaults; *s; s++)
218 const char *oldr = *s;
219 char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
220 strcpy (newr, progclass);
228 /* Make the X errors print out the name of this program, so we have some
229 clue which one has a bug when they die under the screensaver.
233 screenhack_ehandler (Display *dpy, XErrorEvent *error)
235 fprintf (stderr, "\nX error in %s:\n", progname);
236 if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
239 fprintf (stderr, " (nonfatal.)\n");
244 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
246 return (event->xany.type == MapNotify &&
247 event->xvisibility.window == (Window) window);
251 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
253 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
254 Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
255 Returns False if the screen saver should now terminate.
258 screenhack_handle_event_1 (Display *dpy, XEvent *event)
260 switch (event->xany.type)
266 XLookupString (&event->xkey, &c, 1, &keysym, 0);
271 return False; /* exit */
272 else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
273 XBell (dpy, 0); /* beep for non-chord keys */
281 if (event->xclient.message_type != XA_WM_PROTOCOLS)
283 char *s = XGetAtomName(dpy, event->xclient.message_type);
284 if (!s) s = "(null)";
285 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
288 else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
290 char *s1 = XGetAtomName(dpy, event->xclient.message_type);
291 char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
292 if (!s1) s1 = "(null)";
293 if (!s2) s2 = "(null)";
294 fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
299 return False; /* exit */
309 pick_visual (Screen *screen)
311 struct xscreensaver_function_table *ft = xscreensaver_function_table;
313 if (ft->pick_visual_hook)
315 Visual *v = ft->pick_visual_hook (screen);
319 return get_visual_resource (screen, "visualID", "VisualID", False);
323 /* Notice when the user has requested a different visual or colormap
324 on a pre-existing window (e.g., "-root -visual truecolor" or
325 "-window-id 0x2c00001 -install") and complain, since when drawing
326 on an existing window, we have no choice about these things.
329 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
332 struct xscreensaver_function_table *ft = xscreensaver_function_table;
334 char *visual_string = get_string_resource (DisplayOfScreen (screen),
335 "visualID", "VisualID");
336 Visual *desired_visual = pick_visual (screen);
340 if (window == RootWindowOfScreen (screen))
341 strcpy (win, "root window");
343 sprintf (win, "window 0x%lx", (unsigned long) window);
346 sprintf (why, "-window-id 0x%lx", (unsigned long) window);
348 strcpy (why, "-root");
350 if (visual_string && *visual_string)
353 for (s = visual_string; *s; s++)
354 if (isupper (*s)) *s = _tolower (*s);
356 if (!strcmp (visual_string, "default") ||
357 !strcmp (visual_string, "default") ||
358 !strcmp (visual_string, "best"))
359 /* don't warn about these, just silently DWIM. */
361 else if (visual != desired_visual)
363 fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
364 progname, visual_string, why);
365 fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
366 progname, win, XVisualIDFromVisual (visual));
368 free (visual_string);
371 if (visual == DefaultVisualOfScreen (screen) &&
372 has_writable_cells (screen, visual) &&
373 get_boolean_resource (DisplayOfScreen (screen),
374 "installColormap", "InstallColormap"))
376 fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
378 fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
379 progname, win, (unsigned long) cmap);
382 if (ft->validate_visual_hook)
384 if (! ft->validate_visual_hook (screen, win, visual))
393 /* Bad Things Happen if stdin, stdout, and stderr have been closed
394 (as by the `sh incantation "attraction >&- 2>&-"). When you do
395 that, the X connection gets allocated to one of these fds, and
396 then some random library writes to stderr, and random bits get
397 stuffed down the X pipe, causing "Xlib: sequence lost" errors.
398 So, we cause the first three file descriptors to be open to
399 /dev/null if they aren't open to something else already. This
400 must be done before any other files are opened (or the closing
401 of that other file will again free up one of the "magic" first
404 We do this by opening /dev/null three times, and then closing
405 those fds, *unless* any of them got allocated as #0, #1, or #2,
406 in which case we leave them open. Gag.
408 Really, this crap is technically required of *every* X program,
409 if you want it to be robust in the face of "2>&-".
411 int fd0 = open ("/dev/null", O_RDWR);
412 int fd1 = open ("/dev/null", O_RDWR);
413 int fd2 = open ("/dev/null", O_RDWR);
414 if (fd0 > 2) close (fd0);
415 if (fd1 > 2) close (fd1);
416 if (fd2 > 2) close (fd2);
421 screenhack_table_handle_events (Display *dpy,
422 const struct xscreensaver_function_table *ft,
423 Window window, void *closure
425 , Window window2, void *closure2
429 XtAppContext app = XtDisplayToApplicationContext (dpy);
431 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
432 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
434 while (XPending (dpy))
437 XNextEvent (dpy, &event);
439 if (event.xany.type == ConfigureNotify)
441 if (event.xany.window == window)
442 ft->reshape_cb (dpy, window, closure,
443 event.xconfigure.width, event.xconfigure.height);
445 if (event.xany.window == window2)
446 ft->reshape_cb (dpy, window2, closure2,
447 event.xconfigure.width, event.xconfigure.height);
450 else if (event.xany.type == ClientMessage ||
451 (! (event.xany.window == window
452 ? ft->event_cb (dpy, window, closure, &event)
454 : event.xany.window == window2
455 ? ft->event_cb (dpy, window2, closure2, &event)
458 if (! screenhack_handle_event_1 (dpy, &event))
461 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
462 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
469 usleep_and_process_events (Display *dpy,
470 const struct xscreensaver_function_table *ft,
471 Window window, fps_state *fpst, void *closure,
474 , Window window2, fps_state *fpst2, void *closure2,
480 unsigned long quantum = 100000; /* 1/10th second */
489 if (fpst) fps_slept (fpst, quantum);
491 if (fpst2) fps_slept (fpst2, quantum);
495 if (! screenhack_table_handle_events (dpy, ft, window, closure
508 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
510 fps_compute (fpst, 0);
516 run_screenhack_table (Display *dpy,
521 const struct xscreensaver_function_table *ft)
524 /* Kludge: even though the init_cb functions are declared to take 2 args,
525 actually call them with 3, for the benefit of xlockmore_init() and
528 void *(*init_cb) (Display *, Window, void *) =
529 (void *(*) (Display *, Window, void *)) ft->init_cb;
531 void (*fps_cb) (Display *, Window, fps_state *, void *) = ft->fps_cb;
533 void *closure = init_cb (dpy, window, ft->setup_arg);
534 fps_state *fpst = fps_init (dpy, window);
538 fps_state *fpst2 = 0;
539 if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
540 if (window2) fpst2 = fps_init (dpy, window2);
543 if (! closure) /* if it returns nothing, it can't possibly be re-entrant. */
546 if (! fps_cb) fps_cb = screenhack_do_fps;
550 unsigned long delay = ft->draw_cb (dpy, window, closure);
552 unsigned long delay2 = 0;
553 if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
556 if (fpst) fps_cb (dpy, window, fpst, closure);
558 if (fpst2) fps_cb (dpy, window, fpst2, closure);
561 if (! usleep_and_process_events (dpy, ft,
562 window, fpst, closure, delay
564 , window2, fpst2, closure2, delay2
570 ft->free_cb (dpy, window, closure);
571 if (fpst) fps_free (fpst);
574 if (window2) ft->free_cb (dpy, window2, closure2);
575 if (window2) fps_free (fpst2);
581 make_shell (Screen *screen, Widget toplevel, int width, int height)
583 Display *dpy = DisplayOfScreen (screen);
584 Visual *visual = pick_visual (screen);
585 Boolean def_visual_p = (toplevel &&
586 visual == DefaultVisualOfScreen (screen));
588 if (width <= 0) width = 600;
589 if (height <= 0) height = 480;
594 XtVaSetValues (toplevel,
595 XtNmappedWhenManaged, False,
598 XtNinput, True, /* for WM_HINTS */
600 XtRealizeWidget (toplevel);
601 window = XtWindow (toplevel);
603 if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
606 XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
608 XSetWindowColormap (dpy, window, cmap);
615 Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
617 bg = get_pixel_resource (dpy, cmap, "background", "Background");
618 bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
620 new = XtVaAppCreateShell (progname, progclass,
621 topLevelShellWidgetClass, dpy,
622 XtNmappedWhenManaged, False,
624 XtNdepth, visual_depth (screen, visual),
628 XtNbackground, (Pixel) bg,
629 XtNborderColor, (Pixel) bd,
630 XtNinput, True, /* for WM_HINTS */
633 if (!toplevel) /* kludge for the second window in -pair mode... */
634 XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
636 XtRealizeWidget (new);
644 init_window (Display *dpy, Widget toplevel, const char *title)
647 XWindowAttributes xgwa;
648 XtPopup (toplevel, XtGrabNone);
649 XtVaSetValues (toplevel, XtNtitle, title, NULL);
651 /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
653 window = XtWindow (toplevel);
654 XGetWindowAttributes (dpy, window, &xgwa);
655 XSelectInput (dpy, window,
656 (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
657 ButtonPressMask | ButtonReleaseMask));
658 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
660 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
665 main (int argc, char **argv)
667 struct xscreensaver_function_table *ft = xscreensaver_function_table;
669 XWindowAttributes xgwa;
675 Widget toplevel2 = 0;
679 Window on_window = 0;
686 progname = argv[0]; /* reset later */
687 progclass = ft->progclass;
690 ft->setup_cb (ft, ft->setup_arg);
695 /* We have to do this on SGI to prevent the background color from being
696 overridden by the current desktop color scheme (we'd like our backgrounds
697 to be black, thanks.) This should be the same as setting the
698 "*useSchemes: none" resource, but it's not -- if that resource is
699 present in the `default_defaults' above, it doesn't work, though it
700 does work when passed as an -xrm arg on the command line. So screw it,
701 turn them off from C instead.
703 SgiUseSchemes ("none");
706 toplevel = XtAppInitialize (&app, progclass, merged_options,
707 merged_options_size, &argc, argv,
708 merged_defaults, 0, 0);
710 dpy = XtDisplay (toplevel);
712 XtGetApplicationNameAndClass (dpy,
714 (char **) &progclass);
716 /* half-assed way of avoiding buffer-overrun attacks. */
717 if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
719 XSetErrorHandler (screenhack_ehandler);
721 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
722 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
725 char *v = (char *) strdup(strchr(screensaver_id, ' '));
726 char *s1, *s2, *s3, *s4;
727 s1 = (char *) strchr(v, ' '); s1++;
728 s2 = (char *) strchr(s1, ' ');
729 s3 = (char *) strchr(v, '('); s3++;
730 s4 = (char *) strchr(s3, ')');
733 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
744 Bool help_p = (!strcmp(argv[1], "-help") ||
745 !strcmp(argv[1], "--help"));
746 fprintf (stderr, "%s\n", version);
747 for (s = progclass; *s; s++) fprintf(stderr, " ");
748 fprintf (stderr, " http://www.jwz.org/xscreensaver/\n\n");
751 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
752 fprintf (stderr, "Options include: ");
753 for (i = 0; i < merged_options_size; i++)
755 char *sw = merged_options [i].option;
756 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
757 int size = strlen (sw) + (argp ? 6 : 0) + 2;
760 fprintf (stderr, "\n\t\t ");
764 fprintf (stderr, "%s", sw);
765 if (argp) fprintf (stderr, " <arg>");
766 if (i != merged_options_size - 1) fprintf (stderr, ", ");
769 fprintf (stderr, ".\n");
774 fprintf (stderr, "\nResources:\n\n");
775 for (i = 0; i < merged_options_size; i++)
777 const char *opt = merged_options [i].option;
778 const char *res = merged_options [i].specifier + 1;
779 const char *val = merged_options [i].value;
780 char *s = get_string_resource (dpy, (char *) res, (char *) res);
785 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
789 fprintf (stderr, " %-16s %-18s ", opt, res);
790 if (merged_options [i].argKind == XrmoptionSepArg)
792 fprintf (stderr, "[%s]", (s ? s : "?"));
796 fprintf (stderr, "%s", (val ? val : "(null)"));
797 if (val && s && !strcasecmp (val, s))
798 fprintf (stderr, " [default]");
800 fprintf (stderr, "\n");
802 fprintf (stderr, "\n");
806 exit (help_p ? 0 : 1);
809 free (merged_options);
810 free (merged_defaults);
814 dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
815 mono_p = get_boolean_resource (dpy, "mono", "Boolean");
816 if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
819 root_p = get_boolean_resource (dpy, "root", "Boolean");
822 char *s = get_string_resource (dpy, "windowID", "WindowID");
824 on_window = get_integer_resource (dpy, "windowID", "WindowID");
830 window = (Window) on_window;
831 XtDestroyWidget (toplevel);
832 XGetWindowAttributes (dpy, window, &xgwa);
833 visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
835 /* Select KeyPress and resize events on the external window.
837 xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
838 XSelectInput (dpy, window, xgwa.your_event_mask);
840 /* Select ButtonPress and ButtonRelease events on the external window,
841 if no other app has already selected them (only one app can select
842 ButtonPress at a time: BadAccess results.)
844 if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
845 XSelectInput (dpy, window,
846 (xgwa.your_event_mask |
847 ButtonPressMask | ButtonReleaseMask));
851 window = VirtualRootWindowOfScreen (XtScreen (toplevel));
852 XtDestroyWidget (toplevel);
853 XGetWindowAttributes (dpy, window, &xgwa);
854 /* With RANDR, the root window can resize! */
855 XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask);
856 visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
860 Widget new = make_shell (XtScreen (toplevel), toplevel,
861 toplevel->core.width,
862 toplevel->core.height);
865 XtDestroyWidget (toplevel);
869 init_window (dpy, toplevel, version);
870 window = XtWindow (toplevel);
871 XGetWindowAttributes (dpy, window, &xgwa);
874 if (get_boolean_resource (dpy, "pair", "Boolean"))
876 toplevel2 = make_shell (xgwa.screen, 0,
877 toplevel->core.width,
878 toplevel->core.height);
879 init_window (dpy, toplevel2, version);
880 window2 = XtWindow (toplevel2);
882 # endif /* DEBUG_PAIR */
887 unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
888 "background", "Background");
889 XSetWindowBackground (dpy, window, bg);
890 XClearWindow (dpy, window);
894 XSetWindowBackground (dpy, window2, bg);
895 XClearWindow (dpy, window2);
900 if (!root_p && !on_window)
901 /* wait for it to be mapped */
902 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
906 /* This is the one and only place that the random-number generator is
907 seeded in any screenhack. You do not need to seed the RNG again,
908 it is done for you before your code is invoked. */
912 run_screenhack_table (dpy, window,
918 XtDestroyWidget (toplevel);
919 XtDestroyApplicationContext (app);