1 /* xscreensaver, Copyright (c) 1992-2012 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 "*multiSample: false",
161 "*visualID: default",
163 "*desktopGrabber: xscreensaver-getimage %s",
167 static XrmOptionDescRec *merged_options;
168 static int merged_options_size;
169 static char **merged_defaults;
175 struct xscreensaver_function_table *ft = xscreensaver_function_table;
177 const XrmOptionDescRec *options = ft->options;
178 const char * const *defaults = ft->defaults;
179 const char *progclass = ft->progclass;
181 int def_opts_size, opts_size;
182 int def_defaults_size, defaults_size;
184 for (def_opts_size = 0; default_options[def_opts_size].option;
187 for (opts_size = 0; options[opts_size].option; opts_size++)
190 merged_options_size = def_opts_size + opts_size;
191 merged_options = (XrmOptionDescRec *)
192 malloc ((merged_options_size + 1) * sizeof(*default_options));
193 memcpy (merged_options, default_options,
194 (def_opts_size * sizeof(*default_options)));
195 memcpy (merged_options + def_opts_size, options,
196 ((opts_size + 1) * sizeof(*default_options)));
198 for (def_defaults_size = 0; default_defaults[def_defaults_size];
201 for (defaults_size = 0; defaults[defaults_size]; defaults_size++)
203 merged_defaults = (char **)
204 malloc ((def_defaults_size + defaults_size + 1) * sizeof (*defaults));;
205 memcpy (merged_defaults, default_defaults,
206 def_defaults_size * sizeof(*defaults));
207 memcpy (merged_defaults + def_defaults_size, defaults,
208 (defaults_size + 1) * sizeof(*defaults));
210 /* This totally sucks. Xt should behave like this by default.
211 If the string in `defaults' looks like ".foo", change that
216 for (s = merged_defaults; *s; s++)
219 const char *oldr = *s;
220 char *newr = (char *) malloc(strlen(oldr) + strlen(progclass) + 3);
221 strcpy (newr, progclass);
231 /* Make the X errors print out the name of this program, so we have some
232 clue which one has a bug when they die under the screensaver.
236 screenhack_ehandler (Display *dpy, XErrorEvent *error)
238 fprintf (stderr, "\nX error in %s:\n", progname);
239 if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
242 fprintf (stderr, " (nonfatal.)\n");
247 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
249 return (event->xany.type == MapNotify &&
250 event->xvisibility.window == (Window) window);
254 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
256 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
257 Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
258 Returns False if the screen saver should now terminate.
261 screenhack_handle_event_1 (Display *dpy, XEvent *event)
263 switch (event->xany.type)
269 XLookupString (&event->xkey, &c, 1, &keysym, 0);
274 return False; /* exit */
275 else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
276 XBell (dpy, 0); /* beep for non-chord keys */
284 if (event->xclient.message_type != XA_WM_PROTOCOLS)
286 char *s = XGetAtomName(dpy, event->xclient.message_type);
287 if (!s) s = "(null)";
288 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
291 else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
293 char *s1 = XGetAtomName(dpy, event->xclient.message_type);
294 char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
295 if (!s1) s1 = "(null)";
296 if (!s2) s2 = "(null)";
297 fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
302 return False; /* exit */
312 pick_visual (Screen *screen)
314 struct xscreensaver_function_table *ft = xscreensaver_function_table;
316 if (ft->pick_visual_hook)
318 Visual *v = ft->pick_visual_hook (screen);
322 return get_visual_resource (screen, "visualID", "VisualID", False);
326 /* Notice when the user has requested a different visual or colormap
327 on a pre-existing window (e.g., "-root -visual truecolor" or
328 "-window-id 0x2c00001 -install") and complain, since when drawing
329 on an existing window, we have no choice about these things.
332 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
335 struct xscreensaver_function_table *ft = xscreensaver_function_table;
337 char *visual_string = get_string_resource (DisplayOfScreen (screen),
338 "visualID", "VisualID");
339 Visual *desired_visual = pick_visual (screen);
343 if (window == RootWindowOfScreen (screen))
344 strcpy (win, "root window");
346 sprintf (win, "window 0x%lx", (unsigned long) window);
349 sprintf (why, "-window-id 0x%lx", (unsigned long) window);
351 strcpy (why, "-root");
353 if (visual_string && *visual_string)
356 for (s = visual_string; *s; s++)
357 if (isupper (*s)) *s = _tolower (*s);
359 if (!strcmp (visual_string, "default") ||
360 !strcmp (visual_string, "default") ||
361 !strcmp (visual_string, "best"))
362 /* don't warn about these, just silently DWIM. */
364 else if (visual != desired_visual)
366 fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
367 progname, visual_string, why);
368 fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
369 progname, win, XVisualIDFromVisual (visual));
371 free (visual_string);
374 if (visual == DefaultVisualOfScreen (screen) &&
375 has_writable_cells (screen, visual) &&
376 get_boolean_resource (DisplayOfScreen (screen),
377 "installColormap", "InstallColormap"))
379 fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
381 fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
382 progname, win, (unsigned long) cmap);
385 if (ft->validate_visual_hook)
387 if (! ft->validate_visual_hook (screen, win, visual))
396 /* Bad Things Happen if stdin, stdout, and stderr have been closed
397 (as by the `sh incantation "attraction >&- 2>&-"). When you do
398 that, the X connection gets allocated to one of these fds, and
399 then some random library writes to stderr, and random bits get
400 stuffed down the X pipe, causing "Xlib: sequence lost" errors.
401 So, we cause the first three file descriptors to be open to
402 /dev/null if they aren't open to something else already. This
403 must be done before any other files are opened (or the closing
404 of that other file will again free up one of the "magic" first
407 We do this by opening /dev/null three times, and then closing
408 those fds, *unless* any of them got allocated as #0, #1, or #2,
409 in which case we leave them open. Gag.
411 Really, this crap is technically required of *every* X program,
412 if you want it to be robust in the face of "2>&-".
414 int fd0 = open ("/dev/null", O_RDWR);
415 int fd1 = open ("/dev/null", O_RDWR);
416 int fd2 = open ("/dev/null", O_RDWR);
417 if (fd0 > 2) close (fd0);
418 if (fd1 > 2) close (fd1);
419 if (fd2 > 2) close (fd2);
424 screenhack_table_handle_events (Display *dpy,
425 const struct xscreensaver_function_table *ft,
426 Window window, void *closure
428 , Window window2, void *closure2
432 XtAppContext app = XtDisplayToApplicationContext (dpy);
434 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
435 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
437 while (XPending (dpy))
440 XNextEvent (dpy, &event);
442 if (event.xany.type == ConfigureNotify)
444 if (event.xany.window == window)
445 ft->reshape_cb (dpy, window, closure,
446 event.xconfigure.width, event.xconfigure.height);
448 if (window2 && event.xany.window == window2)
449 ft->reshape_cb (dpy, window2, closure2,
450 event.xconfigure.width, event.xconfigure.height);
453 else if (event.xany.type == ClientMessage ||
454 (! (event.xany.window == window
455 ? ft->event_cb (dpy, window, closure, &event)
457 : (window2 && event.xany.window == window2)
458 ? ft->event_cb (dpy, window2, closure2, &event)
461 if (! screenhack_handle_event_1 (dpy, &event))
464 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
465 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
472 usleep_and_process_events (Display *dpy,
473 const struct xscreensaver_function_table *ft,
474 Window window, fps_state *fpst, void *closure,
477 , Window window2, fps_state *fpst2, void *closure2,
483 unsigned long quantum = 100000; /* 1/10th second */
492 if (fpst) fps_slept (fpst, quantum);
494 if (fpst2) fps_slept (fpst2, quantum);
498 if (! screenhack_table_handle_events (dpy, ft, window, closure
511 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
513 fps_compute (fpst, 0, -1);
519 run_screenhack_table (Display *dpy,
524 const struct xscreensaver_function_table *ft)
527 /* Kludge: even though the init_cb functions are declared to take 2 args,
528 actually call them with 3, for the benefit of xlockmore_init() and
531 void *(*init_cb) (Display *, Window, void *) =
532 (void *(*) (Display *, Window, void *)) ft->init_cb;
534 void (*fps_cb) (Display *, Window, fps_state *, void *) = ft->fps_cb;
536 void *closure = init_cb (dpy, window, ft->setup_arg);
537 fps_state *fpst = fps_init (dpy, window);
541 fps_state *fpst2 = 0;
542 if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
543 if (window2) fpst2 = fps_init (dpy, window2);
546 if (! closure) /* if it returns nothing, it can't possibly be re-entrant. */
549 if (! fps_cb) fps_cb = screenhack_do_fps;
553 unsigned long delay = ft->draw_cb (dpy, window, closure);
555 unsigned long delay2 = 0;
556 if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
559 if (fpst) fps_cb (dpy, window, fpst, closure);
561 if (fpst2) fps_cb (dpy, window, fpst2, closure);
564 if (! usleep_and_process_events (dpy, ft,
565 window, fpst, closure, delay
567 , window2, fpst2, closure2, delay2
573 ft->free_cb (dpy, window, closure);
574 if (fpst) fps_free (fpst);
577 if (window2) ft->free_cb (dpy, window2, closure2);
578 if (fpst2) fps_free (fpst2);
584 make_shell (Screen *screen, Widget toplevel, int width, int height)
586 Display *dpy = DisplayOfScreen (screen);
587 Visual *visual = pick_visual (screen);
588 Boolean def_visual_p = (toplevel &&
589 visual == DefaultVisualOfScreen (screen));
591 if (width <= 0) width = 600;
592 if (height <= 0) height = 480;
597 XtVaSetValues (toplevel,
598 XtNmappedWhenManaged, False,
601 XtNinput, True, /* for WM_HINTS */
603 XtRealizeWidget (toplevel);
604 window = XtWindow (toplevel);
606 if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
609 XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
611 XSetWindowColormap (dpy, window, cmap);
618 Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
620 bg = get_pixel_resource (dpy, cmap, "background", "Background");
621 bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
623 new = XtVaAppCreateShell (progname, progclass,
624 topLevelShellWidgetClass, dpy,
625 XtNmappedWhenManaged, False,
627 XtNdepth, visual_depth (screen, visual),
631 XtNbackground, (Pixel) bg,
632 XtNborderColor, (Pixel) bd,
633 XtNinput, True, /* for WM_HINTS */
636 if (!toplevel) /* kludge for the second window in -pair mode... */
637 XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
639 XtRealizeWidget (new);
647 init_window (Display *dpy, Widget toplevel, const char *title)
650 XWindowAttributes xgwa;
651 XtPopup (toplevel, XtGrabNone);
652 XtVaSetValues (toplevel, XtNtitle, title, NULL);
654 /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
656 window = XtWindow (toplevel);
657 XGetWindowAttributes (dpy, window, &xgwa);
658 XSelectInput (dpy, window,
659 (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
660 ButtonPressMask | ButtonReleaseMask));
661 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
663 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
668 main (int argc, char **argv)
670 struct xscreensaver_function_table *ft = xscreensaver_function_table;
672 XWindowAttributes xgwa;
678 Widget toplevel2 = 0;
682 Window on_window = 0;
689 progname = argv[0]; /* reset later */
690 progclass = ft->progclass;
693 ft->setup_cb (ft, ft->setup_arg);
698 /* We have to do this on SGI to prevent the background color from being
699 overridden by the current desktop color scheme (we'd like our backgrounds
700 to be black, thanks.) This should be the same as setting the
701 "*useSchemes: none" resource, but it's not -- if that resource is
702 present in the `default_defaults' above, it doesn't work, though it
703 does work when passed as an -xrm arg on the command line. So screw it,
704 turn them off from C instead.
706 SgiUseSchemes ("none");
709 toplevel = XtAppInitialize (&app, progclass, merged_options,
710 merged_options_size, &argc, argv,
711 merged_defaults, 0, 0);
713 dpy = XtDisplay (toplevel);
715 XtGetApplicationNameAndClass (dpy,
717 (char **) &progclass);
719 /* half-assed way of avoiding buffer-overrun attacks. */
720 if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
722 XSetErrorHandler (screenhack_ehandler);
724 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
725 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
728 char *v = (char *) strdup(strchr(screensaver_id, ' '));
729 char *s1, *s2, *s3, *s4;
730 s1 = (char *) strchr(v, ' '); s1++;
731 s2 = (char *) strchr(s1, ' ');
732 s3 = (char *) strchr(v, '('); s3++;
733 s4 = (char *) strchr(s3, ')');
736 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
747 Bool help_p = (!strcmp(argv[1], "-help") ||
748 !strcmp(argv[1], "--help"));
749 fprintf (stderr, "%s\n", version);
750 for (s = progclass; *s; s++) fprintf(stderr, " ");
751 fprintf (stderr, " http://www.jwz.org/xscreensaver/\n\n");
754 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
755 fprintf (stderr, "Options include: ");
756 for (i = 0; i < merged_options_size; i++)
758 char *sw = merged_options [i].option;
759 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
760 int size = strlen (sw) + (argp ? 6 : 0) + 2;
763 fprintf (stderr, "\n\t\t ");
767 fprintf (stderr, "%s", sw);
768 if (argp) fprintf (stderr, " <arg>");
769 if (i != merged_options_size - 1) fprintf (stderr, ", ");
772 fprintf (stderr, ".\n");
777 fprintf (stderr, "\nResources:\n\n");
778 for (i = 0; i < merged_options_size; i++)
780 const char *opt = merged_options [i].option;
781 const char *res = merged_options [i].specifier + 1;
782 const char *val = merged_options [i].value;
783 char *s = get_string_resource (dpy, (char *) res, (char *) res);
788 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
792 fprintf (stderr, " %-16s %-18s ", opt, res);
793 if (merged_options [i].argKind == XrmoptionSepArg)
795 fprintf (stderr, "[%s]", (s ? s : "?"));
799 fprintf (stderr, "%s", (val ? val : "(null)"));
800 if (val && s && !strcasecmp (val, s))
801 fprintf (stderr, " [default]");
803 fprintf (stderr, "\n");
805 fprintf (stderr, "\n");
809 exit (help_p ? 0 : 1);
814 for (s = merged_defaults; *s; s++)
818 free (merged_options);
819 free (merged_defaults);
823 dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
824 mono_p = get_boolean_resource (dpy, "mono", "Boolean");
825 if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
828 root_p = get_boolean_resource (dpy, "root", "Boolean");
831 char *s = get_string_resource (dpy, "windowID", "WindowID");
833 on_window = get_integer_resource (dpy, "windowID", "WindowID");
839 window = (Window) on_window;
840 XtDestroyWidget (toplevel);
841 XGetWindowAttributes (dpy, window, &xgwa);
842 visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
844 /* Select KeyPress and resize events on the external window.
846 xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
847 XSelectInput (dpy, window, xgwa.your_event_mask);
849 /* Select ButtonPress and ButtonRelease events on the external window,
850 if no other app has already selected them (only one app can select
851 ButtonPress at a time: BadAccess results.)
853 if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
854 XSelectInput (dpy, window,
855 (xgwa.your_event_mask |
856 ButtonPressMask | ButtonReleaseMask));
860 window = VirtualRootWindowOfScreen (XtScreen (toplevel));
861 XtDestroyWidget (toplevel);
862 XGetWindowAttributes (dpy, window, &xgwa);
863 /* With RANDR, the root window can resize! */
864 XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask);
865 visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
869 Widget new = make_shell (XtScreen (toplevel), toplevel,
870 toplevel->core.width,
871 toplevel->core.height);
874 XtDestroyWidget (toplevel);
878 init_window (dpy, toplevel, version);
879 window = XtWindow (toplevel);
880 XGetWindowAttributes (dpy, window, &xgwa);
883 if (get_boolean_resource (dpy, "pair", "Boolean"))
885 toplevel2 = make_shell (xgwa.screen, 0,
886 toplevel->core.width,
887 toplevel->core.height);
888 init_window (dpy, toplevel2, version);
889 window2 = XtWindow (toplevel2);
891 # endif /* DEBUG_PAIR */
896 unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
897 "background", "Background");
898 XSetWindowBackground (dpy, window, bg);
899 XClearWindow (dpy, window);
903 XSetWindowBackground (dpy, window2, bg);
904 XClearWindow (dpy, window2);
909 if (!root_p && !on_window)
910 /* wait for it to be mapped */
911 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
915 /* This is the one and only place that the random-number generator is
916 seeded in any screenhack. You do not need to seed the RNG again,
917 it is done for you before your code is invoked. */
921 run_screenhack_table (dpy, window,
927 XtDestroyWidget (toplevel);
928 XtDestroyApplicationContext (app);