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;
186 extern Time passwd_timeout;
188 extern Time pointer_timeout;
189 extern Time notice_events_timeout;
190 extern XtIntervalId lock_id, cycle_id;
194 Bool lock_p, locked_p;
196 extern char **screenhacks;
197 extern int screenhacks_count;
199 extern int nice_inferior;
200 extern Window screensaver_window;
201 extern Cursor cursor;
202 extern Colormap cmap, cmap2;
203 extern Bool fade_p, unfade_p;
204 extern int fade_seconds, fade_ticks;
205 extern Bool install_cmap_p;
206 extern Bool locking_disabled_p;
207 extern char *nolock_reason;
208 extern Bool demo_mode_p;
209 extern Bool dbox_up_p;
210 extern int next_mode_p;
212 static time_t initial_delay;
214 extern Atom XA_VROOT, XA_XSETROOT_ID;
215 extern Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;
217 static Atom XA_SCREENSAVER;
218 static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
219 static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_LOCK;
221 #ifdef NO_MOTIF /* kludge */
222 Bool demo_mode_p = 0;
225 Time passwd_timeout = 0;
231 # define demo_mode() abort()
233 extern void demo_mode P((void));
236 static XrmOptionDescRec options [] = {
237 { "-timeout", ".timeout", XrmoptionSepArg, 0 },
238 { "-idelay", ".initialDelay",XrmoptionSepArg, 0 },
239 { "-cycle", ".cycle", XrmoptionSepArg, 0 },
240 { "-visual", ".visualID", XrmoptionSepArg, 0 },
241 { "-lock-timeout", ".lockTimeout", XrmoptionSepArg, 0 },
242 { "-verbose", ".verbose", XrmoptionNoArg, "on" },
243 { "-silent", ".verbose", XrmoptionNoArg, "off" },
244 { "-xidle", ".xidle", XrmoptionNoArg, "on" },
245 { "-no-xidle", ".xidle", XrmoptionNoArg, "off" },
246 { "-lock", ".lock", XrmoptionNoArg, "on" },
247 { "-no-lock", ".lock", XrmoptionNoArg, "off" }
250 static char *defaults[] = {
251 #include "XScreenSaver.ad.h"
259 xscreensaver %s, copyright (c) 1991-1993 by Jamie Zawinski <jwz@lucid.com>.\n\
260 The standard Xt command-line options are accepted; other options include:\n\
262 -timeout <minutes> when the screensaver should activate\n\
263 -cycle <minutes> how long to let each hack run\n\
264 -idelay <seconds> how long to sleep before startup\n\
265 -demo enter interactive demo mode on startup\n\
268 -xidle use the XIdle server extension\n\
270 -lock require a password before deactivating\n\
272 -lock-timeout <minutes> grace period before locking; default 0\n\
273 -help this message\n\
275 Use the `xscreensaver-command' program to control a running screensaver.\n\
277 The *programs, *colorPrograms, and *monoPrograms resources control which\n\
278 graphics demos will be launched by the screensaver. See the man page for\n\
280 screensaver_version);
283 printf("Support for locking was not enabled at compile-time.\n");
286 printf("Support for demo mode was not enabled at compile-time.\n");
289 printf("Support for the XIdle extension was not enabled at compile-time.\n");
301 int i, hacks_size = 10;
303 data[0] = get_string_resource ("programs", "Programs");
304 data[1] = ((CellsOfScreen (screen) <= 2)
305 ? get_string_resource ("monoPrograms", "MonoPrograms")
306 : get_string_resource ("colorPrograms", "ColorPrograms"));
308 if (! data[0]) data[0] = data[1], data[1] = 0;
310 screenhacks = (char **) malloc (sizeof (char *) * hacks_size);
311 screenhacks_count = 0;
313 for (i = 0; data[i]; i++)
317 int size = strlen (d);
321 if (d[j] == ' ' || d[j] == '\t' || d[j] == '\n' || d[j] == 0)
326 if (hacks_size <= screenhacks_count)
327 screenhacks = (char **) realloc (screenhacks,
328 (hacks_size = hacks_size * 2) *
330 screenhacks [screenhacks_count++] = d + j;
331 while (d[j] != 0 && d[j] != '\n')
334 while (j > start && (d[j-1] == ' ' || d[j-1] == '\t'))
341 /* shrink all whitespace to one space, for the benefit of the "demo"
342 mode display. We only do this when we can easily tell that the
343 whitespace is not significant (no shell metachars).
345 for (i = 0; i < screenhacks_count; i++)
347 char *s = screenhacks [i];
351 for (j = 0; j < L; j++)
355 case '\'': case '"': case '`': case '\\':
361 for (s2 = s+j+1; *s2 == ' ' || *s2 == '\t'; s2++)
364 for (s2 = s + j + 1; *s2; s2++)
373 if (screenhacks_count)
375 /* Shrink down the screenhacks array to be only as big as it needs to.
376 This doesn't really matter at all. */
377 screenhacks = (char **)
378 realloc (screenhacks, ((screenhacks_count + 1) * sizeof(char *)));
379 screenhacks [screenhacks_count] = 0;
392 /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
393 visual = get_visual_resource (dpy, "visualID", "VisualID");
394 timeout = 1000 * get_minutes_resource ("timeout", "Time");
395 cycle = 1000 * get_minutes_resource ("cycle", "Time");
396 lock_timeout = 1000 * get_minutes_resource ("lockTimeout", "Time");
397 nice_inferior = get_integer_resource ("nice", "Nice");
398 verbose_p = get_boolean_resource ("verbose", "Boolean");
399 lock_p = get_boolean_resource ("lock", "Boolean");
400 install_cmap_p = get_boolean_resource ("installColormap", "Boolean");
401 fade_p = get_boolean_resource ("fade", "Boolean");
402 unfade_p = get_boolean_resource ("unfade", "Boolean");
403 fade_seconds = get_seconds_resource ("fadeSeconds", "Time");
404 fade_ticks = get_integer_resource ("fadeTicks", "Integer");
405 shell = get_string_resource ("bourneShell", "BourneShell");
406 initial_delay = get_seconds_resource ("initialDelay", "Time");
407 pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
408 notice_events_timeout = 1000 * get_seconds_resource ("windowCreationTimeout",
411 passwd_timeout = 1000 * get_seconds_resource ("passwdTimeout", "Time");
412 if (passwd_timeout == 0) passwd_timeout = 30000;
414 if (timeout < 10000) timeout = 10000;
415 if (cycle < 2000) cycle = 2000;
416 if (pointer_timeout == 0) pointer_timeout = 5000;
417 if (notice_events_timeout == 0) notice_events_timeout = 10000;
418 if (fade_seconds == 0 || fade_ticks == 0) fade_p = False;
419 if (! fade_p) unfade_p = False;
421 visual_depth = get_visual_depth (dpy, visual);
423 if (visual_depth <= 1 || CellsOfScreen (screen) <= 2)
424 install_cmap_p = False;
427 locking_disabled_p = True;
428 nolock_reason = "not compiled with locking support";
432 fprintf (stderr, "%s: %snot compiled with support for locking.\n",
433 progname, (verbose_p ? "## " : ""));
435 #else /* ! NO_LOCKING */
436 if (lock_p && locking_disabled_p)
438 fprintf (stderr, "%s: %slocking is disabled (%s).\n", progname,
439 (verbose_p ? "## " : ""), nolock_reason);
442 #endif /* ! NO_LOCKING */
444 /* don't set use_xidle unless it is explicitly specified */
445 if (get_string_resource ("xidle", "Boolean"))
446 use_xidle = get_boolean_resource ("xidle", "Boolean");
448 #ifdef HAVE_XIDLE /* pick a default */
459 long now = time ((time_t *) 0);
460 char *str = (char *) ctime (&now);
461 char *nl = (char *) strchr (str, '\n');
462 if (nl) *nl = 0; /* take off that dang newline */
468 # define hack_uid_warn()
469 #else /* !NO_SETUID */
470 extern void hack_uid P((void));
471 extern void hack_uid_warn P((void));
472 #endif /* NO_SETUID */
476 extern Bool unlock_p P((Widget));
477 extern Bool lock_init P((void));
480 static void initialize ();
481 static void main_loop ();
482 static void initialize ();
489 initialize (argc, argv);
495 initialize_connection (argc, argv)
499 toplevel_shell = XtAppInitialize (&app, progclass,
500 options, XtNumber (options),
501 &argc, argv, defaults, 0, 0);
503 dpy = XtDisplay (toplevel_shell);
504 screen = XtScreen (toplevel_shell);
505 db = XtDatabase (dpy);
506 XtGetApplicationNameAndClass (dpy, &progname, &progclass);
508 if (argc == 2 && !strcmp (argv[1], "-help"))
512 fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
518 XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
519 XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
520 XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION", False);
521 XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
522 XA_XSETROOT_ID = XInternAtom (dpy, "_XSETROOT_ID", False);
523 XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
524 XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
525 XA_RESTART = XInternAtom (dpy, "RESTART", False);
526 XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
527 XA_NEXT = XInternAtom (dpy, "NEXT", False);
528 XA_PREV = XInternAtom (dpy, "PREV", False);
529 XA_EXIT = XInternAtom (dpy, "EXIT", False);
530 XA_DEMO = XInternAtom (dpy, "DEMO", False);
531 XA_LOCK = XInternAtom (dpy, "LOCK", False);
534 extern void init_sigchld P((void));
537 initialize (argc, argv)
541 Bool initial_demo_mode_p = False;
542 screensaver_version = (char *) malloc (5);
543 memcpy (screensaver_version, screensaver_id + 17, 4);
544 screensaver_version [4] = 0;
545 progname = argv[0]; /* reset later; this is for the benefit of lock_init() */
548 locking_disabled_p = True;
549 nolock_reason = "not compiled with locking support";
551 locking_disabled_p = False;
552 if (! lock_init ()) /* before hack_uid() for proper permissions */
554 locking_disabled_p = True;
555 nolock_reason = "error getting password";
560 progclass = "XScreenSaver";
562 /* remove -demo switch before saving argv */
565 for (i = 1; i < argc; i++)
566 while (!strcmp ("-demo", argv [i]))
569 initial_demo_mode_p = True;
570 for (j = i; j < argc; j++)
571 argv [j] = argv [j+1];
574 if (argc <= i) break;
577 save_argv (argc, argv);
578 initialize_connection (argc, argv);
579 ensure_no_screensaver_running ();
583 %s %s, copyright (c) 1991-1993 by Jamie Zawinski <jwz@lucid.com>.\n\
584 pid = %d.\n", progname, screensaver_version, getpid ());
585 ensure_no_screensaver_running ();
587 demo_mode_p = initial_demo_mode_p;
588 screensaver_window = 0;
590 initialize_screensaver_window ();
591 srandom ((int) time ((time_t *) 0));
599 int first_event, first_error;
600 if (! XidleQueryExtension (dpy, &first_event, &first_error))
603 "%s: display %s does not support the XIdle extension.\n",
604 progname, DisplayString (dpy));
608 fprintf (stderr, "%s: not compiled with support for XIdle.\n",
616 disable_builtin_screensaver ();
618 if (initial_demo_mode_p)
619 /* If the user wants demo mode, don't wait around before doing it. */
630 printf ("%s: waiting for %d second%s...", progname,
631 initial_delay, (initial_delay == 1 ? "" : "s"));
634 sleep (initial_delay);
640 printf ("%s: selecting events on extant windows...", progname);
643 notice_events_timer ((XtPointer)
644 RootWindowOfScreen (XtScreen (toplevel_shell)),
652 extern void suspend_screenhack P((Bool suspend_p));
660 sleep_until_idle (True);
667 printf ("%s: user is idle; waking up at %s.\n", progname,
670 spawn_screenhack (True);
672 cycle_id = XtAppAddTimeOut (app, cycle, cycle_timer, 0);
675 if (lock_p && lock_timeout == 0)
677 if (lock_p && !locked_p)
678 /* locked_p might be true already because of ClientMessage */
679 lock_id = XtAppAddTimeOut (app,lock_timeout,activate_lock_timer,0);
684 sleep_until_idle (False); /* until not idle */
690 if (locking_disabled_p) abort ();
692 ungrab_keyboard_and_mouse ();
693 suspend_screenhack (True);
694 XUndefineCursor (dpy, screensaver_window);
696 printf ("%s: prompting for password.\n", progname);
697 val = unlock_p (toplevel_shell);
698 if (verbose_p && val == False)
699 printf ("%s: password incorrect!\n", progname);
701 XDefineCursor (dpy, screensaver_window, cursor);
702 suspend_screenhack (False);
703 grab_keyboard_and_mouse ();
713 XtRemoveTimeOut (cycle_id);
719 XtRemoveTimeOut (lock_id);
724 printf ("%s: user is active; going to sleep at %s.\n", progname,
733 handle_clientmessage (event, until_idle_p)
738 if (event->xclient.message_type != XA_SCREENSAVER)
741 str = XGetAtomName (dpy, event->xclient.message_type);
742 fprintf (stderr, "%s: %sunrecognised ClientMessage type %s received\n",
743 progname, (verbose_p ? "## " : ""),
744 (str ? str : "(null)"));
745 if (str) XFree (str);
747 if (event->xclient.format != 32)
749 fprintf (stderr, "%s: %sClientMessage of format %d received, not 32\n",
750 progname, (verbose_p ? "## " : ""), event->xclient.format);
752 type = event->xclient.data.l[0];
753 if (type == XA_ACTIVATE)
758 printf ("%s: ACTIVATE ClientMessage received.\n", progname);
762 "%s: %sClientMessage ACTIVATE received while already active.\n",
763 progname, (verbose_p ? "## " : ""));
765 else if (type == XA_DEACTIVATE)
770 printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
774 "%s: %sClientMessage DEACTIVATE received while inactive.\n",
775 progname, (verbose_p ? "## " : ""));
777 else if (type == XA_CYCLE)
782 printf ("%s: CYCLE ClientMessage received.\n", progname);
784 XtRemoveTimeOut (cycle_id);
790 "%s: %sClientMessage CYCLE received while inactive.\n",
791 progname, (verbose_p ? "## " : ""));
793 else if (type == XA_NEXT || type == XA_PREV)
796 printf ("%s: %s ClientMessage received.\n", progname,
797 (type == XA_NEXT ? "NEXT" : "PREV"));
798 next_mode_p = 1 + (type == XA_PREV);
803 XtRemoveTimeOut (cycle_id);
810 else if (type == XA_EXIT)
812 /* Ignore EXIT message if the screen is locked. */
813 if (until_idle_p || !locked_p)
816 printf ("%s: EXIT ClientMessage received.\n", progname);
826 fprintf (stderr, "%s: %sEXIT ClientMessage received while locked.\n",
827 progname, (verbose_p ? "## " : ""));
829 else if (type == XA_RESTART)
831 /* The RESTART message works whether the screensaver is active or not,
832 unless the screen is locked, in which case it doesn't work.
834 if (until_idle_p || !locked_p)
837 printf ("%s: RESTART ClientMessage received.\n", progname);
847 fprintf(stderr, "%s: %sRESTART ClientMessage received while locked.\n",
848 progname, (verbose_p ? "## " : ""));
850 else if (type == XA_DEMO)
854 "%s: %snot compiled with support for DEMO mode\n",
855 progname, (verbose_p ? "## " : ""));
860 printf ("%s: DEMO ClientMessage received.\n", progname);
865 "%s: %sDEMO ClientMessage received while active.\n",
866 progname, (verbose_p ? "## " : ""));
869 else if (type == XA_LOCK)
872 fprintf (stderr, "%s: %snot compiled with support for LOCK mode\n",
873 progname, (verbose_p ? "## " : ""));
875 if (locking_disabled_p)
877 "%s: %sLOCK ClientMessage received, but locking is disabled.\n",
878 progname, (verbose_p ? "## " : ""));
881 "%s: %sLOCK ClientMessage received while already locked.\n",
882 progname, (verbose_p ? "## " : ""));
887 printf ("%s: LOCK ClientMessage received;%s locking.\n",
888 progname, until_idle_p ? " activating and" : "");
890 if (lock_id) /* we're doing it now, so lose the timeout */
892 XtRemoveTimeOut (lock_id);
904 str = XGetAtomName (dpy, type);
907 "%s: %sunrecognised screensaver ClientMessage %s received\n",
908 progname, (verbose_p ? "## " : ""), str);
911 "%s: %sunrecognised screensaver ClientMessage 0x%x received\n",
912 progname, (verbose_p ? "## " : ""),
913 event->xclient.data.l[0]);
914 if (str) XFree (str);