From 38886de0c3e9ea5729ef23e4c653fa2822f52e8f Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 6 Apr 2021 14:43:39 +0200 Subject: xscreensaver 6.00 --- driver/xscreensaver.c | 4290 ++++++++++++++++++++++++------------------------- 1 file changed, 2092 insertions(+), 2198 deletions(-) (limited to 'driver/xscreensaver.c') diff --git a/driver/xscreensaver.c b/driver/xscreensaver.c index b9c54f1..4ac350d 100644 --- a/driver/xscreensaver.c +++ b/driver/xscreensaver.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-2020 Jamie Zawinski +/* xscreensaver, Copyright © 1991-2021 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -7,2535 +7,2429 @@ * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. - */ - -/* ======================================================================== - * First we wait until the keyboard and mouse become idle for the specified - * amount of time. We do this in one of three different ways: periodically - * checking with the XIdle server extension; selecting key and mouse events - * on (nearly) all windows; or by waiting for the MIT-SCREEN-SAVER extension - * to send us a "you are idle" event. * - * Then, we map a full screen black window. + * XScreenSaver Daemon, version 6. + * + * Having started its life in 1991, XScreenSaver acquired a lot of optional + * features to allow it to detect user activity, lock the screen and + * authenticate on systems as varied as VMS, SunOS, HPUX, SGI, and of course + * that young upstart, Linux. In such a heterogeneous environment, many + * features that would have simplified things were not universally available. + * Though I made an effort to follow a principle of minimal library usage in + * the XScreenSaver daemon, as described in my 2004 article "On Toolkits" + * , there was still quite a + * lot of code in the daemon, more than is strictly necessary when we consider + * only modern, still-maintained operating systems. + * + * This 2021 refactor had one goal: to minimize the amount of code in which a + * crash will cause the screen to unlock. In service of that goal, the process + * which holds the keyboard an mouse grabbed should: * - * We place a __SWM_VROOT property on this window, so that newly-started - * clients will think that this window is a "virtual root" window (as per - * the logic in the historical "vroot.h" header.) + * - Be as small as technically possible, for ease of auditing; * - * If there is an existing "virtual root" window (one that already had - * an __SWM_VROOT property) then we remove that property from that window. - * Otherwise, clients would see that window (the real virtual root) instead - * of ours (the impostor.) + * - Link against as few libraries as possible, to bring in as little + * third-party code as possible; * - * Then we pick a random program to run, and start it. Two assumptions - * are made about this program: that it has been specified with whatever - * command-line options are necessary to make it run on the root window; - * and that it has been compiled with vroot.h, so that it is able to find - * the root window when a virtual-root window manager (or this program) is - * running. + * - Delegate other tasks to helper programs running in other processes, + * in such a way that if those other processes should crash, the grabs + * remain intact and the screen remains locked. * - * Then, we wait for keyboard or mouse events to be generated on the window. - * When they are, we kill the inferior process, unmap the window, and restore - * the __SWM_VROOT property to the real virtual root window if there was one. + * In the XScreenSaver 6 design, the division of labor is as follows: * - * On multi-screen systems, we do the above on each screen, and start - * multiple programs, each with a different value of $DISPLAY. + * A) "xscreensaver" -- This is the main screen saver daemon. If this + * process crashes, the screen unlocks, so this is the one where + * minimizing the attack surface is critical. * - * On Xinerama systems, we do a similar thing, but instead create multiple - * windows on the (only) display, and tell the subprocess which one to use - * via the $XSCREENSAVER_WINDOW environment variable -- this trick requires - * a recent (Aug 2003) revision of vroot.h. + * - It reads events using the XInput extension version 2, which is + * required. This means that X11R7 is also required, which was + * released in 2009, and is ubiquitous as of 2021. * - * (See comments in screens.c for more details about Xinerama/RANDR stuff.) + * - It links against only libX11 and libXi. * - * While we are waiting for user activity, we also set up timers so that, - * after a certain amount of time has passed, we can start a different - * screenhack. We do this by killing the running child process with - * SIGTERM, and then starting a new one in the same way. + * - It maps no windows and renders no graphics or text. It only + * acquires keyboard and mouse grabs, handles timer logic, and + * launches and manages several sub-processes. * - * If there was a real virtual root, meaning that we removed the __SWM_VROOT - * property from it, meaning we must (absolutely must) restore it before we - * exit, then we set up signal handlers for most signals (SIGINT, SIGTERM, - * etc.) that do this. Most Xlib and Xt routines are not reentrant, so it - * is not generally safe to call them from signal handlers; however, this - * program spends most of its time waiting, so the window of opportunity - * when code could be called reentrantly is fairly small; and also, the worst - * that could happen is that the call would fail. If we've gotten one of - * these signals, then we're on our way out anyway. If we didn't restore the - * __SWM_VROOT property, that would be very bad, so it's worth a shot. Note - * that this means that, if you're using a virtual-root window manager, you - * can really fuck up the world by killing this process with "kill -9". + * B) "xscreensaver-gfx" -- When the time comes for the screen to blank, + * this process is launched to fade out, black out every monitor on + * the system, launch graphics demos to render on those blanked screens, + * and cycle them from time to time. * - * This program accepts ClientMessages of type SCREENSAVER; these messages - * may contain the atoms ACTIVATE, DEACTIVATE, etc, meaning to turn the - * screensaver on or off now, regardless of the idleness of the user, - * and a few other things. The included "xscreensaver-command" program - * sends these messsages. + * - If this program crashes, the screen does not unlock. The keyboard + * and mouse remain grabbed by the "xscreensaver" process, which will + * re-launch this process as necessary. * - * If we don't have the XIdle, MIT-SCREEN-SAVER, or SGI SCREEN_SAVER - * extensions, then we do the XAutoLock trick: notice every window that - * gets created, and wait 30 seconds or so until its creating process has - * settled down, and then select KeyPress events on those windows which - * already select for KeyPress events. It's important that we not select - * KeyPress on windows which don't select them, because that would - * interfere with event propagation. This will break if any program - * changes its event mask to contain KeyRelease or PointerMotion more than - * 30 seconds after creating the window, but such programs do not seem to - * occur in nature (I've never seen it happen in all these years.) + * - If it does crash, the logged in user's desktop may be momentarily + * visible before it re-launches, but it will be impossible to interact + * with it. * - * The reason that we can't select KeyPresses on windows that don't have - * them already is that, when dispatching a KeyPress event, X finds the - * lowest (leafmost) window in the hierarchy on which *any* client selects - * for KeyPress, and sends the event to that window. This means that if a - * client had a window with subwindows, and expected to receive KeyPress - * events on the parent window instead of the subwindows, then that client - * would malfunction if some other client selected KeyPress events on the - * subwindows. It is an incredible misdesign that one client can make - * another client malfunction in this way. + * - This process must react to hot-swapping of monitors in order to + * keep the screen blanked and the desktop obscured, and manages the + * life cycle of the graphics demos, each running in their own + * sub-process of "xscreensaver-gfx". * - * But here's a new kink that started showing up in late 2014: GNOME programs - * don't actually select for or receive KeyPress events! They do it behind - * the scenes through some kind of Input Method magic, even when running in - * an en_US locale. However, in that case, those applications *do* seem to - * update the _NET_WM_USER_TIME on their own windows every time they have - * received a secret KeyPress, so we *also* monitor that property on every - * window, and treat changes to it as identical to KeyPress. + * C) The graphics demos themselves. Launched by "xscreensaver-gfx" to run + * on the windows provided, there is one of these processes for each + * screen. These can use X11 or OpenGL, and have no impact on security; + * if it breaks, you can keep both pieces. * - * To detect mouse motion, we periodically wake up and poll the mouse - * position and button/modifier state, and notice when something has - * changed. We make this check every five seconds by default, and since the - * screensaver timeout has a granularity of one minute, this makes the - * chance of a false positive very small. We could detect mouse motion in - * the same way as keyboard activity, but that would suffer from the same - * "client changing event mask" problem that the KeyPress events hack does. - * I think polling is more reliable. + * D) "xscreensaver-auth" -- When the time comes to prompt the user for their + * password to unlock the screen, the "xscreensaver" daemon launches this + * process. If it exits with a "success" error code, the screen unlocks, + * otherwise it does not. This means that if it crashes, the screen + * remains locked. * - * On systems with /proc/interrupts (Linux) we poll that file and note when - * the interrupt counter numbers on the "keyboard" and "PS/2" lines change. - * (There is no reliable way, using /proc/interrupts, to detect non-PS/2 - * mice, so it doesn't help for serial or USB mice.) + * - It turns out that programs using the XInput 2 extension are able to + * snoop the keyboard even when the keyboard is grabbed by another + * process! So that's how this program reads user input, even while + * the "xscreensaver" process has the keyboard grabbed. * - * None of this crap happens if we're using one of the extensions. Sadly, - * the XIdle extension hasn't been available for many years; the SGI - * extension only exists on SGIs; and the MIT extension, while widely - * deployed, is garbage in several ways. + * - This program runs PAM, or whatever other authorization mechanisms + * are configured. On some systems, it may need to be setuid in order + * to read /etc/shadow, meaning it must disavow privileges after + * initialization but before connecting to the X server. * - * A third idle-detection option could be implemented (but is not): when - * running on the console display ($DISPLAY is `localhost`:0) and we're on a - * machine where /dev/tty and /dev/mouse have reasonable last-modification - * times, we could just stat() those. But the incremental benefit of - * implementing this is really small, so forget I said anything. + * - It gets to use libXft, so the fonts don't suck. * - * Debugging hints: - * - Have a second terminal handy. - * - Be careful where you set your breakpoints, you don't want this to - * stop under the debugger with the keyboard grabbed or the blackout - * window exposed. - * - If you run your debugger under XEmacs, try M-ESC (x-grab-keyboard) - * to keep your emacs window alive even when xscreensaver has grabbed. - * - Go read the code related to `debug_p'. - * - You probably can't set breakpoints in functions that are called on - * the other side of a call to fork() -- if your subprocesses are - * dying with signal 5, Trace/BPT Trap, you're losing in this way. - * - If you aren't using a server extension, don't leave this stopped - * under the debugger for very long, or the X input buffer will get - * huge because of the keypress events it's selecting for. This can - * make your X server wedge with "no more input buffers." + * - In theory, this program could be implemented using any GUI toolkit, + * and thus take advantage of input methods, on-screen keyboards, + * screen readers, etc. Again, this is permissible because this + * program fails SAFE: if it crashes, the screen remains locked. * - * ======================================================================== */ + * E) "xscreensaver-systemd" -- This runs in the background and monitors + * requests on the systemd dbus. This is how closing the laptop lid + * causes the screen to lock, and how video players request that blanking + * be inhibited. This program invokes "xscreensaver-command" as needed + * to pass those requests along to "xscreensaver" via ClientMessages. + */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include -#include -#include - -#ifdef ENABLE_NLS -# include -# include -#endif /* ENABLE_NLS */ +#include "version.h" -#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif -#include -#include -#include -#include -#include +#include +#include #include #include -#include /* for gethostbyname() */ #include -#include -#ifdef HAVE_XMU -# ifndef VMS -# include -# else /* !VMS */ -# include -# endif /* !VMS */ -#else /* !HAVE_XMU */ -# include "xmu.h" -#endif /* !HAVE_XMU */ - -#ifdef HAVE_MIT_SAVER_EXTENSION -#include -#endif /* HAVE_MIT_SAVER_EXTENSION */ +#include +#include +#include -#ifdef HAVE_XIDLE_EXTENSION -# include -#endif /* HAVE_XIDLE_EXTENSION */ +#include /* for getpwuid() */ +#include /* for getgrgid() */ -#ifdef HAVE_SGI_VC_EXTENSION -# include -#endif /* HAVE_SGI_VC_EXTENSION */ - -#ifdef HAVE_READ_DISPLAY_EXTENSION -# include -#endif /* HAVE_READ_DISPLAY_EXTENSION */ - -#ifdef HAVE_XSHM_EXTENSION -# include -#endif /* HAVE_XSHM_EXTENSION */ - -#ifdef HAVE_DPMS_EXTENSION -# include -#endif /* HAVE_DPMS_EXTENSION */ - - -#ifdef HAVE_DOUBLE_BUFFER_EXTENSION -# include -#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ - -#ifdef HAVE_XF86VMODE -# include -#endif /* HAVE_XF86VMODE */ - -#ifdef HAVE_XF86MISCSETGRABKEYSSTATE -# include -#endif /* HAVE_XF86MISCSETGRABKEYSSTATE */ - -#ifdef HAVE_XINERAMA -# include -#endif /* HAVE_XINERAMA */ - -#ifdef HAVE_RANDR -# include -#endif /* HAVE_RANDR */ - - -#include "xscreensaver.h" -#include "version.h" -#include "yarandom.h" -#include "resources.h" -#include "visual.h" -#include "usleep.h" -#include "auth.h" - -saver_info *global_si_kludge = 0; /* I hate C so much... */ - -char *progname = 0; -char *progclass = 0; -XrmDatabase db = 0; +#ifdef HAVE_UNAME +# include /* for uname() */ +#endif /* HAVE_UNAME */ +#ifdef HAVE_SYS_WAIT_H +# include /* for waitpid() and associated macros */ +#endif -static Atom XA_SCREENSAVER_RESPONSE; -static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_SUSPEND, XA_CYCLE, XA_NEXT, XA_PREV; -static Atom XA_RESTART, XA_SELECT; -static Atom XA_THROTTLE, XA_UNTHROTTLE; -Atom XA_DEMO, XA_PREFS, XA_EXIT, XA_LOCK, XA_BLANK; +#include +#include +#include +#include +#include +#include - -static XrmOptionDescRec options [] = { +#include "xmu.h" +#include "blurb.h" +#include "atoms.h" +#include "clientmsg.h" +#include "xinput.h" +#include "prefs.h" - { "-verbose", ".verbose", XrmoptionNoArg, "on" }, - { "-silent", ".verbose", XrmoptionNoArg, "off" }, - /* xscreensaver-demo uses this one */ - { "-nosplash", ".splash", XrmoptionNoArg, "off" }, - { "-no-splash", ".splash", XrmoptionNoArg, "off" }, +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) - /* useful for debugging */ - { "-no-capture-stderr", ".captureStderr", XrmoptionNoArg, "off" }, - { "-log", ".logFile", XrmoptionSepArg, 0 }, -}; +#undef ABS +#define ABS(x)((x)<0?-(x):(x)) -#ifdef __GNUC__ - __extension__ /* shut up about "string length is greater than the length - ISO C89 compilers are required to support" when including - the .ad file... */ -#endif +#undef MAX +#define MAX(x,y)((x)>(y)?(x):(y)) -static char *defaults[] = { -#include "XScreenSaver_ad.h" - 0 -}; -#ifdef _VROOT_H_ -ERROR! You must not include vroot.h in this file. -#endif +/* Globals used in this file. + */ +Bool debug_p = False; +static Bool splash_p = True; +static const char *version_number = 0; + +/* Preferences. */ +static Bool lock_p = False; +static Bool locking_disabled_p = False; +static unsigned int blank_timeout = 0; +static unsigned int lock_timeout = 0; +static unsigned int pointer_hysteresis = 0; +static char *external_ungrab_command = NULL; + +/* Subprocesses. */ +#define SAVER_GFX_PROGRAM "xscreensaver-gfx" +#define SAVER_AUTH_PROGRAM "xscreensaver-auth" +#define SAVER_SYSTEMD_PROGRAM "xscreensaver-systemd" +static pid_t saver_gfx_pid = 0; +static pid_t saver_auth_pid = 0; +static pid_t saver_systemd_pid = 0; +static int sighup_received = 0; +static int sigterm_received = 0; +static int sigchld_received = 0; +static Bool gfx_stopped_p = False; + +Window daemon_window = 0; +Cursor blank_cursor = None; +Cursor auth_cursor = None; + + +/* for the --restart command. + */ +static char **saved_argv; static void -do_help (saver_info *si) +save_argv (int ac, char **av) { - char *s, year[5]; - s = strchr (screensaver_id, '-'); - s = strrchr (s, '-'); - s++; - strncpy (year, s, 4); - year[4] = 0; - - fflush (stdout); - fflush (stderr); - fprintf (stdout, "\ -xscreensaver %s, copyright (c) 1991-%s by Jamie Zawinski \n\ -\n\ - All xscreensaver configuration is via the `~/.xscreensaver' file.\n\ - Rather than editing that file by hand, just run `xscreensaver-demo':\n\ - that program lets you configure the screen saver graphically,\n\ - including timeouts, locking, and display modes.\n\ -\n", - si->version, year); - fprintf (stdout, "\ - Just getting started? Try this:\n\ -\n\ - xscreensaver &\n\ - xscreensaver-demo\n\ -\n\ - For updates, online manual, and FAQ, please see the web page:\n\ -\n\ - https://www.jwz.org/xscreensaver/\n\ -\n"); - - fflush (stdout); - fflush (stderr); - exit (1); + saved_argv = (char **) calloc (ac+2, sizeof (char *)); + saved_argv [ac] = 0; + while (ac--) + { + int i = strlen (av[ac]) + 1; + saved_argv[ac] = (char *) malloc (i); + memcpy (saved_argv[ac], av[ac], i); + } } -Bool in_signal_handler_p = 0; /* I hate C so much... */ - -char * -timestring (time_t when) +static void +kill_all_subprocs (void) { - if (in_signal_handler_p) + if (saver_gfx_pid) { - /* Turns out that ctime() and even localtime_r() call malloc() on Linux! - So we can't call them from inside SIGCHLD. WTF. - */ - static char buf[30]; - strcpy (buf, "... ... .. signal ...."); - return buf; + if (verbose_p) + fprintf (stderr, "%s: pid %lu: killing " SAVER_GFX_PROGRAM "\n", + blurb(), (unsigned long) saver_gfx_pid); + kill (saver_gfx_pid, SIGTERM); + + if (gfx_stopped_p) /* SIGCONT to allow SIGTERM to proceed */ + { + if (verbose_p) + fprintf (stderr, "%s: pid %lu: sending " SAVER_GFX_PROGRAM + " SIGCONT\n", + blurb(), (unsigned long) saver_gfx_pid); + gfx_stopped_p = False; + kill (-saver_gfx_pid, SIGCONT); /* send to process group */ + } } - else + + if (saver_auth_pid) { - char *str, *nl; - if (! when) when = time ((time_t *) 0); - str = (char *) ctime (&when); - nl = (char *) strchr (str, '\n'); - if (nl) *nl = 0; /* take off that dang newline */ - return str; + if (verbose_p) + fprintf (stderr, "%s: pid %lu: killing " SAVER_AUTH_PROGRAM "\n", + blurb(), (unsigned long) saver_auth_pid); + kill (saver_auth_pid, SIGTERM); } -} -static Bool blurb_timestamp_p = True; /* kludge */ - -const char * -blurb (void) -{ - if (!blurb_timestamp_p) - return progname; - else + if (saver_systemd_pid) { - static char buf[255]; - char *ct = timestring(0); - int n = strlen(progname); - if (n > 100) n = 99; - strncpy(buf, progname, n); - buf[n++] = ':'; - buf[n++] = ' '; - strncpy(buf+n, ct+11, 8); - strcpy(buf+n+9, ": "); - return buf; + if (verbose_p) + fprintf (stderr, "%s: pid %lu: killing " SAVER_SYSTEMD_PROGRAM "\n", + blurb(), (unsigned long) saver_systemd_pid); + kill (saver_systemd_pid, SIGTERM); } } -int -saver_ehandler (Display *dpy, XErrorEvent *error) +static void +saver_exit (int status) { - saver_info *si = global_si_kludge; /* I hate C so much... */ - int i; - Bool fatal_p; - - if (!real_stderr) real_stderr = stderr; - - fprintf (real_stderr, "\n" - "#######################################" - "#######################################\n\n" - "%s: X Error! PLEASE REPORT THIS BUG.\n", - blurb()); - - for (i = 0; i < si->nscreens; i++) - { - saver_screen_info *ssi = &si->screens[i]; - fprintf (real_stderr, "%s: screen %d/%d: 0x%x, 0x%x, 0x%x\n", - blurb(), ssi->real_screen_number, ssi->number, - (unsigned int) RootWindowOfScreen (si->screens[i].screen), - (unsigned int) si->screens[i].real_vroot, - (unsigned int) si->screens[i].screensaver_window); - } - - fprintf (real_stderr, "\n" - "#######################################" - "#######################################\n\n"); - - fatal_p = XmuPrintDefaultErrorMessage (dpy, error, real_stderr); - - fatal_p = True; /* The only time I've ever seen a supposedly nonfatal error, - it has been BadImplementation / Xlib sequence lost, which - are in truth pretty damned fatal. - */ + kill_all_subprocs(); + exit (status); +} - fprintf (real_stderr, "\n"); - if (! fatal_p) - fprintf (real_stderr, "%s: nonfatal error.\n\n", blurb()); - else +static void +catch_signal (int sig, RETSIGTYPE (*handler) (int)) +{ +# ifdef HAVE_SIGACTION + + struct sigaction a; + a.sa_handler = handler; + sigemptyset (&a.sa_mask); + a.sa_flags = 0; + + /* On Linux 2.4.9 (at least) we need to tell the kernel to not mask delivery + of this signal from inside its handler, or else when we execvp() the + process again, it starts up with SIGHUP blocked, meaning that killing + it with -HUP only works *once*. You'd think that execvp() would reset + all the signal masks, but it doesn't. + */ +# if defined(SA_NOMASK) + a.sa_flags |= SA_NOMASK; +# elif defined(SA_NODEFER) + a.sa_flags |= SA_NODEFER; +# endif + + if (sigaction (sig, &a, 0) < 0) +# else /* !HAVE_SIGACTION */ + if (((long) signal (sig, handler)) == -1L) +# endif /* !HAVE_SIGACTION */ { - if (si->prefs.xsync_p) - { - saver_exit (si, -1, "because of synchronous X Error"); - } - else - { -#ifdef __GNUC__ - __extension__ /* don't warn about "string length is greater than the - length ISO C89 compilers are required to support". */ -#endif - fprintf (real_stderr, - "#######################################################################\n" - "\n" - " If at all possible, please re-run xscreensaver with the command\n" - " line arguments `-sync -verbose -log log.txt', and reproduce this\n" - " bug. That will cause xscreensaver to dump a `core' file to the\n" - " current directory. Please include the stack trace from that core\n" - " file in your bug report. *DO NOT* mail the core file itself! That\n" - " won't work. A \"log.txt\" file will also be written. Please *do*\n" - " include the complete \"log.txt\" file with your bug report.\n" - "\n" - " https://www.jwz.org/xscreensaver/bugs.html explains how to create\n" - " the most useful bug reports, and how to examine core files.\n" - "\n" - " The more information you can provide, the better. But please\n" - " report this bug, regardless!\n" - "\n" - "#######################################################################\n" - "\n" - "\n"); - - saver_exit (si, -1, 0); - } + char buf [255]; + sprintf (buf, "%s: couldn't catch signal %d", blurb(), sig); + perror (buf); + saver_exit (1); } - - return 0; } -#ifdef __GNUC__ /* Silence warning */ -static void startup_ehandler (String, String, String, String, String *, - Cardinal *) __attribute__((noreturn)); -#endif /* __GNUC__ */ - -/* This error handler is used only while the X connection is being set up; - after we've got a connection, we don't use this handler again. The only - reason for having this is so that we can present a more idiot-proof error - message than "cannot open display." +/* Re-execs the process with the arguments in saved_argv. Does not return. + Do not call this while the screen is locked: user must unlock first. */ -static void -startup_ehandler (String name, String type, String class, - String defalt, /* one can't even spel properly - in this joke of a language */ - String *av, Cardinal *ac) +static void +restart_process (void) { - char fmt[512]; - String p[10]; - saver_info *si = global_si_kludge; /* I hate C so much... */ - XrmDatabase *db = XtAppGetErrorDatabase(si->app); - *fmt = 0; - XtAppGetErrorDatabaseText(si->app, name, type, class, defalt, - fmt, sizeof(fmt)-1, *db); - - fprintf (stderr, "%s: ", blurb()); - - memset (p, 0, sizeof(p)); - if (*ac > countof (p)) *ac = countof (p); - memcpy ((char *) p, (char *) av, (*ac) * sizeof(*av)); - fprintf (stderr, fmt, /* Did I mention that I hate C? */ - p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9]); - fprintf (stderr, "\n"); - - describe_uids (si, stderr); - - if (si->orig_uid && !strncmp (si->orig_uid, "root/", 5)) - { - fprintf (stderr, "\n" - "%s: This is probably because you're logging in as root. You\n" -" shouldn't log in as root: you should log in as a normal user,\n" -" and then `su' as needed. If you insist on logging in as\n" -" root, you will have to turn off X's security features before\n" -" xscreensaver will work.\n" - "\n" -" Please read the manual and FAQ for more information:\n", - blurb()); - } - else - { - fprintf (stderr, "\n" - "%s: Errors at startup are usually authorization problems.\n" -" But you're not logging in as root (good!) so something\n" -" else must be wrong. Did you read the manual and the FAQ?\n", - blurb()); - } - - fprintf (stderr, "\n" - " https://www.jwz.org/xscreensaver/faq.html\n" - " https://www.jwz.org/xscreensaver/man.html\n" - "\n"); - - fflush (stderr); - fflush (stdout); - exit (1); + kill_all_subprocs(); + execvp (saved_argv [0], saved_argv); /* shouldn't return */ + { + char buf [512]; + sprintf (buf, "%s: could not restart process", blurb()); + perror(buf); + fflush(stderr); + abort(); + } } - -/* The zillions of initializations. - */ +static RETSIGTYPE saver_sighup_handler (int sig) { sighup_received = sig; } +static RETSIGTYPE saver_sigchld_handler (int sig) { sigchld_received = sig; } +static RETSIGTYPE saver_sigterm_handler (int sig) { sigterm_received = sig; } -/* Set progname, version, etc. This is done very early. - */ static void -set_version_string (saver_info *si, int *argc, char **argv) +handle_signals (void) { - progclass = "XScreenSaver"; + catch_signal (SIGHUP, saver_sighup_handler); + catch_signal (SIGCHLD, saver_sigchld_handler); + catch_signal (SIGTERM, saver_sigterm_handler); /* kill */ + catch_signal (SIGINT, saver_sigterm_handler); /* shell ^C */ + catch_signal (SIGQUIT, saver_sigterm_handler); /* shell ^| */ +} - /* progname is reset later, after we connect to X. */ - progname = strrchr(argv[0], '/'); - if (progname) progname++; - else progname = argv[0]; - if (strlen(progname) > 100) /* keep it short. */ - progname[99] = 0; +static pid_t +fork_and_exec (Display *dpy, int argc, char **argv) +{ + char buf [255]; + pid_t forked = fork(); + switch ((int) forked) { + case -1: + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + break; + + case 0: + close (ConnectionNumber (dpy)); /* close display fd */ + execvp (argv[0], argv); /* shouldn't return. */ + + snprintf (buf, 255, "%s: pid %lu: couldn't exec %s", blurb(), + (unsigned long) getpid(), argv[0]); + perror (buf); + exit (1); /* exits child fork */ + break; + + default: /* parent fork */ + if (verbose_p) + { + int i; + fprintf (stderr, "%s: pid %lu: launched", + blurb(), (unsigned long) forked); + for (i = 0; i < argc; i++) + fprintf (stderr, " %s", argv[i]); + fprintf (stderr, "\n"); + } - /* The X resource database blows up if argv[0] has a "." in it. */ - { - char *s = argv[0]; - while ((s = strchr (s, '.'))) - *s = '_'; + /* Put each launched process in its own process group so that + SIGSTOP will affect all of its inferior processes as well. + */ + if (setpgid (forked, 0)) + { + char buf [255]; + sprintf (buf, "%s: setpgid %d", blurb(), forked); + perror (buf); + } + break; } - si->version = (char *) malloc (32); - memcpy (si->version, screensaver_id + 17, 4); - si->version [4] = 0; + return forked; } -/* Initializations that potentially take place as a priveleged user: - If the xscreensaver executable is setuid root, then these initializations - are run as root, before discarding privileges. +/* Execute command in another process and wait for it to + * finish. Return exit code of process, or -1 on error + * with fork() or exec(). */ -static void -privileged_initialization (saver_info *si, int *argc, char **argv) +static int +exec_and_wait (Display *dpy, int argc, char **argv) { -#ifndef NO_LOCKING - /* before hack_uid() for proper permissions */ - lock_priv_init (*argc, argv, si->prefs.verbose_p); -#endif /* NO_LOCKING */ + char buf [255]; + pid_t forked = fork(); + switch ((int) forked) { + case -1: + sprintf (buf, "%s: couldn't fork", blurb()); + perror (buf); + break; + + case 0: + close (ConnectionNumber (dpy)); /* close display fd */ + execvp (argv[0], argv); /* shouldn't return. */ + + snprintf (buf, 255, "%s: pid %lu: couldn't exec %s", blurb(), + (unsigned long) getpid(), argv[0]); + perror (buf); + exit (1); /* exits child fork */ + break; + + default: /* parent fork */ + if (verbose_p) + { + int i; + fprintf (stderr, "%s: pid %lu: launched", + blurb(), (unsigned long) forked); + for (i = 0; i < argc; i++) + fprintf (stderr, " %s", argv[i]); + fprintf (stderr, "\n"); + } - hack_uid (si); + /* Put each launched process in its own process group so that + SIGSTOP will affect all of its inferior processes as well. + */ + if (setpgid (forked, 0)) + { + char buf [255]; + sprintf (buf, "%s: setpgid %d", blurb(), forked); + perror (buf); + } + { + pid_t retpid; + int wstatus; + while ((retpid = waitpid (forked, &wstatus, 0)) == -1) { + if (errno == EINTR) + continue; + perror ("Could not waitpid for child."); + return -1; + } + if (WIFEXITED(wstatus)) + return WEXITSTATUS(wstatus); + if (WIFSIGNALED(wstatus)) + return WTERMSIG(wstatus) + 128; + return -1; + } + } + return -1; } -/* Figure out what locking mechanisms are supported. +static int respawn_thrashing_count = 0; + +/* Called from the main loop some time after the SIGCHLD signal has fired. + Returns true if: + - the process that died was "xscreensaver-auth", and + - it exited with a "success" status meaning "user is authenticated". */ -static void -lock_initialization (saver_info *si, int *argc, char **argv) +static Bool +handle_sigchld (Display *dpy, Bool blanked_p) { -#ifdef NO_LOCKING - si->locking_disabled_p = True; - si->nolock_reason = "not compiled with locking support"; -#else /* !NO_LOCKING */ - - /* Finish initializing locking, now that we're out of privileged code. */ - if (! lock_init (*argc, argv, si->prefs.verbose_p)) - { - si->locking_disabled_p = True; - si->nolock_reason = "error getting password"; - } + Bool authenticated_p = False; - /* If locking is currently enabled, but the environment indicates that - we have been launched as GDM's "Background" program, then disable - locking just in case. - */ - if (!si->locking_disabled_p && getenv ("RUNNING_UNDER_GDM")) - { - si->locking_disabled_p = True; - si->nolock_reason = "running under GDM"; - } + sigchld_received = 0; + if (debug_p) + fprintf (stderr, "%s: SIGCHLD received\n", blurb()); - /* If the server is XDarwin (MacOS X) then disable locking. - (X grabs only affect X programs, so you can use Command-Tab - to bring any other Mac program to the front, e.g., Terminal.) - */ - if (!si->locking_disabled_p) + /* Reap every now-dead inferior without blocking. */ + while (1) { - int op = 0, event = 0, error = 0; - Bool macos_p = False; + int wait_status = 0; + pid_t kid = waitpid (-1, &wait_status, WNOHANG | WUNTRACED); + char how[100]; -#ifdef __APPLE__ - /* Disable locking if *running* on Apple hardware, since we have no - reliable way to determine whether the server is running on MacOS. - Hopefully __APPLE__ means "MacOS" and not "Linux on Mac hardware" - but I'm not really sure about that. + /* 0 means no more children to reap. + -1 means error -- except "interrupted system call" + isn't a "real" error, so if we get that, try again. */ - macos_p = True; -#endif + if (kid == 0 || + (kid < 0 && errno != EINTR)) + break; - if (!macos_p) - /* This extension exists on the Apple X11 server, but not - on earlier versions of the XDarwin server. */ - macos_p = XQueryExtension (si->dpy, "Apple-DRI", &op, &event, &error); + /* We get SIGCHLD after sending SIGSTOP, but no action is required. */ + if (WIFSTOPPED (wait_status)) + { + if (verbose_p) + fprintf (stderr, "%s: pid %lu: %s stopped\n", blurb(), + (unsigned long) kid, + (kid == saver_gfx_pid ? SAVER_GFX_PROGRAM : + kid == saver_auth_pid ? SAVER_AUTH_PROGRAM : + kid == saver_systemd_pid ? SAVER_SYSTEMD_PROGRAM : + "unknown")); + continue; + } - if (macos_p) + if (WIFSIGNALED (wait_status)) { - si->locking_disabled_p = True; - si->nolock_reason = "Cannot lock securely on MacOS X"; + if (WTERMSIG (wait_status) == SIGTERM) + strcpy (how, "with SIGTERM"); + else + sprintf (how, "with signal %d", WTERMSIG (wait_status)); } - } + else if (WIFEXITED (wait_status)) + { + int exit_status = WEXITSTATUS (wait_status); + /* Treat exit code as a signed 8-bit quantity. */ + if (exit_status & 0x80) exit_status |= ~0xFF; + if (exit_status) + sprintf (how, "with status %d", exit_status); + else + strcpy (how, "normally"); + } + else + sprintf (how, "for unknown reason %d", wait_status); - /* Like MacOS, locking under Wayland's embedded X11 server does not work. - (X11 grabs don't work because the Wayland window manager lives at a - higher level than the X11 emulation layer.) - */ - if (!si->locking_disabled_p && getenv ("WAYLAND_DISPLAY")) - { - si->locking_disabled_p = True; - si->nolock_reason = "Cannot lock securely under Wayland"; - } + if (kid == saver_gfx_pid) + { + saver_gfx_pid = 0; + gfx_stopped_p = False; + if (blanked_p) + { + if (respawn_thrashing_count > 5) + { + /* If we have tried to re-launch this pid N times in a row + without unblanking, give up instead of forking it in a + tight loop. Screen will remain grabbed, but desktop will + be visible. + */ + fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM + " won't re-launch!\n", + blurb(), (unsigned long) kid); + } + else + { + char *av[10]; + int ac = 0; + av[ac++] = SAVER_GFX_PROGRAM; + av[ac++] = "--emergency"; + if (verbose_p) av[ac++] = "--verbose"; + if (debug_p) av[ac++] = "--debug"; + av[ac] = 0; + fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM + " exited unexpectedly %s: re-launching\n", + blurb(), (unsigned long) kid, how); + gfx_stopped_p = False; + saver_gfx_pid = fork_and_exec (dpy, ac, av); + respawn_thrashing_count++; + } + } + else + { + /* Should not have been running anyway. */ + if (verbose_p) + fprintf (stderr, "%s: pid %lu: " SAVER_GFX_PROGRAM + " exited %s\n", blurb(), (unsigned long) kid, how); + } + } + else if (kid == saver_systemd_pid) + { + /* xscreensaver-systemd might fail if systemd isn't running, or if + xscreensaver-systemd was already running, or if some other + program has bound to our targets. Or if it doesn't exist because + this system doesn't use systemd. So don't re-launch it if it + failed to start, or dies. + */ + saver_systemd_pid = 0; + fprintf (stderr, "%s: pid %lu: " SAVER_SYSTEMD_PROGRAM + " exited unexpectedly %s\n", + blurb(), (unsigned long) kid, how); + } + else if (kid == saver_auth_pid) + { + saver_auth_pid = 0; + + /* xscreensaver-auth exits with status 200 to mean "ok to unlock". + Any other exit code, or dying with a signal, means "nope". + */ + if (!WIFSIGNALED (wait_status) && + WIFEXITED (wait_status)) + { + unsigned int status = (unsigned int) WEXITSTATUS (wait_status); + if (status == 200) + { + authenticated_p = True; + strcpy (how, "and authenticated"); + } + else if (status == 0 && !blanked_p) + strcpy (how, "normally"); /* This was the splash dialog */ + else if (status == 0) + strcpy (how, "and authentication canceled"); + else if (status == 255) /* which is -1 */ + strcpy (how, "and authentication failed"); + } - if (si->prefs.debug_p) /* But allow locking anyway in debug mode. */ - si->locking_disabled_p = False; + if (verbose_p) + fprintf (stderr, "%s: pid %lu: " SAVER_AUTH_PROGRAM " exited %s\n", + blurb(), (unsigned long) kid, how); + } + else if (verbose_p) + { + fprintf (stderr, "%s: pid %lu: unknown child" + " exited unexpectedly %s\n", + blurb(), (unsigned long) kid, how); + } + } -#endif /* NO_LOCKING */ + return authenticated_p; } -/* Open the connection to the X server, and intern our Atoms. +/* Add DEFAULT_PATH_PREFIX to the front of $PATH. + Typically "/usr/libexec/xscreensaver". */ -static Widget -connect_to_server (saver_info *si, int *argc, char **argv) +static void +hack_environment (void) { - Widget toplevel_shell; - -#ifdef HAVE_PUTENV - char *d = getenv ("DISPLAY"); - if (!d || !*d) - { - char *ndpy = strdup("DISPLAY=:0.0"); - /* if (si->prefs.verbose_p) */ /* sigh, too early to test this... */ - fprintf (stderr, - "%s: warning: $DISPLAY is not set: defaulting to \"%s\".\n", - blurb(), ndpy+8); - if (putenv (ndpy)) - abort (); - /* don't free (ndpy) -- some implementations of putenv (BSD 4.4, - glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) - do not. So we must leak it (and/or the previous setting). Yay. - */ - } -#endif /* HAVE_PUTENV */ - - XSetErrorHandler (saver_ehandler); - - XtAppSetErrorMsgHandler (si->app, startup_ehandler); - toplevel_shell = XtAppInitialize (&si->app, progclass, - options, XtNumber (options), - argc, argv, defaults, 0, 0); - XtAppSetErrorMsgHandler (si->app, 0); - - si->dpy = XtDisplay (toplevel_shell); - si->prefs.db = XtDatabase (si->dpy); - XtGetApplicationNameAndClass (si->dpy, &progname, &progclass); - - if(strlen(progname) > 100) /* keep it short. */ - progname [99] = 0; - - db = si->prefs.db; /* resources.c needs this */ - - XA_VROOT = XInternAtom (si->dpy, "__SWM_VROOT", False); - XA_SCREENSAVER = XInternAtom (si->dpy, "SCREENSAVER", False); - XA_SCREENSAVER_VERSION = XInternAtom (si->dpy, "_SCREENSAVER_VERSION",False); - XA_SCREENSAVER_ID = XInternAtom (si->dpy, "_SCREENSAVER_ID", False); - XA_SCREENSAVER_STATUS = XInternAtom (si->dpy, "_SCREENSAVER_STATUS", False); - XA_SCREENSAVER_RESPONSE = XInternAtom (si->dpy, "_SCREENSAVER_RESPONSE", - False); - XA_XSETROOT_ID = XInternAtom (si->dpy, "_XSETROOT_ID", False); - XA_ESETROOT_PMAP_ID = XInternAtom (si->dpy, "ESETROOT_PMAP_ID", False); - XA_XROOTPMAP_ID = XInternAtom (si->dpy, "_XROOTPMAP_ID", False); - XA_NET_WM_USER_TIME = XInternAtom (si->dpy, "_NET_WM_USER_TIME", False); - XA_ACTIVATE = XInternAtom (si->dpy, "ACTIVATE", False); - XA_DEACTIVATE = XInternAtom (si->dpy, "DEACTIVATE", False); - XA_SUSPEND = XInternAtom (si->dpy, "SUSPEND", False); - XA_RESTART = XInternAtom (si->dpy, "RESTART", False); - XA_CYCLE = XInternAtom (si->dpy, "CYCLE", False); - XA_NEXT = XInternAtom (si->dpy, "NEXT", False); - XA_PREV = XInternAtom (si->dpy, "PREV", False); - XA_SELECT = XInternAtom (si->dpy, "SELECT", False); - XA_EXIT = XInternAtom (si->dpy, "EXIT", False); - XA_DEMO = XInternAtom (si->dpy, "DEMO", False); - XA_PREFS = XInternAtom (si->dpy, "PREFS", False); - XA_LOCK = XInternAtom (si->dpy, "LOCK", False); - XA_BLANK = XInternAtom (si->dpy, "BLANK", False); - XA_THROTTLE = XInternAtom (si->dpy, "THROTTLE", False); - XA_UNTHROTTLE = XInternAtom (si->dpy, "UNTHROTTLE", False); - - return toplevel_shell; + static const char *def_path = DEFAULT_PATH_PREFIX; + const char *opath = getenv("PATH"); + char *npath; + if (! opath) opath = "/bin:/usr/bin"; /* WTF */ + npath = (char *) malloc(strlen(def_path) + strlen(opath) + 20); + strcpy (npath, "PATH="); + strcat (npath, def_path); + strcat (npath, ":"); + strcat (npath, opath); + + /* Can fail if out of memory, I guess. Ignore errors. */ + putenv (npath); + + /* don't free (npath) -- some implementations of putenv (BSD 4.4, + glibc 2.0) copy the argument, but some (libc4,5, glibc 2.1.2) + do not. So we must leak it (and/or the previous setting). Yay. + */ } -/* Handle the command-line arguments that were not handled for us by Xt. - Issue an error message and exit if there are unknown options. - */ static void -process_command_line (saver_info *si, int *argc, char **argv) +print_banner(void) { - int i; - for (i = 1; i < *argc; i++) - { - if (!strcmp (argv[i], "-debug")) - /* no resource for this one, out of paranoia. */ - si->prefs.debug_p = True; + const time_t rel = XSCREENSAVER_RELEASED; + struct tm *tm = localtime (&rel); + char buf[100]; + int months = (time ((time_t *) 0) - XSCREENSAVER_RELEASED) / (60*60*24*30); + int years = months / 12.0 + 0.7; - else if (!strcmp (argv[i], "-h") || - !strcmp (argv[i], "-help") || - !strcmp (argv[i], "--help")) - do_help (si); + strftime (buf, sizeof(buf), "%b %Y", tm); - else - { - const char *s = argv[i]; - fprintf (stderr, "%s: unknown option \"%s\". Try \"-help\".\n", - blurb(), s); - - if (s[0] == '-' && s[1] == '-') s++; - if (!strcmp (s, "-activate") || - !strcmp (s, "-deactivate") || - !strcmp (s, "-cycle") || - !strcmp (s, "-next") || - !strcmp (s, "-prev") || - !strcmp (s, "-exit") || - !strcmp (s, "-restart") || - !strcmp (s, "-demo") || - !strcmp (s, "-prefs") || - !strcmp (s, "-preferences") || - !strcmp (s, "-lock") || - !strcmp (s, "-version") || - !strcmp (s, "-time")) - { - - if (!strcmp (s, "-demo") || !strcmp (s, "-prefs")) - fprintf (stderr, "\n\ - Perhaps you meant to run the `xscreensaver-demo' program instead?\n"); - else - fprintf (stderr, "\n\ - However, `%s' is an option to the `xscreensaver-command' program.\n", s); - - fprintf (stderr, "\ - The `xscreensaver' program is a daemon that runs in the background.\n\ - You control a running xscreensaver process by sending it messages\n\ - with `xscreensaver-demo' or `xscreensaver-command'.\n\ -. See the man pages for details, or check the web page:\n\ - https://www.jwz.org/xscreensaver/\n\n"); - } - - exit (1); - } + if (months > 18) + sprintf (buf + strlen(buf), " -- %d years ago", years); + else if (months > 1) + sprintf (buf + strlen(buf), " -- %d months ago", months); + + if (verbose_p || debug_p) + { + fprintf (stderr, + "\tXScreenSaver " XSCREENSAVER_VERSION ", released %s\n" + "\tCopyright \302\251 1991-%d by" + " Jamie Zawinski \n\n", + buf, tm->tm_year + 1900); + if (months > 18) + fprintf (stderr, + /* Hey jerks, the only time someone will see this particular + message is if they are running xscreensaver with '-log' in + order to send me a bug report, and they had damned well + better try the latest release before they do that. */ + "\t ###################################################\n" + "\t ### ###\n" + "\t ### THIS VERSION IS VERY OLD! PLEASE UPGRADE! ###\n" + "\t ### ###\n" + "\t ###################################################\n" + "\n"); } + + if (debug_p) + fprintf (stderr, + "#####################################" + "#####################################\n" + "\n" + "\t\t\t DEBUG MODE IS NOT SECURE\n" + "\n" + "\tThe XScreenSaver window will only cover the left half of\n" + "\tthe screen. Position your terminal window on the right.\n" + "\tWARNING: stderr and the log file will include every\n" + "\tcharacter that you type, including passwords.\n" + "\n" + "#####################################" + "#####################################\n" + "\n"); + + version_number = XSCREENSAVER_VERSION; } -/* Print out the xscreensaver banner to the tty if applicable; - Issue any other warnings that are called for at this point. - */ static void -print_banner (saver_info *si) +fix_fds (void) { - saver_preferences *p = &si->prefs; - - char *s, year[5]; - s = strchr (screensaver_id, '-'); - s = strrchr (s, '-'); - s++; - strncpy (year, s, 4); - year[4] = 0; - - /* This resource gets set some time before the others, so that we know - whether to print the banner (and so that the banner gets printed before - any resource-database-related error messages.) - */ - p->verbose_p = (p->debug_p || - get_boolean_resource (si->dpy, "verbose", "Boolean")); + /* Bad Things Happen if stdin, stdout, and stderr have been closed + (as by the `sh incantation "xscreensaver >&- 2>&-"). When you do + that, the X connection gets allocated to one of these fds, and + then some random library writes to stderr, and random bits get + stuffed down the X pipe, causing "Xlib: sequence lost" errors. + So, we cause the first three file descriptors to be open to + /dev/null if they aren't open to something else already. This + must be done before any other files are opened (or the closing + of that other file will again free up one of the "magic" first + three FDs.) + + We do this by opening /dev/null three times, and then closing + those fds, *unless* any of them got allocated as #0, #1, or #2, + in which case we leave them open. Gag. - /* Ditto, for the locking_disabled_p message. */ - p->lock_p = get_boolean_resource (si->dpy, "lock", "Boolean"); + Really, this crap is technically required of *every* X program, + if you want it to be robust in the face of "2>&-". + */ + int fd0 = open ("/dev/null", O_RDWR); + int fd1 = open ("/dev/null", O_RDWR); + int fd2 = open ("/dev/null", O_RDWR); + if (fd0 > 2) close (fd0); + if (fd1 > 2) close (fd1); + if (fd2 > 2) close (fd2); +} - if (p->verbose_p) - fprintf (stderr, - "%s %s, copyright (c) 1991-%s " - "by Jamie Zawinski .\n", - progname, si->version, year); - if (p->debug_p) - fprintf (stderr, "\n" - "%s: Warning: running in DEBUG MODE. Be afraid.\n" - "\n" - "\tNote that in debug mode, the xscreensaver window will only\n" - "\tcover the left half of the screen. (The idea is that you\n" - "\tcan still see debugging output in a shell, if you position\n" - "\tit on the right side of the screen.)\n" - "\n" - "\tDebug mode is NOT SECURE. Do not run with -debug in\n" - "\tuntrusted environments.\n" - "\n", - blurb()); - - if (p->verbose_p && senesculent_p ()) - fprintf (stderr, "\n" - "*************************************" - "**************************************\n" - "%s: Warning: this version of xscreensaver is VERY OLD!\n" - "%s: Please upgrade! https://www.jwz.org/xscreensaver/\n" - "*************************************" - "**************************************\n" - "\n", - blurb(), blurb()); - - if (p->verbose_p) +/* Mostly duplicated in resources.c. + */ +static int +parse_time (const char *string) +{ + unsigned int h, m, s; + char c; + if (3 == sscanf (string, " %u : %2u : %2u %c", &h, &m, &s, &c)) + ; + else if (2 == sscanf (string, " : %2u : %2u %c", &m, &s, &c) || + 2 == sscanf (string, " %u : %2u %c", &m, &s, &c)) + h = 0; + else if (1 == sscanf (string, " : %2u %c", &s, &c)) + h = m = 0; + else if (1 == sscanf (string, " %u %c", &m, &c)) + h = s = 0; + else { - if (!si->uid_message || !*si->uid_message) - describe_uids (si, stderr); - else - { - if (si->orig_uid && *si->orig_uid) - fprintf (stderr, "%s: initial effective uid/gid was %s.\n", - blurb(), si->orig_uid); - fprintf (stderr, "%s: %s\n", blurb(), si->uid_message); - } - - fprintf (stderr, "%s: in process %lu.\n", blurb(), - (unsigned long) getpid()); + fprintf (stderr, "%s: unparsable duration \"%s\"\n", blurb(), string); + return -1; + } + if (s >= 60 && (h != 0 || m != 0)) + { + fprintf (stderr, "%s: seconds > 59 in \"%s\"\n", blurb(), string); + return -1; + } + if (m >= 60 && h > 0) + { + fprintf (stderr, "%s: minutes > 59 in \"%s\"\n", blurb(), string); + return -1; } + return ((h * 60 * 60) + (m * 60) + s); } -static void -print_lock_failure_banner (saver_info *si) -{ - saver_preferences *p = &si->prefs; - /* If locking was not able to be initalized for some reason, explain why. - (This has to be done after we've read the lock_p resource.) - */ - if (si->locking_disabled_p) +/* This program only needs a very few options from the init file, so it + just reads the .ad file and the .xscreensaver file directly rather + than going through Xt and Xrm. + */ +static void init_line_handler (int lineno, + const char *key, const char *val, + void *closure) +{ + if (!strcmp (key, "verbose")) verbose_p = !strcasecmp (val, "true"); + else if (!strcmp (key, "splash")) splash_p = !strcasecmp (val, "true"); + else if (!strcmp (key, "lock")) lock_p = !strcasecmp (val, "true"); + else if (!strcmp (key, "timeout")) { - p->lock_p = False; - fprintf (stderr, "%s: locking is disabled (%s).\n", blurb(), - si->nolock_reason); - if (strstr (si->nolock_reason, "passw")) - fprintf (stderr, "%s: does xscreensaver need to be setuid? " - "consult the manual.\n", blurb()); - else if (strstr (si->nolock_reason, "running as ")) - fprintf (stderr, - "%s: locking only works when xscreensaver is launched\n" - "\t by a normal, non-privileged user (e.g., not \"root\".)\n" - "\t See the manual for details.\n", - blurb()); + int t = parse_time (val); + if (t > 0) blank_timeout = t; + } + if (!strcmp (key, "lockTimeout")) + { + int t = parse_time (val); + if (t >= 0) lock_timeout = t; + } + if (!strcmp (key, "pointerHysteresis")) + { + int i = atoi (val); + if (i >= 0) + pointer_hysteresis = i; + } + if (!strcmp (key, "externalUngrabCommand")) + { + free(external_ungrab_command); + external_ungrab_command = strdup(val); } - } - -/* called from screens.c so that all the Xt crud is here. */ -void -initialize_screen_root_widget (saver_screen_info *ssi) +static void +read_init_file_simple (const char *filename) { - saver_info *si = ssi->global; - if (ssi->toplevel_shell) - XtDestroyWidget (ssi->toplevel_shell); - ssi->toplevel_shell = - XtVaAppCreateShell (progname, progclass, - applicationShellWidgetClass, - si->dpy, - XtNscreen, ssi->screen, - XtNvisual, ssi->current_visual, - XtNdepth, visual_depth (ssi->screen, - ssi->current_visual), - NULL); + if (debug_p) + fprintf (stderr, "%s: reading %s\n", blurb(), filename); + parse_init_file (filename, init_line_handler, 0); } -/* Examine all of the display's screens, and populate the `saver_screen_info' - structures. Make sure this is called after hack_environment() sets $PATH. - */ +static time_t init_file_time = 0; + static void -initialize_per_screen_info (saver_info *si, Widget toplevel_shell) +read_init_files (Bool both_p) { - int i; + static const char *home = 0; + char *fn; + struct stat st; + Bool read_p = False; - update_screen_layout (si); + if (!home) + { + home = getenv("HOME"); + if (!home) home = ""; + } - /* Check to see whether fading is ever possible -- if any of the - screens on the display has a PseudoColor visual, then fading can - work (on at least some screens.) If no screen has a PseudoColor - visual, then don't bother ever trying to fade, because it will - just cause a delay without causing any visible effect. - */ - for (i = 0; i < si->nscreens; i++) + if (both_p) + { + read_init_file_simple (AD_DIR "/XScreenSaver"); + read_p = True; + } + + fn = (char *) malloc (strlen(home) + 40); + sprintf (fn, "%s/.xscreensaver", home); + + if (!stat (fn, &st)) { - saver_screen_info *ssi = &si->screens[i]; - if (has_writable_cells (ssi->screen, ssi->current_visual) || - get_visual (ssi->screen, "PseudoColor", True, False) || - get_visual (ssi->screen, "GrayScale", True, False)) + if (both_p || st.st_mtime != init_file_time) { - si->fading_possible_p = True; - break; + Bool ov = verbose_p; + init_file_time = st.st_mtime; + read_init_file_simple (fn); + read_p = True; + + /* Changes to verbose in .xscreenaver after startup are ignored; else + running xscreensaver-settings would turn off cmd line -verbose. */ + if (!both_p) verbose_p = ov; } } -#ifdef HAVE_XF86VMODE_GAMMA - si->fading_possible_p = True; /* if we can gamma fade, go for it */ -#endif -} - - -/* If any server extensions have been requested, try and initialize them. - Issue warnings if requests can't be honored. - */ -static void -initialize_server_extensions (saver_info *si) -{ - saver_preferences *p = &si->prefs; - - Bool server_has_xidle_extension_p = False; - Bool server_has_sgi_saver_extension_p = False; - Bool server_has_mit_saver_extension_p = False; - Bool system_has_proc_interrupts_p = False; - Bool server_has_xinput_extension_p = False; - const char *piwhy = 0; - - si->using_xidle_extension = p->use_xidle_extension; - si->using_sgi_saver_extension = p->use_sgi_saver_extension; - si->using_mit_saver_extension = p->use_mit_saver_extension; - si->using_proc_interrupts = p->use_proc_interrupts; - si->using_xinput_extension = p->use_xinput_extension; - -#ifdef HAVE_XIDLE_EXTENSION - { - int ev, er; - server_has_xidle_extension_p = XidleQueryExtension (si->dpy, &ev, &er); - } -#endif -#ifdef HAVE_SGI_SAVER_EXTENSION - server_has_sgi_saver_extension_p = - XScreenSaverQueryExtension (si->dpy, - &si->sgi_saver_ext_event_number, - &si->sgi_saver_ext_error_number); -#endif -#ifdef HAVE_MIT_SAVER_EXTENSION - server_has_mit_saver_extension_p = - XScreenSaverQueryExtension (si->dpy, - &si->mit_saver_ext_event_number, - &si->mit_saver_ext_error_number); -#endif -#ifdef HAVE_PROC_INTERRUPTS - system_has_proc_interrupts_p = query_proc_interrupts_available (si, &piwhy); -#endif + if (blank_timeout < 5) + blank_timeout = 60 * 60 * 10; -#ifdef HAVE_XINPUT - server_has_xinput_extension_p = query_xinput_extension (si); -#endif + free (fn); - if (!server_has_xidle_extension_p) - si->using_xidle_extension = False; - else if (p->verbose_p) + if (read_p && verbose_p) { - if (si->using_xidle_extension) - fprintf (stderr, "%s: using XIDLE extension.\n", blurb()); - else - fprintf (stderr, "%s: not using server's XIDLE extension.\n", blurb()); + fprintf (stderr, "%s: blank after: %d\n", blurb(), blank_timeout); + if (lock_p) + fprintf (stderr, "%s: lock after: %d\n", blurb(), lock_timeout); } +} - if (!server_has_sgi_saver_extension_p) - si->using_sgi_saver_extension = False; - else if (p->verbose_p) - { - if (si->using_sgi_saver_extension) - fprintf (stderr, "%s: using SGI SCREEN_SAVER extension.\n", blurb()); - else - fprintf (stderr, - "%s: not using server's SGI SCREEN_SAVER extension.\n", - blurb()); - } - if (!server_has_mit_saver_extension_p) - si->using_mit_saver_extension = False; - else if (p->verbose_p) - { - if (si->using_mit_saver_extension) - fprintf (stderr, "%s: using lame MIT-SCREEN-SAVER extension.\n", - blurb()); - else - fprintf (stderr, - "%s: not using server's lame MIT-SCREEN-SAVER extension.\n", - blurb()); - } -#ifdef HAVE_RANDR - if (XRRQueryExtension (si->dpy, - &si->randr_event_number, &si->randr_error_number)) - { - int nscreens = ScreenCount (si->dpy); /* number of *real* screens */ - int i; - si->using_randr_extension = TRUE; - - if (p->verbose_p) - fprintf (stderr, "%s: selecting RANDR events\n", blurb()); - for (i = 0; i < nscreens; i++) -# ifdef RRScreenChangeNotifyMask /* randr.h 1.5, 2002/09/29 */ - XRRSelectInput (si->dpy, RootWindow (si->dpy, i), - RRScreenChangeNotifyMask); -# else /* !RRScreenChangeNotifyMask */ /* Xrandr.h 1.4, 2001/06/07 */ - XRRScreenChangeSelectInput (si->dpy, RootWindow (si->dpy, i), True); -# endif /* !RRScreenChangeNotifyMask */ - } -# endif /* HAVE_RANDR */ +/* We trap and ignore ALL protocol errors that happen after initialization. + By default, Xlib would exit. Ignoring an X error is less bad than + crashing and unlocking. + */ +static Bool ignore_x11_error_p = False; +static Bool error_handler_hit_p = False; +static Bool print_x11_error_p = True; -#ifdef HAVE_XINPUT - if (!server_has_xinput_extension_p) - si->using_xinput_extension = False; - else +static int +error_handler (Display *dpy, XErrorEvent *event) +{ + error_handler_hit_p = True; + + if (print_x11_error_p) { - if (si->using_xinput_extension) - init_xinput_extension(si); + const char *b = blurb(); + const char *p = progname; + fprintf (stderr, "\n%s: X ERROR! PLEASE REPORT THIS BUG!\n\n", b); + progname = b; + XmuPrintDefaultErrorMessage (dpy, event, stderr); + progname = p; + +# ifdef __GNUC__ + __extension__ /* don't warn about "string length is greater than the + length ISO C89 compilers are required to support". */ +# endif + fprintf (stderr, "\n" + "#######################################################################\n" + "\n" + " If at all possible, please re-run xscreensaver with the command\n" + " line arguments \"-sync -log log.txt\", and reproduce this bug.\n" + " Please include the complete \"log.txt\" file with your bug report.\n" + "\n" + " https://www.jwz.org/xscreensaver/bugs.html explains how to create\n" + " the most useful bug reports.\n" + "\n" + " The more information you can provide, the better. But please\n" + " report this bug, regardless!\n" + "\n" + "#######################################################################\n" + "\n" + "\n"); - if (p->verbose_p) - { - if (si->using_xinput_extension) - fprintf (stderr, - "%s: selecting events from %d XInputExtension devices.\n", - blurb(), si->num_xinput_devices); - else - fprintf (stderr, - "%s: not using XInputExtension.\n", - blurb()); - } - } -#endif + fflush (stderr); - if (!system_has_proc_interrupts_p) - { - si->using_proc_interrupts = False; - if (p->verbose_p && piwhy) - fprintf (stderr, "%s: not using /proc/interrupts: %s.\n", blurb(), - piwhy); - } - else if (p->verbose_p) - { - if (si->using_proc_interrupts) - fprintf (stderr, - "%s: consulting /proc/interrupts for keyboard activity.\n", - blurb()); - else - fprintf (stderr, - "%s: not consulting /proc/interrupts for keyboard activity.\n", - blurb()); + if (! ignore_x11_error_p) + abort(); } + + return 0; } -#ifdef DEBUG_MULTISCREEN +/* Check for other running instances of XScreenSaver, gnome-screensaver, etc. + */ static void -debug_multiscreen_timer (XtPointer closure, XtIntervalId *id) +ensure_no_screensaver_running (Display *dpy) { - saver_info *si = (saver_info *) closure; - saver_preferences *p = &si->prefs; - if (update_screen_layout (si)) + int screen, nscreens = ScreenCount (dpy); + + /* Silently ignore BadWindow race conditions. */ + Bool op = print_x11_error_p; + print_x11_error_p = False; + + for (screen = 0; screen < nscreens; screen++) { - if (p->verbose_p) + int i; + Window root = RootWindow (dpy, screen); + Window root2 = 0, parent = 0, *kids = 0; + unsigned int nkids = 0; + + if (! XQueryTree (dpy, root, &root2, &parent, &kids, &nkids)) + continue; + if (root != root2) + continue; + if (parent) + continue; + for (i = 0; i < nkids; i++) { - fprintf (stderr, "%s: new layout:\n", blurb()); - describe_monitor_layout (si); + Atom type; + int format; + unsigned long nitems, bytesafter; + unsigned char *version; + + if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_VERSION, 0, 1, + False, XA_STRING, &type, &format, &nitems, + &bytesafter, &version) + == Success + && type != None) + { + unsigned char *id; + if (XGetWindowProperty (dpy, kids[i], XA_SCREENSAVER_ID, 0, 512, + False, XA_STRING, &type, &format, + &nitems, &bytesafter, &id) + != Success + || type == None) + id = (unsigned char *) "???"; + + fprintf (stderr, + "%s: already running on display %s" + " (window 0x%x)\n from process %s\n", + blurb(), DisplayString (dpy), (int) kids [i], + (char *) id); + saver_exit (1); + } + else if (XGetWindowProperty (dpy, kids[i], XA_WM_COMMAND, 0, 128, + False, XA_STRING, &type, &format, + &nitems, &bytesafter, &version) + == Success + && type != None + && (!strcmp ((char *) version, "gnome-screensaver") || + !strcmp ((char *) version, "mate-screensaver") || + !strcmp ((char *) version, "cinnamon-screensaver"))) + { + fprintf (stderr, + "%s: \"%s\" is already running on display %s" + " (window 0x%x)\n", + blurb(), (char *) version, + DisplayString (dpy), (int) kids [i]); + saver_exit (1); + } } - resize_screensaver_window (si); + if (kids) XFree ((char *) kids); } - XtAppAddTimeOut (si->app, 1000*4, debug_multiscreen_timer, (XtPointer) si); -} -#endif /* DEBUG_MULTISCREEN */ + print_x11_error_p = op; +} -/* For the case where we aren't using an server extensions, select user events - on all the existing windows, and launch timers to select events on - newly-created windows as well. - If a server extension is being used, this does nothing. +/* Store a property on the root window indicating that xscreensaver is + running, and whether it is blanked or locked. This property is read + by "xscreensaver-command" and by ensure_no_screensaver_running(). + This property is also overwritten by "xscreensaver-gfx" to indicate + which screenhacks are running. */ static void -select_events (saver_info *si) +store_saver_status (Display *dpy, + Bool blanked_p, Bool locked_p, time_t blank_time) { - saver_preferences *p = &si->prefs; - int i; + /* The contents of XA_SCREENSAVER_STATUS has LOCK/BLANK/0 in the first slot, + the time at which that state began in the second slot, and the ordinal of + the running hacks on each screen (1-based) in subsequent slots. Since + we don't know the hacks here (or even how many monitors are attached) we + leave whatever was there before unchanged: it will be updated by + "xscreensaver-gfx". + + XA_SCREENSAVER_STATUS is stored on the (real) root window of screen 0. - if (si->using_xidle_extension || - si->using_mit_saver_extension || - si->using_sgi_saver_extension) - return; + XA_SCREENSAVER_VERSION and XA_SCREENSAVER_ID are stored on the unmapped + window created by the "xscreensaver" process. ClientMessage events are + sent to that window, and the responses are sent via the + XA_SCREENSAVER_RESPONSE property on it. - if (p->initial_delay) + These properties are not used on the windows created by "xscreensaver-gfx" + for use by the display hacks. + + See the different version of this function in windows.c. + */ + Window w = RootWindow (dpy, 0); /* always screen 0 */ + Atom type; + unsigned char *dataP = 0; + PROP32 *status = 0; + int format; + unsigned long nitems, bytesafter; + + /* Read the old property, so we can change just parts. */ + if (XGetWindowProperty (dpy, w, + XA_SCREENSAVER_STATUS, + 0, 999, False, XA_INTEGER, + &type, &format, &nitems, &bytesafter, + &dataP) + == Success + && type == XA_INTEGER + && nitems >= 3 + && dataP) + status = (PROP32 *) dataP; + + if (!status) /* There was no existing property */ { - if (p->verbose_p) - { - fprintf (stderr, "%s: waiting for %d second%s...", blurb(), - (int) p->initial_delay/1000, - (p->initial_delay == 1000 ? "" : "s")); - fflush (stderr); - fflush (stdout); - } - usleep (p->initial_delay); - if (p->verbose_p) - fprintf (stderr, " done.\n"); + nitems = 3; + status = (PROP32 *) malloc (nitems * sizeof(*status)); } - if (p->verbose_p) + status[0] = (PROP32) (locked_p ? XA_LOCK : blanked_p ? XA_BLANK : 0); + status[1] = (PROP32) blank_time; /* Y2038 bug: unsigned 32 bit time_t */ + XChangeProperty (dpy, w, XA_SCREENSAVER_STATUS, XA_INTEGER, 32, + PropModeReplace, (unsigned char *) status, nitems); + XSync (dpy, False); + +# if 0 + if (debug_p && verbose_p) { - fprintf (stderr, "%s: selecting events on extant windows...", blurb()); - fflush (stderr); - fflush (stdout); + int i; + fprintf (stderr, "%s: wrote status property: 0x%lx: %s", blurb(), + (unsigned long) w, + (status[0] == XA_LOCK ? "LOCK" : + status[0] == XA_BLANK ? "BLANK" : + status[0] == 0 ? "0" : "???")); + for (i = 1; i < nitems; i++) + fprintf (stderr, ", %lu", status[i]); + fprintf (stderr, "\n"); + if (system ("xprop -root _SCREENSAVER_STATUS") <= 0) + fprintf (stderr, "%s: xprop exec failed\n", blurb()); } +# endif /* 0 */ - /* Select events on the root windows of every screen. This also selects - for window creation events, so that new subwindows will be noticed. - */ - for (i = 0; i < si->nscreens; i++) + if (status != (PROP32 *) dataP) + free (status); + if (dataP) + XFree (dataP); +} + + +/* This process does not map any windows on the screen. However, it creates + one hidden window on screen 0, which is the rendezvous point for + communication with xscreensaver-command: that window is how it can tell + that XScreenSaver is running, what the version number is, and it is where + bidirectional ClientMessage communication takes place. Since there are + no "blanking" windows around at all when xscreensaver-gfx is not running, + this window is needed. We could have instead re-tooled xscreensaver-command + to do all of its communication through the root window instead, but this + seemed easier. + */ +static void +create_daemon_window (Display *dpy) +{ + XClassHint class_hints; + XSetWindowAttributes attrs; + unsigned long attrmask = 0; + const char *name = "???"; + const char *host = "???"; + char buf[20]; + pid_t pid = getpid(); + struct passwd *p = getpwuid (getuid ()); + time_t now = time ((time_t *) 0); + char *id; +# ifdef HAVE_UNAME + struct utsname uts; +# endif + + if (p && p->pw_name && *p->pw_name) + name = p->pw_name; + else if (p) { - saver_screen_info *ssi = &si->screens[i]; - if (ssi->real_screen_p) - start_notice_events_timer (si, - RootWindowOfScreen (si->screens[i].screen), False); + sprintf (buf, "%lu", (unsigned long) p->pw_uid); + name = buf; } + else + name = "???"; - if (p->verbose_p) - fprintf (stderr, " done.\n"); - -# ifdef DEBUG_MULTISCREEN - if (p->debug_p) debug_multiscreen_timer ((XtPointer) si, 0); +# ifdef HAVE_UNAME + { + if (! uname (&uts)) + host = uts.nodename; + } # endif + + class_hints.res_name = (char *) progname; /* not const? */ + class_hints.res_class = "XScreenSaver"; + id = (char *) malloc (strlen(name) + strlen(host) + 50); + sprintf (id, "%lu (%s@%s)", (unsigned long) pid, name, host); + + attrmask = CWOverrideRedirect | CWEventMask; + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask; + + daemon_window = XCreateWindow (dpy, RootWindow (dpy, 0), + 0, 0, 1, 1, 0, + DefaultDepth (dpy, 0), InputOutput, + DefaultVisual (dpy, 0), attrmask, &attrs); + XStoreName (dpy, daemon_window, "XScreenSaver Daemon"); + XSetClassHint (dpy, daemon_window, &class_hints); + XChangeProperty (dpy, daemon_window, XA_WM_COMMAND, XA_STRING, + 8, PropModeReplace, (unsigned char *) progname, + strlen (progname)); + XChangeProperty (dpy, daemon_window, XA_SCREENSAVER_VERSION, XA_STRING, + 8, PropModeReplace, (unsigned char *) version_number, + strlen (version_number)); + XChangeProperty (dpy, daemon_window, XA_SCREENSAVER_ID, XA_STRING, + 8, PropModeReplace, (unsigned char *) id, strlen (id)); + + store_saver_status (dpy, False, False, now); + free (id); } -void -maybe_reload_init_file (saver_info *si) +static const char * +grab_string (int status) { - saver_preferences *p = &si->prefs; - if (init_file_changed_p (p)) + switch (status) { + case GrabSuccess: return "GrabSuccess"; + case AlreadyGrabbed: return "AlreadyGrabbed"; + case GrabInvalidTime: return "GrabInvalidTime"; + case GrabNotViewable: return "GrabNotViewable"; + case GrabFrozen: return "GrabFrozen"; + default: { - if (p->verbose_p) - fprintf (stderr, "%s: file \"%s\" has changed, reloading.\n", - blurb(), init_file_name()); - - load_init_file (si->dpy, p); - - /* If a server extension is in use, and p->timeout has changed, - we need to inform the server of the new timeout. */ - disable_builtin_screensaver (si, False); - - /* If the DPMS settings in the init file have changed, - change the settings on the server to match. */ - sync_server_dpms_settings (si->dpy, - (p->dpms_enabled_p && - p->mode != DONT_BLANK), - p->dpms_quickoff_p, - p->dpms_standby / 1000, - p->dpms_suspend / 1000, - p->dpms_off / 1000, - False); + static char buf[255]; + sprintf(buf, "unknown status: %d", status); + return buf; } + } +} + +static int +grab_kbd (Screen *screen) +{ + Display *dpy = DisplayOfScreen (screen); + Window w = RootWindowOfScreen (screen); + int status = XGrabKeyboard (dpy, w, True, GrabModeAsync, GrabModeAsync, + CurrentTime); + if (verbose_p) + fprintf (stderr, "%s: grabbing keyboard on 0x%lx: %s\n", + blurb(), (unsigned long) w, grab_string (status)); + return status; } -/* Loop forever: +static int +grab_mouse (Screen *screen, Cursor cursor) +{ + Display *dpy = DisplayOfScreen (screen); + Window w = RootWindowOfScreen (screen); + int status = XGrabPointer (dpy, w, True, + (ButtonPressMask | ButtonReleaseMask | + EnterWindowMask | LeaveWindowMask | + PointerMotionMask | PointerMotionHintMask | + Button1MotionMask | Button2MotionMask | + Button3MotionMask | Button4MotionMask | + Button5MotionMask | ButtonMotionMask), + GrabModeAsync, GrabModeAsync, w, + cursor, CurrentTime); + if (verbose_p) + fprintf (stderr, "%s: grabbing mouse on 0x%lx... %s\n", + blurb(), (unsigned long) w, grab_string (status)); + return status; +} - - wait until the user is idle; - - blank the screen; - - wait until the user is active; - - unblank the screen; - - repeat. - */ static void -main_loop (saver_info *si) +ungrab_kbd (Display *dpy) { - saver_preferences *p = &si->prefs; - Bool ok_to_unblank; - int i; + if (verbose_p) + fprintf (stderr, "%s: ungrabbing keyboard\n", blurb()); + XUngrabKeyboard (dpy, CurrentTime); +} - while (1) - { - Bool was_locked = False; - - if (p->verbose_p) - fprintf (stderr, "%s: awaiting idleness.\n", blurb()); - - check_for_leaks ("unblanked A"); - sleep_until_idle (si, True); - check_for_leaks ("unblanked B"); - - if (p->verbose_p) - { - if (si->demoing_p) - fprintf (stderr, "%s: demoing %d at %s.\n", blurb(), - si->selection_mode, timestring(0)); - else - fprintf (stderr, "%s: blanking screen at %s.\n", blurb(), - timestring(0)); - } - - maybe_reload_init_file (si); - - /* Treat DONT_BLANK as BLANK_ONLY in emergency-lock when locking - is enabled. */ - - if (p->mode == DONT_BLANK && - (!si->emergency_lock_p || - !p->lock_p || - si->locking_disabled_p)) - { - if (p->verbose_p) - fprintf (stderr, "%s: idle with blanking disabled at %s.\n", - blurb(), timestring(0)); - /* Go around the loop and wait for the next bout of idleness, - or for the init file to change, or for a remote command to - come in, or something. +static void +ungrab_mouse (Display *dpy) +{ + if (verbose_p) + fprintf (stderr, "%s: ungrabbing mouse\n", blurb()); + XUngrabPointer (dpy, CurrentTime); +} - But, if locked_p is true, go ahead. This can only happen - if we're in "disabled" mode but a "lock" clientmessage came - in: in that case, we should go ahead and blank/lock the screen. - */ - if (!si->locked_p) - continue; - } - /* Since we're about to blank the screen, kill the de-race timer, - if any. It might still be running if we have unblanked and then - re-blanked in a short period (e.g., when using the "next" button - in xscreensaver-demo.) - */ - if (si->de_race_id) - { - if (p->verbose_p) - fprintf (stderr, "%s: stopping de-race timer (%d remaining.)\n", - blurb(), si->de_race_ticks); - XtRemoveTimeOut (si->de_race_id); - si->de_race_id = 0; - } +/* Some remote desktop clients (e.g., "rdesktop") hold the keyboard GRABBED the + whole time they have focus! This is idiotic because the whole point of + grabbing is to get events when you do *not* have focus, so grabbing only + when* you have focus is redundant. Anyway, that prevents us from getting a + keyboard grab. It turns out that for some of these apps, de-focusing them + forces them to release their grab. + So if we fail to grab the keyboard four times in a row, we forcibly set + focus to "None" and try four more times. We don't touch focus unless we're + already having a hard time getting a grab. + */ +static void +nuke_focus (Screen *screen) +{ + Display *dpy = DisplayOfScreen (screen); + Window focus = 0; + int rev = 0; - /* Now, try to blank. - */ + XGetInputFocus (dpy, &focus, &rev); - if (! blank_screen (si)) - { - /* We were unable to grab either the keyboard or mouse. - This means we did not (and must not) blank the screen. - If we were to blank the screen while some other program - is holding both the mouse and keyboard grabbed, then - we would never be able to un-blank it! We would never - see any events, and the display would be wedged. - - In particular, without that keyboard grab, we will be - unable to ever read keypresses on the unlock dialog. - You can't unlock if you can't type your password. - - So, just go around the loop again and wait for the - next bout of idleness. (If the user remains idle, we - will next try to blank the screen again in no more than - 60 seconds.) - */ - Time retry = 60 * 1000; - if (p->timeout < retry) - retry = p->timeout; - - if (p->debug_p) - { - fprintf (stderr, - "%s: DEBUG MODE: unable to grab -- BLANKING ANYWAY.\n", - blurb()); - } - else - { - fprintf (stderr, - "%s: unable to grab keyboard or mouse! Blanking aborted.\n", - blurb()); + if (verbose_p) + { + char w[255], r[255]; - /* Since we were unable to blank, clearly we're not locked, - but we might have been prematurely marked as locked by - the LOCK ClientMessage. */ - if (si->locked_p) - set_locked_p (si, False); + if (focus == PointerRoot) strcpy (w, "PointerRoot"); + else if (focus == None) strcpy (w, "None"); + else sprintf (w, "0x%lx", (unsigned long) focus); - schedule_wakeup_event (si, retry, p->debug_p); - continue; - } - } + if (rev == RevertToParent) strcpy (r, "RevertToParent"); + else if (rev == RevertToPointerRoot) strcpy (r, "RevertToPointerRoot"); + else if (rev == RevertToNone) strcpy (r, "RevertToNone"); + else sprintf (r, "0x%x", rev); - for (i = 0; i < si->nscreens; i++) - kill_screenhack (&si->screens[i]); + fprintf (stderr, "%s: removing focus from %s / %s\n", + blurb(), w, r); + } - raise_window (si, True, True, False); - if (si->throttled_p) - fprintf (stderr, "%s: not launching hack (throttled.)\n", blurb()); - else - for (i = 0; i < si->nscreens; i++) - spawn_screenhack (&si->screens[i]); + XSetInputFocus (dpy, None, RevertToNone, CurrentTime); +} - /* If we are blanking only, optionally power down monitor right now. */ - if (p->mode == BLANK_ONLY && - p->dpms_enabled_p && - p->dpms_quickoff_p) - { - sync_server_dpms_settings (si->dpy, True, - p->dpms_quickoff_p, - p->dpms_standby / 1000, - p->dpms_suspend / 1000, - p->dpms_off / 1000, - False); - monitor_power_on (si, False); - } - /* Don't start the cycle timer in demo mode. */ - if (!si->demoing_p && p->cycle) - si->cycle_id = XtAppAddTimeOut (si->app, - (si->selection_mode - /* see comment in cycle_timer() */ - ? 1000 * 60 * 60 - : p->cycle), - cycle_timer, - (XtPointer) si); +static void +ungrab_keyboard_and_mouse (Display *dpy) +{ + ungrab_mouse (dpy); + ungrab_kbd (dpy); +} -#ifndef NO_LOCKING - /* Maybe start locking the screen. - */ - { - Time lock_timeout = p->lock_timeout; +/* Returns true if it succeeds. + */ +static Bool +grab_keyboard_and_mouse_real (Screen *screen) +{ + Display *dpy = DisplayOfScreen (screen); + Status mstatus = 0, kstatus = 0; + int i; + int retries = 4; + Bool focus_fuckus = False; - /* If we're fading, don't lock until the fade finishes. */ - if (si->fading_possible_p && p->fade_p) - lock_timeout += p->fade_seconds / 1000; + AGAIN: - if (si->emergency_lock_p) - lock_timeout = 0; + for (i = 0; i < retries; i++) + { + XSync (dpy, False); + kstatus = grab_kbd (screen); + if (kstatus == GrabSuccess) + break; - if (si->emergency_lock_p && p->lock_p && lock_timeout) - { - int secs = p->lock_timeout / 1000; - if (p->verbose_p) - fprintf (stderr, - "%s: locking now, instead of waiting for %d:%02d:%02d.\n", - blurb(), - (secs / (60 * 60)), ((secs / 60) % 60), (secs % 60)); - lock_timeout = 0; - } + /* else, wait a second and try to grab again. */ + sleep (1); + } - si->emergency_lock_p = False; - - if (!si->demoing_p && /* if not going into demo mode */ - p->lock_p && /* and locking is enabled */ - !si->locking_disabled_p && /* and locking is possible */ - lock_timeout == 0) /* and locking is not timer-deferred */ - set_locked_p (si, True); /* then lock right now. */ - - /* locked_p might be true already because of the above, or because of - the LOCK ClientMessage. But if not, and if we're supposed to lock - after some time, set up a timer to do so. - */ - if (p->lock_p && - !si->locked_p && - lock_timeout > 0) - si->lock_id = XtAppAddTimeOut (si->app, lock_timeout, - activate_lock_timer, - (XtPointer) si); - } -#endif /* !NO_LOCKING */ + if (kstatus != GrabSuccess) + { + fprintf (stderr, "%s: couldn't grab keyboard: %s\n", + blurb(), grab_string (kstatus)); + if (! focus_fuckus) + { + focus_fuckus = True; + nuke_focus (screen); + goto AGAIN; + } + } - ok_to_unblank = True; - do { + for (i = 0; i < retries; i++) + { + XSync (dpy, False); + mstatus = grab_mouse (screen, blank_cursor); + if (mstatus == GrabSuccess) + break; - check_for_leaks ("blanked A"); - sleep_until_idle (si, False); /* until not idle */ - check_for_leaks ("blanked B"); + /* else, wait a second and try to grab again. */ + sleep (1); + } - maybe_reload_init_file (si); + if (mstatus != GrabSuccess) + fprintf (stderr, "%s: couldn't grab pointer: %s\n", + blurb(), grab_string (mstatus)); + + + /* When should we allow blanking to proceed? The current theory + is that a keyboard grab is mandatory; a mouse grab is optional. + + - If we don't have a keyboard grab, then we won't be able to + read a password to unlock, so the kbd grab is mandatory. + (We can't conditionalize this on locked_p, because someone + might run "xscreensaver-command -lock" at any time.) + + - If we don't have a mouse grab, then we might not see mouse + clicks as a signal to unblank -- but we will still see kbd + activity, so that's not a disaster. + + If the mouse grab failed with AlreadyGrabbed, then I *think* + that means that we will still see the mouse events via XInput2. + But if it failed with GrabFrozen, that means that the grabber + used GrabModeSync, and we will only receive those mouse events + as a replay after they release the grab, which doesn't help us. + + If the keyboard grab failed with AlreadyGrabbed rather than + GrabFrozen then we may still get those keypresses -- but so will + the program holding the grab, so that's unacceptable for our + purpose of reading passwords. + + It has been suggested that we should allow blanking if locking + is disabled, and we have a mouse grab but no keyboard grab. + That would allow screen blanking (but not locking) while the gdm + login screen had the keyboard grabbed, but one would have to use + the mouse to unblank. Keyboard characters would go to the gdm + login field without unblanking. I have not made this change + because I'm not completely convinced it is a safe thing to do. + */ -#ifndef NO_LOCKING - /* Maybe unlock the screen. - */ - if (si->locked_p) - { - saver_screen_info *ssi = si->default_screen; - if (si->locking_disabled_p) abort (); + if (kstatus != GrabSuccess) /* Do not blank without a kbd grab. */ + { + /* If we didn't get both grabs, release the one we did get. */ + ungrab_keyboard_and_mouse (dpy); + return False; + } - was_locked = True; - si->dbox_up_p = True; - if (p->dpms_full_throttle_p) - { - for (i = 0; i < si->nscreens; i++) - if (si->screens[i].pid == 0) - spawn_screenhack (&si->screens[i]); - usleep(100000); - } - else - for (i = 0; i < si->nscreens; i++) - suspend_screenhack (&si->screens[i], True); /* suspend */ - XUndefineCursor (si->dpy, ssi->screensaver_window); + return True; /* Grab is good, go ahead and blank. */ +} - ok_to_unblank = unlock_p (si); - si->dbox_up_p = False; - XDefineCursor (si->dpy, ssi->screensaver_window, ssi->cursor); - for (i = 0; i < si->nscreens; i++) - suspend_screenhack (&si->screens[i], False); /* resume */ +static Bool +grab_keyboard_and_mouse (Screen *screen) +{ + Bool ret; + char op[10]; + char *args[3]; + args[0] = external_ungrab_command; /* Because C89 */ + args[1] = op; + args[2] = NULL; + if (external_ungrab_command && *external_ungrab_command) + { + snprintf (op, sizeof(op), "pre"); + exec_and_wait (DisplayOfScreen(screen), 2, args); + } + ret = grab_keyboard_and_mouse_real (screen); + if (external_ungrab_command && *external_ungrab_command) + { + snprintf (op, sizeof(op), "post"); + exec_and_wait (DisplayOfScreen(screen), 2, args); + } + return ret; +} - if (!ok_to_unblank && - !screenhack_running_p (si)) - { - /* If the lock dialog has been dismissed and we're not about to - unlock the screen, and there is currently no hack running, - then launch one. (There might be no hack running if DPMS - had kicked in. But DPMS is off now, so bring back the hack) - */ - if (si->cycle_id) - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - cycle_timer ((XtPointer) si, 0); - } - } -#endif /* !NO_LOCKING */ - } while (!ok_to_unblank); +/* Which screen is the mouse on? + */ +static Screen * +mouse_screen (Display *dpy) +{ + int i, nscreens = ScreenCount (dpy); + if (nscreens > 1) + for (i = 0; i < nscreens; i++) + { + Window pointer_root, pointer_child; + int root_x, root_y, win_x, win_y; + unsigned int mask; + int status = XQueryPointer (dpy, RootWindow (dpy, i), + &pointer_root, &pointer_child, + &root_x, &root_y, &win_x, &win_y, &mask); + if (status != None) + { + if (verbose_p) + fprintf (stderr, "%s: mouse is on screen %d of %d\n", + blurb(), i, nscreens); + return ScreenOfDisplay (dpy, i); + } + } + return ScreenOfDisplay (dpy, 0); +} - if (p->verbose_p) - fprintf (stderr, "%s: unblanking screen at %s.\n", - blurb(), timestring (0)); - /* Kill before unblanking, to stop drawing as soon as possible. */ - for (i = 0; i < si->nscreens; i++) - kill_screenhack (&si->screens[i]); - unblank_screen (si); +static void +maybe_disable_locking (Display *dpy) +{ + const char *why = 0; - set_locked_p (si, False); - si->emergency_lock_p = False; - si->demoing_p = 0; - si->selection_mode = 0; +# ifdef NO_LOCKING + why = "locking disabled at compile time"; +# endif - /* If we're throttled, and the user has explicitly unlocked the screen, - then unthrottle. If we weren't locked, then don't unthrottle - automatically, because someone might have just bumped the desk... */ - if (was_locked) - { - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - } + if (!why) + { + uid_t u = getuid(); + if (u == 0 || u == (uid_t) -1 || u == (uid_t) -2) + why = "cannot lock when running as root"; + } - if (si->cycle_id) - { - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - } + if (!why && getenv ("RUNNING_UNDER_GDM")) + /* Launched as GDM's "Background" program */ + why = "cannot lock when running under GDM"; - if (si->lock_id) - { - XtRemoveTimeOut (si->lock_id); - si->lock_id = 0; - } + /* X11 grabs don't work under Wayland's embedded X11 server. The Wayland + window manager lives at a higher level than the X11 emulation layer. */ + if (!why && getenv ("WAYLAND_DISPLAY")) + why = "cannot lock securely under Wayland"; -# ifdef HAVE_LIBSYSTEMD - /* This might be a good spot to re-launch si->systemd_pid - if it has died unexpectedly. Which shouldn't happen. */ -# endif + if (!why) + { + /* Grabs under the macOS XDarwin server only affect other X11 programs: + you can Cmd-Tab to "Terminal". These extensions exist on later + releases XQuartz. */ + int op = 0, event = 0, error = 0; + if (XQueryExtension (dpy, "Apple-DRI", &op, &event, &error) || + XQueryExtension (dpy, "Apple-WM", &op, &event, &error)) + why = "cannot lock securely under macOS X11"; + } - /* Since we're unblanked now, break race conditions and make - sure we stay that way (see comment in timers.c.) */ - if (! si->de_race_id) - de_race_timer ((XtPointer) si, 0); + if (why) + { + if (debug_p) + { + fprintf (stderr, "%s: %s\n", blurb(), why); + fprintf (stderr, "%s: DEBUG MODE: allowing locking anyway!\n", + blurb()); + } + else + { + locking_disabled_p = True; + if (lock_p || verbose_p) + fprintf (stderr, "%s: locking disabled: %s\n", blurb(), why); + } } } -static void analyze_display (saver_info *si); -static void fix_fds (void); -int -main (int argc, char **argv) +static void +main_loop (Display *dpy) { - Widget shell; - saver_info the_si; - saver_info *si = &the_si; - saver_preferences *p = &si->prefs; - struct passwd *spasswd; - int i; + int xi_opcode; + time_t now = time ((time_t *) 0); + time_t active_at = now; + time_t blanked_at = 0; + time_t ignore_activity_before = now; + time_t last_checked_init_file = now; + Bool authenticated_p = False; + Bool ignore_motion_p = False; - /* It turns out that if we do setlocale (LC_ALL, "") here, people - running in Japanese locales get font craziness on the password - dialog, presumably because it is displaying Japanese characters - in a non-Japanese font. However, if we don't call setlocale() - at all, then XLookupString() never returns multi-byte UTF-8 - characters when people type non-Latin1 characters on the - keyboard. + enum { UNBLANKED, BLANKED, LOCKED, AUTH } current_state = UNBLANKED; - The current theory (and at this point, I'm really guessing!) is - that using LC_CTYPE instead of LC_ALL will make XLookupString() - behave usefully, without having the side-effect of screwing up - the fonts on the unlock dialog. + struct { time_t time; int x, y; } last_mouse = { 0, 0, 0 }; - See https://bugs.launchpad.net/ubuntu/+source/xscreensaver/+bug/671923 - from comment #20 onward. + maybe_disable_locking (dpy); + init_xscreensaver_atoms (dpy); + ensure_no_screensaver_running (dpy); - -- jwz, 24-Sep-2011 - */ -#ifdef ENABLE_NLS - if (!setlocale (LC_CTYPE, "")) - fprintf (stderr, "%s: warning: could not set default locale\n", - progname); + if (! init_xinput (dpy, &xi_opcode)) + saver_exit (1); - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - textdomain (GETTEXT_PACKAGE); -#endif /* ENABLE_NLS */ + create_daemon_window (dpy); - memset(si, 0, sizeof(*si)); - global_si_kludge = si; /* I hate C so much... */ + handle_signals(); - fix_fds(); + blank_cursor = None; /* Cursor of window under mouse (which is blank). */ + auth_cursor = XCreateFontCursor (dpy, XC_top_left_arrow); -# undef ya_rand_init - ya_rand_init (0); + if (strchr (version_number, 'a') || strchr (version_number, 'b')) + splash_p = True; /* alpha and beta releases */ - save_argv (argc, argv); - set_version_string (si, &argc, argv); - privileged_initialization (si, &argc, argv); - hack_environment (si); + /* Launch "xscreensaver-auth" once at startup with either --splash or --init, + The latter to handle the OOM-killer while setuid. */ + { + char *av[10]; + int ac = 0; + av[ac++] = SAVER_AUTH_PROGRAM; + av[ac++] = (splash_p ? "--splash" : "--init"); + if (verbose_p) av[ac++] = "--verbose"; + if (verbose_p > 1) av[ac++] = "--verbose"; + if (verbose_p > 2) av[ac++] = "--verbose"; + if (debug_p) av[ac++] = "--debug"; + av[ac] = 0; + saver_auth_pid = fork_and_exec (dpy, ac, av); + } - spasswd = getpwuid(getuid()); - if (!spasswd) - { - fprintf(stderr, "Could not figure out who the current user is!\n"); - return 1; - } +# ifdef HAVE_LIBSYSTEMD + /* Launch xscreensaver-systemd at startup. */ + { + char *av[10]; + int ac = 0; + av[ac++] = SAVER_SYSTEMD_PROGRAM; + if (verbose_p || debug_p) + av[ac++] = "--verbose"; + av[ac] = 0; + saver_systemd_pid = fork_and_exec (dpy, ac, av); + } +# endif /* HAVE_LIBSYSTEMD */ - si->user = strdup(spasswd->pw_name ? spasswd->pw_name : "(unknown)"); -# ifndef NO_LOCKING - si->unlock_cb = gui_auth_conv; - si->auth_finished_cb = auth_finished_cb; -# endif /* !NO_LOCKING */ + /* X11 errors during startup initialization were fatal. + Once we enter the main loop, they are printed but ignored. + */ + XSync (dpy, False); + ignore_x11_error_p = True; - shell = connect_to_server (si, &argc, argv); - process_command_line (si, &argc, argv); - stderr_log_file (si); - print_banner (si); + /************************************************************************ + Main loop + ************************************************************************/ - load_init_file(si->dpy, p); /* must be before initialize_per_screen_info() */ - blurb_timestamp_p = p->timestamp_p; /* kludge */ - initialize_per_screen_info (si, shell); /* also sets si->fading_possible_p */ + while (1) + { + Bool force_blank_p = False; + Bool force_lock_p = False; + Atom blank_mode = 0; + char blank_mode_arg[20] = { 0 }; - /* We can only issue this warning now. */ - if (p->verbose_p && !si->fading_possible_p && (p->fade_p || p->unfade_p)) - fprintf (stderr, - "%s: there are no PseudoColor or GrayScale visuals.\n" - "%s: ignoring the request for fading/unfading.\n", - blurb(), blurb()); + /* Wait until an event comes in, or a timeout. */ + { + int xfd = ConnectionNumber (dpy); + fd_set in_fds; + struct timeval tv; + time_t until; + switch (current_state) { + case UNBLANKED: until = active_at + blank_timeout; break; + case BLANKED: until = blanked_at + lock_timeout; break; + default: until = 0; + } - for (i = 0; i < si->nscreens; i++) - { - saver_screen_info *ssi = &si->screens[i]; - if (ssi->real_screen_p) - if (ensure_no_screensaver_running (si->dpy, si->screens[i].screen)) - exit (1); - ssi->current_hack = -1; - } + tv.tv_sec = 0; + tv.tv_usec = 0; + if (until >= now) + tv.tv_sec = until - now; + + if (verbose_p > 3) + { + if (!tv.tv_sec) + fprintf (stderr, "%s: block until input\n", blurb()); + else + { + struct tm tm; + time_t t = now + tv.tv_sec; + localtime_r (&t, &tm); + fprintf (stderr, + "%s: block for %ld sec until %02d:%02d:%02d\n", + blurb(), tv.tv_sec, tm.tm_hour, tm.tm_min, tm.tm_sec); + } + } - lock_initialization (si, &argc, argv); - print_lock_failure_banner (si); + FD_ZERO (&in_fds); + FD_SET (xfd, &in_fds); + select (xfd + 1, &in_fds, NULL, NULL, (tv.tv_sec ? &tv : NULL)); + } - if (p->xsync_p) XSynchronize (si->dpy, True); + now = time ((time_t *) 0); - if (p->verbose_p) analyze_display (si); - initialize_server_extensions (si); - si->blank_time = time ((time_t *) 0); /* must be before ..._window */ - initialize_screensaver_window (si); + /******************************************************************** + Signal handlers + ********************************************************************/ - select_events (si); - init_sigchld (); - disable_builtin_screensaver (si, True); - sync_server_dpms_settings (si->dpy, - (p->dpms_enabled_p && - p->mode != DONT_BLANK), - p->dpms_quickoff_p, - p->dpms_standby / 1000, - p->dpms_suspend / 1000, - p->dpms_off / 1000, - False); + /* If a signal handler fired, it set a flag, and that caused select() + to return. Now that we are back on the program stack, handle + those signals. */ - initialize_stderr (si); - handle_signals (si); + /* SIGHUP is the same as "xscreensaver-command -restart". */ + if (sighup_received) + { + sighup_received = 0; + if (current_state == LOCKED) + { + fprintf (stderr, + "%s: SIGHUP received while locked: ignoring\n", + blurb()); + } + else + { + fprintf (stderr, "%s: SIGHUP received: restarting\n", blurb()); + restart_process(); /* Does not return */ + fprintf (stderr, "%s: SIGHUP RESTART FAILED!\n", blurb()); + } + } - store_saver_status (si); /* for xscreensaver-command -status */ + /* Upon SIGTERM, SIGINT or SIGQUIT we must kill our subprocesses + before exiting. + */ + if (sigterm_received) + { + int sig = sigterm_received; /* Same handler for all 3 signals */ + const char *sn = (sig == SIGINT ? "SIGINT" : + sig == SIGQUIT ? "SIGQUIT" : "SIGTERM"); + sigterm_received = 0; + fprintf (stderr, "%s: %s received%s: exiting\n", blurb(), sn, + (current_state == LOCKED ? " while locked" : "")); + + /* Rather than calling saver_exit(), set our SIGTERM handler back to + the default and re-signal it so that this process actually dies + with a signal. */ + signal (sig, SIG_DFL); + kill_all_subprocs(); + kill (getpid(), sig); + abort(); + } -# ifdef HAVE_LIBSYSTEMD /* Launch it in the background */ - si->systemd_pid = fork_and_exec_1 (si, 0, "xscreensaver-systemd"); -# endif + /* SIGCHLD is fired any time one of our subprocesses dies. + When "xscreensaver-auth" dies, it analyzes its exit code. + */ + if (sigchld_received) + authenticated_p = handle_sigchld (dpy, current_state != UNBLANKED); - make_splash_dialog (si); + /* Now process any outstanding X11 events on the queue: user activity + from XInput, and ClientMessages from xscreensaver-command. + */ + while (XPending (dpy)) + { + XEvent xev; + XNextEvent (dpy, &xev); + now = time ((time_t *) 0); + + /**************************************************************** + Client Messages + Both xscreensaver and xscreensaver-gfx handle these; some are + handled exclusively by one program or another, and some of them + (select, next, prev) are handled by xscreensaver only if + xscreensaver-gfx is not already running. + ****************************************************************/ + + switch (xev.xany.type) { + case ClientMessage: + { + Atom msg = xev.xclient.data.l[0]; - main_loop (si); /* doesn't return */ - return 0; -} + /* Re-load the .xscreensaver file before processing the results + of any ClientMessage, in case they just changed the timeouts. + */ + last_checked_init_file = 0; -static void -fix_fds (void) -{ - /* Bad Things Happen if stdin, stdout, and stderr have been closed - (as by the `sh incantation "xscreensaver >&- 2>&-"). When you do - that, the X connection gets allocated to one of these fds, and - then some random library writes to stderr, and random bits get - stuffed down the X pipe, causing "Xlib: sequence lost" errors. - So, we cause the first three file descriptors to be open to - /dev/null if they aren't open to something else already. This - must be done before any other files are opened (or the closing - of that other file will again free up one of the "magic" first - three FDs.) + if (! (xev.xclient.message_type == XA_SCREENSAVER && + xev.xclient.format == 32 && + xev.xclient.data.l[0])) + { + goto BAD_CM; + } + else if (msg == XA_ACTIVATE || + msg == XA_SELECT || + msg == XA_DEMO || + msg == XA_NEXT || + msg == XA_PREV) + { + /* The others are the same as -activate except that they + cause some extra args to be added to the xscreensaver-gfx + command line. + */ + if (msg != XA_ACTIVATE) + blank_mode = msg; + if (msg == XA_SELECT || msg == XA_DEMO) + { /* see remote.c */ + unsigned long n = xev.xclient.data.l[1]; + if (n == 5000) n = xev.xclient.data.l[2]; + sprintf (blank_mode_arg, "%lu", n); + } + + if (msg == XA_DEMO) ignore_motion_p = True; + + if (current_state == UNBLANKED) + { + force_blank_p = True; + ignore_activity_before = now + 2; + clientmessage_response (dpy, &xev, True, "blanking"); + } + else if (msg == XA_SELECT || + msg == XA_NEXT || + msg == XA_PREV) + { + /* When active, these are handled by xscreensaver-gfx + instead of xscreensaver, so silently ignore them, + and allow xscreensaver-gfx to reply instead. */ + } + else + clientmessage_response (dpy, &xev, False, + "already active"); + } + else if (msg == XA_CYCLE) + { + if (current_state == UNBLANKED) + /* Only allowed when screen already blanked */ + clientmessage_response (dpy, &xev, False, "not blanked"); + /* else xscreensaver-gfx will respond to this. */ + } + else if (msg == XA_DEACTIVATE) + { + if (current_state == UNBLANKED) + { + clientmessage_response (dpy, &xev, True, + "already inactive, resetting activity time"); + active_at = now; + ignore_activity_before = now; + } + else + { + /* This behaves just like user input: if state is + LOCKED, it will advance to AUTH. */ + active_at = now; + ignore_activity_before = now; + clientmessage_response (dpy, &xev, True, "deactivating"); + } + + /* DEACTIVATE while inactive also needs to reset the + server's DPMS time, but doing that here would mean + linking with additional libraries, doing additional X + protocol, and also some finicky error handling, since + the DPMS extension is a pain in the ass. So instead, + I made xscreensaver-command do that instead. This + somewhat breaks the abstraction of ClientMessage + handling, but it's more robust. */ + } + else if (msg == XA_LOCK) + { + if (locking_disabled_p) + clientmessage_response (dpy, &xev, False, + "locking disabled"); + else if (current_state == UNBLANKED || + current_state == BLANKED) + { + force_lock_p = True; + ignore_activity_before = now + 2; + clientmessage_response (dpy, &xev, True, "locking"); + } + else + clientmessage_response (dpy, &xev, False, + "already locked"); + } + else if (msg == XA_SUSPEND) + { + force_blank_p = True; + if (lock_p) force_lock_p = True; + ignore_activity_before = now + 2; + blank_mode = msg; + clientmessage_response (dpy, &xev, True, "suspending"); + } + else if (msg == XA_EXIT) + { + if (current_state == UNBLANKED || + current_state == BLANKED) + { + clientmessage_response (dpy, &xev, True, "exiting"); + XSync (dpy, False); + saver_exit (0); + } + else + clientmessage_response (dpy, &xev, False, + "screen is locked"); + } + else if (msg == XA_RESTART) + { + if (current_state == UNBLANKED || + current_state == BLANKED) + { + clientmessage_response (dpy, &xev, True, "restarting"); + XSync (dpy, False); + restart_process(); /* Does not return */ + fprintf (stderr, "%s: RESTART FAILED!\n", blurb()); + } + else + clientmessage_response (dpy, &xev, False, + "screen is locked"); + } + else + { + BAD_CM: + if (verbose_p) + { + Atom type; + char *tstr, *name; + Bool op = print_x11_error_p; + print_x11_error_p = False; /* Ignore BadAtom */ + type = xev.xclient.message_type; + tstr = type ? XGetAtomName (dpy, type) : 0; + name = msg ? XGetAtomName (dpy, msg) : 0; + fprintf (stderr, + "%s: unrecognized ClientMessage %s %s\n", + blurb(), + (tstr ? tstr : "???"), + (name ? name : "???")); + if (tstr) XFree (tstr); + if (name) XFree (name); + print_x11_error_p = op; + } + } + continue; + } - We do this by opening /dev/null three times, and then closing - those fds, *unless* any of them got allocated as #0, #1, or #2, - in which case we leave them open. Gag. - Really, this crap is technically required of *every* X program, - if you want it to be robust in the face of "2>&-". - */ - int fd0 = open ("/dev/null", O_RDWR); - int fd1 = open ("/dev/null", O_RDWR); - int fd2 = open ("/dev/null", O_RDWR); - if (fd0 > 2) close (fd0); - if (fd1 > 2) close (fd1); - if (fd2 > 2) close (fd2); -} + /**************************************************************** + Normal X11 keyboard and mouse events + ****************************************************************/ + + /* XInput2 "raw" events bypass X11 grabs, but grabs take priority. + If this process has the keyboard and mouse grabbed, it receives + the following events: + + - X11 KeyPress, KeyRelease + - X11 ButtonPress, ButtonRelease + - X11 MotionNotify + - XInput XI_RawButtonPress, XI_RawButtonRelease + - XInput XI_RawMotion + + Note that button and motion events are doubly reported, but + keyboard events are not. + + If this process does *not* have the keyboard and mouse grabbed, + it receives the following events, regardless of the window in + which they occur: + + - XInput XI_RawKeyPress, XI_RawKeyRelease + - XInput XI_RawButtonPress, XI_RawButtonRelease + - XInput XI_RawMotion + + But here's an irritating kink: though XInput2 generally allows + snooping of everything, it respects GrabModeSync. What this + means is that if some other process has the keyboard grabbed with + "Sync" instead of "Async", then this process will not see any of + the events until that process releases its grab, and then the + events come in late, all at once. Verify this by running: + + test-xinput & + Note that keyboard and mouse events are detected. + test-grab --mouse --mouse-async --kbd-async + Same. + test-grab --mouse --mouse-sync --kbd-async + Keyboard events are detected. + No motion or button events until "test-grab" is killed. + test-grab --mouse --mouse-async --kbd-sync + Vice versa. + */ + case KeyPress: + case KeyRelease: + if (current_state != AUTH && /* logged by xscreensaver-auth */ + (verbose_p > 1 || + (verbose_p && now - active_at > 1))) + print_xinput_event (dpy, &xev, ""); + active_at = now; + continue; + break; + case ButtonPress: + case ButtonRelease: + active_at = now; + if (verbose_p) + print_xinput_event (dpy, &xev, ""); + continue; + break; + case MotionNotify: + /* Since we always get XI_RawMotion, but only get MotionNotify + when grabbed, we can just ignore MotionNotify and let the + XI_RawMotion clause handle hysteresis. */ + if (verbose_p > 1) + print_xinput_event (dpy, &xev, "ignored"); + continue; + break; + default: + break; + } - -/* Processing ClientMessage events. - */ + /**************************************************************** + XInput keyboard and mouse events + ****************************************************************/ + + if (xev.xcookie.type != GenericEvent || + xev.xcookie.extension != xi_opcode) + continue; /* not an XInput event */ + + if (!xev.xcookie.data) + XGetEventData (dpy, &xev.xcookie); + if (!xev.xcookie.data) + continue; /* Bogus XInput event */ + + switch (xev.xcookie.evtype) { + case XI_RawKeyPress: + case XI_RawKeyRelease: + case XI_RawButtonPress: + case XI_RawButtonRelease: + if (current_state != AUTH && /* logged by xscreensaver-auth */ + (verbose_p > 1 || + (verbose_p && now - active_at > 1))) + print_xinput_event (dpy, &xev, ""); + active_at = now; + break; + + case XI_RawMotion: + { + /* Mouse wheel scrolling sends Button4 and Button5 events as well + as motion, so we handled those above, in XI_RawButtonPress. + The raw_values in the motion event for a mouse wheel reflect + the position of the wheel sensor. + + On a trackpad where two-finger-swipe is a scroll gesture, I + saw behavior identical to a mouse wheel -- it does not send + RawTouch events. + */ + + /* Don't poll the mouse position more frequently than once + a second. The motion only counts as activity if it has + moved farther than N pixels per second. + */ + int secs = now - last_mouse.time; + if (secs >= 1) + { + Window root_ret, child_ret; + int root_x, root_y; + int win_x, win_y; + unsigned int mask; + int dist; + Bool ignored_p = False; + + XQueryPointer (dpy, DefaultRootWindow (dpy), + &root_ret, &child_ret, &root_x, &root_y, + &win_x, &win_y, &mask); + dist = MAX (ABS (last_mouse.x - root_x), + ABS (last_mouse.y - root_y)); + + ignored_p = (ignore_motion_p || dist < pointer_hysteresis); + + if (! ignored_p) + { + active_at = now; + last_mouse.time = now; + last_mouse.x = root_x; + last_mouse.y = root_y; + } + + if (verbose_p > 1 || + (verbose_p && now - active_at > 5)) + print_xinput_event (dpy, &xev, + (ignored_p ? " ignored" : "")); + } + } + break; -static Bool error_handler_hit_p = False; + default: + if (verbose_p) + print_xinput_event (dpy, &xev, ""); + break; + } -static int -ignore_all_errors_ehandler (Display *dpy, XErrorEvent *error) -{ - error_handler_hit_p = True; - return 0; -} + XFreeEventData (dpy, &xev.xcookie); + } -/* Sometimes some systems send us ClientMessage events with bogus atoms in - them. We only look up the atom names for printing warning messages, - so don't bomb out when it happens... - */ -static char * -XGetAtomName_safe (Display *dpy, Atom atom) -{ - char *result; - XErrorHandler old_handler; - if (!atom) return 0; - XSync (dpy, False); - error_handler_hit_p = False; - old_handler = XSetErrorHandler (ignore_all_errors_ehandler); - result = XGetAtomName (dpy, atom); - XSync (dpy, False); - XSetErrorHandler (old_handler); - XSync (dpy, False); - if (error_handler_hit_p) result = 0; + /******************************************************************** + Advancing the state machine + ********************************************************************/ - if (result) - return result; - else - { - char buf[100]; - sprintf (buf, "<>", (unsigned int) atom); - return strdup (buf); - } -} + /* If it's time, see if the .xscreensaver file has changed, since that + might change the blank and lock timeouts. + */ + if (now >= last_checked_init_file + 60) + { + last_checked_init_file = now; + if (verbose_p) + fprintf(stderr,"%s: checking init file\n", blurb()); + read_init_files (False); + } + /* Now that events have been processed, see if the state should change, + based on any events received and the current time. + */ + switch (current_state) { + case UNBLANKED: + if (!locking_disabled_p && + (force_lock_p || + (lock_p && + now >= active_at + blank_timeout + lock_timeout))) + { + fprintf (stderr, "%s: locking\n", blurb()); + if (grab_keyboard_and_mouse (mouse_screen (dpy))) + { + current_state = LOCKED; + blanked_at = now; + authenticated_p = False; + store_saver_status (dpy, True, True, now); + } + else + fprintf (stderr, "%s: unable to grab -- locking aborted!\n", + blurb()); -static void -clientmessage_response (saver_info *si, Window w, Bool error, - const char *stderr_msg, - const char *protocol_msg) -{ - char *proto; - int L; - saver_preferences *p = &si->prefs; - XErrorHandler old_handler; - - if (error || p->verbose_p) - fprintf (stderr, "%s: %s\n", blurb(), stderr_msg); - - L = strlen(protocol_msg); - proto = (char *) malloc (L + 2); - proto[0] = (error ? '-' : '+'); - strcpy (proto+1, protocol_msg); - L++; - - /* Ignore all X errors while sending a response to a ClientMessage. - Pretty much the only way we could get an error here is if the - window we're trying to send the reply on has been deleted, in - which case, the sender of the ClientMessage won't see our response - anyway. - */ - XSync (si->dpy, False); - error_handler_hit_p = False; - old_handler = XSetErrorHandler (ignore_all_errors_ehandler); + force_lock_p = False; /* Single shot */ + } + else if (force_blank_p || + now >= active_at + blank_timeout) + { + fprintf (stderr, "%s: blanking\n", blurb()); + if (grab_keyboard_and_mouse (mouse_screen (dpy))) + { + current_state = BLANKED; + blanked_at = now; + store_saver_status (dpy, True, False, now); + } + else + fprintf (stderr, "%s: unable to grab -- blanking aborted!\n", + blurb()); + } - XChangeProperty (si->dpy, w, XA_SCREENSAVER_RESPONSE, XA_STRING, 8, - PropModeReplace, (unsigned char *) proto, L); + if (current_state == BLANKED || current_state == LOCKED) + { + /* Grab succeeded and state changed: launch graphics. */ + if (! saver_gfx_pid) + { + static Bool first_time_p = True; + char *av[20]; + int ac = 0; + av[ac++] = SAVER_GFX_PROGRAM; + if (first_time_p) av[ac++] = "--init"; + if (verbose_p) av[ac++] = "--verbose"; + if (debug_p) av[ac++] = "--debug"; + + if (blank_mode == XA_NEXT) + av[ac++] = "--next"; + else if (blank_mode == XA_PREV) + av[ac++] = "--prev"; + else if (blank_mode == XA_SELECT) + av[ac++] = "--select"; + else if (blank_mode == XA_DEMO) + av[ac++] = "--demo"; + else if (blank_mode == XA_SUSPEND) + av[ac++] = "--emergency"; + + if (blank_mode == XA_SELECT || blank_mode == XA_DEMO) + av[ac++] = blank_mode_arg; + + av[ac] = 0; + gfx_stopped_p = False; + saver_gfx_pid = fork_and_exec (dpy, ac, av); + respawn_thrashing_count = 0; + first_time_p = False; + } + } + break; - XSync (si->dpy, False); - XSetErrorHandler (old_handler); - XSync (si->dpy, False); + case BLANKED: + if (!locking_disabled_p && + (force_lock_p || + (lock_p && + now >= blanked_at + lock_timeout))) + { + if (verbose_p) + fprintf (stderr, "%s: locking%s\n", blurb(), + (force_lock_p ? "" : " after timeout")); + current_state = LOCKED; + authenticated_p = False; + store_saver_status (dpy, True, True, now); + force_lock_p = False; /* Single shot */ + } + else if (active_at >= now && + active_at >= ignore_activity_before) + { + UNBLANK: + if (verbose_p) + fprintf (stderr, "%s: unblanking\n", blurb()); + current_state = UNBLANKED; + ignore_motion_p = False; + store_saver_status (dpy, False, False, now); + + if (saver_gfx_pid) + { + if (verbose_p) + fprintf (stderr, + "%s: pid %lu: killing " SAVER_GFX_PROGRAM "\n", + blurb(), (unsigned long) saver_gfx_pid); + kill (saver_gfx_pid, SIGTERM); + respawn_thrashing_count = 0; + + if (gfx_stopped_p) /* SIGCONT to allow SIGTERM to proceed */ + { + if (verbose_p) + fprintf (stderr, "%s: pid %lu: sending " + SAVER_GFX_PROGRAM " SIGCONT\n", + blurb(), (unsigned long) saver_gfx_pid); + gfx_stopped_p = False; + kill (-saver_gfx_pid, SIGCONT); /* send to process group */ + } + } - free (proto); -} + ungrab_keyboard_and_mouse (dpy); + } + break; + case LOCKED: + if (active_at >= now && + active_at >= ignore_activity_before) + { + char *av[10]; + int ac = 0; -static void -bogus_clientmessage_warning (saver_info *si, XEvent *event) -{ -#if 0 /* Oh, fuck it. GNOME likes to spew random ClientMessages at us - all the time. This is presumably indicative of an error in - the sender of that ClientMessage: if we're getting it and - ignoring it, then it's not reaching the intended recipient. - But people complain to me about this all the time ("waaah! - xscreensaver is printing to it's stderr and that gets my - panties all in a bunch!") And I'm sick of hearing about it. - So we'll just ignore these messages and let GNOME go right - ahead and continue to stumble along in its malfunction. - */ - - saver_preferences *p = &si->prefs; - char *str = XGetAtomName_safe (si->dpy, event->xclient.message_type); - Window w = event->xclient.window; - char wdesc[255]; - int screen = 0; - Bool root_p = False; - - *wdesc = 0; - for (screen = 0; screen < si->nscreens; screen++) - if (w == si->screens[screen].screensaver_window) - { - strcpy (wdesc, "xscreensaver"); + if (saver_gfx_pid) + { + /* To suspend or not suspend? If we don't suspend, then a + misbehaving or heavily-loaded screenhack might make it more + difficult to type in the password. Though that seems pretty + unlikely. + + But if we do suspend, then attaching a new monitor while + the unlock dialog is up will cause a new desktop to be + visible, since "xscreensaver-gfx" is the process that + handles RANDR. You can't interact with that new desktop, + but you can see it, possibly including (depending on the + window manager) the names of file icons on the desktop, or + other things. */ +# if 0 + if (verbose_p) + fprintf (stderr, "%s: pid %lu: sending " SAVER_GFX_PROGRAM + " SIGSTOP\n", blurb(), + (unsigned long) saver_gfx_pid); + gfx_stopped_p = True; + kill (-saver_gfx_pid, SIGSTOP); /* send to process group */ +# endif + } + + if (verbose_p) + fprintf (stderr, "%s: authorizing\n", blurb()); + current_state = AUTH; + + /* We already hold the mouse grab, but try to re-grab it with + a different mouse pointer, so that the pointer shows up while + the auth dialog is raised. We can ignore failures here. */ + grab_mouse (mouse_screen (dpy), auth_cursor); + + av[ac++] = SAVER_AUTH_PROGRAM; + if (verbose_p) av[ac++] = "--verbose"; + if (verbose_p > 1) av[ac++] = "--verbose"; + if (verbose_p > 2) av[ac++] = "--verbose"; + if (debug_p) av[ac++] = "--debug"; + av[ac] = 0; + saver_auth_pid = fork_and_exec (dpy, ac, av); + } break; - } - else if (w == RootWindow (si->dpy, screen)) - { - strcpy (wdesc, "root"); - root_p = True; + + case AUTH: + if (saver_auth_pid) + { + /* xscreensaver-auth still running -- wait for it to exit. */ + } + else if (authenticated_p) + { + /* xscreensaver-auth exited with "success" status */ + if (verbose_p) + fprintf (stderr, "%s: unlocking\n", blurb()); + authenticated_p = False; + goto UNBLANK; + } + else + { + /* xscreensaver-auth exited with non-success, or with signal. */ + if (verbose_p) + fprintf (stderr, "%s: authorization failed\n", blurb()); + current_state = LOCKED; + authenticated_p = False; + + /* We already hold the mouse grab, but try to re-grab it with + a different mouse pointer, to hide the pointer again now that + the auth dialog is gone. We can ignore failures here. */ + grab_mouse (mouse_screen (dpy), blank_cursor); + + /* When the unlock dialog is dismissed, ignore any input for a + second to give the user time to take their hands off of the + keyboard and mouse, so that it doesn't pop up again + immediately. */ + ignore_activity_before = now + 1; + + if (gfx_stopped_p) /* SIGCONT to resume savers */ + { + if (verbose_p) + fprintf (stderr, "%s: pid %lu: sending " SAVER_GFX_PROGRAM + " SIGCONT\n", + blurb(), (unsigned long) saver_gfx_pid); + gfx_stopped_p = False; + kill (-saver_gfx_pid, SIGCONT); /* send to process group */ + } + } + break; + + default: + /* abort(); */ break; } + } +} - /* If this ClientMessage was sent to the real root window instead of to the - xscreensaver window, then it might be intended for someone else who is - listening on the root window (e.g., the window manager). So only print - the warning if: we are in debug mode; or if the bogus message was - actually sent to one of the xscreensaver-created windows. + +/* There is no good reason for this executable to be setuid, but in case + it is, turn that off. + */ +static const char * +disavow_privileges (void) +{ + static char msg[255]; + uid_t euid = geteuid(); + gid_t egid = getegid(); + uid_t uid = getuid(); + gid_t gid = getgid(); + + if (uid == euid && gid == egid) + return 0; + + /* Without setgroups(), the process will retain any supplementary groups + associated with the uid, e.g. the default groups of the "root" user. + But setgroups() can only be called by root, and returns EPERM for other + users even if the call would be a no-op. So ignore its error status. */ - if (root_p && !p->debug_p) - return; + setgroups (1, &gid); - if (!*wdesc) + if (gid != egid && setgid (gid) != 0) { - XErrorHandler old_handler; - XClassHint hint; - XWindowAttributes xgwa; - memset (&hint, 0, sizeof(hint)); - memset (&xgwa, 0, sizeof(xgwa)); - - XSync (si->dpy, False); - old_handler = XSetErrorHandler (ignore_all_errors_ehandler); - XGetClassHint (si->dpy, w, &hint); - XGetWindowAttributes (si->dpy, w, &xgwa); - XSync (si->dpy, False); - XSetErrorHandler (old_handler); - XSync (si->dpy, False); - - screen = (xgwa.screen ? screen_number (xgwa.screen) : -1); - - sprintf (wdesc, "%.20s / %.20s", - (hint.res_name ? hint.res_name : "(null)"), - (hint.res_class ? hint.res_class : "(null)")); - if (hint.res_name) XFree (hint.res_name); - if (hint.res_class) XFree (hint.res_class); + fprintf (stderr, "%s: setgid %d -> %d failed\n", blurb(), egid, gid); + exit (1); } - fprintf (stderr, "%s: %d: unrecognised ClientMessage \"%s\" received\n", - blurb(), screen, (str ? str : "(null)")); - fprintf (stderr, "%s: %d: for window 0x%lx (%s)\n", - blurb(), screen, (unsigned long) w, wdesc); - if (str) XFree (str); + if (uid != euid && setgid (gid) != 0) + { + fprintf (stderr, "%s: setuid %d -> %d failed\n", blurb(), euid, uid); + exit (1); + } -#endif /* 0 */ + /* Return the message since this is before verbose_p is set or log opened. */ + sprintf (msg, "setuid %d:%d -> %d:%d", euid, egid, uid, gid); + return msg; } -Bool -handle_clientmessage (saver_info *si, XEvent *event, Bool until_idle_p) +int +main (int argc, char **argv) { - saver_preferences *p = &si->prefs; - Atom type = 0; - Window window = event->xclient.window; + Display *dpy = 0; + char *dpy_str = getenv ("DISPLAY"); + char *logfile = 0; + Bool sync_p = False; + Bool cmdline_verbose_val = False, cmdline_verbose_p = False; + Bool cmdline_splash_val = False, cmdline_splash_p = False; + const char *s; + const char *pmsg; + int i; - /* Preferences might affect our handling of client messages. */ - maybe_reload_init_file (si); + progname = argv[0]; + s = strrchr (progname, '/'); + if (s) progname = s+1; - if (event->xclient.message_type != XA_SCREENSAVER || - event->xclient.format != 32) - { - bogus_clientmessage_warning (si, event); - return False; - } + pmsg = disavow_privileges(); - type = event->xclient.data.l[0]; - if (type == XA_ACTIVATE) - { - if (until_idle_p) - { - if (p->mode == DONT_BLANK) - { - clientmessage_response(si, window, True, - "ACTIVATE ClientMessage received in DONT_BLANK mode.", - "screen blanking is currently disabled."); - return False; - } + fclose (stdin); + fix_fds(); - clientmessage_response(si, window, False, - "ACTIVATE ClientMessage received.", - "activating."); - si->selection_mode = 0; - si->demoing_p = False; - - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - - if (si->using_mit_saver_extension || si->using_sgi_saver_extension) - { - XForceScreenSaver (si->dpy, ScreenSaverActive); - return False; - } - else - { - return True; - } - } - clientmessage_response(si, window, True, - "ClientMessage ACTIVATE received while already active.", - "already active."); - } - else if (type == XA_DEACTIVATE) + for (i = 1; i < argc; i++) { + const char *oa = argv[i]; + /* XScreenSaver predates the "--arg" convention. */ + if (argv[i][0] == '-' && argv[i][1] == '-') + argv[i]++; - /* Regardless of whether the screen saver is active, a DEACTIVATE - message should cause the monitor to become powered on. */ - monitor_power_on (si, True); - -# if 0 - /* When -deactivate is received while locked, pop up the dialog box - instead of just ignoring it. Some people depend on this behavior - to be able to unlock by using e.g. a fingerprint reader without - also having to click the mouse first. - */ - if (si->locked_p) + if (!strcmp (argv[i], "-debug")) + debug_p = True; + else if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "-verbose")) { - clientmessage_response(si, window, False, - "DEACTIVATE ClientMessage received while locked: ignored.", - "screen is locked."); + verbose_p++; + cmdline_verbose_p = True, cmdline_verbose_val = verbose_p; } - else -# endif /* 0 */ + else if (!strcmp (argv[i], "-vv")) { - if (! until_idle_p) - { - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - - clientmessage_response(si, window, False, - "DEACTIVATE ClientMessage received.", - "deactivating."); - if (si->using_mit_saver_extension || - si->using_sgi_saver_extension) - { - XForceScreenSaver (si->dpy, ScreenSaverReset); - return False; - } - else - { - return True; - } - } - clientmessage_response(si, window, False, - "ClientMessage DEACTIVATE received while inactive: " - "resetting idle timer.", - "not active: idle timer reset."); - reset_timers (si); + verbose_p += 2; + cmdline_verbose_p = True, cmdline_verbose_val = verbose_p; } - } - else if (type == XA_SUSPEND) - { - clientmessage_response(si, window, False, - "SUSPEND ClientMessage received.", - "suspending."); - si->selection_mode = 0; - si->demoing_p = False; - si->throttled_p = True; - - /* When suspending, immediately lock, if locking enabled. */ -# ifndef NO_LOCKING - if (p->lock_p && !si->locked_p && !si->locking_disabled_p) + else if (!strcmp (argv[i], "-vvv")) { - si->emergency_lock_p = True; - if (p->verbose_p) - fprintf (stderr, "%s: locking.\n", blurb()); - set_locked_p (si, True); + verbose_p += 3; + cmdline_verbose_p = True, cmdline_verbose_val = verbose_p; } -# endif - - /* When suspending, immediately power off the display. */ - monitor_power_on (si, False); - - if (until_idle_p) - return True; /* Blank now */ - else - return False; /* Do not unblank now */ - } - else if (type == XA_CYCLE) - { - if (! until_idle_p) - { - clientmessage_response(si, window, False, - "CYCLE ClientMessage received.", - "cycling."); - si->selection_mode = 0; /* 0 means randomize when its time. */ - si->demoing_p = False; - - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - - if (si->cycle_id) - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - cycle_timer ((XtPointer) si, 0); - return False; - } - clientmessage_response(si, window, True, - "ClientMessage CYCLE received while inactive.", - "not active."); - } - else if (type == XA_NEXT || type == XA_PREV) - { - clientmessage_response(si, window, False, - (type == XA_NEXT - ? "NEXT ClientMessage received." - : "PREV ClientMessage received."), - "cycling."); - si->selection_mode = (type == XA_NEXT ? -1 : -2); - si->demoing_p = False; - - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - - if (! until_idle_p) - { - if (si->cycle_id) - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - cycle_timer ((XtPointer) si, 0); - } - else - return True; - } - else if (type == XA_SELECT) - { - char buf [255]; - char buf2 [255]; - long which = event->xclient.data.l[1]; - - if (p->mode == DONT_BLANK) + else if (!strcmp (argv[i], "-vvvv")) { - clientmessage_response(si, window, True, - "SELECT ClientMessage received in DONT_BLANK mode.", - "screen blanking is currently disabled."); - return False; + verbose_p += 4; + cmdline_verbose_p = True, cmdline_verbose_val = verbose_p; + } + else if (!strcmp (argv[i], "-q") || !strcmp (argv[i], "-quiet")) + { + verbose_p = 0; + cmdline_verbose_p = True, cmdline_verbose_val = verbose_p; + } + else if (!strcmp (argv[i], "-splash")) + { + splash_p = True; + cmdline_splash_p = True, cmdline_splash_val = splash_p; + } + else if (!strcmp (argv[i], "-no-splash") || + !strcmp (argv[i], "-nosplash")) + { + splash_p = False; + cmdline_splash_p = True, cmdline_splash_val = splash_p; + } + else if (!strcmp (argv[i], "-log")) + { + logfile = argv[++i]; + if (!logfile) goto HELP; + } + else if (!strcmp (argv[i], "-d") || + !strcmp (argv[i], "-dpy") || + !strcmp (argv[i], "-disp") || + !strcmp (argv[i], "-display")) + { + dpy_str = argv[++i]; + if (!dpy_str) goto HELP; + } + else if (!strcmp (argv[i], "-sync") || + !strcmp (argv[i], "-synch") || + !strcmp (argv[i], "-synchronize") || + !strcmp (argv[i], "-synchronise")) + sync_p = True; + else if (!strcmp (argv[i], "-h") || !strcmp (argv[i], "-help")) + { + HELP: + print_banner(); + fprintf (stderr, + "\t\thttps://www.jwz.org/xscreensaver/\n" + "\n" + "\tOptions:\n\n" + "\t\t--dpy host:display.screen\n" + "\t\t--verbose\n" + "\t\t--no-splash\n" + "\t\t--log logfile\n" + "\n" + "\tRun 'xscreensaver-settings' to configure.\n" + "\n"); + saver_exit (1); } - - sprintf (buf, "SELECT %ld ClientMessage received.", which); - sprintf (buf2, "activating (%ld).", which); - clientmessage_response (si, window, False, buf, buf2); - - if (which < 0) which = 0; /* 0 == "random" */ - si->selection_mode = which; - si->demoing_p = False; - - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - - if (! until_idle_p) - { - if (si->cycle_id) - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - cycle_timer ((XtPointer) si, 0); - } - else - return True; - } - else if (type == XA_EXIT) - { - /* Ignore EXIT message if the screen is locked. */ - if (until_idle_p || !si->locked_p) - { - clientmessage_response (si, window, False, - "EXIT ClientMessage received.", - "exiting."); - if (! until_idle_p) - { - int i; - for (i = 0; i < si->nscreens; i++) - kill_screenhack (&si->screens[i]); - unblank_screen (si); - XSync (si->dpy, False); - } - saver_exit (si, 0, 0); - } - else - clientmessage_response (si, window, True, - "EXIT ClientMessage received while locked.", - "screen is locked."); - } - else if (type == XA_RESTART) - { - /* The RESTART message works whether the screensaver is active or not, - unless the screen is locked, in which case it doesn't work. - */ - if (until_idle_p || !si->locked_p) - { - clientmessage_response (si, window, False, - "RESTART ClientMessage received.", - "restarting."); - if (! until_idle_p) - { - int i; - for (i = 0; i < si->nscreens; i++) - kill_screenhack (&si->screens[i]); - unblank_screen (si); - XSync (si->dpy, False); - } - - restart_process (si); /* does not return */ - abort(); - } - else - clientmessage_response (si, window, True, - "RESTART ClientMessage received while locked.", - "screen is locked."); - } - else if (type == XA_DEMO) - { - long arg = event->xclient.data.l[1]; - Bool demo_one_hack_p = (arg == 5000); - - if (demo_one_hack_p) - { - if (until_idle_p) - { - long which = event->xclient.data.l[2]; - char buf [255]; - char buf2 [255]; - sprintf (buf, "DEMO %ld ClientMessage received.", which); - sprintf (buf2, "demoing (%ld).", which); - clientmessage_response (si, window, False, buf, buf2); - - if (which < 0) which = 0; /* 0 == "random" */ - si->selection_mode = which; - si->demoing_p = True; - - if (si->throttled_p && p->verbose_p) - fprintf (stderr, "%s: unthrottled.\n", blurb()); - si->throttled_p = False; - - return True; - } - - clientmessage_response (si, window, True, - "DEMO ClientMessage received while active.", - "already active."); - } - else - { - clientmessage_response (si, window, True, - "obsolete form of DEMO ClientMessage.", - "obsolete form of DEMO ClientMessage."); - } - } - else if (type == XA_PREFS) - { - clientmessage_response (si, window, True, - "the PREFS client-message is obsolete.", - "the PREFS client-message is obsolete."); - } - else if (type == XA_LOCK) - { -#ifdef NO_LOCKING - clientmessage_response (si, window, True, - "not compiled with support for locking.", - "locking not enabled."); -#else /* !NO_LOCKING */ - if (si->locking_disabled_p) - clientmessage_response (si, window, True, - "LOCK ClientMessage received, but locking is disabled.", - "locking not enabled."); - else if (si->locked_p) - clientmessage_response (si, window, True, - "LOCK ClientMessage received while already locked.", - "already locked."); - else - { - char buf [255]; - char *response = (until_idle_p - ? "activating and locking." - : "locking."); - sprintf (buf, "LOCK ClientMessage received; %s", response); - clientmessage_response (si, window, False, buf, response); - - /* Have to set the time or xscreensaver-command doesn't report - the LOCK state change. Must come before set_locked_p(). */ - si->blank_time = time ((time_t *) 0); - - /* Note that this leaves things in a slightly inconsistent state: - we are blanked but not locked. And blanking might actually - fail if we can't get the grab. */ - set_locked_p (si, True); - - si->selection_mode = 0; - si->demoing_p = False; - - if (si->lock_id) /* we're doing it now, so lose the timeout */ - { - XtRemoveTimeOut (si->lock_id); - si->lock_id = 0; - } - - if (until_idle_p) - { - if (si->using_mit_saver_extension || - si->using_sgi_saver_extension) - { - XForceScreenSaver (si->dpy, ScreenSaverActive); - return False; - } - else - { - return True; - } - } - } -#endif /* !NO_LOCKING */ - } - else if (type == XA_THROTTLE) - { - /* The THROTTLE command is deprecated -- it predates the XDPMS - extension. Instead of using -throttle, users should instead - just power off the monitor (e.g., "xset dpms force off".) - In a few minutes, xscreensaver will notice that the monitor - is off, and cease running hacks. - */ - if (si->throttled_p) - clientmessage_response (si, window, True, - "THROTTLE ClientMessage received, but " - "already throttled.", - "already throttled."); - else - { - char buf [255]; - char *response = "throttled."; - si->throttled_p = True; - si->selection_mode = 0; - si->demoing_p = False; - sprintf (buf, "THROTTLE ClientMessage received; %s", response); - clientmessage_response (si, window, False, buf, response); - - if (! until_idle_p) - { - if (si->cycle_id) - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - cycle_timer ((XtPointer) si, 0); - } - } - } - else if (type == XA_UNTHROTTLE) - { - if (! si->throttled_p) - clientmessage_response (si, window, True, - "UNTHROTTLE ClientMessage received, but " - "not throttled.", - "not throttled."); else - { - char buf [255]; - char *response = "unthrottled."; - si->throttled_p = False; - si->selection_mode = 0; - si->demoing_p = False; - sprintf (buf, "UNTHROTTLE ClientMessage received; %s", response); - clientmessage_response (si, window, False, buf, response); - - if (! until_idle_p) - { - if (si->cycle_id) - XtRemoveTimeOut (si->cycle_id); - si->cycle_id = 0; - cycle_timer ((XtPointer) si, 0); - } - } + { + fprintf (stderr, "\n%s: unknown option: %s\n\n", blurb(), oa); + goto HELP; + } } - else + + if (logfile) { - char buf [1024]; - char *str; - str = XGetAtomName_safe (si->dpy, type); - - if (str) - { - if (strlen (str) > 80) - strcpy (str+70, "..."); - sprintf (buf, "unrecognised screensaver ClientMessage %s received.", - str); - free (str); - } - else - { - sprintf (buf, - "unrecognised screensaver ClientMessage 0x%x received.", - (unsigned int) event->xclient.data.l[0]); - } + int stdout_fd = 1; + int stderr_fd = 2; + int fd = open (logfile, O_WRONLY | O_APPEND | O_CREAT, 0666); + if (fd < 0) + { + char buf[255]; + FAIL: + sprintf (buf, "%.100s: %.100s", blurb(), logfile); + perror (buf); + fflush (stderr); + fflush (stdout); + saver_exit (1); + } - clientmessage_response (si, window, True, buf, buf); - } - return False; -} + fprintf (stderr, "%s: logging to file %s\n", blurb(), logfile); - -/* Some random diagnostics printed in -verbose mode. - */ + if (dup2 (fd, stdout_fd) < 0) goto FAIL; + if (dup2 (fd, stderr_fd) < 0) goto FAIL; -static void -analyze_display (saver_info *si) -{ - int i, j; - static struct { - const char *name; const char *desc; - Bool useful_p; - Status (*version_fn) (Display *, int *majP, int *minP); - } exts[] = { - - { "SCREEN_SAVER", /* underscore */ "SGI Screen-Saver", -# ifdef HAVE_SGI_SAVER_EXTENSION - True, 0 -# else - False, 0 -# endif - }, { "SCREEN-SAVER", /* dash */ "SGI Screen-Saver", -# ifdef HAVE_SGI_SAVER_EXTENSION - True, 0 -# else - False, 0 -# endif - }, { "MIT-SCREEN-SAVER", "MIT Screen-Saver", -# ifdef HAVE_MIT_SAVER_EXTENSION - True, XScreenSaverQueryVersion -# else - False, 0 -# endif - }, { "XIDLE", "XIdle", -# ifdef HAVE_XIDLE_EXTENSION - True, 0 -# else - False, 0 -# endif - }, { "SGI-VIDEO-CONTROL", "SGI Video-Control", -# ifdef HAVE_SGI_VC_EXTENSION - True, XSGIvcQueryVersion -# else - False, 0 -# endif - }, { "READDISPLAY", "SGI Read-Display", -# ifdef HAVE_READ_DISPLAY_EXTENSION - True, XReadDisplayQueryVersion -# else - False, 0 -# endif - }, { "MIT-SHM", "Shared Memory", -# ifdef HAVE_XSHM_EXTENSION - True, (Status (*) (Display*,int*,int*)) XShmQueryVersion /* 4 args */ -# else - False, 0 -# endif - }, { "DOUBLE-BUFFER", "Double-Buffering", -# ifdef HAVE_DOUBLE_BUFFER_EXTENSION - True, XdbeQueryExtension -# else - False, 0 -# endif - }, { "DPMS", "Power Management", -# ifdef HAVE_DPMS_EXTENSION - True, DPMSGetVersion -# else - False, 0 -# endif - }, { "GLX", "GLX", -# ifdef HAVE_GL - True, 0 -# else - False, 0 -# endif - }, { "XFree86-VidModeExtension", "XF86 Video-Mode", -# ifdef HAVE_XF86VMODE - True, XF86VidModeQueryVersion -# else - False, 0 -# endif - }, { "XC-VidModeExtension", "XC Video-Mode", -# ifdef HAVE_XF86VMODE - True, XF86VidModeQueryVersion -# else - False, 0 -# endif - }, { "XFree86-MISC", "XF86 Misc", -# ifdef HAVE_XF86MISCSETGRABKEYSSTATE - True, XF86MiscQueryVersion -# else - False, 0 -# endif - }, { "XC-MISC", "XC Misc", -# ifdef HAVE_XF86MISCSETGRABKEYSSTATE - True, XF86MiscQueryVersion -# else - False, 0 -# endif - }, { "XINERAMA", "Xinerama", -# ifdef HAVE_XINERAMA - True, XineramaQueryVersion -# else - False, 0 -# endif - }, { "RANDR", "Resize-and-Rotate", -# ifdef HAVE_RANDR - True, XRRQueryVersion -# else - False, 0 -# endif - }, { "DRI", "DRI", - True, 0 - }, { "NV-CONTROL", "NVidia", - True, 0 - }, { "NV-GLX", "NVidia GLX", - True, 0 - }, { "Apple-DRI", "Apple-DRI (XDarwin)", - True, 0 - }, { "XInputExtension", "XInput", - True, 0 - }, - }; - - fprintf (stderr, "%s: running on display \"%s\"\n", blurb(), - DisplayString(si->dpy)); - fprintf (stderr, "%s: vendor is %s, %d.\n", blurb(), - ServerVendor(si->dpy), VendorRelease(si->dpy)); - - fprintf (stderr, "%s: useful extensions:\n", blurb()); - for (i = 0; i < countof(exts); i++) - { - int op = 0, event = 0, error = 0; - char buf [255]; - int maj = 0, min = 0; - int dummy1, dummy2, dummy3; + fprintf (stderr, "\n\n" + "#####################################" + "#####################################\n" + "%s: logging to \"%s\"\n" + "#####################################" + "#####################################\n" + "\n", + blurb(), logfile); - /* Most of the extension version functions take 3 args, - writing results into args 2 and 3, but some take more. - We only ever care about the first two results, but we - pass in three extra pointers just in case. - */ - Status (*version_fn_2) (Display*,int*,int*,int*,int*,int*) = - (Status (*) (Display*,int*,int*,int*,int*,int*)) exts[i].version_fn; + if (!verbose_p) + verbose_p = True; + cmdline_verbose_val = verbose_p; + } - if (!XQueryExtension (si->dpy, exts[i].name, &op, &event, &error)) - continue; - sprintf (buf, "%s: ", blurb()); - strcat (buf, exts[i].desc); + save_argv (argc, argv); + hack_environment(); + print_banner(); + read_init_files (True); - if (!version_fn_2) - ; - else if (version_fn_2 (si->dpy, &maj, &min, &dummy1, &dummy2, &dummy3)) - sprintf (buf+strlen(buf), " (%d.%d)", maj, min); - else - strcat (buf, " (unavailable)"); + /* Command line overrides init file */ + if (cmdline_verbose_p) verbose_p = cmdline_verbose_val; + if (cmdline_splash_p) splash_p = cmdline_splash_val; - if (!exts[i].useful_p) - strcat (buf, " (disabled at compile time)"); - fprintf (stderr, "%s\n", buf); - } + if (verbose_p) + fprintf (stderr, "%s: running in process %lu\n", blurb(), + (unsigned long) getpid()); -# ifdef HAVE_LIBSYSTEMD - fprintf (stderr, "%s: libsystemd\n", blurb()); -# else - fprintf (stderr, "%s: libsystemd (disabled at compile time)\n", blurb()); -# endif + if (verbose_p && pmsg) + fprintf (stderr, "%s: %s\n", blurb(), pmsg); - for (i = 0; i < si->nscreens; i++) + if (! dpy_str) { - saver_screen_info *ssi = &si->screens[i]; - unsigned long colormapped_depths = 0; - unsigned long non_mapped_depths = 0; - XVisualInfo vi_in, *vi_out; - int out_count; - - if (!ssi->real_screen_p) continue; - - vi_in.screen = ssi->real_screen_number; - vi_out = XGetVisualInfo (si->dpy, VisualScreenMask, &vi_in, &out_count); - if (!vi_out) continue; - for (j = 0; j < out_count; j++) { - if (vi_out[j].depth >= 32) continue; - if (vi_out[j].class == PseudoColor) - colormapped_depths |= (1 << vi_out[j].depth); - else - non_mapped_depths |= (1 << vi_out[j].depth); - } - XFree ((char *) vi_out); - - if (colormapped_depths) - { - fprintf (stderr, "%s: screen %d colormapped depths:", blurb(), - ssi->real_screen_number); - for (j = 0; j < 32; j++) - if (colormapped_depths & (1 << j)) - fprintf (stderr, " %d", j); - fprintf (stderr, ".\n"); - } - if (non_mapped_depths) - { - fprintf (stderr, "%s: screen %d non-colormapped depths:", - blurb(), ssi->real_screen_number); - for (j = 0; j < 32; j++) - if (non_mapped_depths & (1 << j)) - fprintf (stderr, " %d", j); - fprintf (stderr, ".\n"); - } + dpy_str = ":0.0"; + fprintf (stderr, + "%s: warning: $DISPLAY is not set: defaulting to \"%s\"\n", + blurb(), dpy_str); } - describe_monitor_layout (si); -} + /* Copy the -dpy arg to $DISPLAY for subprocesses. */ + { + char *s = (char *) malloc (strlen(dpy_str) + 20); + sprintf (s, "DISPLAY=%s", dpy_str); + putenv (s); + /* free (s); */ /* some versions of putenv do not copy */ + } + dpy = XOpenDisplay (dpy_str); + if (!dpy) saver_exit (1); -Bool -display_is_on_console_p (saver_info *si) -{ - Bool not_on_console = True; - char *dpystr = DisplayString (si->dpy); - char *tail = (char *) strchr (dpystr, ':'); - if (! tail || strncmp (tail, ":0", 2)) - not_on_console = True; - else + if (sync_p) { - char dpyname[255], localname[255]; - strncpy (dpyname, dpystr, tail-dpystr); - dpyname [tail-dpystr] = 0; - if (!*dpyname || - !strcmp(dpyname, "unix") || - !strcmp(dpyname, "localhost")) - not_on_console = False; - else if (gethostname (localname, sizeof (localname))) - not_on_console = True; /* can't find hostname? */ - else if (!strncmp (dpyname, "/tmp/launch-", 12)) /* MacOS X launchd */ - not_on_console = False; - else - { - /* We have to call gethostbyname() on the result of gethostname() - because the two aren't guarenteed to be the same name for the - same host: on some losing systems, one is a FQDN and the other - is not. Here in the wide wonderful world of Unix it's rocket - science to obtain the local hostname in a portable fashion. - - And don't forget, gethostbyname() reuses the structure it - returns, so we have to copy the fucker before calling it again. - Thank you master, may I have another. - */ - struct hostent *h = gethostbyname (dpyname); - if (!h) - not_on_console = True; - else - { - char hn [255]; - struct hostent *l; - strcpy (hn, h->h_name); - l = gethostbyname (localname); - not_on_console = (!l || !!(strcmp (l->h_name, hn))); - } - } + XSynchronize (dpy, True); + XSync (dpy, False); } - return !not_on_console; -} + XSetErrorHandler (error_handler); -/* Do a little bit of heap introspection... - */ -void -check_for_leaks (const char *where) -{ -#if defined(HAVE_SBRK) && defined(LEAK_PARANOIA) - static unsigned long last_brk = 0; - int b = (unsigned long) sbrk(0); - if (last_brk && last_brk < b) - fprintf (stderr, "%s: %s: brk grew by %luK.\n", - blurb(), where, - (((b - last_brk) + 1023) / 1024)); - last_brk = b; -#endif /* HAVE_SBRK */ + main_loop (dpy); + saver_exit (0); + return (1); } -- cgit v1.2.3-55-g7522