/* xscreensaver, Copyright (c) 1991-1995 Jamie Zawinski <jwz@netscape.com>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

#include "version.h"

/*   ========================================================================
 *   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 (or, in the case of the 
 *   MIT-SCREEN-SAVER extension, use the one it gave us.)
 *
 *   We place a __SWM_VROOT property on this window, so that newly-started
 *   clients will think that this window is a "virtual root" window.
 *
 *   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.)
 *
 *   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.
 *
 *   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.
 *
 *   While we are waiting, 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.
 *
 *   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".
 *
 *   This program accepts ClientMessages of type SCREENSAVER; these messages
 *   may contain the atom ACTIVATE or DEACTIVATE, 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 we don't have the XIdle or MIT-SCREENSAVER 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 that's probably pretty rare.
 *   
 *   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.
 *
 *   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.
 *
 *   None of this crap happens if we're using one of the extensions, so install
 *   one of them if the description above sounds just too flaky to live.  It
 *   is, but those are your choices.
 *
 *   A third idle-detection option could be implement (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.
 *
 *   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.
 *     - you probably can't set breakpoints in functions that are called on
 *       the other side of a call to fork() -- if your clients are dying 
 *       with signal 5, Trace/BPT Trap, you're losing in this way.
 *     - If you aren't using XIdle, 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."
 *       
 *   ======================================================================== */

#if __STDC__
#include <stdlib.h>
#include <unistd.h>
#endif

#ifdef VMS
#include <signal.h>
#include <unixlib.h>    /*for getpid*/
#endif

#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#include <X11/Xos.h>
#ifndef VMS
#include <X11/Xmu/Error.h>
#else
#include "sys$common:[decw$include.xmu]Error.h"
#endif

#ifdef HAVE_XIDLE_EXTENSION
#include <X11/extensions/xidle.h>
#endif /* HAVE_XIDLE_EXTENSION */

#ifdef HAVE_SAVER_EXTENSION
#include <X11/extensions/scrnsaver.h>
#endif /* HAVE_SAVER_EXTENSION */

#include "yarandom.h"
#include "xscreensaver.h"

extern char *get_string_resource P((char *, char *));
extern Bool get_boolean_resource P((char *, char *));
extern int get_integer_resource P((char *, char *));
extern unsigned int get_minutes_resource P((char *, char *));
extern unsigned int get_seconds_resource P((char *, char *));

extern Visual *get_visual_resource P((Display *, char *, char *));
extern int get_visual_depth P((Display *, Visual *));

extern void notice_events_timer P((XtPointer closure, XtIntervalId *timer));
extern void cycle_timer P((void *junk1, XtPointer junk2));
extern void activate_lock_timer P((void *junk1, XtPointer junk2));
extern void sleep_until_idle P((Bool until_idle_p));

extern void ensure_no_screensaver_running P((void));
extern void initialize_screensaver_window P((void));
extern void disable_builtin_screensaver P((void));

extern void hack_environment P((void));
extern void grab_keyboard_and_mouse P((void));
extern void ungrab_keyboard_and_mouse P((void));

extern void save_argv P((int argc, char **argv));

extern void initialize_stderr P((void));

char *screensaver_version;
char *progname;
char *progclass;
XrmDatabase db;

XtAppContext app;

Display *dpy;
Screen *screen;
Visual *visual;
int visual_depth;

Widget toplevel_shell;

Time lock_timeout;

extern Time timeout;
extern Time cycle;
#ifndef NO_LOCKING
extern Time passwd_timeout;
#endif
extern Time pointer_timeout;
extern Time notice_events_timeout;
extern XtIntervalId lock_id, cycle_id;

Bool use_xidle_extension;
Bool use_saver_extension;
Bool verbose_p;
Bool lock_p, locked_p;

extern char **screenhacks;
extern int screenhacks_count;
extern char *shell;
extern int nice_inferior;
extern Window screensaver_window;
extern Cursor cursor;
extern Colormap cmap, cmap2;
extern Bool fade_p, unfade_p;
extern int fade_seconds, fade_ticks;
extern Bool install_cmap_p;
extern Bool locking_disabled_p;
extern char *nolock_reason;
extern Bool demo_mode_p;
extern Bool dbox_up_p;
extern int next_mode_p;

#ifdef HAVE_SAVER_EXTENSION
int saver_ext_event_number = 0;
int saver_ext_error_number = 0;
#endif /* HAVE_SAVER_EXTENSION */

static time_t initial_delay;

extern Atom XA_VROOT, XA_XSETROOT_ID;
extern Atom XA_SCREENSAVER_VERSION, XA_SCREENSAVER_ID;

static Atom XA_SCREENSAVER;
static Atom XA_ACTIVATE, XA_DEACTIVATE, XA_CYCLE, XA_NEXT, XA_PREV;
static Atom XA_EXIT, XA_RESTART, XA_DEMO, XA_LOCK;

#ifdef NO_MOTIF /* kludge */
Bool demo_mode_p = 0;
Bool dbox_up_p = 0;
#endif


#ifdef NO_DEMO_MODE
# define demo_mode() abort()
#else
extern void demo_mode P((void));
#endif

static XrmOptionDescRec options [] = {
  { "-timeout",		   ".timeout",		XrmoptionSepArg, 0 },
  { "-cycle",		   ".cycle",		XrmoptionSepArg, 0 },
  { "-idelay",		   ".initialDelay",	XrmoptionSepArg, 0 },
  { "-nice",		   ".nice",		XrmoptionSepArg, 0 },
  { "-visual",		   ".visualID",		XrmoptionSepArg, 0 },
  { "-lock-timeout",	   ".lockTimeout",	XrmoptionSepArg, 0 },
  { "-install",		   ".installColormap",	XrmoptionNoArg, "on" },
  { "-no-install",	   ".installColormap",	XrmoptionNoArg, "off" },
  { "-verbose",		   ".verbose",		XrmoptionNoArg, "on" },
  { "-silent",		   ".verbose",		XrmoptionNoArg, "off" },
  { "-xidle-extension",	   ".xidleExtension",	XrmoptionNoArg, "on" },
  { "-no-xidle-extension", ".xidleExtension",	XrmoptionNoArg, "off" },
  { "-ss-extension",	   ".saverExtension",	XrmoptionNoArg, "on" },
  { "-no-ss-extension",	   ".saverExtension",	XrmoptionNoArg, "off" },
  { "-lock",		   ".lock",		XrmoptionNoArg, "on" },
  { "-no-lock",		   ".lock",		XrmoptionNoArg, "off" }
};

static char *defaults[] = {
#ifndef VMS
#include "XScreenSaver.ad.h"
#else
#include "XScreenSaver_ad.h"
#endif
 0
};

static void
do_help P((void))
{
  printf ("\
xscreensaver %s, copyright (c) 1991-1995 by Jamie Zawinski <jwz@netscape.com>.\n\
The standard Xt command-line options are accepted; other options include:\n\
\n\
    -timeout <minutes>         When the screensaver should activate.\n\
    -cycle <minutes>           How long to let each hack run.\n\
    -idelay <seconds>          How long to sleep before startup.\n\
    -visual <id-or-class>      Which X visual to run on.\n\
    -demo                      Enter interactive demo mode on startup.\n\
    -install                   Install a private colormap.\n\
    -no-install                Don't.\n\
    -verbose                   Be loud.\n\
    -silent                    Don't.\n\
    -xidle-extension           Use the R5 XIdle server extension.\n\
    -no-xidle-extension        Don't.\n\
    -ss-extension              Use the R6 MIT-SCREEN-SAVER server extension.\n\
    -no-ss-extension           Don't.\n\
    -lock                      Require a password before deactivating.\n\
    -no-lock                   Don't.\n\
    -lock-timeout <minutes>    Grace period before locking; default 0.\n\
    -help                      This message.\n\
\n\
Use the `xscreensaver-command' program to control a running screensaver.\n\
\n\
The *programs, *colorPrograms, and *monoPrograms resources control which\n\
graphics demos will be launched by the screensaver.  See the man page for\n\
more details.\n\n",
	  screensaver_version);

#ifdef NO_LOCKING
  printf ("Support for locking was not enabled at compile-time.\n");
#endif
#ifdef NO_DEMO_MODE
  printf ("Support for demo mode was not enabled at compile-time.\n");
#endif
#if !defined(HAVE_XIDLE_EXTENSION) && !defined(HAVE_SAVER_EXTENSION)
  printf ("Support for the XIDLE and MIT-SCREEN-SAVER server extensions\
 was not\nenabled at compile-time.\n");
#endif /* !HAVE_XIDLE_EXTENSION && !HAVE_SAVER_EXTENSION */

  fflush (stdout);
  exit (1);
}


static void
get_screenhacks P((void))
{
  char *data[3];
  int i, hacks_size = 10;

  data[0] = get_string_resource ("programs", "Programs");
  data[1] = ((CellsOfScreen (screen) <= 2)
	     ? get_string_resource ("monoPrograms", "MonoPrograms")
	     : get_string_resource ("colorPrograms", "ColorPrograms"));
  data[2] = 0;
  if (! data[0]) data[0] = data[1], data[1] = 0;

  screenhacks = (char **) malloc (sizeof (char *) * hacks_size);
  screenhacks_count = 0;

  for (i = 0; data[i]; i++)
    {
      int j = 0;
      char *d = data [i];
      int size = strlen (d);
      while (j < size)
	{
	  int end, start = j;
	  if (d[j] == ' ' || d[j] == '\t' || d[j] == '\n' || d[j] == 0)
	    {
	      j++;
	      continue;
	    }
	  if (hacks_size <= screenhacks_count)
	    screenhacks = (char **) realloc (screenhacks,
					     (hacks_size = hacks_size * 2) *
					     sizeof (char *));
	  screenhacks [screenhacks_count++] = d + j;
	  while (d[j] != 0 && d[j] != '\n')
	    j++;
	  end = j;
	  while (j > start && (d[j-1] == ' ' || d[j-1] == '\t'))
	    j--;
	  d[j] = 0;
	  j = end + 1;
	}
    }

  /* shrink all whitespace to one space, for the benefit of the "demo"
     mode display.  We only do this when we can easily tell that the
     whitespace is not significant (no shell metachars).
   */
  for (i = 0; i < screenhacks_count; i++)
    {
      char *s = screenhacks [i];
      char *s2;
      int L = strlen (s);
      int j, k;
      for (j = 0; j < L; j++)
	{
	  switch (s[j])
	    {
	    case '\'': case '"': case '`': case '\\':
	      goto DONE;
	    case '\t':
	      s[j] = ' ';
	    case ' ':
	      k = 0;
	      for (s2 = s+j+1; *s2 == ' ' || *s2 == '\t'; s2++)
		k++;
	      if (k > 0)
		for (s2 = s + j + 1; *s2; s2++)
		  s2 [0] = s2 [k];
	      break;
	    }
	}
    DONE:
      ;
    }

  if (screenhacks_count)
    {
      /* Shrink down the screenhacks array to be only as big as it needs to.
	 This doesn't really matter at all. */
      screenhacks = (char **)
	realloc (screenhacks, ((screenhacks_count + 1) * sizeof(char *)));
      screenhacks [screenhacks_count] = 0;
    }
  else
    {
      free (screenhacks);
      screenhacks = 0;
    }
}


static void
get_resources P((void))
{
  /* Note: we can't use the resource ".visual" because Xt is SO FUCKED. */
  visual	  = get_visual_resource (dpy, "visualID", "VisualID");
  timeout         = 1000 * get_minutes_resource ("timeout", "Time");
  cycle           = 1000 * get_minutes_resource ("cycle",   "Time");
  lock_timeout	  = 1000 * get_minutes_resource ("lockTimeout", "Time");
  nice_inferior   = get_integer_resource ("nice", "Nice");
  verbose_p       = get_boolean_resource ("verbose", "Boolean");
  lock_p          = get_boolean_resource ("lock", "Boolean");
  install_cmap_p  = get_boolean_resource ("installColormap", "Boolean");
  fade_p	  = get_boolean_resource ("fade", "Boolean");
  unfade_p	  = get_boolean_resource ("unfade", "Boolean");
  fade_seconds	  = get_seconds_resource ("fadeSeconds", "Time");
  fade_ticks	  = get_integer_resource ("fadeTicks", "Integer");
  shell           = get_string_resource ("bourneShell", "BourneShell");
  initial_delay   = get_seconds_resource ("initialDelay", "Time");
  pointer_timeout = 1000 * get_seconds_resource ("pointerPollTime", "Time");
  notice_events_timeout = 1000 * get_seconds_resource ("windowCreationTimeout",
						       "Time");
#ifndef NO_LOCKING
  passwd_timeout  = 1000 * get_seconds_resource ("passwdTimeout", "Time");
  if (passwd_timeout == 0) passwd_timeout = 30000;
#endif
  if (timeout < 10000) timeout = 10000;
  if (cycle != 0 && cycle < 2000) cycle = 2000;
  if (pointer_timeout == 0) pointer_timeout = 5000;
  if (notice_events_timeout == 0) notice_events_timeout = 10000;
  if (fade_seconds == 0 || fade_ticks == 0) fade_p = False;
  if (! fade_p) unfade_p = False;

  visual_depth = get_visual_depth (dpy, visual);

  if (visual_depth <= 1 || CellsOfScreen (screen) <= 2)
    install_cmap_p = False;

#ifdef NO_LOCKING
  locking_disabled_p = True;
  nolock_reason = "not compiled with locking support";
  if (lock_p)
    {
      lock_p = False;
      fprintf (stderr, "%s: %snot compiled with support for locking.\n",
	       progname, (verbose_p ? "## " : ""));
    }
#else  /* ! NO_LOCKING */
  if (lock_p && locking_disabled_p)
    {
      fprintf (stderr, "%s: %slocking is disabled (%s).\n", progname,
	       (verbose_p ? "## " : ""), nolock_reason);
      lock_p = False;
    }
#endif /* ! NO_LOCKING */

  /* don't set use_xidle_extension unless it is explicitly specified */
  if (get_string_resource ("xidleExtension", "Boolean"))
    use_xidle_extension = get_boolean_resource ("xidleExtension", "Boolean");
  else
#ifdef HAVE_XIDLE_EXTENSION	/* pick a default */
    use_xidle_extension = True;
#else  /* !HAVE_XIDLE_EXTENSION */
    use_xidle_extension = False;
#endif /* !HAVE_XIDLE_EXTENSION */

  /* don't set use_saver_extension unless it is explicitly specified */
  if (get_string_resource ("saverExtension", "Boolean"))
    use_saver_extension = get_boolean_resource ("saverExtension", "Boolean");
  else
#ifdef HAVE_SAVER_EXTENSION	/* pick a default */
    use_saver_extension = True;
#else  /* !HAVE_SAVER_EXTENSION */
    use_saver_extension = False;
#endif /* !HAVE_SAVER_EXTENSION */


  get_screenhacks ();
}

char *
timestring P((void))
{
  long now = time ((time_t *) 0);
  char *str = (char *) ctime (&now);
  char *nl = (char *) strchr (str, '\n');
  if (nl) *nl = 0; /* take off that dang newline */
  return str;
}

#ifdef NO_SETUID
# define hack_uid()
# define hack_uid_warn()
#else /* !NO_SETUID */
extern void hack_uid P((void));
extern void hack_uid_warn P((void));
#endif /* NO_SETUID */


#ifndef NO_LOCKING
extern Bool unlock_p P((Widget));
extern Bool lock_init P((void));
#endif

static void initialize P((int argc, char **argv));
static void main_loop P((void));

#ifndef VMS
void
#else
int
#endif
main (argc, argv)
     int argc;
     char **argv;
{
  initialize (argc, argv);
  main_loop ();
}


static int
saver_ehandler (dpy, error)
     Display *dpy;
     XErrorEvent *error;
{
  fprintf (real_stderr, "\nX error in %s:\n", progname);
  if (XmuPrintDefaultErrorMessage (dpy, error, real_stderr))
    exit (-1);
  else
    fprintf (real_stderr, " (nonfatal.)\n");
  return 0;
}

static void
#if __STDC__
initialize_connection (int argc, char **argv)
#else
initialize_connection (argc, argv)
     int argc;
     char **argv;
#endif
{
  toplevel_shell = XtAppInitialize (&app, progclass,
				    options, XtNumber (options),
				    &argc, argv, defaults, 0, 0);

  dpy = XtDisplay (toplevel_shell);
  screen = XtScreen (toplevel_shell);
  db = XtDatabase (dpy);
  XtGetApplicationNameAndClass (dpy, &progname, &progclass);

  if (argc == 2 && !strcmp (argv[1], "-help"))
    do_help ();
  else if (argc > 1)
    {
      fprintf (stderr, "%s: unknown option %s\n", progname, argv [1]);
      exit (1);
    }
  get_resources ();
  hack_uid_warn ();
  hack_environment ();
  XA_VROOT = XInternAtom (dpy, "__SWM_VROOT", False);
  XA_SCREENSAVER = XInternAtom (dpy, "SCREENSAVER", False);
  XA_SCREENSAVER_VERSION = XInternAtom (dpy, "_SCREENSAVER_VERSION", False);
  XA_SCREENSAVER_ID = XInternAtom (dpy, "_SCREENSAVER_ID", False);
  XA_XSETROOT_ID = XInternAtom (dpy, "_XSETROOT_ID", False);
  XA_ACTIVATE = XInternAtom (dpy, "ACTIVATE", False);
  XA_DEACTIVATE = XInternAtom (dpy, "DEACTIVATE", False);
  XA_RESTART = XInternAtom (dpy, "RESTART", False);
  XA_CYCLE = XInternAtom (dpy, "CYCLE", False);
  XA_NEXT = XInternAtom (dpy, "NEXT", False);
  XA_PREV = XInternAtom (dpy, "PREV", False);
  XA_EXIT = XInternAtom (dpy, "EXIT", False);
  XA_DEMO = XInternAtom (dpy, "DEMO", False);
  XA_LOCK = XInternAtom (dpy, "LOCK", False);
}

#ifdef HAVE_SAVER_EXTENSION

static int
ignore_all_errors_ehandler (dpy, error)
     Display *dpy;
     XErrorEvent *error;
{
  return 0;
}

static void
init_saver_extension ()
{
  XID kill_id;
  Atom kill_type;
  Window root = RootWindowOfScreen (screen);
  XScreenSaverInfo *info;
  Pixmap blank_pix = XCreatePixmap (dpy, root, 1, 1, 1);

  /* Kill off the old MIT-SCREEN-SAVER client if there is one.
     This tends to generate X errors, though (possibly due to a bug
     in the server extension itself?) so just ignore errors here. */
  if (XScreenSaverGetRegistered (dpy, XScreenNumberOfScreen (screen),
				 &kill_id, &kill_type)
      && kill_id != blank_pix)
    {
      int (*old_handler) ();
      old_handler = XSetErrorHandler (ignore_all_errors_ehandler);
      XKillClient (dpy, kill_id);
      XSync (dpy, False);
      XSetErrorHandler (old_handler);
    }

  XScreenSaverSelectInput (dpy, root, ScreenSaverNotifyMask);

  XScreenSaverRegister (dpy, XScreenNumberOfScreen (screen),
			(XID) blank_pix, XA_PIXMAP);
  info = XScreenSaverAllocInfo ();

#if 0
  /* #### I think this is noticing that the saver is on, and replacing it
     without turning it off first. */
  saver = info->window;
  if (info->state == ScreenSaverOn)
    {
      if (info->kind != ScreenSaverExternal) 
	{
	  XResetScreenSaver (display);
	  XActivateScreenSaver (display);
	}
      StartSaver ();
    }
#endif
}
#endif /* HAVE_SAVER_EXTENSION */


extern void init_sigchld P((void));

static void
initialize (argc, argv)
     int argc;
     char **argv;
{
  Bool initial_demo_mode_p = False;
  screensaver_version = (char *) malloc (5);
  memcpy (screensaver_version, screensaver_id + 17, 4);
  screensaver_version [4] = 0;
  progname = argv[0]; /* reset later; this is for the benefit of lock_init() */

#ifdef NO_LOCKING
  locking_disabled_p = True;
  nolock_reason = "not compiled with locking support";
#else
  locking_disabled_p = False;

#ifdef SCO
  set_auth_parameters(argc, argv);
#endif

  if (! lock_init ())	/* before hack_uid() for proper permissions */
    {
      locking_disabled_p = True;
      nolock_reason = "error getting password";
    }
#endif

  hack_uid ();
  progclass = "XScreenSaver";

  /* remove -demo switch before saving argv */
  {
    int i;
    for (i = 1; i < argc; i++)
      while (!strcmp ("-demo", argv [i]))
	{
	  int j;
	  initial_demo_mode_p = True;
	  for (j = i; j < argc; j++)
	    argv [j] = argv [j+1];
	  argv [j] = 0;
	  argc--;
	  if (argc <= i) break;
	}
  }
  save_argv (argc, argv);
  initialize_connection (argc, argv);
  ensure_no_screensaver_running ();

  if (verbose_p)
    printf ("\
%s %s, copyright (c) 1991-1995 by Jamie Zawinski <jwz@netscape.com>.\n\
 pid = %d.\n", progname, screensaver_version, getpid ());
  ensure_no_screensaver_running ();

  demo_mode_p = initial_demo_mode_p;
  screensaver_window = 0;
  cursor = 0;
  initialize_screensaver_window ();
  srandom ((int) time ((time_t *) 0));
  cycle_id = 0;
  lock_id = 0;
  locked_p = False;

  if (use_saver_extension)
    {
#ifdef HAVE_SAVER_EXTENSION
      if (! XScreenSaverQueryExtension (dpy,
					&saver_ext_event_number,
					&saver_ext_error_number))
	{
	  fprintf (stderr,
	 "%s: %sdisplay %s does not support the MIT-SCREEN-SAVER extension.\n",
		   progname, (verbose_p ? "## " : ""), DisplayString (dpy));
	  use_saver_extension = False;
	}
      else if (use_xidle_extension)
	{
	  fprintf (stderr,
	 "%s: %sMIT-SCREEN-SAVER extension used instead of XIDLE extension.\n",
		   progname, (verbose_p ? "## " : ""));
	  use_xidle_extension = False;
	}
#else  /* !HAVE_SAVER_EXTENSION */
      fprintf (stderr,
       "%s: %snot compiled with support for the MIT-SCREEN-SAVER extension.\n",
	       progname, (verbose_p ? "## " : ""));
      use_saver_extension = False;
#endif /* !HAVE_SAVER_EXTENSION */
    }

  if (use_xidle_extension)
    {
#ifdef HAVE_XIDLE_EXTENSION
      int first_event, first_error;
      if (! XidleQueryExtension (dpy, &first_event, &first_error))
	{
	  fprintf (stderr,
		   "%s: %sdisplay %s does not support the XIdle extension.\n",
		   progname, (verbose_p ? "## " : ""), DisplayString (dpy));
	  use_xidle_extension = False;
	}
#else  /* !HAVE_XIDLE_EXTENSION */
      fprintf (stderr, "%s: %snot compiled with support for XIdle.\n",
	       progname, (verbose_p ? "## " : ""));
      use_xidle_extension = False;
#endif /* !HAVE_XIDLE_EXTENSION */
    }

  init_sigchld ();

  disable_builtin_screensaver ();

#ifdef HAVE_SAVER_EXTENSION
  if (use_saver_extension)
    init_saver_extension ();
#endif /* HAVE_SAVER_EXTENSION */

  if (verbose_p && use_saver_extension)
    fprintf (stderr, "%s: using MIT-SCREEN-SAVER server extension.\n",
	     progname);
  if (verbose_p && use_xidle_extension)
    fprintf (stderr, "%s: using XIdle server extension.\n",
	     progname);

  initialize_stderr ();
  XSetErrorHandler (saver_ehandler);

  if (initial_demo_mode_p)
    /* If the user wants demo mode, don't wait around before doing it. */
    initial_delay = 0;

  if (!use_xidle_extension && !use_saver_extension)
    {
      if (initial_delay)
	{
	  if (verbose_p)
	    {
	      printf ("%s: waiting for %d second%s...", progname,
		      (int) initial_delay, (initial_delay == 1 ? "" : "s"));
	      fflush (stdout);
	    }
	  sleep (initial_delay);
	  if (verbose_p)
	    printf (" done.\n");
	}
      if (verbose_p)
	{
	  printf ("%s: selecting events on extant windows...", progname);
	  fflush (stdout);
	}
      notice_events_timer ((XtPointer)
			   RootWindowOfScreen (XtScreen (toplevel_shell)),
			   0);
      if (verbose_p)
	printf (" done.\n");
    }
}


extern void suspend_screenhack P((Bool suspend_p));

static void
main_loop ()
{
#ifdef VMS
#ifndef __DECC  /* problems with VAX C */
 void (*cycle_timer_ptr)() = cycle_timer; 
 void (*activate_lock_timer_ptr)() = activate_lock_timer;
#endif
#endif
  while (1)
    {
      if (! demo_mode_p)
	sleep_until_idle (True);

      if (demo_mode_p)
	demo_mode ();
      else
	{
	  if (verbose_p)
	    printf ("%s: user is idle; waking up at %s.\n", progname,
		    timestring());
	  blank_screen ();
	  spawn_screenhack (True);
	  if (cycle)
#ifdef VMS
#ifndef __DECC
	    cycle_id = XtAppAddTimeOut (app, cycle, cycle_timer_ptr, 0);
#endif
#else
	    cycle_id = XtAppAddTimeOut (app, cycle,
					(XtTimerCallbackProc)cycle_timer, 0);
#endif
#ifndef NO_LOCKING
	  if (lock_p && lock_timeout == 0)
	    locked_p = True;
	  if (lock_p && !locked_p)
	    /* locked_p might be true already because of ClientMessage */
#ifdef VMS
#ifndef __DECC
	    lock_id = XtAppAddTimeOut (app,lock_timeout,
				       activate_lock_timer_ptr,0);
#endif
#else
	    lock_id = XtAppAddTimeOut (app,lock_timeout,
				       (XtTimerCallbackProc)
				       activate_lock_timer,0);
#endif /* VMS */
#endif

	PASSWD_INVALID:

	  sleep_until_idle (False); /* until not idle */

#ifndef NO_LOCKING
	  if (locked_p)
	    {
	      Bool val;
	      if (locking_disabled_p) abort ();
	      dbox_up_p = True;

	      /* We used to ungrab the keyboard here, before calling unlock_p()
		 to pop up the dialog box.  This left the keyboard ungrabbed
		 for a small window, during an insecure state.  Bennett Todd
		 was seeing the bahavior that, when the load was high, he could
		 actually get characters through to a shell under the saver
		 window (he accidentally typed his password there...)

		 So the ungrab has been moved down into pop_passwd_dialog()
		 just after the server is grabbed, closing this window
		 entirely.
	       */
	      /* ungrab_keyboard_and_mouse (); */

	      suspend_screenhack (True);
	      XUndefineCursor (dpy, screensaver_window);
	      if (verbose_p)
		printf ("%s: prompting for password.\n", progname);
	      val = unlock_p (toplevel_shell);
	      if (verbose_p && val == False)
		printf ("%s: password incorrect!\n", progname);
	      dbox_up_p = False;
	      XDefineCursor (dpy, screensaver_window, cursor);
	      suspend_screenhack (False);

	      /* I think this grab is now redundant, but it shouldn't hurt. */
	      grab_keyboard_and_mouse ();

	      if (! val)
		goto PASSWD_INVALID;
	      locked_p = False;
	    }
#endif
	  unblank_screen ();
	  kill_screenhack ();
	  if (cycle_id)
	    {
	      XtRemoveTimeOut (cycle_id);
	      cycle_id = 0;
	    }
#ifndef NO_LOCKING
	  if (lock_id)
	    {
	      XtRemoveTimeOut (lock_id);
	      lock_id = 0;
	    }
#endif
	  if (verbose_p)
	    printf ("%s: user is active; going to sleep at %s.\n", progname,
		    timestring ());
	}
    }
}



Bool
handle_clientmessage (event, until_idle_p)
     XEvent *event;
     Bool until_idle_p;
{
  Atom type = 0;
  if (event->xclient.message_type != XA_SCREENSAVER)
    {
      char *str;
      str = XGetAtomName (dpy, event->xclient.message_type);
#ifdef VMS
      if (str)
      {
      	  if (str[0] == 'V' &&
	      str[1] == 'U' &&
	      str[2] == 'E' &&
	      str[3] == '_')
	  {
	      if (verbose_p)
	       printf ("%s: VUE_ ClientMessage ignored: %s .\n", progname, str);
	      return False;
	  }
      }
#endif   /* Patche made by sandre@clamart.wireline.slb.com - Oct 95 */

      fprintf (stderr, "%s: %sunrecognised ClientMessage type %s received\n",
	       progname, (verbose_p ? "## " : ""),
	       (str ? str : "(null)"));
      if (str) XFree (str);
      return False;
    }
  if (event->xclient.format != 32)
    {
      fprintf (stderr, "%s: %sClientMessage of format %d received, not 32\n",
	       progname, (verbose_p ? "## " : ""), event->xclient.format);
      return False;
    }
  type = event->xclient.data.l[0];
  if (type == XA_ACTIVATE)
    {
      if (until_idle_p)
	{
	  if (verbose_p)
	    printf ("%s: ACTIVATE ClientMessage received.\n", progname);
	  if (use_saver_extension)
	    {
	      XForceScreenSaver (dpy, ScreenSaverActive);
	      return False;
	    }
	  else
	    {
	      return True;
	    }
	}
      fprintf (stderr,
	       "%s: %sClientMessage ACTIVATE received while already active.\n",
	       progname, (verbose_p ? "## " : ""));
    }
  else if (type == XA_DEACTIVATE)
    {
      if (! until_idle_p)
	{
	  if (verbose_p)
	    printf ("%s: DEACTIVATE ClientMessage received.\n", progname);
	  if (use_saver_extension)
	    {
	      XForceScreenSaver (dpy, ScreenSaverReset);
	      return False;
	    }
	  else
	    {
	      return True;
	    }
	}
      fprintf (stderr,
	       "%s: %sClientMessage DEACTIVATE received while inactive.\n",
	       progname, (verbose_p ? "## " : ""));
    }
  else if (type == XA_CYCLE)
    {
      if (! until_idle_p)
	{
	  if (verbose_p)
	    printf ("%s: CYCLE ClientMessage received.\n", progname);
	  if (cycle_id)
	    XtRemoveTimeOut (cycle_id);
	  cycle_id = 0;
	  cycle_timer (0, 0);
	  return False;
	}
      fprintf (stderr,
	       "%s: %sClientMessage CYCLE received while inactive.\n",
	       progname, (verbose_p ? "## " : ""));
    }
  else if (type == XA_NEXT || type == XA_PREV)
    {
      if (verbose_p)
	printf ("%s: %s ClientMessage received.\n", progname,
		(type == XA_NEXT ? "NEXT" : "PREV"));
      next_mode_p = 1 + (type == XA_PREV);

      if (! until_idle_p)
	{
	  if (cycle_id)
	    XtRemoveTimeOut (cycle_id);
	  cycle_id = 0;
	  cycle_timer (0, 0);
	}
      else
	return True;
    }
  else if (type == XA_EXIT)
    {
      /* Ignore EXIT message if the screen is locked. */
      if (until_idle_p || !locked_p)
	{
	  if (verbose_p)
	    printf ("%s: EXIT ClientMessage received.\n", progname);
	  if (! until_idle_p)
	    {
	      unblank_screen ();
	      kill_screenhack ();
	      XSync (dpy, False);
	    }
	  exit (0);
	}
      else
	fprintf (stderr, "%s: %sEXIT ClientMessage received while locked.\n",
		 progname, (verbose_p ? "## " : ""));
    }
  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 || !locked_p)
	{
	  if (verbose_p)
	    printf ("%s: RESTART ClientMessage received.\n", progname);
	  if (! until_idle_p)
	    {
	      unblank_screen ();
	      kill_screenhack ();
	      XSync (dpy, False);
	    }
	  restart_process ();
	}
      else
	fprintf(stderr, "%s: %sRESTART ClientMessage received while locked.\n",
		progname, (verbose_p ? "## " : ""));
    }
  else if (type == XA_DEMO)
    {
#ifdef NO_DEMO_MODE
      fprintf (stderr,
	       "%s: %snot compiled with support for DEMO mode\n",
	       progname, (verbose_p ? "## " : ""));
#else
      if (until_idle_p)
	{
	  if (verbose_p)
	    printf ("%s: DEMO ClientMessage received.\n", progname);
	  demo_mode_p = True;
	  return True;
	}
      fprintf (stderr,
	       "%s: %sDEMO ClientMessage received while active.\n",
	       progname, (verbose_p ? "## " : ""));
#endif
    }
  else if (type == XA_LOCK)
    {
#ifdef NO_LOCKING
      fprintf (stderr, "%s: %snot compiled with support for LOCK mode\n",
	       progname, (verbose_p ? "## " : ""));
#else
      if (locking_disabled_p)
	fprintf (stderr,
	       "%s: %sLOCK ClientMessage received, but locking is disabled.\n",
		 progname, (verbose_p ? "## " : ""));
      else if (locked_p)
	fprintf (stderr,
	       "%s: %sLOCK ClientMessage received while already locked.\n",
		 progname, (verbose_p ? "## " : ""));
      else
	{
	  locked_p = True;
	  if (verbose_p) 
	    printf ("%s: LOCK ClientMessage received;%s locking.\n",
		    progname, until_idle_p ? " activating and" : "");

	  if (lock_id)	/* we're doing it now, so lose the timeout */
	    {
	      XtRemoveTimeOut (lock_id);
	      lock_id = 0;
	    }

	  if (until_idle_p)
	    {
	      if (use_saver_extension)
		{
		  XForceScreenSaver (dpy, ScreenSaverActive);
		  return False;
		}
	      else
		{
		  return True;
		}
	    }
	}
#endif
    }
  else
    {
      char *str;
      str = (type ? XGetAtomName(dpy, type) : 0);
      if (str)
	fprintf (stderr,
		 "%s: %sunrecognised screensaver ClientMessage %s received\n",
		 progname, (verbose_p ? "## " : ""), str);
      else
	fprintf (stderr,
		"%s: %sunrecognised screensaver ClientMessage 0x%x received\n",
		 progname, (verbose_p ? "## " : ""),
		 (unsigned int) event->xclient.data.l[0]);
      if (str) XFree (str);
    }
  return False;
}
