1 /* xscreensaver, Copyright (c) 1991-1995 Jamie Zawinski <jwz@netscape.com>
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
14 #include <X11/Xutil.h>
15 #include <X11/Xatom.h>
17 #include <X11/Xmu/SysUtil.h>
19 #include <signal.h> /* for the signal names */
21 #include "xscreensaver.h"
23 #ifdef HAVE_SAVER_EXTENSION
24 #include <X11/extensions/scrnsaver.h>
25 extern Bool use_saver_extension;
26 #endif /* HAVE_SAVER_EXTENSION */
29 extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
34 extern Bool lock_p, demo_mode_p;
36 Atom XA_VROOT, XA_XSETROOT_ID;
37 Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
40 extern void describe_visual (FILE *, Display *, Visual *);
41 extern void reset_stderr (void);
44 Window screensaver_window = 0;
48 Bool fade_p, unfade_p;
49 int fade_seconds, fade_ticks;
51 static unsigned long black_pixel;
52 static Window real_vroot, real_vroot_value;
54 #ifdef HAVE_SAVER_EXTENSION
55 Window server_saver_window = 0;
56 #endif /* HAVE_SAVER_EXTENSION */
58 #define ALL_POINTER_EVENTS \
59 (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \
60 LeaveWindowMask | PointerMotionMask | PointerMotionHintMask | \
61 Button1MotionMask | Button2MotionMask | Button3MotionMask | \
62 Button4MotionMask | Button5MotionMask | ButtonMotionMask)
64 /* I don't really understand Sync vs Async, but these seem to work... */
65 #define grab_kbd(win) \
66 XGrabKeyboard (dpy, (win), True, GrabModeSync, GrabModeAsync, CurrentTime)
67 #define grab_mouse(win) \
68 XGrabPointer (dpy, (win), True, ALL_POINTER_EVENTS, \
69 GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime)
72 grab_keyboard_and_mouse P((void))
77 if (demo_mode_p) return;
79 status = grab_kbd (screensaver_window);
80 if (status != GrabSuccess)
81 { /* try again in a second */
83 status = grab_kbd (screensaver_window);
84 if (status != GrabSuccess)
85 fprintf (stderr, "%s: %scouldn't grab keyboard! (%d)\n",
86 progname, (verbose_p ? "## " : ""), status);
88 status = grab_mouse (screensaver_window);
89 if (status != GrabSuccess)
90 { /* try again in a second */
92 status = grab_mouse (screensaver_window);
93 if (status != GrabSuccess)
94 fprintf (stderr, "%s: %scouldn't grab pointer! (%d)\n",
95 progname, (verbose_p ? "## " : ""), status);
100 ungrab_keyboard_and_mouse P((void))
102 XUngrabPointer (dpy, CurrentTime);
103 XUngrabKeyboard (dpy, CurrentTime);
108 ensure_no_screensaver_running ()
111 Window root = RootWindowOfScreen (screen);
112 Window root2, parent, *kids;
114 int (*old_handler) ();
116 old_handler = XSetErrorHandler (BadWindow_ehandler);
118 if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
124 for (i = 0; i < nkids; i++)
128 unsigned long nitems, bytesafter;
131 if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1,
132 False, XA_STRING, &type, &format, &nitems,
133 &bytesafter, (unsigned char **) &version)
138 if (!XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512,
139 False, XA_STRING, &type, &format, &nitems,
140 &bytesafter, (unsigned char **) &id)
146 "%s: %salready running on display %s (window 0x%x)\n from process %s.\n",
147 progname, (verbose_p ? "## " : ""), DisplayString (dpy),
153 if (kids) XFree ((char *) kids);
155 XSetErrorHandler (old_handler);
160 disable_builtin_screensaver ()
162 int server_timeout, server_interval, prefer_blank, allow_exp;
163 /* Turn off the server builtin saver if it is now running. */
164 XForceScreenSaver (dpy, ScreenSaverReset);
165 XGetScreenSaver (dpy, &server_timeout, &server_interval,
166 &prefer_blank, &allow_exp);
168 #ifdef HAVE_SAVER_EXTENSION
169 if (use_saver_extension)
171 /* Override the values specified with "xset" with our own parameters. */
172 prefer_blank = False;
175 server_timeout = (timeout / 1000);
178 "%s: configuring server for saver timeout of %d seconds.\n",
179 progname, server_timeout);
180 XSetScreenSaver (dpy, server_timeout, server_interval,
181 prefer_blank, allow_exp);
184 #endif /* HAVE_SAVER_EXTENSION */
185 if (server_timeout != 0)
188 XSetScreenSaver (dpy, server_timeout, server_interval,
189 prefer_blank, allow_exp);
190 printf ("%s%sisabling server builtin screensaver.\n\
191 You can re-enable it with \"xset s on\".\n",
192 (verbose_p ? "" : progname), (verbose_p ? "\n\tD" : ": d"));
197 /* Virtual-root hackery */
200 ERROR! You must not include vroot.h in this file.
205 store_vroot_property (Window win, Window value)
207 store_vroot_property (win, value)
212 printf ("%s: storing XA_VROOT = 0x%x (%s) = 0x%x (%s)\n", progname,
214 (win == screensaver_window ? "ScreenSaver" :
215 (win == real_vroot ? "VRoot" :
216 (win == real_vroot_value ? "Vroot_value" : "???"))),
218 (value == screensaver_window ? "ScreenSaver" :
219 (value == real_vroot ? "VRoot" :
220 (value == real_vroot_value ? "Vroot_value" : "???"))));
222 XChangeProperty (dpy, win, XA_VROOT, XA_WINDOW, 32, PropModeReplace,
223 (unsigned char *) &value, 1);
228 remove_vroot_property (Window win)
230 remove_vroot_property (win)
235 printf ("%s: removing XA_VROOT from 0x%x (%s)\n", progname, win,
236 (win == screensaver_window ? "ScreenSaver" :
237 (win == real_vroot ? "VRoot" :
238 (win == real_vroot_value ? "Vroot_value" : "???"))));
240 XDeleteProperty (dpy, win, XA_VROOT);
245 kill_xsetroot_data P((void))
249 unsigned long nitems, bytesafter;
252 /* If the user has been using xv or xsetroot as a screensaver (to display
253 an image on the screensaver window, as a kind of slideshow) then the
254 pixmap and its associated color cells have been put in RetainPermanent
255 CloseDown mode. Since we're not destroying the xscreensaver window,
256 but merely unmapping it, we need to free these resources or those
257 colormap cells will stay allocated while the screensaver is off. (We
258 could just delete the screensaver window and recreate it later, but
259 that could cause other problems.) This code does an atomic read-and-
260 delete of the _XSETROOT_ID property, and if it held a pixmap, then we
261 cause the RetainPermanent resources of the client which created it
262 (and which no longer exists) to be freed.
264 if (XGetWindowProperty (dpy, screensaver_window, XA_XSETROOT_ID, 0, 1,
265 True, AnyPropertyType, &type, &format, &nitems,
266 &bytesafter, (unsigned char **) &dataP)
270 if (dataP && *dataP && type == XA_PIXMAP && format == 32 &&
271 nitems == 1 && bytesafter == 0)
274 printf ("%s: destroying xsetroot data (0x%lX).\n",
276 XKillClient (dpy, *dataP);
279 fprintf (stderr, "%s: %sdeleted unrecognised _XSETROOT_ID property: \n\
280 %lu, %lu; type: %lu, format: %d, nitems: %lu, bytesafter %ld\n",
281 progname, (verbose_p ? "## " : ""),
282 (unsigned long) dataP, (dataP ? *dataP : 0), type,
283 format, nitems, bytesafter);
288 static void handle_signals P((Bool on_p));
291 save_real_vroot P((void))
294 Window root = RootWindowOfScreen (screen);
295 Window root2, parent, *kids;
299 real_vroot_value = 0;
300 if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
306 for (i = 0; i < nkids; i++)
310 unsigned long nitems, bytesafter;
313 if (XGetWindowProperty (dpy, kids[i], XA_VROOT, 0, 1, False, XA_WINDOW,
314 &type, &format, &nitems, &bytesafter,
315 (unsigned char **) &vrootP)
322 if (*vrootP == screensaver_window) abort ();
324 "%s: %smore than one virtual root window found (0x%x and 0x%x).\n",
325 progname, (verbose_p ? "## " : ""),
326 (int) real_vroot, (int) kids [i]);
329 real_vroot = kids [i];
330 real_vroot_value = *vrootP;
335 handle_signals (True);
336 remove_vroot_property (real_vroot);
340 XFree ((char *) kids);
344 restore_real_vroot_1 P((void))
346 if (verbose_p && real_vroot)
347 printf ("%s: restoring __SWM_VROOT property on the real vroot (0x%lx).\n",
348 progname, (unsigned long) real_vroot);
349 remove_vroot_property (screensaver_window);
352 store_vroot_property (real_vroot, real_vroot_value);
354 real_vroot_value = 0;
355 /* make sure the property change gets there before this process
356 terminates! We might be doing this because we have intercepted
357 SIGTERM or something. */
365 restore_real_vroot ()
367 if (restore_real_vroot_1 ())
368 handle_signals (False);
372 /* Signal hackery to ensure that the vroot doesn't get left in an
376 static const char *sig_names [255] = { 0 };
379 restore_real_vroot_handler (sig)
382 signal (sig, SIG_DFL);
383 if (restore_real_vroot_1 ())
384 fprintf (stderr, "\n%s: %s%s (%d) intercepted, vroot restored.\n",
385 progname, (verbose_p ? "## " : ""),
386 ((sig < sizeof(sig_names) && sig >= 0 && sig_names [sig])
387 ? sig_names [sig] : "unknown signal"),
389 kill (getpid (), sig);
395 catch_signal (int sig, char *signame, Bool on_p)
397 catch_signal (sig, signame, on_p)
404 signal (sig, SIG_DFL);
407 sig_names [sig] = signame;
408 if (((int) signal (sig, restore_real_vroot_handler)) == -1)
411 sprintf (buf, "%s: %scouldn't catch %s (%d)", progname,
412 (verbose_p ? "## " : ""), signame, sig);
414 restore_real_vroot ();
421 handle_signals (on_p)
425 if (on_p) printf ("handling signals\n");
426 else printf ("unhandling signals\n");
429 catch_signal (SIGHUP, "SIGHUP", on_p);
430 catch_signal (SIGINT, "SIGINT", on_p);
431 catch_signal (SIGQUIT, "SIGQUIT", on_p);
432 catch_signal (SIGILL, "SIGILL", on_p);
433 catch_signal (SIGTRAP, "SIGTRAP", on_p);
434 catch_signal (SIGIOT, "SIGIOT", on_p);
435 catch_signal (SIGABRT, "SIGABRT", on_p);
437 catch_signal (SIGEMT, "SIGEMT", on_p);
439 catch_signal (SIGFPE, "SIGFPE", on_p);
440 catch_signal (SIGBUS, "SIGBUS", on_p);
441 catch_signal (SIGSEGV, "SIGSEGV", on_p);
443 catch_signal (SIGSYS, "SIGSYS", on_p);
445 catch_signal (SIGTERM, "SIGTERM", on_p);
447 catch_signal (SIGXCPU, "SIGXCPU", on_p);
450 catch_signal (SIGXFSZ, "SIGXFSZ", on_p);
453 catch_signal (SIGDANGER, "SIGDANGER", on_p);
458 /* Managing the actual screensaver window */
461 window_exists_p (dpy, window)
465 int (*old_handler) ();
466 XWindowAttributes xgwa;
468 old_handler = XSetErrorHandler (BadWindow_ehandler);
469 XGetWindowAttributes (dpy, window, &xgwa);
471 XSetErrorHandler (old_handler);
472 return (xgwa.screen != 0);
476 initialize_screensaver_window P((void))
478 /* This resets the screensaver window as fully as possible, since there's
479 no way of knowing what some random client may have done to us in the
480 meantime. We could just destroy and recreate the window, but that has
481 its own set of problems...
484 XClassHint class_hints;
485 XSetWindowAttributes attrs;
486 unsigned long attrmask;
487 int width = WidthOfScreen (screen);
488 int height = HeightOfScreen (screen);
493 black.red = black.green = black.blue = 0;
495 if (cmap == DefaultColormapOfScreen (screen))
498 if (install_cmap_p || visual != DefaultVisualOfScreen (screen))
502 cmap = XCreateColormap (dpy, RootWindowOfScreen (screen),
504 if (! XAllocColor (dpy, cmap, &black)) abort ();
505 black_pixel = black.pixel;
512 XFreeColors (dpy, cmap, &black_pixel, 1, 0);
513 XFreeColormap (dpy, cmap);
515 cmap = DefaultColormapOfScreen (screen);
516 black_pixel = BlackPixelOfScreen (screen);
521 XFreeColormap (dpy, cmap2);
527 cmap2 = copy_colormap (dpy, cmap, 0);
529 fade_p = unfade_p = 0;
532 attrmask = (CWOverrideRedirect | CWEventMask | CWBackingStore | CWColormap |
533 CWBackPixel | CWBackingPixel | CWBorderPixel);
534 attrs.override_redirect = True;
535 attrs.event_mask = (KeyPressMask | KeyReleaseMask |
536 ButtonPressMask | ButtonReleaseMask |
538 attrs.backing_store = NotUseful;
539 attrs.colormap = cmap;
540 attrs.background_pixel = black_pixel;
541 attrs.backing_pixel = black_pixel;
542 attrs.border_pixel = black_pixel;
545 if (demo_mode_p || lock_p) width = width / 2; /* #### */
548 if (screensaver_window || !verbose_p)
550 else if (visual == DefaultVisualOfScreen (screen))
552 fprintf (stderr, "%s: using default visual ", progname);
553 describe_visual (stderr, dpy, visual);
557 fprintf (stderr, "%s: using visual: ", progname);
558 describe_visual (stderr, dpy, visual);
559 fprintf (stderr, "%s: default visual: ", progname);
560 describe_visual (stderr, dpy, DefaultVisualOfScreen (screen));
563 #ifdef HAVE_SAVER_EXTENSION
564 if (use_saver_extension)
566 XScreenSaverInfo *info;
567 Window root = RootWindowOfScreen (screen);
569 /* This call sets the server screensaver timeouts to what we think
570 they should be (based on the resources and args xscreensaver was
571 started with.) It's important that we do this to sync back up
572 with the server - if we have turned on prematurely, as by an
573 ACTIVATE ClientMessage, then the server may decide to activate
574 the screensaver while it's already active. That's ok for us,
575 since we would know to ignore that ScreenSaverActivate event,
576 but a side effect of this would be that the server would map its
577 saver window (which we then hide again right away) meaning that
578 the bits currently on the screen get blown away. Ugly. */
580 /* #### Ok, that doesn't work - when we tell the server that the
581 screensaver is "off" it sends us a Deactivate event, which is
582 sensible... but causes the saver to never come on. Hmm. */
583 disable_builtin_screensaver ();
587 /* #### The MIT-SCREEN-SAVER extension gives us access to the
588 window that the server itself uses for saving the screen.
589 However, using this window in any way, in particular, calling
590 XScreenSaverSetAttributes() as below, tends to make the X server
591 crash. So fuck it, let's try and get along without using it...
593 It's also inconvenient to use this window because it doesn't
594 always exist (though the ID is constant.) So to use this
595 window, we'd have to reimplement the ACTIVATE ClientMessage to
596 tell the *server* to tell *us* to turn on, to cause the window
597 to get created at the right time. Gag. */
598 XScreenSaverSetAttributes (dpy, root,
599 0, 0, width, height, 0,
600 visual_depth, InputOutput, visual,
605 info = XScreenSaverAllocInfo ();
606 XScreenSaverQueryInfo (dpy, root, info);
607 server_saver_window = info->window;
608 if (! server_saver_window) abort ();
611 #endif /* HAVE_SAVER_EXTENSION */
613 if (screensaver_window)
615 XWindowChanges changes;
616 unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
619 changes.width = width;
620 changes.height = height;
621 changes.border_width = 0;
623 XConfigureWindow (dpy, screensaver_window, changesmask, &changes);
624 XChangeWindowAttributes (dpy, screensaver_window, attrmask, &attrs);
629 XCreateWindow (dpy, RootWindowOfScreen (screen), 0, 0, width, height,
630 0, visual_depth, InputOutput, visual, attrmask,
634 #ifdef HAVE_SAVER_EXTENSION
635 if (!use_saver_extension ||
636 window_exists_p (dpy, screensaver_window))
637 /* When using the MIT-SCREEN-SAVER extension, the window pointed to
638 by screensaver_window only exists while the saver is active.
639 So we must be careful to only try and manipulate it while it
642 #endif /* HAVE_SAVER_EXTENSION */
644 class_hints.res_name = progname;
645 class_hints.res_class = progclass;
646 XSetClassHint (dpy, screensaver_window, &class_hints);
647 XStoreName (dpy, screensaver_window, "screensaver");
648 XChangeProperty (dpy, screensaver_window, XA_SCREENSAVER_VERSION,
649 XA_STRING, 8, PropModeReplace,
650 (unsigned char *) screensaver_version,
651 strlen (screensaver_version));
653 sprintf (id, "%d on host ", getpid ());
654 if (! XmuGetHostname (id + strlen (id), sizeof (id) - strlen (id) - 1))
656 XChangeProperty (dpy, screensaver_window, XA_SCREENSAVER_ID, XA_STRING,
657 8, PropModeReplace, (unsigned char *) id, strlen (id));
662 bit = XCreatePixmapFromBitmapData (dpy, screensaver_window, "\000",
664 BlackPixelOfScreen (screen),
665 BlackPixelOfScreen (screen), 1);
666 cursor = XCreatePixmapCursor (dpy, bit, bit, &black, &black, 0, 0);
667 XFreePixmap (dpy, bit);
670 XSetWindowBackground (dpy, screensaver_window, black_pixel);
672 XDefineCursor (dpy, screensaver_window, cursor);
674 XUndefineCursor (dpy, screensaver_window);
680 raise_window (inhibit_fade, between_hacks_p)
681 Bool inhibit_fade, between_hacks_p;
683 initialize_screensaver_window ();
685 if (fade_p && !inhibit_fade && !demo_mode_p)
688 Colormap current_map = (between_hacks_p
690 : DefaultColormapOfScreen (screen));
691 copy_colormap (dpy, current_map, cmap2);
693 /* grab and blacken mouse on the root window (saver not mapped yet) */
694 grabbed = grab_mouse (RootWindowOfScreen (screen));
695 /* fade what's on the screen to black */
696 XInstallColormap (dpy, cmap2);
697 fade_colormap (dpy, current_map, cmap2, fade_seconds, fade_ticks, True);
698 XClearWindow (dpy, screensaver_window);
699 XMapRaised (dpy, screensaver_window);
701 #ifdef HAVE_SAVER_EXTENSION
702 if (server_saver_window && window_exists_p (dpy, server_saver_window))
703 XUnmapWindow (dpy, server_saver_window);
704 #endif /* HAVE_SAVER_EXTENSION */
706 /* Once the saver window is up, restore the colormap.
707 (The "black" pixels of the two colormaps are compatible.) */
708 XInstallColormap (dpy, cmap);
709 if (grabbed == GrabSuccess)
710 XUngrabPointer (dpy, CurrentTime);
715 XClearWindow (dpy, screensaver_window);
716 XMapRaised (dpy, screensaver_window);
717 #ifdef HAVE_SAVER_EXTENSION
718 if (server_saver_window && window_exists_p (dpy, server_saver_window))
719 XUnmapWindow (dpy, server_saver_window);
720 #endif /* HAVE_SAVER_EXTENSION */
724 XInstallColormap (dpy, cmap);
728 /* Calls to XHPDisableReset and XHPEnableReset must be balanced,
729 or BadAccess errors occur. */
730 static Bool hp_locked_p = False;
737 store_vroot_property (screensaver_window, screensaver_window);
738 raise_window (False, False);
739 grab_keyboard_and_mouse ();
741 if (lock_p && !hp_locked_p)
742 XHPDisableReset (dpy); /* turn off C-Sh-Reset */
750 if (unfade_p && !demo_mode_p)
753 Colormap default_map = DefaultColormapOfScreen (screen);
754 blacken_colormap (dpy, cmap2);
756 /* grab and blacken mouse on the root window. */
757 grabbed = grab_mouse (RootWindowOfScreen (screen));
758 XInstallColormap (dpy, cmap2);
759 XUnmapWindow (dpy, screensaver_window);
760 fade_colormap (dpy, default_map, cmap2, fade_seconds, fade_ticks, False);
761 XInstallColormap (dpy, default_map);
762 if (grabbed == GrabSuccess)
763 XUngrabPointer (dpy, CurrentTime);
770 XClearWindow (dpy, screensaver_window); /* avoid technicolor */
771 XInstallColormap (dpy, DefaultColormapOfScreen (screen));
773 XUnmapWindow (dpy, screensaver_window);
775 kill_xsetroot_data ();
776 ungrab_keyboard_and_mouse ();
777 restore_real_vroot ();
779 if (lock_p && hp_locked_p)
780 XHPEnableReset (dpy); /* turn C-Sh-Reset back on */