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.
97 * A third idle-detection option could be implement (but is not): when running
98 * on the console display ($DISPLAY is `localhost`:0) and we're on a machine
99 * where /dev/tty and /dev/mouse have reasonable last-modification times, we
100 * could just stat those. But the incremental benefit of implementing this
101 * is really small, so forget I said anything.
104 * - Have a second terminal handy.
105 * - Be careful where you set your breakpoints, you don't want this to
106 * stop under the debugger with the keyboard grabbed or the blackout
108 * - you probably can't set breakpoints in functions that are called on
109 * the other side of a call to fork() -- if your clients are dying
110 * with signal 5, Trace/BPT Trap, you're losing in this way.
111 * - If you aren't using XIdle, don't leave this stopped under the
112 * debugger for very long, or the X input buffer will get huge because
113 * of the keypress events it's selecting for. This can make your X
114 * server wedge with "no more input buffers."
116 * ========================================================================
125 #include <X11/Xlib.h>
126 #include <X11/Xatom.h>
127 #include <X11/Intrinsic.h>
131 #include <X11/extensions/xidle.h>
134 #include "xscreensaver.h"
136 #if defined(SVR4) || defined(SYSV)
137 # define srandom(i) srand((unsigned int)(i))
139 extern void srandom P((int)); /* srand() is in stdlib.h... */
142 extern char *get_string_resource P((char *, char *));
143 extern Bool get_boolean_resource P((char *, char *));
144 extern int get_integer_resource P((char *, char *));
145 extern unsigned int get_minutes_resource P((char *, char *));
146 extern unsigned int get_seconds_resource P((char *, char *));
148 extern Visual *get_visual_resource P((Display *, char *, char *));
149 extern int get_visual_depth P((Display *, Visual *));
151 extern void notice_events_timer P((XtPointer closure, void *timer));
152 extern void cycle_timer P((void *junk1, XtPointer junk2));
153 extern void activate_lock_timer P((void *junk1, XtPointer junk2));
154 extern void sleep_until_idle P((Bool until_idle_p));
156 extern void ensure_no_screensaver_running P((void));
157 extern void initialize_screensaver_window P((void));
158 extern void disable_builtin_screensaver P((void));
160 extern void hack_environment P((void));
161 extern void grab_keyboard_and_mouse P((void));
162 extern void ungrab_keyboard_and_mouse P((void));
164 extern void save_argv P((int argc, char **argv));
167 char *screensaver_version;
179 Widget toplevel_shell;
185 extern Time passwd_timeout;
186 extern Time pointer_timeout;
187 extern Time notice_events_timeout;
188 extern XtIntervalId lock_id, cycle_id;
192 Bool lock_p, locked_p;
194 extern char **screenhacks;
195 extern int screenhacks_count;
197 extern int nice_inferior;
198 extern Window screensaver_window;
199 extern Cursor cursor;
200 extern Colormap cmap, cmap2;
201 extern Bool fade_p, unfade_p;
202 extern int fade_seconds, fade_ticks;
203 extern Bool install_cmap_p;
204 extern Bool locking_disabled_p;
205 extern char *nolock_reason;
206 extern Bool demo_mode_p;
207 extern Bool dbox_up_p;
208 extern int next_mode_p;
210 static time_t initial_delay;
212 extern Atom XA_VROOT, XA_XSETROOT_ID;
213 extern Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
215 static Atom XA_SCREENSAVER;
216 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
217 static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_LOCK;
219 #ifdef NO_MOTIF /* kludge */
220 Bool demo_mode_p = 0;
222 Time passwd_timeout = 0;
227 # define demo_mode() abort()
229 extern void demo_mode P((void));
232 static XrmOptionDescRec options [] = {
233 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
234 { "-idelay", ".initialDelay",XrmoptionSepArg, 0 },
235 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
236 { "-visual", ".visual", XrmoptionSepArg, 0 },
237 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
238 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
239 { "-silent", ".verbose", XrmoptionNoArg, "off" },
240 { "-xidle", ".xidle", XrmoptionNoArg, "on" },
241 { "-no-xidle", ".xidle", XrmoptionNoArg, "off" },
242 { "-lock", ".lock", XrmoptionNoArg, "on" },
243 { "-no-lock", ".lock", XrmoptionNoArg, "off" }
246 static char *defaults[] = {
247 #include "XScreenSaver.ad.h"
255 xscreensaver %s, copyright (c) 1991-1993 by Jamie Zawinski <jwz@lucid.com>.\n\
256 The standard Xt command-line options are accepted; other options include:\n\
258 -timeout <minutes> when the screensaver should activate\n\
259 -cycle <minutes> how long to let each hack run\n\
260 -idelay <seconds> how long to sleep before startup\n\
261 -demo enter interactive demo mode on startup\n\
264 -xidle use the XIdle server extension\n\
266 -lock require a password before deactivating\n\
268 -lock-timeout <minutes> grace period before locking; default 0\n\
269 -help this message\n\
271 Use the `xscreensaver-command' program to control a running screensaver.\n\
273 The *programs, *colorPrograms, and *monoPrograms resources control which\n\
274 graphics demos will be launched by the screensaver. See the man page for\n\
276 screensaver_version);
279 printf("Support for locking was not enabled at compile-time.\n");
282 printf("Support for demo mode was not enabled at compile-time.\n");
285 printf("Support for the XIdle extension was not enabled at compile-time.\n");
297 int i, hacks_size = 10;
299 data[0] = get_string_resource ("programs", "Programs");
300 data[1] = ((CellsOfScreen (screen) <= 2)
301 ? get_string_resource ("monoPrograms", "MonoPrograms")
302 : get_string_resource ("colorPrograms", "ColorPrograms"));
304 if (! data[0]) data[0] = data[1], data[1] = 0;
306 screenhacks = (char **) malloc (sizeof (char *) * hacks_size);
307 screenhacks_count = 0;
309 for (i = 0; data[i]; i++)
313 int size = strlen (d);
317 if (d[j] == ' ' || d[j] == '\t' || d[j] == '\n' || d[j] == 0)
322 if (hacks_size <= screenhacks_count)
323 screenhacks = (char **) realloc (screenhacks,
324 (hacks_size = hacks_size * 2) *
326 screenhacks [screenhacks_count++] = d + j;
327 while (d[j] != 0 && d[j] != '\n')
330 while (j > start && (d[j-1] == ' ' || d[j-1] == '\t'))
337 /* shrink all whitespace to one space, for the benefit of the "demo"
338 mode display. We only do this when we can easily tell that the
339 whitespace is not significant (no shell metachars).
341 for (i = 0; i < screenhacks_count; i++)
343 char *s = screenhacks [i];
347 for (j = 0; j < L; j++)
351 case '\'': case '"': case '`': case '\\':
357 for (s2 = s+j+1; *s2 == ' ' || *s2 == '\t'; s2++)
360 for (s2 = s + j + 1; *s2; s2++)
369 if (screenhacks_count)
371 /* Shrink down the screenhacks array to be only as big as it needs to.
372 This doesn't really matter at all. */
373 screenhacks = (char **)
374 realloc (screenhacks, ((screenhacks_count + 1) * sizeof(char *)));
375 screenhacks [screenhacks_count] = 0;
388 visual = get_visual_resource (dpy, "visual", "Visual");
389 timeout = 1000 * get_minutes_resource ("timeout", "Time");
390 cycle = 1000 * get_minutes_resource ("cycle", "Time");
391 lock_timeout = 1000 * get_minutes_resource ("lockTimeout", "Time");
392 nice_inferior = get_integer_resource ("nice", "Nice");
393 verbose_p = get_boolean_resource ("verbose", "Boolean");
394 lock_p = get_boolean_resource ("lock", "Boolean");
395 install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
396 fade_p = get_boolean_resource ("fade", "Boolean");
397 unfade_p = get_boolean_resource ("unfade", "Boolean");
398 fade_seconds = get_seconds_resource ("fadeSeconds", "Time");
399 fade_ticks = get_integer_resource ("fadeTicks", "Integer");
400 shell = get_string_resource ("bourneShell", "BourneShell");
401 initial_delay = get_seconds_resource ("initialDelay", "Time");
402 passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
403 pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
404 notice_events_timeout = 1000 * get_seconds_resource ("windowCreationTimeout",
406 if (timeout < 10000) timeout = 10000;
407 if (cycle < 2000) cycle = 2000;
408 if (passwd_timeout == 0) passwd_timeout = 30000;
409 if (pointer_timeout == 0) pointer_timeout = 5000;
410 if (notice_events_timeout == 0) notice_events_timeout = 10000;
411 if (fade_seconds == 0 || fade_ticks == 0) fade_p = False;
412 if (! fade_p) unfade_p = False;
414 visual_depth = get_visual_depth (dpy, visual);
416 if (visual_depth <= 1 || CellsOfScreen (screen) <= 2)
417 install_cmap_p = False;
420 locking_disabled_p = True;
421 nolock_reason = "not compiled with locking support";
425 fprintf (stderr, "%s: %snot compiled with support for locking.\n",
426 progname, (verbose_p ? "## " : ""));
428 #else /* ! NO_LOCKING */
429 if (lock_p && locking_disabled_p)
431 fprintf (stderr, "%s: %slocking is disabled (%s).\n", progname,
432 (verbose_p ? "## " : ""), nolock_reason);
435 #endif /* ! NO_LOCKING */
437 /* don't set use_xidle unless it is explicitly specified */
438 if (get_string_resource ("xidle", "Boolean"))
439 use_xidle = get_boolean_resource ("xidle", "Boolean");
441 #ifdef HAVE_XIDLE /* pick a default */
452 long now = time ((time_t *) 0);
453 char *str = (char *) ctime (&now);
454 char *nl = (char *) strchr (str, '\n');
455 if (nl) *nl = 0; /* take off that dang newline */
461 # define hack_uid_warn()
462 #else /* !NO_SETUID */
463 extern void hack_uid P((void));
464 extern void hack_uid_warn P((void));
465 #endif /* NO_SETUID */
469 extern Bool unlock_p P((Widget));
470 extern Bool lock_init P((void));
473 static void initialize ();
474 static void main_loop ();
475 static void initialize ();
482 initialize (argc, argv);
488 initialize_connection (argc, argv)
492 toplevel_shell = XtAppInitialize (&app, progclass,
493 options, XtNumber (options),
494 &argc, argv, defaults, 0, 0);
496 dpy = XtDisplay (toplevel_shell);
497 screen = XtScreen (toplevel_shell);
498 db = XtDatabase (dpy);
499 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
501 if (argc == 2 && !strcmp (argv[1], "-help"))
505 fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
511 XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
512 XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
513 XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION", False);
514 XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
515 XA_XSETROOT_ID = XInternAtom (dpy, "_XSETROOT_ID", False);
516 XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
517 XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
518 XA_RESTART = XInternAtom (dpy, "RESTART", False);
519 XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
520 XA_NEXT = XInternAtom (dpy, "NEXT", False);
521 XA_PREV = XInternAtom (dpy, "PREV", False);
522 XA_EXIT = XInternAtom (dpy, "EXIT", False);
523 XA_DEMO = XInternAtom (dpy, "DEMO", False);
524 XA_LOCK = XInternAtom (dpy, "LOCK", False);
527 extern void init_sigchld P((void));
530 initialize (argc, argv)
534 Bool initial_demo_mode_p = False;
535 screensaver_version = (char *) malloc (5);
536 memcpy (screensaver_version, screensaver_id + 17, 4);
537 screensaver_version [4] = 0;
538 progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
541 locking_disabled_p = True;
542 nolock_reason = "not compiled with locking support";
544 locking_disabled_p = False;
545 if (! lock_init ()) /* before hack_uid() for proper permissions */
547 locking_disabled_p = True;
548 nolock_reason = "error getting password";
553 progclass = "XScreenSaver";
555 /* remove -demo switch before saving argv */
558 for (i = 1; i < argc; i++)
559 while (!strcmp ("-demo", argv [i]))
562 initial_demo_mode_p = True;
563 for (j = i; j < argc; j++)
564 argv [j] = argv [j+1];
567 if (argc <= i) break;
570 save_argv (argc, argv);
571 initialize_connection (argc, argv);
573 ensure_no_screensaver_running ();
574 demo_mode_p = initial_demo_mode_p;
575 screensaver_window = 0;
577 initialize_screensaver_window ();
578 srandom ((int) time ((time_t *) 0));
585 int first_event, first_error;
586 if (! XidleQueryExtension (dpy, &first_event, &first_error))
589 "%s: display %s does not support the XIdle extension.\n",
590 progname, DisplayString (dpy));
594 fprintf (stderr, "%s: not compiled with support for XIdle.\n",
604 %s %s, copyright (c) 1991-1993 by Jamie Zawinski <jwz@lucid.com>.\n\
605 pid = %d.\n", progname, screensaver_version, getpid ());
607 disable_builtin_screensaver ();
609 if (initial_demo_mode_p)
610 /* If the user wants demo mode, don't wait around before doing it. */
621 printf ("%s: waiting for %d second%s...", progname,
622 initial_delay, (initial_delay == 1 ? "" : "s"));
625 sleep (initial_delay);
631 printf ("%s: selecting events on extant windows...", progname);
634 notice_events_timer ((XtPointer)
635 RootWindowOfScreen (XtScreen (toplevel_shell)),
643 extern void suspend_screenhack P((Bool suspend_p));
651 sleep_until_idle (True);
658 printf ("%s: user is idle; waking up at %s.\n", progname,
661 spawn_screenhack (True);
663 cycle_id = XtAppAddTimeOut (app, cycle, cycle_timer, 0);
666 if (lock_p && lock_timeout == 0)
668 if (lock_p && !locked_p)
669 /* locked_p might be true already because of ClientMessage */
670 lock_id = XtAppAddTimeOut (app,lock_timeout,activate_lock_timer,0);
675 sleep_until_idle (False); /* until not idle */
681 if (locking_disabled_p) abort ();
683 ungrab_keyboard_and_mouse ();
684 suspend_screenhack (True);
685 XUndefineCursor (dpy, screensaver_window);
687 printf ("%s: prompting for password.\n", progname);
688 val = unlock_p (toplevel_shell);
689 if (verbose_p && val == False)
690 printf ("%s: password incorrect!\n", progname);
692 XDefineCursor (dpy, screensaver_window, cursor);
693 suspend_screenhack (False);
694 grab_keyboard_and_mouse ();
704 XtRemoveTimeOut (cycle_id);
710 XtRemoveTimeOut (lock_id);
715 printf ("%s: user is active; going to sleep at %s.\n", progname,
724 handle_clientmessage (event, until_idle_p)
729 if (event->xclient.message_type != XA_SCREENSAVER)
732 str = XGetAtomName (dpy, event->xclient.message_type);
733 fprintf (stderr, "%s: %sunrecognised ClientMessage type %s received\n",
734 progname, (verbose_p ? "## " : ""),
735 (str ? str : "(null)"));
736 if (str) XFree (str);
738 if (event->xclient.format != 32)
740 fprintf (stderr, "%s: %sClientMessage of format %d received, not 32\n",
741 progname, (verbose_p ? "## " : ""), event->xclient.format);
743 type = event->xclient.data.l[0];
744 if (type == XA_ACTIVATE)
749 printf ("%s: ACTIVATE ClientMessage received.\n", progname);
753 "%s: %sClientMessage ACTIVATE received while already active.\n",
754 progname, (verbose_p ? "## " : ""));
756 else if (type == XA_DEACTIVATE)
761 printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
765 "%s: %sClientMessage DEACTIVATE received while inactive.\n",
766 progname, (verbose_p ? "## " : ""));
768 else if (type == XA_CYCLE)
773 printf ("%s: CYCLE ClientMessage received.\n", progname);
775 XtRemoveTimeOut (cycle_id);
781 "%s: %sClientMessage CYCLE received while inactive.\n",
782 progname, (verbose_p ? "## " : ""));
784 else if (type == XA_NEXT || type == XA_PREV)
787 printf ("%s: %s ClientMessage received.\n", progname,
788 (type == XA_NEXT ? "NEXT" : "PREV"));
789 next_mode_p = 1 + (type == XA_PREV);
794 XtRemoveTimeOut (cycle_id);
801 else if (type == XA_EXIT)
803 /* Ignore EXIT message if the screen is locked. */
804 if (until_idle_p || !locked_p)
807 printf ("%s: EXIT ClientMessage received.\n", progname);
817 fprintf (stderr, "%s: %sEXIT ClientMessage received while locked.\n",
818 progname, (verbose_p ? "## " : ""));
820 else if (type == XA_RESTART)
822 /* The RESTART message works whether the screensaver is active or not,
823 unless the screen is locked, in which case it doesn't work.
825 if (until_idle_p || !locked_p)
828 printf ("%s: RESTART ClientMessage received.\n", progname);
838 fprintf(stderr, "%s: %sRESTART ClientMessage received while locked.\n",
839 progname, (verbose_p ? "## " : ""));
841 else if (type == XA_DEMO)
845 "%s: %snot compiled with support for DEMO mode\n",
846 progname, (verbose_p ? "## " : ""));
851 printf ("%s: DEMO ClientMessage received.\n", progname);
856 "%s: %sDEMO ClientMessage received while active.\n",
857 progname, (verbose_p ? "## " : ""));
860 else if (type == XA_LOCK)
863 fprintf (stderr, "%s: %snot compiled with support for LOCK mode\n",
864 progname, (verbose_p ? "## " : ""));
866 if (locking_disabled_p)
868 "%s: %sLOCK ClientMessage received, but locking is disabled.\n",
869 progname, (verbose_p ? "## " : ""));
872 "%s: %sLOCK ClientMessage received while already locked.\n",
873 progname, (verbose_p ? "## " : ""));
878 printf ("%s: LOCK ClientMessage received;%s locking.\n",
879 progname, until_idle_p ? " activating and" : "");
881 if (lock_id) /* we're doing it now, so lose the timeout */
883 XtRemoveTimeOut (lock_id);
895 str = XGetAtomName (dpy, type);
898 "%s: %sunrecognised screensaver ClientMessage %s received\n",
899 progname, (verbose_p ? "## " : ""), str);
902 "%s: %sunrecognised screensaver ClientMessage 0x%x received\n",
903 progname, (verbose_p ? "## " : ""),
904 event->xclient.data.l[0]);
905 if (str) XFree (str);