1 /* xscreensaver, Copyright (c) 1992-2010 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);
230 /* Make the X errors print out the name of this program, so we have some
231 clue which one has a bug when they die under the screensaver.
235 screenhack_ehandler (Display *dpy, XErrorEvent *error)
237 fprintf (stderr, "\nX error in %s:\n", progname);
238 if (XmuPrintDefaultErrorMessage (dpy, error, stderr))
241 fprintf (stderr, " (nonfatal.)\n");
246 MapNotify_event_p (Display *dpy, XEvent *event, XPointer window)
248 return (event->xany.type == MapNotify &&
249 event->xvisibility.window == (Window) window);
253 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
255 /* Dead-trivial event handling: exits if "q" or "ESC" are typed.
256 Exit if the WM_PROTOCOLS WM_DELETE_WINDOW ClientMessage is received.
257 Returns False if the screen saver should now terminate.
260 screenhack_handle_event_1 (Display *dpy, XEvent *event)
262 switch (event->xany.type)
268 XLookupString (&event->xkey, &c, 1, &keysym, 0);
273 return False; /* exit */
274 else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
275 XBell (dpy, 0); /* beep for non-chord keys */
283 if (event->xclient.message_type != XA_WM_PROTOCOLS)
285 char *s = XGetAtomName(dpy, event->xclient.message_type);
286 if (!s) s = "(null)";
287 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
290 else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
292 char *s1 = XGetAtomName(dpy, event->xclient.message_type);
293 char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
294 if (!s1) s1 = "(null)";
295 if (!s2) s2 = "(null)";
296 fprintf (stderr, "%s: unknown ClientMessage %s[%s] received!\n",
301 return False; /* exit */
311 pick_visual (Screen *screen)
313 struct xscreensaver_function_table *ft = xscreensaver_function_table;
315 if (ft->pick_visual_hook)
317 Visual *v = ft->pick_visual_hook (screen);
321 return get_visual_resource (screen, "visualID", "VisualID", False);
325 /* Notice when the user has requested a different visual or colormap
326 on a pre-existing window (e.g., "-root -visual truecolor" or
327 "-window-id 0x2c00001 -install") and complain, since when drawing
328 on an existing window, we have no choice about these things.
331 visual_warning (Screen *screen, Window window, Visual *visual, Colormap cmap,
334 struct xscreensaver_function_table *ft = xscreensaver_function_table;
336 char *visual_string = get_string_resource (DisplayOfScreen (screen),
337 "visualID", "VisualID");
338 Visual *desired_visual = pick_visual (screen);
342 if (window == RootWindowOfScreen (screen))
343 strcpy (win, "root window");
345 sprintf (win, "window 0x%lx", (unsigned long) window);
348 sprintf (why, "-window-id 0x%lx", (unsigned long) window);
350 strcpy (why, "-root");
352 if (visual_string && *visual_string)
355 for (s = visual_string; *s; s++)
356 if (isupper (*s)) *s = _tolower (*s);
358 if (!strcmp (visual_string, "default") ||
359 !strcmp (visual_string, "default") ||
360 !strcmp (visual_string, "best"))
361 /* don't warn about these, just silently DWIM. */
363 else if (visual != desired_visual)
365 fprintf (stderr, "%s: ignoring `-visual %s' because of `%s'.\n",
366 progname, visual_string, why);
367 fprintf (stderr, "%s: using %s's visual 0x%lx.\n",
368 progname, win, XVisualIDFromVisual (visual));
370 free (visual_string);
373 if (visual == DefaultVisualOfScreen (screen) &&
374 has_writable_cells (screen, visual) &&
375 get_boolean_resource (DisplayOfScreen (screen),
376 "installColormap", "InstallColormap"))
378 fprintf (stderr, "%s: ignoring `-install' because of `%s'.\n",
380 fprintf (stderr, "%s: using %s's colormap 0x%lx.\n",
381 progname, win, (unsigned long) cmap);
384 if (ft->validate_visual_hook)
386 if (! ft->validate_visual_hook (screen, win, visual))
395 /* Bad Things Happen if stdin, stdout, and stderr have been closed
396 (as by the `sh incantation "attraction >&- 2>&-"). When you do
397 that, the X connection gets allocated to one of these fds, and
398 then some random library writes to stderr, and random bits get
399 stuffed down the X pipe, causing "Xlib: sequence lost" errors.
400 So, we cause the first three file descriptors to be open to
401 /dev/null if they aren't open to something else already. This
402 must be done before any other files are opened (or the closing
403 of that other file will again free up one of the "magic" first
406 We do this by opening /dev/null three times, and then closing
407 those fds, *unless* any of them got allocated as #0, #1, or #2,
408 in which case we leave them open. Gag.
410 Really, this crap is technically required of *every* X program,
411 if you want it to be robust in the face of "2>&-".
413 int fd0 = open ("/dev/null", O_RDWR);
414 int fd1 = open ("/dev/null", O_RDWR);
415 int fd2 = open ("/dev/null", O_RDWR);
416 if (fd0 > 2) close (fd0);
417 if (fd1 > 2) close (fd1);
418 if (fd2 > 2) close (fd2);
423 screenhack_table_handle_events (Display *dpy,
424 const struct xscreensaver_function_table *ft,
425 Window window, void *closure
427 , Window window2, void *closure2
431 XtAppContext app = XtDisplayToApplicationContext (dpy);
433 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
434 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
436 while (XPending (dpy))
439 XNextEvent (dpy, &event);
441 if (event.xany.type == ConfigureNotify)
443 if (event.xany.window == window)
444 ft->reshape_cb (dpy, window, closure,
445 event.xconfigure.width, event.xconfigure.height);
447 if (window2 && event.xany.window == window2)
448 ft->reshape_cb (dpy, window2, closure2,
449 event.xconfigure.width, event.xconfigure.height);
452 else if (event.xany.type == ClientMessage ||
453 (! (event.xany.window == window
454 ? ft->event_cb (dpy, window, closure, &event)
456 : (window2 && event.xany.window == window2)
457 ? ft->event_cb (dpy, window2, closure2, &event)
460 if (! screenhack_handle_event_1 (dpy, &event))
463 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
464 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
471 usleep_and_process_events (Display *dpy,
472 const struct xscreensaver_function_table *ft,
473 Window window, fps_state *fpst, void *closure,
476 , Window window2, fps_state *fpst2, void *closure2,
482 unsigned long quantum = 100000; /* 1/10th second */
491 if (fpst) fps_slept (fpst, quantum);
493 if (fpst2) fps_slept (fpst2, quantum);
497 if (! screenhack_table_handle_events (dpy, ft, window, closure
510 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
512 fps_compute (fpst, 0);
518 run_screenhack_table (Display *dpy,
523 const struct xscreensaver_function_table *ft)
526 /* Kludge: even though the init_cb functions are declared to take 2 args,
527 actually call them with 3, for the benefit of xlockmore_init() and
530 void *(*init_cb) (Display *, Window, void *) =
531 (void *(*) (Display *, Window, void *)) ft->init_cb;
533 void (*fps_cb) (Display *, Window, fps_state *, void *) = ft->fps_cb;
535 void *closure = init_cb (dpy, window, ft->setup_arg);
536 fps_state *fpst = fps_init (dpy, window);
540 fps_state *fpst2 = 0;
541 if (window2) closure2 = init_cb (dpy, window2, ft->setup_arg);
542 if (window2) fpst2 = fps_init (dpy, window2);
545 if (! closure) /* if it returns nothing, it can't possibly be re-entrant. */
548 if (! fps_cb) fps_cb = screenhack_do_fps;
552 unsigned long delay = ft->draw_cb (dpy, window, closure);
554 unsigned long delay2 = 0;
555 if (window2) delay2 = ft->draw_cb (dpy, window2, closure2);
558 if (fpst) fps_cb (dpy, window, fpst, closure);
560 if (fpst2) fps_cb (dpy, window, fpst2, closure);
563 if (! usleep_and_process_events (dpy, ft,
564 window, fpst, closure, delay
566 , window2, fpst2, closure2, delay2
572 ft->free_cb (dpy, window, closure);
573 if (fpst) fps_free (fpst);
576 if (window2) ft->free_cb (dpy, window2, closure2);
577 if (window2) fps_free (fpst2);
583 make_shell (Screen *screen, Widget toplevel, int width, int height)
585 Display *dpy = DisplayOfScreen (screen);
586 Visual *visual = pick_visual (screen);
587 Boolean def_visual_p = (toplevel &&
588 visual == DefaultVisualOfScreen (screen));
590 if (width <= 0) width = 600;
591 if (height <= 0) height = 480;
596 XtVaSetValues (toplevel,
597 XtNmappedWhenManaged, False,
600 XtNinput, True, /* for WM_HINTS */
602 XtRealizeWidget (toplevel);
603 window = XtWindow (toplevel);
605 if (get_boolean_resource (dpy, "installColormap", "InstallColormap"))
608 XCreateColormap (dpy, window, DefaultVisualOfScreen (screen),
610 XSetWindowColormap (dpy, window, cmap);
617 Colormap cmap = XCreateColormap (dpy, VirtualRootWindowOfScreen(screen),
619 bg = get_pixel_resource (dpy, cmap, "background", "Background");
620 bd = get_pixel_resource (dpy, cmap, "borderColor", "Foreground");
622 new = XtVaAppCreateShell (progname, progclass,
623 topLevelShellWidgetClass, dpy,
624 XtNmappedWhenManaged, False,
626 XtNdepth, visual_depth (screen, visual),
630 XtNbackground, (Pixel) bg,
631 XtNborderColor, (Pixel) bd,
632 XtNinput, True, /* for WM_HINTS */
635 if (!toplevel) /* kludge for the second window in -pair mode... */
636 XtVaSetValues (new, XtNx, 0, XtNy, 550, NULL);
638 XtRealizeWidget (new);
646 init_window (Display *dpy, Widget toplevel, const char *title)
649 XWindowAttributes xgwa;
650 XtPopup (toplevel, XtGrabNone);
651 XtVaSetValues (toplevel, XtNtitle, title, NULL);
653 /* Select KeyPress, and announce that we accept WM_DELETE_WINDOW.
655 window = XtWindow (toplevel);
656 XGetWindowAttributes (dpy, window, &xgwa);
657 XSelectInput (dpy, window,
658 (xgwa.your_event_mask | KeyPressMask | KeyReleaseMask |
659 ButtonPressMask | ButtonReleaseMask));
660 XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
662 (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
667 main (int argc, char **argv)
669 struct xscreensaver_function_table *ft = xscreensaver_function_table;
671 XWindowAttributes xgwa;
677 Widget toplevel2 = 0;
681 Window on_window = 0;
688 progname = argv[0]; /* reset later */
689 progclass = ft->progclass;
692 ft->setup_cb (ft, ft->setup_arg);
697 /* We have to do this on SGI to prevent the background color from being
698 overridden by the current desktop color scheme (we'd like our backgrounds
699 to be black, thanks.) This should be the same as setting the
700 "*useSchemes: none" resource, but it's not -- if that resource is
701 present in the `default_defaults' above, it doesn't work, though it
702 does work when passed as an -xrm arg on the command line. So screw it,
703 turn them off from C instead.
705 SgiUseSchemes ("none");
708 toplevel = XtAppInitialize (&app, progclass, merged_options,
709 merged_options_size, &argc, argv,
710 merged_defaults, 0, 0);
712 dpy = XtDisplay (toplevel);
714 XtGetApplicationNameAndClass (dpy,
716 (char **) &progclass);
718 /* half-assed way of avoiding buffer-overrun attacks. */
719 if (strlen (progname) >= 100) ((char *) progname)[100] = 0;
721 XSetErrorHandler (screenhack_ehandler);
723 XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
724 XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
727 char *v = (char *) strdup(strchr(screensaver_id, ' '));
728 char *s1, *s2, *s3, *s4;
729 s1 = (char *) strchr(v, ' '); s1++;
730 s2 = (char *) strchr(s1, ' ');
731 s3 = (char *) strchr(v, '('); s3++;
732 s4 = (char *) strchr(s3, ')');
735 sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
746 Bool help_p = (!strcmp(argv[1], "-help") ||
747 !strcmp(argv[1], "--help"));
748 fprintf (stderr, "%s\n", version);
749 for (s = progclass; *s; s++) fprintf(stderr, " ");
750 fprintf (stderr, " http://www.jwz.org/xscreensaver/\n\n");
753 fprintf(stderr, "Unrecognised option: %s\n", argv[1]);
754 fprintf (stderr, "Options include: ");
755 for (i = 0; i < merged_options_size; i++)
757 char *sw = merged_options [i].option;
758 Bool argp = (merged_options [i].argKind == XrmoptionSepArg);
759 int size = strlen (sw) + (argp ? 6 : 0) + 2;
762 fprintf (stderr, "\n\t\t ");
766 fprintf (stderr, "%s", sw);
767 if (argp) fprintf (stderr, " <arg>");
768 if (i != merged_options_size - 1) fprintf (stderr, ", ");
771 fprintf (stderr, ".\n");
776 fprintf (stderr, "\nResources:\n\n");
777 for (i = 0; i < merged_options_size; i++)
779 const char *opt = merged_options [i].option;
780 const char *res = merged_options [i].specifier + 1;
781 const char *val = merged_options [i].value;
782 char *s = get_string_resource (dpy, (char *) res, (char *) res);
787 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
791 fprintf (stderr, " %-16s %-18s ", opt, res);
792 if (merged_options [i].argKind == XrmoptionSepArg)
794 fprintf (stderr, "[%s]", (s ? s : "?"));
798 fprintf (stderr, "%s", (val ? val : "(null)"));
799 if (val && s && !strcasecmp (val, s))
800 fprintf (stderr, " [default]");
802 fprintf (stderr, "\n");
804 fprintf (stderr, "\n");
808 exit (help_p ? 0 : 1);
813 for (s = merged_defaults; *s; s++)
817 free (merged_options);
818 free (merged_defaults);
822 dont_clear = get_boolean_resource (dpy, "dontClearRoot", "Boolean");
823 mono_p = get_boolean_resource (dpy, "mono", "Boolean");
824 if (CellsOfScreen (DefaultScreenOfDisplay (dpy)) <= 2)
827 root_p = get_boolean_resource (dpy, "root", "Boolean");
830 char *s = get_string_resource (dpy, "windowID", "WindowID");
832 on_window = get_integer_resource (dpy, "windowID", "WindowID");
838 window = (Window) on_window;
839 XtDestroyWidget (toplevel);
840 XGetWindowAttributes (dpy, window, &xgwa);
841 visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, True);
843 /* Select KeyPress and resize events on the external window.
845 xgwa.your_event_mask |= KeyPressMask | StructureNotifyMask;
846 XSelectInput (dpy, window, xgwa.your_event_mask);
848 /* Select ButtonPress and ButtonRelease events on the external window,
849 if no other app has already selected them (only one app can select
850 ButtonPress at a time: BadAccess results.)
852 if (! (xgwa.all_event_masks & (ButtonPressMask | ButtonReleaseMask)))
853 XSelectInput (dpy, window,
854 (xgwa.your_event_mask |
855 ButtonPressMask | ButtonReleaseMask));
859 window = VirtualRootWindowOfScreen (XtScreen (toplevel));
860 XtDestroyWidget (toplevel);
861 XGetWindowAttributes (dpy, window, &xgwa);
862 /* With RANDR, the root window can resize! */
863 XSelectInput (dpy, window, xgwa.your_event_mask | StructureNotifyMask);
864 visual_warning (xgwa.screen, window, xgwa.visual, xgwa.colormap, False);
868 Widget new = make_shell (XtScreen (toplevel), toplevel,
869 toplevel->core.width,
870 toplevel->core.height);
873 XtDestroyWidget (toplevel);
877 init_window (dpy, toplevel, version);
878 window = XtWindow (toplevel);
879 XGetWindowAttributes (dpy, window, &xgwa);
882 if (get_boolean_resource (dpy, "pair", "Boolean"))
884 toplevel2 = make_shell (xgwa.screen, 0,
885 toplevel->core.width,
886 toplevel->core.height);
887 init_window (dpy, toplevel2, version);
888 window2 = XtWindow (toplevel2);
890 # endif /* DEBUG_PAIR */
895 unsigned int bg = get_pixel_resource (dpy, xgwa.colormap,
896 "background", "Background");
897 XSetWindowBackground (dpy, window, bg);
898 XClearWindow (dpy, window);
902 XSetWindowBackground (dpy, window2, bg);
903 XClearWindow (dpy, window2);
908 if (!root_p && !on_window)
909 /* wait for it to be mapped */
910 XIfEvent (dpy, &event, MapNotify_event_p, (XPointer) window);
914 /* This is the one and only place that the random-number generator is
915 seeded in any screenhack. You do not need to seed the RNG again,
916 it is done for you before your code is invoked. */
920 run_screenhack_table (dpy, window,
926 XtDestroyWidget (toplevel);
927 XtDestroyApplicationContext (app);