1 /* xscreensaver, Copyright (c) 1991-1993 Jamie Zawinski <jwz@lucid.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 /* ========================================================================
15 * First we wait until the keyboard and mouse become idle for the specified
16 * amount of time. We do this by periodically checking with the XIdle
19 * Then, we map a full screen black window.
21 * We place a __SWM_VROOT property on this window, so that newly-started
22 * clients will think that this window is a "virtual root" window.
24 * If there is an existing "virtual root" window (one that already had
25 * an __SWM_VROOT property) then we remove that property from that window.
26 * Otherwise, clients would see that window (the real virtual root) instead
27 * of ours (the impostor.)
29 * Then we pick a random program to run, and start it. Two assumptions
30 * are made about this program: that it has been specified with whatever
31 * command-line options are necessary to make it run on the root window;
32 * and that it has been compiled with vroot.h, so that it is able to find
33 * the root window when a virtual-root window manager (or this program) is
36 * Then, we wait for keyboard or mouse events to be generated on the window.
37 * When they are, we kill the inferior process, unmap the window, and restore
38 * the __SWM_VROOT property to the real virtual root window if there was one.
40 * While we are waiting, we also set up timers so that, after a certain
41 * amount of time has passed, we can start a different screenhack. We do
42 * this by killing the running child process with SIGTERM, and then starting
43 * a new one in the same way.
45 * If there was a real virtual root, meaning that we removed the __SWM_VROOT
46 * property from it, meaning we must (absolutely must) restore it before we
47 * exit, then we set up signal handlers for most signals (SIGINT, SIGTERM,
48 * etc.) that do this. Most Xlib and Xt routines are not reentrant, so it
49 * is not generally safe to call them from signal handlers; however, this
50 * program spends most of its time waiting, so the window of opportunity
51 * when code could be called reentrantly is fairly small; and also, the worst
52 * that could happen is that the call would fail. If we've gotten one of
53 * these signals, then we're on our way out anyway. If we didn't restore the
54 * __SWM_VROOT property, that would be very bad, so it's worth a shot. Note
55 * that this means that, if you're using a virtual-root window manager, you
56 * can really fuck up the world by killing this process with "kill -9".
58 * This program accepts ClientMessages of type SCREENSAVER; these messages
59 * may contain the atom ACTIVATE or DEACTIVATE, meaning to turn the
60 * screensaver on or off now, regardless of the idleness of the user,
61 * and a few other things. The included "xscreensaver_command" program
62 * sends these messsages.
64 * If we don't have the XIdle extension, then we do the XAutoLock trick:
65 * notice every window that gets created, and wait 30 seconds or so until
66 * its creating process has settled down, and then select KeyPress events on
67 * those windows which already select for KeyPress events. It's important
68 * that we not select KeyPress on windows which don't select them, because
69 * that would interfere with event propagation. This will break if any
70 * program changes its event mask to contain KeyRelease or PointerMotion
71 * more than 30 seconds after creating the window, but that's probably
74 * The reason that we can't select KeyPresses on windows that don't have
75 * them already is that, when dispatching a KeyPress event, X finds the
76 * lowest (leafmost) window in the hierarchy on which *any* client selects
77 * for KeyPress, and sends the event to that window. This means that if a
78 * client had a window with subwindows, and expected to receive KeyPress
79 * events on the parent window instead of the subwindows, then that client
80 * would malfunction if some other client selected KeyPress events on the
81 * subwindows. It is an incredible misdesign that one client can make
82 * another client malfunction in this way.
84 * To detect mouse motion, we periodically wake up and poll the mouse
85 * position and button/modifier state, and notice when something has
86 * changed. We make this check every five seconds by default, and since the
87 * screensaver timeout has a granularity of one minute, this makes the
88 * chance of a false positive very small. We could detect mouse motion in
89 * the same way as keyboard activity, but that would suffer from the same
90 * "client changing event mask" problem that the KeyPress events hack does.
91 * I think polling is more reliable.
93 * None of this crap happens if we're using the XIdle extension, so install
94 * it if the description above sounds just too flaky to live. It is, but
95 * those are your choices.
98 * - Have a second terminal handy.
99 * - Be careful where you set your breakpoints, you don't want this to
100 * stop under the debugger with the keyboard grabbed or the blackout
102 * - you probably can't set breakpoints in functions that are called on
103 * the other side of a call to fork() -- if your clients are dying
104 * with signal 5, Trace/BPT Trap, you're losing in this way.
105 * - If you aren't using XIdle, don't leave this stopped under the
106 * debugger for very long, or the X input buffer will get huge because
107 * of the keypress events it's selecting for. This can make your X
108 * server wedge with "no more input buffers."
110 * ========================================================================
119 #include <X11/Xlib.h>
120 #include <X11/Xatom.h>
121 #include <X11/Intrinsic.h>
125 #include <X11/extensions/xidle.h>
128 #include "xscreensaver.h"
130 #if defined(SVR4) || defined(SYSV)
131 # define srandom(i) srand((unsigned int)(i))
133 extern void srandom P((int)); /* srand() is in stdlib.h... */
136 extern char *get_string_resource P((char *, char *));
137 extern Bool get_boolean_resource P((char *, char *));
138 extern int get_integer_resource P((char *, char *));
139 extern unsigned int get_minutes_resource P((char *, char *));
140 extern unsigned int get_seconds_resource P((char *, char *));
142 extern Visual *get_visual_resource P((Display *, char *, char *));
143 extern int get_visual_depth P((Display *, Visual *));
145 extern void notice_events_timer P((XtPointer closure, void *timer));
146 extern void cycle_timer P((void *junk1, XtPointer junk2));
147 extern void activate_lock_timer P((void *junk1, XtPointer junk2));
148 extern void sleep_until_idle P((Bool until_idle_p));
150 extern void ensure_no_screensaver_running P((void));
151 extern void initialize_screensaver_window P((void));
152 extern void disable_builtin_screensaver P((void));
154 extern void hack_environment P((void));
155 extern void grab_keyboard_and_mouse P((void));
156 extern void ungrab_keyboard_and_mouse P((void));
158 extern void save_argv P((int argc, char **argv));
161 char *screensaver_version;
173 Widget toplevel_shell;
179 extern Time passwd_timeout;
180 extern Time pointer_timeout;
181 extern Time notice_events_timeout;
182 extern XtIntervalId lock_id, cycle_id;
186 Bool lock_p, locked_p;
188 extern char **screenhacks;
189 extern int screenhacks_count;
191 extern int nice_inferior;
192 extern Window screensaver_window;
193 extern Cursor cursor;
194 extern Colormap cmap, cmap2;
195 extern Bool fade_p, unfade_p;
196 extern int fade_seconds, fade_ticks;
197 extern Bool install_cmap_p;
198 extern Bool locking_disabled_p;
199 extern Bool demo_mode_p;
200 extern Bool dbox_up_p;
201 extern int next_mode_p;
203 static time_t initial_delay;
205 extern Atom XA_VROOT, XA_XSETROOT_ID;
206 extern Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
208 static Atom XA_SCREENSAVER;
209 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
210 static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_LOCK;
212 #ifdef NO_MOTIF /* kludge */
213 Bool demo_mode_p = 0;
215 Time passwd_timeout = 0;
220 # define demo_mode() abort()
222 extern void demo_mode P((void));
225 static XrmOptionDescRec options [] = {
226 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
227 { "-idelay", ".initialDelay",XrmoptionSepArg, 0 },
228 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
229 { "-visual", ".visual", XrmoptionSepArg, 0 },
230 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
231 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
232 { "-silent", ".verbose", XrmoptionNoArg, "off" },
233 { "-xidle", ".xidle", XrmoptionNoArg, "on" },
234 { "-no-xidle", ".xidle", XrmoptionNoArg, "off" },
235 { "-lock", ".lock", XrmoptionNoArg, "on" },
236 { "-no-lock", ".lock", XrmoptionNoArg, "off" }
239 static char *defaults[] = {
240 #include "XScreenSaver.ad.h"
248 xscreensaver %s, copyright (c) 1991-1993 by Jamie Zawinski <jwz@lucid.com>.\n\
249 The standard Xt command-line options are accepted; other options include:\n\
251 -timeout <minutes> when the screensaver should activate\n\
252 -cycle <minutes> how long to let each hack run\n\
253 -idelay <seconds> how long to sleep before startup\n\
254 -demo enter interactive demo mode on startup\n\
257 -xidle use the XIdle server extension\n\
259 -lock require a password before deactivating\n\
261 -lock-timeout <minutes> grace period before locking; default 0\n\
262 -help this message\n\
264 Use the `xscreensaver-command' program to control a running screensaver.\n\
266 The *programs, *colorPrograms, and *monoPrograms resources control which\n\
267 graphics demos will be launched by the screensaver. See the man page for\n\
269 screensaver_version);
272 printf("Support for locking was not enabled at compile-time.\n");
275 printf("Support for demo mode was not enabled at compile-time.\n");
278 printf("Support for the XIdle extension was not enabled at compile-time.\n");
290 int i, hacks_size = 10;
292 data[0] = get_string_resource ("programs", "Programs");
293 data[1] = ((CellsOfScreen (screen) <= 2)
294 ? get_string_resource ("monoPrograms", "MonoPrograms")
295 : get_string_resource ("colorPrograms", "ColorPrograms"));
297 if (! data[0]) data[0] = data[1], data[1] = 0;
299 screenhacks = (char **) malloc (sizeof (char *) * hacks_size);
300 screenhacks_count = 0;
302 for (i = 0; data[i]; i++)
306 int size = strlen (d);
310 if (d[j] == ' ' || d[j] == '\t' || d[j] == '\n' || d[j] == 0)
315 if (hacks_size <= screenhacks_count)
316 screenhacks = (char **) realloc (screenhacks,
317 (hacks_size = hacks_size * 2) *
319 screenhacks [screenhacks_count++] = d + j;
320 while (d[j] != 0 && d[j] != '\n')
323 while (j > start && (d[j-1] == ' ' || d[j-1] == '\t'))
330 /* shrink all whitespace to one space, for the benefit of the "demo"
331 mode display. We only do this when we can easily tell that the
332 whitespace is not significant (no shell metachars).
334 for (i = 0; i < screenhacks_count; i++)
336 char *s = screenhacks [i];
340 for (j = 0; j < L; j++)
344 case '\'': case '"': case '`': case '\\':
350 for (s2 = s+j+1; *s2 == ' ' || *s2 == '\t'; s2++)
353 for (s2 = s + j + 1; *s2; s2++)
362 if (screenhacks_count)
364 /* Shrink down the screenhacks array to be only as big as it needs to.
365 This doesn't really matter at all. */
366 screenhacks = (char **)
367 realloc (screenhacks, ((screenhacks_count + 1) * sizeof(char *)));
368 screenhacks [screenhacks_count] = 0;
381 visual = get_visual_resource (dpy, "visual", "Visual");
382 timeout = 1000 * get_minutes_resource ("timeout", "Time");
383 cycle = 1000 * get_minutes_resource ("cycle", "Time");
384 lock_timeout = 1000 * get_minutes_resource ("lockTimeout", "Time");
385 nice_inferior = get_integer_resource ("nice", "Nice");
386 verbose_p = get_boolean_resource ("verbose", "Boolean");
387 lock_p = get_boolean_resource ("lock", "Boolean");
388 install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
389 fade_p = get_boolean_resource ("fade", "Boolean");
390 unfade_p = get_boolean_resource ("unfade", "Boolean");
391 fade_seconds = get_seconds_resource ("fadeSeconds", "Time");
392 fade_ticks = get_integer_resource ("fadeTicks", "Integer");
393 shell = get_string_resource ("bourneShell", "BourneShell");
394 initial_delay = get_seconds_resource ("initialDelay", "Time");
395 passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
396 pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
397 notice_events_timeout = 1000 * get_seconds_resource ("windowCreationTimeout",
399 if (timeout < 10000) timeout = 10000;
400 if (cycle < 2000) cycle = 2000;
401 if (passwd_timeout == 0) passwd_timeout = 30000;
402 if (pointer_timeout == 0) pointer_timeout = 5000;
403 if (notice_events_timeout == 0) notice_events_timeout = 10000;
404 if (fade_seconds == 0 || fade_ticks == 0) fade_p = False;
405 if (! fade_p) unfade_p = False;
407 visual_depth = get_visual_depth (dpy, visual);
409 if (visual_depth <= 1 || CellsOfScreen (screen) <= 2)
410 install_cmap_p = False;
413 locking_disabled_p = True;
417 fprintf (stderr, "%s: %snot compiled with support for locking.\n",
418 progname, (verbose_p ? "## " : ""));
420 #else /* ! NO_LOCKING */
421 if (lock_p && locking_disabled_p)
423 fprintf (stderr, "%s: %slocking is disabled.\n", progname,
424 (verbose_p ? "## " : ""));
427 #endif /* ! NO_LOCKING */
429 /* don't set use_xidle unless it is explicitly specified */
430 if (get_string_resource ("xidle", "Boolean"))
431 use_xidle = get_boolean_resource ("xidle", "Boolean");
433 #ifdef HAVE_XIDLE /* pick a default */
444 long now = time ((time_t *) 0);
445 char *str = (char *) ctime (&now);
446 char *nl = (char *) strchr (str, '\n');
447 if (nl) *nl = 0; /* take off that dang newline */
453 # define hack_uid_warn()
454 #else /* !NO_SETUID */
455 extern void hack_uid P((void));
456 extern void hack_uid_warn P((void));
457 #endif /* NO_SETUID */
461 extern Bool unlock_p P((Widget));
462 extern Bool lock_init P((void));
465 static void initialize ();
466 static void main_loop ();
467 static void initialize ();
474 initialize (argc, argv);
480 initialize_connection (argc, argv)
484 toplevel_shell = XtAppInitialize (&app, progclass,
485 options, XtNumber (options),
486 &argc, argv, defaults, 0, 0);
488 dpy = XtDisplay (toplevel_shell);
489 screen = XtScreen (toplevel_shell);
490 db = XtDatabase (dpy);
491 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
493 if (argc == 2 && !strcmp (argv[1], "-help"))
497 fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
503 XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
504 XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
505 XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION", False);
506 XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
507 XA_XSETROOT_ID = XInternAtom (dpy, "_XSETROOT_ID", False);
508 XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
509 XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
510 XA_RESTART = XInternAtom (dpy, "RESTART", False);
511 XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
512 XA_NEXT = XInternAtom (dpy, "NEXT", False);
513 XA_PREV = XInternAtom (dpy, "PREV", False);
514 XA_EXIT = XInternAtom (dpy, "EXIT", False);
515 XA_DEMO = XInternAtom (dpy, "DEMO", False);
516 XA_LOCK = XInternAtom (dpy, "LOCK", False);
519 extern void init_sigchld P((void));
522 initialize (argc, argv)
526 Bool initial_demo_mode_p = False;
527 screensaver_version = (char *) malloc (5);
528 memcpy (screensaver_version, screensaver_id + 17, 4);
529 screensaver_version [4] = 0;
530 progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
533 locking_disabled_p = True;
535 locking_disabled_p = False;
536 if (! lock_init ()) /* before hack_uid() for proper permissions */
537 locking_disabled_p = True;
541 progclass = "XScreenSaver";
543 /* remove -demo switch before saving argv */
546 for (i = 1; i < argc; i++)
547 while (!strcmp ("-demo", argv [i]))
550 initial_demo_mode_p = True;
551 for (j = i; j < argc; j++)
552 argv [j] = argv [j+1];
555 if (argc <= i) break;
558 save_argv (argc, argv);
559 initialize_connection (argc, argv);
561 ensure_no_screensaver_running ();
562 demo_mode_p = initial_demo_mode_p;
563 screensaver_window = 0;
565 initialize_screensaver_window ();
566 srandom ((int) time ((time_t *) 0));
573 int first_event, first_error;
574 if (! XidleQueryExtension (dpy, &first_event, &first_error))
577 "%s: display %s does not support the XIdle extension.\n",
578 progname, DisplayString (dpy));
582 fprintf (stderr, "%s: not compiled with support for XIdle.\n",
592 %s %s, copyright (c) 1991-1993 by Jamie Zawinski <jwz@lucid.com>.\n\
593 pid = %d.\n", progname, screensaver_version, getpid ());
595 disable_builtin_screensaver ();
597 if (initial_demo_mode_p)
598 /* If the user wants demo mode, don't wait around before doing it. */
609 printf ("%s: waiting for %d second%s...", progname,
610 initial_delay, (initial_delay == 1 ? "" : "s"));
613 sleep (initial_delay);
619 printf ("%s: selecting events on extant windows...", progname);
622 notice_events_timer ((XtPointer)
623 RootWindowOfScreen (XtScreen (toplevel_shell)),
631 extern void suspend_screenhack P((Bool suspend_p));
639 sleep_until_idle (True);
646 printf ("%s: user is idle; waking up at %s.\n", progname,
649 spawn_screenhack (True);
651 cycle_id = XtAppAddTimeOut (app, cycle, cycle_timer, 0);
654 if (lock_p && lock_timeout == 0)
656 if (lock_p && !locked_p)
657 /* locked_p might be true already because of ClientMessage */
658 lock_id = XtAppAddTimeOut (app,lock_timeout,activate_lock_timer,0);
663 sleep_until_idle (False); /* until not idle */
669 if (locking_disabled_p) abort ();
671 ungrab_keyboard_and_mouse ();
672 suspend_screenhack (True);
673 XUndefineCursor (dpy, screensaver_window);
675 printf ("%s: prompting for password.\n", progname);
676 val = unlock_p (toplevel_shell);
677 if (verbose_p && val == False)
678 printf ("%s: password incorrect!\n", progname);
680 XDefineCursor (dpy, screensaver_window, cursor);
681 suspend_screenhack (False);
682 grab_keyboard_and_mouse ();
692 XtRemoveTimeOut (cycle_id);
698 XtRemoveTimeOut (lock_id);
703 printf ("%s: user is active; going to sleep at %s.\n", progname,
712 handle_clientmessage (event, until_idle_p)
717 if (event->xclient.message_type != XA_SCREENSAVER)
720 str = XGetAtomName (dpy, event->xclient.message_type);
721 fprintf (stderr, "%s: %sunrecognised ClientMessage type %s received\n",
722 progname, (verbose_p ? "## " : ""),
723 (str ? str : "(null)"));
724 if (str) XFree (str);
726 if (event->xclient.format != 32)
728 fprintf (stderr, "%s: %sClientMessage of format %d received, not 32\n",
729 progname, (verbose_p ? "## " : ""), event->xclient.format);
731 type = event->xclient.data.l[0];
732 if (type == XA_ACTIVATE)
737 printf ("%s: ACTIVATE ClientMessage received.\n", progname);
741 "%s: %sClientMessage ACTIVATE received while already active.\n",
742 progname, (verbose_p ? "## " : ""));
744 else if (type == XA_DEACTIVATE)
749 printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
753 "%s: %sClientMessage DEACTIVATE received while inactive.\n",
754 progname, (verbose_p ? "## " : ""));
756 else if (type == XA_CYCLE)
761 printf ("%s: CYCLE ClientMessage received.\n", progname);
763 XtRemoveTimeOut (cycle_id);
769 "%s: %sClientMessage CYCLE received while inactive.\n",
770 progname, (verbose_p ? "## " : ""));
772 else if (type == XA_NEXT || type == XA_PREV)
775 printf ("%s: %s ClientMessage received.\n", progname,
776 (type == XA_NEXT ? "NEXT" : "PREV"));
777 next_mode_p = 1 + (type == XA_PREV);
782 XtRemoveTimeOut (cycle_id);
789 else if (type == XA_EXIT)
791 /* Ignore EXIT message if the screen is locked. */
792 if (until_idle_p || !locked_p)
795 printf ("%s: EXIT ClientMessage received.\n", progname);
805 fprintf (stderr, "%s: %sEXIT ClientMessage received while locked.\n",
806 progname, (verbose_p ? "## " : ""));
808 else if (type == XA_RESTART)
810 /* The RESTART message works whether the screensaver is active or not,
811 unless the screen is locked, in which case it doesn't work.
813 if (until_idle_p || !locked_p)
816 printf ("%s: RESTART ClientMessage received.\n", progname);
826 fprintf(stderr, "%s: %sRESTART ClientMessage received while locked.\n",
827 progname, (verbose_p ? "## " : ""));
829 else if (type == XA_DEMO)
833 "%s: %snot compiled with support for DEMO mode\n",
834 progname, (verbose_p ? "## " : ""));
839 printf ("%s: DEMO ClientMessage received.\n", progname);
844 "%s: %sDEMO ClientMessage received while active.\n",
845 progname, (verbose_p ? "## " : ""));
848 else if (type == XA_LOCK)
851 fprintf (stderr, "%s: %snot compiled with support for LOCK mode\n",
852 progname, (verbose_p ? "## " : ""));
854 if (locking_disabled_p)
856 "%s: %sLOCK ClientMessage received, but locking is disabled.\n",
857 progname, (verbose_p ? "## " : ""));
860 "%s: %sLOCK ClientMessage received while already locked.\n",
861 progname, (verbose_p ? "## " : ""));
866 printf ("%s: LOCK ClientMessage received;%s locking.\n",
867 progname, until_idle_p ? " activating and" : "");
869 if (lock_id) /* we're doing it now, so lose the timeout */
871 XtRemoveTimeOut (lock_id);
883 str = XGetAtomName (dpy, type);
886 "%s: %sunrecognised screensaver ClientMessage %s received\n",
887 progname, (verbose_p ? "## " : ""), str);
890 "%s: %sunrecognised screensaver ClientMessage 0x%x received\n",
891 progname, (verbose_p ? "## " : ""),
892 event->xclient.data.l[0]);
893 if (str) XFree (str);