1 /* xscreensaver, Copyright (c) 1991-1996 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_MIT_SAVER_EXTENSION
24 #include <X11/extensions/scrnsaver.h>
25 #endif /* HAVE_MIT_SAVER_EXTENSION */
27 #ifdef HAVE_SGI_SAVER_EXTENSION
28 #include <X11/extensions/XScreenSaver.h>
29 #endif /* HAVE_SGI_SAVER_EXTENSION */
31 extern Bool use_mit_saver_extension;
32 extern Bool use_sgi_saver_extension;
35 extern int kill (pid_t, int); /* signal() is in sys/signal.h... */
40 extern Bool lock_p, demo_mode_p;
42 Atom XA_VROOT, XA_XSETROOT_ID;
43 Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
46 extern void describe_visual (FILE *, Display *, Visual *);
47 extern void reset_stderr (void);
50 Window screensaver_window = 0;
54 Bool fade_p, unfade_p;
55 int fade_seconds, fade_ticks;
57 static unsigned long black_pixel;
58 static Window real_vroot, real_vroot_value;
60 #ifdef HAVE_MIT_SAVER_EXTENSION
61 Window server_mit_saver_window = 0;
62 #endif /* HAVE_MIT_SAVER_EXTENSION */
64 #define ALL_POINTER_EVENTS \
65 (ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \
66 LeaveWindowMask | PointerMotionMask | PointerMotionHintMask | \
67 Button1MotionMask | Button2MotionMask | Button3MotionMask | \
68 Button4MotionMask | Button5MotionMask | ButtonMotionMask)
70 /* I don't really understand Sync vs Async, but these seem to work... */
71 #define grab_kbd(win) \
72 XGrabKeyboard (dpy, (win), True, GrabModeSync, GrabModeAsync, CurrentTime)
73 #define grab_mouse(win) \
74 XGrabPointer (dpy, (win), True, ALL_POINTER_EVENTS, \
75 GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime)
78 grab_keyboard_and_mouse P((void))
83 if (demo_mode_p) return;
85 status = grab_kbd (screensaver_window);
86 if (status != GrabSuccess)
87 { /* try again in a second */
89 status = grab_kbd (screensaver_window);
90 if (status != GrabSuccess)
91 fprintf (stderr, "%s: %scouldn't grab keyboard! (%d)\n",
92 progname, (verbose_p ? "## " : ""), status);
94 status = grab_mouse (screensaver_window);
95 if (status != GrabSuccess)
96 { /* try again in a second */
98 status = grab_mouse (screensaver_window);
99 if (status != GrabSuccess)
100 fprintf (stderr, "%s: %scouldn't grab pointer! (%d)\n",
101 progname, (verbose_p ? "## " : ""), status);
106 ungrab_keyboard_and_mouse P((void))
108 XUngrabPointer (dpy, CurrentTime);
109 XUngrabKeyboard (dpy, CurrentTime);
114 ensure_no_screensaver_running ()
117 Window root = RootWindowOfScreen (screen);
118 Window root2, parent, *kids;
120 int (*old_handler) ();
122 old_handler = XSetErrorHandler (BadWindow_ehandler);
124 if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
130 for (i = 0; i < nkids; i++)
134 unsigned long nitems, bytesafter;
137 if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1,
138 False, XA_STRING, &type, &format, &nitems,
139 &bytesafter, (unsigned char **) &version)
144 if (!XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512,
145 False, XA_STRING, &type, &format, &nitems,
146 &bytesafter, (unsigned char **) &id)
152 "%s: %salready running on display %s (window 0x%x)\n from process %s.\n",
153 progname, (verbose_p ? "## " : ""), DisplayString (dpy),
159 if (kids) XFree ((char *) kids);
161 XSetErrorHandler (old_handler);
166 disable_builtin_screensaver ()
168 int server_timeout, server_interval, prefer_blank, allow_exp;
169 /* Turn off the server builtin saver if it is now running. */
170 XForceScreenSaver (dpy, ScreenSaverReset);
171 XGetScreenSaver (dpy, &server_timeout, &server_interval,
172 &prefer_blank, &allow_exp);
174 #if defined(HAVE_MIT_SAVER_EXTENSION) || defined(HAVE_SGI_SAVER_EXTENSION)
175 if (use_mit_saver_extension || use_sgi_saver_extension)
177 /* Override the values specified with "xset" with our own parameters. */
180 server_timeout = (timeout / 1000);
182 /* The SGI extension won't give us events unless blanking is on.
183 I think (unsure right now) that the MIT extension is the opposite. */
184 prefer_blank = (use_sgi_saver_extension ? True : False);
188 "%s: configuring server for saver timeout of %d seconds.\n",
189 progname, server_timeout);
190 XSetScreenSaver (dpy, server_timeout, server_interval,
191 prefer_blank, allow_exp);
194 #endif /* HAVE_MIT_SAVER_EXTENSION || HAVE_SGI_SAVER_EXTENSION */
195 if (server_timeout != 0)
198 XSetScreenSaver (dpy, server_timeout, server_interval,
199 prefer_blank, allow_exp);
200 printf ("%s%sisabling server builtin screensaver.\n\
201 You can re-enable it with \"xset s on\".\n",
202 (verbose_p ? "" : progname), (verbose_p ? "\n\tD" : ": d"));
207 /* Virtual-root hackery */
210 ERROR! You must not include vroot.h in this file.
215 store_vroot_property (Window win, Window value)
217 store_vroot_property (win, value)
222 printf ("%s: storing XA_VROOT = 0x%x (%s) = 0x%x (%s)\n", progname,
224 (win == screensaver_window ? "ScreenSaver" :
225 (win == real_vroot ? "VRoot" :
226 (win == real_vroot_value ? "Vroot_value" : "???"))),
228 (value == screensaver_window ? "ScreenSaver" :
229 (value == real_vroot ? "VRoot" :
230 (value == real_vroot_value ? "Vroot_value" : "???"))));
232 XChangeProperty (dpy, win, XA_VROOT, XA_WINDOW, 32, PropModeReplace,
233 (unsigned char *) &value, 1);
238 remove_vroot_property (Window win)
240 remove_vroot_property (win)
245 printf ("%s: removing XA_VROOT from 0x%x (%s)\n", progname, win,
246 (win == screensaver_window ? "ScreenSaver" :
247 (win == real_vroot ? "VRoot" :
248 (win == real_vroot_value ? "Vroot_value" : "???"))));
250 XDeleteProperty (dpy, win, XA_VROOT);
255 kill_xsetroot_data P((void))
259 unsigned long nitems, bytesafter;
262 /* If the user has been using xv or xsetroot as a screensaver (to display
263 an image on the screensaver window, as a kind of slideshow) then the
264 pixmap and its associated color cells have been put in RetainPermanent
265 CloseDown mode. Since we're not destroying the xscreensaver window,
266 but merely unmapping it, we need to free these resources or those
267 colormap cells will stay allocated while the screensaver is off. (We
268 could just delete the screensaver window and recreate it later, but
269 that could cause other problems.) This code does an atomic read-and-
270 delete of the _XSETROOT_ID property, and if it held a pixmap, then we
271 cause the RetainPermanent resources of the client which created it
272 (and which no longer exists) to be freed.
274 if (XGetWindowProperty (dpy, screensaver_window, XA_XSETROOT_ID, 0, 1,
275 True, AnyPropertyType, &type, &format, &nitems,
276 &bytesafter, (unsigned char **) &dataP)
280 if (dataP && *dataP && type == XA_PIXMAP && format == 32 &&
281 nitems == 1 && bytesafter == 0)
284 printf ("%s: destroying xsetroot data (0x%lX).\n",
286 XKillClient (dpy, *dataP);
289 fprintf (stderr, "%s: %sdeleted unrecognised _XSETROOT_ID property: \n\
290 %lu, %lu; type: %lu, format: %d, nitems: %lu, bytesafter %ld\n",
291 progname, (verbose_p ? "## " : ""),
292 (unsigned long) dataP, (dataP ? *dataP : 0), type,
293 format, nitems, bytesafter);
298 static void handle_signals P((Bool on_p));
301 save_real_vroot P((void))
304 Window root = RootWindowOfScreen (screen);
305 Window root2, parent, *kids;
309 real_vroot_value = 0;
310 if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids))
316 for (i = 0; i < nkids; i++)
320 unsigned long nitems, bytesafter;
323 if (XGetWindowProperty (dpy, kids[i], XA_VROOT, 0, 1, False, XA_WINDOW,
324 &type, &format, &nitems, &bytesafter,
325 (unsigned char **) &vrootP)
332 if (*vrootP == screensaver_window) abort ();
334 "%s: %smore than one virtual root window found (0x%x and 0x%x).\n",
335 progname, (verbose_p ? "## " : ""),
336 (int) real_vroot, (int) kids [i]);
339 real_vroot = kids [i];
340 real_vroot_value = *vrootP;
345 handle_signals (True);
346 remove_vroot_property (real_vroot);
350 XFree ((char *) kids);
354 restore_real_vroot_1 P((void))
356 if (verbose_p && real_vroot)
357 printf ("%s: restoring __SWM_VROOT property on the real vroot (0x%lx).\n",
358 progname, (unsigned long) real_vroot);
359 remove_vroot_property (screensaver_window);
362 store_vroot_property (real_vroot, real_vroot_value);
364 real_vroot_value = 0;
365 /* make sure the property change gets there before this process
366 terminates! We might be doing this because we have intercepted
367 SIGTERM or something. */
375 restore_real_vroot ()
377 if (restore_real_vroot_1 ())
378 handle_signals (False);
382 /* Signal hackery to ensure that the vroot doesn't get left in an
386 static const char *sig_names [255] = { 0 };
389 restore_real_vroot_handler (sig)
392 signal (sig, SIG_DFL);
393 if (restore_real_vroot_1 ())
394 fprintf (stderr, "\n%s: %s%s (%d) intercepted, vroot restored.\n",
395 progname, (verbose_p ? "## " : ""),
396 ((sig < sizeof(sig_names) && sig >= 0 && sig_names [sig])
397 ? sig_names [sig] : "unknown signal"),
399 kill (getpid (), sig);
405 catch_signal (int sig, char *signame, Bool on_p)
407 catch_signal (sig, signame, on_p)
414 signal (sig, SIG_DFL);
417 sig_names [sig] = signame;
418 if (((int) signal (sig, restore_real_vroot_handler)) == -1)
421 sprintf (buf, "%s: %scouldn't catch %s (%d)", progname,
422 (verbose_p ? "## " : ""), signame, sig);
424 restore_real_vroot ();
431 handle_signals (on_p)
435 if (on_p) printf ("handling signals\n");
436 else printf ("unhandling signals\n");
439 catch_signal (SIGHUP, "SIGHUP", on_p);
440 catch_signal (SIGINT, "SIGINT", on_p);
441 catch_signal (SIGQUIT, "SIGQUIT", on_p);
442 catch_signal (SIGILL, "SIGILL", on_p);
443 catch_signal (SIGTRAP, "SIGTRAP", on_p);
444 catch_signal (SIGIOT, "SIGIOT", on_p);
445 catch_signal (SIGABRT, "SIGABRT", on_p);
447 catch_signal (SIGEMT, "SIGEMT", on_p);
449 catch_signal (SIGFPE, "SIGFPE", on_p);
450 catch_signal (SIGBUS, "SIGBUS", on_p);
451 catch_signal (SIGSEGV, "SIGSEGV", on_p);
453 catch_signal (SIGSYS, "SIGSYS", on_p);
455 catch_signal (SIGTERM, "SIGTERM", on_p);
457 catch_signal (SIGXCPU, "SIGXCPU", on_p);
460 catch_signal (SIGXFSZ, "SIGXFSZ", on_p);
463 catch_signal (SIGDANGER, "SIGDANGER", on_p);
468 /* Managing the actual screensaver window */
471 window_exists_p (dpy, window)
475 int (*old_handler) ();
476 XWindowAttributes xgwa;
478 old_handler = XSetErrorHandler (BadWindow_ehandler);
479 XGetWindowAttributes (dpy, window, &xgwa);
481 XSetErrorHandler (old_handler);
482 return (xgwa.screen != 0);
486 initialize_screensaver_window P((void))
488 /* This resets the screensaver window as fully as possible, since there's
489 no way of knowing what some random client may have done to us in the
490 meantime. We could just destroy and recreate the window, but that has
491 its own set of problems...
494 XClassHint class_hints;
495 XSetWindowAttributes attrs;
496 unsigned long attrmask;
497 int width = WidthOfScreen (screen);
498 int height = HeightOfScreen (screen);
503 black.red = black.green = black.blue = 0;
505 if (cmap == DefaultColormapOfScreen (screen))
508 if (install_cmap_p || visual != DefaultVisualOfScreen (screen))
512 cmap = XCreateColormap (dpy, RootWindowOfScreen (screen),
514 if (! XAllocColor (dpy, cmap, &black)) abort ();
515 black_pixel = black.pixel;
522 XFreeColors (dpy, cmap, &black_pixel, 1, 0);
523 XFreeColormap (dpy, cmap);
525 cmap = DefaultColormapOfScreen (screen);
526 black_pixel = BlackPixelOfScreen (screen);
531 XFreeColormap (dpy, cmap2);
537 cmap2 = copy_colormap (dpy, cmap, 0);
539 fade_p = unfade_p = 0;
542 attrmask = (CWOverrideRedirect | CWEventMask | CWBackingStore | CWColormap |
543 CWBackPixel | CWBackingPixel | CWBorderPixel);
544 attrs.override_redirect = True;
546 /* When use_mit_saver_extension or use_sgi_saver_extension is true, we won't
547 actually be reading these events during normal operation; but we still
548 need to see Button events for demo-mode to work properly.
550 attrs.event_mask = (KeyPressMask | KeyReleaseMask |
551 ButtonPressMask | ButtonReleaseMask |
554 attrs.backing_store = NotUseful;
555 attrs.colormap = cmap;
556 attrs.background_pixel = black_pixel;
557 attrs.backing_pixel = black_pixel;
558 attrs.border_pixel = black_pixel;
561 if (demo_mode_p || lock_p) width = width / 2; /* #### */
564 if (screensaver_window || !verbose_p)
566 else if (visual == DefaultVisualOfScreen (screen))
568 fprintf (stderr, "%s: using default visual ", progname);
569 describe_visual (stderr, dpy, visual);
573 fprintf (stderr, "%s: using visual: ", progname);
574 describe_visual (stderr, dpy, visual);
575 fprintf (stderr, "%s: default visual: ", progname);
576 describe_visual (stderr, dpy, DefaultVisualOfScreen (screen));
579 #ifdef HAVE_MIT_SAVER_EXTENSION
580 if (use_mit_saver_extension)
582 XScreenSaverInfo *info;
583 Window root = RootWindowOfScreen (screen);
585 /* This call sets the server screensaver timeouts to what we think
586 they should be (based on the resources and args xscreensaver was
587 started with.) It's important that we do this to sync back up
588 with the server - if we have turned on prematurely, as by an
589 ACTIVATE ClientMessage, then the server may decide to activate
590 the screensaver while it's already active. That's ok for us,
591 since we would know to ignore that ScreenSaverActivate event,
592 but a side effect of this would be that the server would map its
593 saver window (which we then hide again right away) meaning that
594 the bits currently on the screen get blown away. Ugly. */
596 /* #### Ok, that doesn't work - when we tell the server that the
597 screensaver is "off" it sends us a Deactivate event, which is
598 sensible... but causes the saver to never come on. Hmm. */
599 disable_builtin_screensaver ();
603 /* #### The MIT-SCREEN-SAVER extension gives us access to the
604 window that the server itself uses for saving the screen.
605 However, using this window in any way, in particular, calling
606 XScreenSaverSetAttributes() as below, tends to make the X server
607 crash. So fuck it, let's try and get along without using it...
609 It's also inconvenient to use this window because it doesn't
610 always exist (though the ID is constant.) So to use this
611 window, we'd have to reimplement the ACTIVATE ClientMessage to
612 tell the *server* to tell *us* to turn on, to cause the window
613 to get created at the right time. Gag. */
614 XScreenSaverSetAttributes (dpy, root,
615 0, 0, width, height, 0,
616 visual_depth, InputOutput, visual,
621 info = XScreenSaverAllocInfo ();
622 XScreenSaverQueryInfo (dpy, root, info);
623 server_mit_saver_window = info->window;
624 if (! server_mit_saver_window) abort ();
627 #endif /* HAVE_MIT_SAVER_EXTENSION */
629 if (screensaver_window)
631 XWindowChanges changes;
632 unsigned int changesmask = CWX|CWY|CWWidth|CWHeight|CWBorderWidth;
635 changes.width = width;
636 changes.height = height;
637 changes.border_width = 0;
639 XConfigureWindow (dpy, screensaver_window, changesmask, &changes);
640 XChangeWindowAttributes (dpy, screensaver_window, attrmask, &attrs);
645 XCreateWindow (dpy, RootWindowOfScreen (screen), 0, 0, width, height,
646 0, visual_depth, InputOutput, visual, attrmask,
650 #ifdef HAVE_MIT_SAVER_EXTENSION
651 if (!use_mit_saver_extension ||
652 window_exists_p (dpy, screensaver_window))
653 /* When using the MIT-SCREEN-SAVER extension, the window pointed to
654 by screensaver_window only exists while the saver is active.
655 So we must be careful to only try and manipulate it while it
658 #endif /* HAVE_MIT_SAVER_EXTENSION */
660 class_hints.res_name = progname;
661 class_hints.res_class = progclass;
662 XSetClassHint (dpy, screensaver_window, &class_hints);
663 XStoreName (dpy, screensaver_window, "screensaver");
664 XChangeProperty (dpy, screensaver_window, XA_SCREENSAVER_VERSION,
665 XA_STRING, 8, PropModeReplace,
666 (unsigned char *) screensaver_version,
667 strlen (screensaver_version));
669 sprintf (id, "%d on host ", getpid ());
670 if (! XmuGetHostname (id + strlen (id), sizeof (id) - strlen (id) - 1))
672 XChangeProperty (dpy, screensaver_window, XA_SCREENSAVER_ID, XA_STRING,
673 8, PropModeReplace, (unsigned char *) id, strlen (id));
678 bit = XCreatePixmapFromBitmapData (dpy, screensaver_window, "\000",
680 BlackPixelOfScreen (screen),
681 BlackPixelOfScreen (screen), 1);
682 cursor = XCreatePixmapCursor (dpy, bit, bit, &black, &black, 0, 0);
683 XFreePixmap (dpy, bit);
686 XSetWindowBackground (dpy, screensaver_window, black_pixel);
688 XDefineCursor (dpy, screensaver_window, cursor);
690 XUndefineCursor (dpy, screensaver_window);
696 raise_window (inhibit_fade, between_hacks_p)
697 Bool inhibit_fade, between_hacks_p;
699 initialize_screensaver_window ();
701 if (fade_p && !inhibit_fade && !demo_mode_p)
704 Colormap current_map = (between_hacks_p
706 : DefaultColormapOfScreen (screen));
707 copy_colormap (dpy, current_map, cmap2);
708 if (verbose_p) fprintf (stderr, "%s: fading... ", progname);
710 /* grab and blacken mouse on the root window (saver not mapped yet) */
711 grabbed = grab_mouse (RootWindowOfScreen (screen));
712 /* fade what's on the screen to black */
713 XInstallColormap (dpy, cmap2);
714 fade_colormap (dpy, current_map, cmap2, fade_seconds, fade_ticks,
716 if (verbose_p) fprintf (stderr, "fading done.\n");
717 XClearWindow (dpy, screensaver_window);
718 XMapRaised (dpy, screensaver_window);
720 #ifdef HAVE_MIT_SAVER_EXTENSION
721 if (server_mit_saver_window &&
722 window_exists_p (dpy, server_mit_saver_window))
723 XUnmapWindow (dpy, server_mit_saver_window);
724 #endif /* HAVE_MIT_SAVER_EXTENSION */
726 /* Once the saver window is up, restore the colormap.
727 (The "black" pixels of the two colormaps are compatible.) */
728 XInstallColormap (dpy, cmap);
729 if (grabbed == GrabSuccess)
730 XUngrabPointer (dpy, CurrentTime);
735 XClearWindow (dpy, screensaver_window);
736 XMapRaised (dpy, screensaver_window);
737 #ifdef HAVE_MIT_SAVER_EXTENSION
738 if (server_mit_saver_window &&
739 window_exists_p (dpy, server_mit_saver_window))
740 XUnmapWindow (dpy, server_mit_saver_window);
741 #endif /* HAVE_MIT_SAVER_EXTENSION */
745 XInstallColormap (dpy, cmap);
749 /* Calls to XHPDisableReset and XHPEnableReset must be balanced,
750 or BadAccess errors occur. */
751 static Bool hp_locked_p = False;
758 store_vroot_property (screensaver_window, screensaver_window);
759 raise_window (False, False);
760 grab_keyboard_and_mouse ();
762 if (lock_p && !hp_locked_p)
763 XHPDisableReset (dpy); /* turn off C-Sh-Reset */
771 if (unfade_p && !demo_mode_p)
774 Colormap default_map = DefaultColormapOfScreen (screen);
775 blacken_colormap (dpy, cmap2);
776 if (verbose_p) fprintf (stderr, "%s: unfading... ", progname);
778 /* grab and blacken mouse on the root window. */
779 grabbed = grab_mouse (RootWindowOfScreen (screen));
780 XInstallColormap (dpy, cmap2);
781 XUnmapWindow (dpy, screensaver_window);
782 fade_colormap (dpy, default_map, cmap2, fade_seconds, fade_ticks,
784 XInstallColormap (dpy, default_map);
785 if (verbose_p) fprintf (stderr, "unfading done.\n");
786 if (grabbed == GrabSuccess)
787 XUngrabPointer (dpy, CurrentTime);
794 XClearWindow (dpy, screensaver_window); /* avoid technicolor */
795 XInstallColormap (dpy, DefaultColormapOfScreen (screen));
797 XUnmapWindow (dpy, screensaver_window);
801 /* If the focus window does has a non-default colormap, then install
802 that colormap as well. (On SGIs, this will cause both the root map
803 and the focus map to be installed simultaniously. It'd be nice to
804 pick up the other colormaps that had been installed, too; perhaps
805 XListInstalledColormaps could be used for that?)
810 XGetInputFocus (dpy, &focus, &revert_to);
811 if (focus && focus != PointerRoot && focus != None)
813 XWindowAttributes xgwa;
814 Colormap default_map = DefaultColormapOfScreen (screen);
816 XGetWindowAttributes (dpy, focus, &xgwa);
818 xgwa.colormap != default_map)
819 XInstallColormap (dpy, xgwa.colormap);
824 kill_xsetroot_data ();
825 ungrab_keyboard_and_mouse ();
826 restore_real_vroot ();
829 if (lock_p && hp_locked_p)
830 XHPEnableReset (dpy); /* turn C-Sh-Reset back on */