/*****************************************************************************
 *
 * xautolock
 * =========
 *
 * Authors   :  S. De Troch (SDT) + M. Eyckmans (MCE)
 *
 * Date      :  22/07/90
 *
 * Comments  :  - Patchlevel 1->6 for private use only.
 *              - Patchlevel  7 released: 07/04/92
 *              - Patchlevel  8 released: 15/05/92
 *              - Patchlevel  9 released: 24/01/95
 *              - Patchlevel 10 released: 22/02/95
 *
 * Review    :  - 12/02/92 (MCE) :
 *                . Hacked around a dxcalendar problem.
 *              - 21/02/92 (MCE) :
 *                . Major rewrite.
 *              - 24/02/92 (MCE) :
 *                . Removed an initialization bug.
 *              - 25/02/92 (MCE) :
 *                . Added code to detect multiple invocations.
 *              - 06/03/92 (MCE) :
 *                . Re-arranged the event loop in order to detect defunct
 *                  children as soon as possible.
 *              - 10/03/92 (SDT & MCE) :
 *                . Added code to detect broken server connections.
 *              - 24/03/92 (MCE) :
 *                . Don't reset the time-out counter after receiving a
 *                  synthetic or otherwise unexpected event.
 *              - 15/04/92 (MCE) :
 *                . Changed the default locker to "xlock 2>&- 1>&-".
 *                . Fixed a couple of event mask bugs. (Thanks to
 *                  jwz@lucid.com for running into these.)
 *                . Corrected a property type bug in CheckConnection ().
 *              - 20/04/92 (MCE) :
 *                . Cut Main () into more managable pieces.
 *                . Periodically call XQueryPointer ().
 *              - 25/04/92 (MCE) :
 *                . Added the `corners' feature. (Suggested by
 *                  weisen@alw.nih.gov.)
 *                . Fixed a problem with pseudo-root windows. (Thanks to
 *                  sherman@unx.sas.com, nedwards@titan.trl.oz.au,
 *                  dave@elxr.jpl.nasa.gov and tmcconne@sedona.intel.com
 *                  for pointing out the problem and testing the patch.)
 *                . Added `disable/enable on SIGHUP'. (Suggested by
 *                  paul_smith@dg.com.)
 *                . Added support for multi-headed displays. 
 *              - 28/04/92 (MCE) :
 *                . Use the X resource manager.
 *              - 06/05/92 (MCE) :
 *                . Fixed a few potential portability problems. (Thanks
 *                  to paul_smith@dg.com again.)
 *                . CheckConnection () now works properly on multi-headed
 *                  displays. (Thanks to brian@natinst.com for testing
 *                  the `multi-headed' support.)
 *                . Better version of Sleep ().
 *                . Recognize X resources for class "Xautolock".
 *                . Don't update timer while sighupped.
 *                . Switched to vfork () and execl ().
 *                . New copyright notice.
 *              - 11/05/92 (MCE) :
 *                . Close stdout and stderr instead of using "2>&- 1>&-".
 *                  (Suggested by sinkwitz@ifi.unizh.ch.)
 *                . Added "-noclose" for debugging. 
 *              - 08/07/92 (MCE) :
 *                . Efficiency improvements and code embellishments
 *                . Improved conditional "#include"s etc. (Thanks to
 *                  jik@pit-manager.mit.edu and fred@cv.ruu.nl.)
 *                . Moved a couple of premature calls to free ().
 *                  (Purify sure is a great tool!)
 *                . Fixed a race condition related to the `corners'
 *                  feature.
 *                . Fixed a minor initialization bug. 
 *              - 21/12/92 (MCE) :
 *                . Added code to circumvent a server initialisation bug
 *                  (OpenWindows 2.0 and 3.0) related to XQueryPointer ().
 *                  (Thanks to engstrom@src.honeywell.com for providing
 *                  the patch.)
 *              - 22/06/93 (MCE) :
 *                . Reset screen saver upon locking the screen.
 *                  (Suggested by mossip@vizlab.rutgers.edu.)
 *                . Improved resource usage.
 *              - 13/08/93 (MCE) :
 *                . Added "-cornerredelay" for reasons described in the
 *                  man page.
 *              - 23/12/93 (MCE) :
 *                . Improved "#ifdef"s for SYSV.
 *                  (Thanks to John.Somerfield@barclays.co.uk.)
 *              - 11/05/94 (MCE) :
 *                . Corrected a "real stupid typo" ;-).
 *              - 25/08/94 (MCE) :
 *                . More accurate "usage" message.
 *              - 21/09/94 (MCE) :
 *                . Several minor code embellishments.
 *                . Better wording of the copyright statement.
 *                . Ported to VMS. (Thanks to bdr@cbnewsg.cb.att.com
 *                  for providing the nitty-gritty details.)
 *                . Resources now have a (dummy) resource class.
 *                  (Thanks to johnny@cett.alcatel-alsthom.fr for
 *                  pointing out that something had to be done here.)
 *                . Reworked resources processing. (Thanks to
 *                  jlehrke@wmi.physik.tu-muenchen.de for providing the
 *                  original patch (stripped by me).)
 *                . Create a dummy window for proper XKillCLient ()
 *                  behaviour when using xdm without XDMCP or similar.
 *                . Added "-nocloseout" and "-nocloseerr".
 *              - 14/10/94 (MCE) :
 *                . Finally added Xidle support.
 *                . Return value of waitpid () on SYSV was being
 *                  used incorrectly.
 *              - 01/11/94 (MCE) :
 *                . Added SIGUSR1 and SIGUSR2 support, as well as
 *                  "-enable", "-disable", "-toggle" options.
 *                  (Thanks to ckd@loiosh.kei.com for the initial patch.)
 *                . Renamed some stuff for better maintainability.
 *              - 06/11/94 (MCE) :
 *                . Several minor corrections for VMS.
 *                . Added #define _HPUX_SOURCE for c89 on HP/UX.
 *                . Totally reworked time-keeping code to protect it
 *                  against incorrect implementations of sleep ().
 *              - 10/11/94 (MCE) :
 *                . Added "-notifier" option. (Based on a suggestion
 *                  by woodard@peach.kodak.com.)
 *                . Made the "xxx_SEMAPHORE_PID" stuff honour the
 *                  prog_name stuff.
 *                . Casting fixes related to use of time_t.
 *              - 21/11/94 (MCE) :
 *                . Added "#ifdef"s as needed by Novell Unixware. 
 *                  (Thanks to tma@encore.com for reporting this.)
 *                . Plugged a minor memory leak in the resource
 *                  management code.
 *              - 03/01/95 (MCE) :
 *                . Finally solved the logout problems under OpenWinDows.
 *                  (Thanks to the many people who reported this one in
 *                  the past, and in particular to badger@ssd.intel.com
 *                  for putting me on the right track.)
 *                . Some minor cosmetic changes.
 *              - 20/01/95 (MCE) :
 *                . Take the modifier mask into account when looking
 *                  for pointer activity. (Idea taken from xscreensaver
 *                  which is by jwz@mcom.com.)
 *                . Fixed a minor oversight in option handling.
 *                . Fixed some uninitialised memory problems, a rare
 *                  null pointer dereference and a small memory leak.
 *                  (Purify sure is a great tool!)
 *              - 23/01/95 (MCE) :
 *                . Fixed various things ProLint complained about.
 *                . Fixed yet another minor oversight in option handling.
 *              - 01/02/95 (MCE) :
 *                . Added a few unused intialisations because otherwise
 *                  some compilers complain about them missing.
 *              - 21/02/95 (MCE) :
 *                . Initial cleaning up the #ifdef and #include stuff.
 *                . Be less pedantic when validating the notification
 *                  margin if the `corners' feature is not being used.
 *                . Fixed a horrificly stupid blooper that was sometimes 
 *                  causing the thing not to work at all. (Thanks to
 *                  gdonl@gv.ssi1.com and ben@telecom.ptt.nl for
 *                  attracting my attention to this one.)
 *
 * ---------------------------------------------------------------------------
 *
 * Please send bug reports to detroch@imec.be or eyckmans@imec.be.
 *
 * ---------------------------------------------------------------------------
 *
 * Copyright 1990, 1992-1995 by S. De Troch and MCE.
 *
 * Permission to use, copy, modify and distribute this software and the
 * supporting documentation without fee is hereby granted, provided that
 *
 *  1 : Both the above copyright notice and this permission notice
 *      appear in all copies of both the software and the supporting
 *      documentation.
 *  2 : No financial profit is made out of it.
 *
 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
 * EVENT SHALL THEY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 *****************************************************************************/



/*
 *  Have a guess what this does...
 *  ==============================
 *
 *  Warning for swm & tvtwm users : xautolock should *not* be compiled
 *  with vroot.h, because it needs to know the real root window.
 */

#if defined(hpux) || defined (__hpux)
#ifndef _HPUX_SOURCE
#define _HPUX_SOURCE
#endif /* _HPUX_SOURCE */
#endif /* hpux || __hpux */

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#ifdef VMS
#include <ssdef.h>    
#include <processes.h>  /* really needed? */
#endif /* VMS */

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>

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

#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>

#ifdef AIXV3    
#include <sys/m_wait.h>
#endif /* AIXV3 */

#ifdef __osf__
#undef BSD4_3
#endif

#if !defined (news1800) && !defined (sun386)

#if !defined (NOSTDHDRS)
#include <stdlib.h>
#endif /* !NOSTDHDRS */

#if !defined (apollo) && !defined (VMS)
#include <malloc.h>
#include <unistd.h>
#endif /* !apollo && !VMS */

#endif /* !news1800 && !sun386 */

#include "patchlevel.h"




/*
 *  Usefull macros and customization stuff
 *  ======================================
 */
#ifdef HasPrototypes
#define PP(x)                      x
#else /* HasPrototypes */
#define PP(x)                      ()
#endif /* HasPrototypes */

#ifdef VMS
#define ALL_OK                     1       /* for use by exit ()           */
#define PROBLEMS                   SS$_ABORT 
                                           /* for use by exit ()           */
#else /* VMS */
#define ALL_OK                     0       /* for use by exit ()           */
#define PROBLEMS                   1       /* for use by exit ()           */
#endif /* VMS */

#define FALSE                      0       /* as it says                   */
#define TRUE                       1       /* as it says                   */
#define BELL_PERCENT               40      /* as is says                   */
#define MIN_MINUTES                1       /* minimum number of minutes
                                              before firing up the locker  */
#define MINUTES                    10      /* default ...                  */
#define MAX_MINUTES                60      /* maximum ...                  */
#define INITIAL_SLEEP              10      /* for machines on which the
                                              login sequence takes forever */
#define CREATION_DELAY             30      /* should be > 10 and
                                              < min (45,(MIN_MINUTES*30))  */
#define CORNER_SIZE                10      /* size in pixels of the
                                              force-lock areas             */
#define CORNER_DELAY               5       /* number of seconds to wait
                                              before forcing a lock        */
#define SLOW_VMS_DELAY             15      /* explained in VMS.NOTES file  */
#define SIGDISABLE                 SIGUSR1 /* as it says                   */
#define SIGENABLE                  SIGUSR2 /* as it says                   */
#define SIGTOGGLE                  SIGHUP  /* as it says                   */
#define APPLIC_CLASS               "Xautolock"
                                           /* Application class.           */
#define DUMMY_RESOURCE_CLASS       "_xAx_" /* some versions of X don't
                                              like the old NULL class name,
                                              and I consider implementing
                                              real classes isn't worth it. */
#ifdef VMS
#define LOCKER                     "sys$system:decw$pausesession"
#else /*VMS */
#define LOCKER                     "xlock" /* NEVER use the -root option!  */
#endif /*VMS */
#define NOTIFIER                   ""

#ifndef HasVFork
#define vfork                      fork
#endif /* HasVFork */

#define Main                       main
#define Min(a,b)                   (a < b ? a : b)
#define Max(a,b)                   (a > b ? a : b)
#define forever                    for (;;)
#define Error0(str)                fprintf (stderr, str)
#define Error1(str,arg1)           fprintf (stderr, str, arg1)
#define Error2(str,arg1,arg2)      fprintf (stderr, str, arg1, arg2)
#define SetTrigger(delta)          trigger = time ((time_t*) NULL) + delta

static caddr_t                     ch_ptr;  /* this is dirty */
#define Skeleton(t,s)              (ch_ptr = (Caddrt) malloc ((Unsigned) s), \
                                      (ch_ptr == (Caddrt) NULL)              \
                                    ? (Error0 ("Out of memory.\n"),          \
                                       exit (PROBLEMS),                      \
                                       /*NOTREACHED*/ (t*) NULL              \
                                      )                                      \
                                    : (t*) ch_ptr                            \
                                   )                                         \

#define New(tp)                    Skeleton (tp, sizeof (tp))
#define NewArray(tp,n)             Skeleton (tp, sizeof (tp) * (Unsigned) n)




/*
 *  New types
 *  =========
 */
#if defined (apollo) || defined (news1800) 
typedef int                        (*XErrorHandler) PP((Display*,
                                                        XErrorEvent*));
#endif /* apollo || news1800 */

#if defined (news1800) || defined (sun386) 
typedef int                        pid_t;
#endif /* news1800  || sun386*/

#ifdef VMS
typedef long                       pid_t;
#endif /* VMS */

#define Void                       void     /* no typedef because of VAX */
typedef int                        Int;
typedef char                       Char;
typedef char*                      String;
typedef int                        Boolean;
typedef caddr_t                    Caddrt;
typedef unsigned int               Unsigned;
typedef unsigned long              Huge;

#ifdef HasVoidSignalReturn
#define SigRet                     Void     /* no typedef because of VAX */
#else /* HasVoidSignalReturn */
typedef Int                        SigRet;
#endif /* HasVoidSignalReturn */

typedef SigRet                     (*SigHandler) PP((/* OS dependent */));
typedef Boolean                    (*OptAction)  PP((Display*, String));
typedef Void                       (*OptChecker) PP((Display*));

typedef enum
        {
          IGNORE,                                 /* ignore this corner  */
          DONT_LOCK,                              /* never lock          */
          FORCE_LOCK                              /* lock immediately    */
        } CornerAction;

typedef struct QueueItem_
        {
          Window                   window;        /* as it says          */
          time_t                   creationtime;  /* as it says          */
          struct QueueItem_*       next;          /* as it says          */
          struct QueueItem_*       prev;          /* as it says          */
        } aQueueItem, *QueueItem;

typedef struct Queue_
        {
          struct QueueItem_*       head;          /* as it says          */
          struct QueueItem_*       tail;          /* as it says          */
        } aQueue, *Queue;

typedef struct Opt_
        {
          String                   name;          /* as it says          */
          XrmOptionKind            kind;          /* as it says          */
          Caddrt                   value;         /* XrmOptionNoArg only */
          OptAction                action;        /* as it says          */
          OptChecker               checker;       /* as it says          */
        } anOpt, *Opt;




/*
 *  Function declarations
 *  =====================
 */
#if defined(news1800) 
extern Void*    malloc                PP((Unsigned));
#endif /* news1800 */
 
static Void     Usage                 PP((Int));
static Void     EvaluateCounter       PP((Display*));
static Void     QueryIdleTime         PP((Display*));
static Void     QueryPointer          PP((Display*));
static Void     ProcessEvents         PP((Display*, Queue));
static Queue    NewQueue              PP((Void));
static Void     AddToQueue            PP((Queue, Window));
static Void     ProcessQueue          PP((Queue, Display*, time_t));
static Void     SelectEvents          PP((Display*, Window, Boolean));
static Void     CheckConnectionAndSendSignal
				      PP((Display*));
#ifdef VMS
static Int      PollSmPauseWindow     PP((Display*, Window));
#endif /* VMS */
static Int      CatchFalseAlarm       PP((Display*, XErrorEvent));
static Void     ProcessOpts           PP((Display*, Int, String*));
static Boolean  TimeAction            PP((Display*, String));
static Boolean  NotifierAction        PP((Display*, String));
static Boolean  LockerAction          PP((Display*, String));
static Boolean  IdleFileAction        PP((Display*, String));
static Boolean  CornersAction         PP((Display*, String));
static Boolean  CornerSizeAction      PP((Display*, String));
static Boolean  CornerDelayAction     PP((Display*, String));
static Boolean  CornerReDelayAction   PP((Display*, String));
static Boolean  NotifyAction          PP((Display*, String));
static Boolean  BellAction            PP((Display*, String));
static Boolean  NoCloseOutAction      PP((Display*, String));
static Boolean  NoCloseErrAction      PP((Display*, String));
static Boolean  NoCloseAction         PP((Display*, String));
static Boolean  EnableAction          PP((Display*, String));
static Boolean  DisableAction         PP((Display*, String));
static Boolean  ToggleAction          PP((Display*, String));
static Boolean  HelpAction            PP((Display*, String));
static Boolean  VersionAction         PP((Display*, String));
static Boolean  GetPositive           PP((String, Int*));
static Void     TimeChecker           PP((Display*));
static Void     NotifierChecker       PP((Display*));
static Void     LockerChecker         PP((Display*));
static Void     NotifyChecker         PP((Display*));
static Void     CornerSizeChecker     PP((Display*));
static Void     CornerReDelayChecker  PP((Display*));
static Void     BellChecker           PP((Display*));
static SigRet   DisableBySignal       PP((Void));
static SigRet   EnableBySignal        PP((Void));
static SigRet   ToggleBySignal        PP((Void));




/*
 *  Global variables
 *  ================
 */
static time_t        trigger = 0;            /* as it says                 */
static String        prog_name;              /* as it says                 */
static String        locker = LOCKER;        /* as it says                 */
static String        notifier = NOTIFIER;    /* as it says                 */
static time_t        time_limit = MINUTES;   /* as it says                 */
static time_t        notify_margin;          /* as it says                 */
static Int           bell_percent = BELL_PERCENT;
                                             /* as it says                 */
static Int           corner_size = CORNER_SIZE;
                                             /* as it says                 */
static time_t        corner_delay = CORNER_DELAY;
                                             /* as it says                 */
static time_t        corner_redelay;         /* as it says                 */
static Boolean       bell_specified = FALSE; /* as it says                 */
static Boolean       notifier_specified = FALSE;
                                             /* as it says                 */
static Boolean       corner_redelay_specified = FALSE;
                                             /* as it says                 */
static Boolean       notify_lock = FALSE;    /* whether to notify the user
                                                before locking             */
static Boolean       disabled = FALSE;       /* whether to ignore all
                                                time-outs                  */
static Int           signal_to_send = 0;     /* signal to send to an 
                                                already running xautolock  */
static Boolean       use_redelay = FALSE;    /* as it says                 */
static CornerAction  corners[4] = { IGNORE, IGNORE, IGNORE, IGNORE };
                                             /* default CornerActions      */
static Boolean       close_out = TRUE;       /* whether to close stdout    */
static Boolean       close_err = TRUE;       /* whether to close stderr    */
static anOpt         options[] = 
                     {
                       {"help"         , XrmoptionNoArg     ,
                        (Caddrt) ""    , HelpAction         ,
                        (OptChecker) NULL                   },
                       {"version"      , XrmoptionNoArg     ,
                        (Caddrt) ""    , VersionAction      ,
                        (OptChecker) NULL                   },
                       {"notifier"     , XrmoptionSepArg    ,
                        (Caddrt) NULL  , NotifierAction     ,
                        NotifierChecker                     },
                       {"locker"       , XrmoptionSepArg    ,
                        (Caddrt) NULL  , LockerAction       ,
                        LockerChecker                       },
                       {"idlefile"     , XrmoptionSepArg    ,
                        (Caddrt) NULL  , IdleFileAction       ,
                        (OptChecker) NULL                   },
                       {"corners"      , XrmoptionSepArg    ,
                        (Caddrt) NULL  , CornersAction      ,
                        (OptChecker) NULL                   },
                       {"cornersize"   , XrmoptionSepArg    ,
                        (Caddrt) NULL  , CornerSizeAction   ,
                        CornerSizeChecker                   },
                       {"cornerdelay"  , XrmoptionSepArg    ,
                        (Caddrt) NULL  , CornerDelayAction  ,
                        (OptChecker) NULL                   },
                       {"cornerredelay", XrmoptionSepArg    ,
                        (Caddrt) NULL  , CornerReDelayAction,
                        CornerReDelayChecker                },
                       {"time"         , XrmoptionSepArg    ,
                        (Caddrt) NULL  , TimeAction         ,
                        TimeChecker                         },
                       {"notify"       , XrmoptionSepArg    ,
                        (Caddrt) NULL  , NotifyAction       ,
                        NotifyChecker                       },
                       {"bell"         , XrmoptionSepArg    ,
                        (Caddrt) NULL  , BellAction         ,
                        BellChecker                         },
                       {"enable"       , XrmoptionNoArg     ,
                        (Caddrt) ""    , EnableAction       ,
                        (OptChecker) NULL                   },
                       {"disable"      , XrmoptionNoArg     ,
                        (Caddrt) ""    , DisableAction      ,
                        (OptChecker) NULL                   },
                       {"toggle"       , XrmoptionNoArg     ,
                        (Caddrt) ""    , ToggleAction       ,
                        (OptChecker) NULL                   },
                       {"noclose"      , XrmoptionNoArg     ,
                        (Caddrt) ""    , NoCloseAction      ,
                        (OptChecker) NULL                   },
                       {"nocloseout"   , XrmoptionNoArg     ,
                        (Caddrt) ""    , NoCloseOutAction   ,
                        (OptChecker) NULL                   },
                       {"nocloseerr"   , XrmoptionNoArg     ,
                        (Caddrt) ""    , NoCloseErrAction   ,
                        (OptChecker) NULL                   },
                     };                      /* as it says, the order is
                                                important                  */




/*
 *  Resource database related functions
 *  ===================================
 *
 *  Support functions
 *  -----------------
 */
static Boolean  GetPositive (arg, pos)
String  arg;  /* string to scan                  */
Int*    pos;  /* adress where to store the stuff */

{
  Char  c;           /* dummy            */
  Int   old = *pos;  /* backup old value */

  if (   sscanf (arg, "%d%c", pos, &c) == 1
      && *pos >= 0
     )
  {
    return TRUE;
  }
  
  *pos = old;
  return FALSE;
}



/*
 *  Action functions
 *  ----------------
 */
/*ARGSUSED*/
static Boolean  HelpAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Usage (ALL_OK);

  /*NOTREACHED*/
  return TRUE;  /* for lint and gcc */
}


/*ARGSUSED*/
static Boolean  VersionAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Error2 ("%s : patchlevel %d\n", prog_name, PATCHLEVEL);
  exit (ALL_OK);

  /*NOTREACHED*/
  return TRUE;  /* for lint and gcc */
}


/*ARGSUSED*/
static Boolean  CornerSizeAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  return GetPositive (arg, &corner_size);
}


/*ARGSUSED*/
static Boolean  CornerDelayAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Int      tmp = 0;  /* temporary storage */
  Boolean  res;      /* return value      */

  if (res = GetPositive (arg, &tmp)) corner_delay = (time_t) tmp;

  return res;
}


/*ARGSUSED*/
static Boolean  CornerReDelayAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Int      tmp = 0;  /* temporary storage */
  Boolean  res;      /* return value      */

  if (res = GetPositive (arg, &tmp)) corner_redelay = (time_t) tmp;

  corner_redelay_specified = TRUE;
  return res;
}


/*ARGSUSED*/
static Boolean  TimeAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Int      tmp = 0 ;  /* temporary storage */
  Boolean  res;       /* return value      */

  if (res = GetPositive (arg, &tmp)) time_limit = (time_t) tmp;

  return res;
}


/*ARGSUSED*/
static Boolean  NotifyAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Int  tmp = 0;  /* temporary storage */

  if (notify_lock = GetPositive (arg, &tmp)) notify_margin  = (time_t) tmp;

  return notify_lock;
}


/*ARGSUSED*/
static Boolean  BellAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  bell_specified = TRUE;
  return GetPositive (arg, &bell_percent);
}


/*ARGSUSED*/
static Boolean  NoCloseOutAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  close_out = FALSE;
  return TRUE;
}


/*ARGSUSED*/
static Boolean  NoCloseErrAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  close_err = FALSE;
  return TRUE;
}


/*ARGSUSED*/
static Boolean  NoCloseAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  NoCloseOutAction (d, arg);
  NoCloseErrAction (d, arg);
  return TRUE;
}


/*ARGSUSED*/
static Boolean  DisableAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* program name    */

{
  if (signal_to_send) return FALSE;
  signal_to_send = SIGDISABLE;
  return TRUE;  
}


/*ARGSUSED*/
static Boolean  EnableAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* program name    */

{
  if (signal_to_send) return FALSE;
  signal_to_send = SIGENABLE;
  return TRUE;  
}


/*ARGSUSED*/
static Boolean  ToggleAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* program name    */

{
  if (signal_to_send) return FALSE;
  signal_to_send = SIGTOGGLE;
  return TRUE;  
}


/*ARGSUSED*/
static Boolean  NotifierAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  notifier_specified = TRUE;
  notifier = arg;
  return TRUE;
}


/*ARGSUSED*/
static Boolean  LockerAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  if (strcmp(arg, "NONE") == 0)
	locker = NULL;
  else
	locker = arg;
  return TRUE;
}

/*ARGSUSED*/
static Boolean  IdleFileAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  if (strcmp(arg, "NONE") == 0)
    return TRUE;

  if (IdleFileInit(arg) < 0) {
    Error0 ("Could not access idlefile.\n");
    exit (PROBLEMS);
  }
  return TRUE;
}


/*ARGSUSED*/
static Boolean  CornersAction (d, arg)
Display*  d;    /* display pointer */
String    arg;  /* argument value  */

{
  Int  c;  /* loop counter */

  if (strlen (arg) != 4) return FALSE;
  
  for (c = -1; ++c < 4; )
  {
    switch (arg[c])
    {
      case '0' :
        corners[c] = IGNORE;
        continue;

      case '-' :
        corners[c] = DONT_LOCK;
        continue;

      case '+' :
        corners[c] = FORCE_LOCK;
        continue;

      default :
        return FALSE;
    }
  }

  return TRUE;
}



/*
 *  Consistency checkers
 *  --------------------
 */
/*ARGSUSED*/
static Void  TimeChecker (d)
Display*  d;  /* display pointer */

{
  if (time_limit < MIN_MINUTES)
  {
    Error1 ("Setting time to minimum value of %ld minute(s).\n",
            time_limit = MIN_MINUTES);
  }
  else if (time_limit > MAX_MINUTES)
  {
    Error1 ("Setting time to maximum value of %ld minute(s).\n",
            time_limit = MAX_MINUTES);
  }

  time_limit *= 60; /* convert to seconds */
}


/*ARGSUSED*/
static Void  NotifierChecker (d)
Display*  d;  /* display pointer */

{
  if (strcmp (notifier, ""))
  {
    if (!notify_lock)
    {
      Error0 ("Using -notifier without -notify makes no sense.\n");
    }
#ifndef VMS
    else
    {
      String  tmp;  /*as it says */

     /*
      *  Add an `&' to the notifier command, so that it always gets put 
      *  run as a background process and things will work out properly 
      *  later. The rationale behind this hack is explained elsewhere.
      */
      sprintf (tmp = NewArray (Char, strlen (notifier) + 3), "%s &", notifier);
      notifier = tmp;
    }
#endif /* VMS */
  }
}


/*ARGSUSED*/
static Void  LockerChecker (d)
Display*  d;  /* display pointer */

{
#ifndef VMS
  String  tmp;  /*as it says */

 /*
  *  Let's manipulate the locker command a bit
  *  in order to reduce resource usage. 
  */
  if (locker) {
    sprintf (tmp = NewArray (Char, strlen (locker) + 6), "exec %s", locker);
    locker = tmp;
  }
#endif /* VMS */
}


/*ARGSUSED*/
static Void  NotifyChecker (d)
Display*  d;  /* display pointer */

{
  if (   notify_lock
      && (   corners[0] == FORCE_LOCK  
	  || corners[1] == FORCE_LOCK
	  || corners[2] == FORCE_LOCK
	  || corners[3] == FORCE_LOCK
         )
     )
  {
    int min_delay = Min (corner_delay, corner_redelay);

    if (notify_margin > min_delay)
    {
      Error1 ("Notification time reset to %ld second(s).\n",
              notify_margin = min_delay);
    }

    if (notify_margin > time_limit / 2)
    {
      Error1 ("Notification time reset to %ld seconds.\n",
              notify_margin = time_limit / 2);
    }
  }
}


/*ARGSUSED*/
static Void  BellChecker (d)
Display*  d;  /* display pointer */

{
  if (bell_specified)
  {
    if (!notify_lock)
    {
      Error0 ("Using -bell without -notify makes no sense.\n");
      bell_percent = 1;
    }
    else if (notifier_specified)
    {
      Error0 ("Using both -bell and -notifier makes no sense.\n");
      bell_percent = 1;
    }
  }

  if (   bell_percent < 1
      || bell_percent > 100
     )
  {
    Error1 ("Bell percentage reset to %d%%.\n",
            bell_percent = BELL_PERCENT);
  }
}


/*ARGSUSED*/
static Void  CornerSizeChecker (d)
Display*  d;  /* display pointer */

{
  Int      s;                /* screen index   */
  Screen*  scr;              /* screen pointer */
  Int      max_corner_size;  /* as it says     */

  for (max_corner_size = 32000, s = -1; ++s < ScreenCount (d); )
  {
    scr = ScreenOfDisplay (d, s);

    if (   max_corner_size > WidthOfScreen (scr) / 4
        || max_corner_size > HeightOfScreen (scr) / 4
       )
    {
      max_corner_size = Min (WidthOfScreen (scr), HeightOfScreen (scr)) / 4;
    }
  }

  if (corner_size > max_corner_size)
  {
    Error1 ("Corner size reset to %d pixels.\n",
            corner_size = max_corner_size);
  }
}


/*ARGSUSED*/
static Void  CornerReDelayChecker (d)
Display*  d;  /* display pointer */

{
  if (!corner_redelay_specified)
  {
    corner_redelay = corner_delay;
  }
}



/*
 *  Function for informing the user about syntax errors
 *  ---------------------------------------------------
 */
static Void  Usage (exit_code)
Int  exit_code;  /* as it says */

{
  String  blanks;  /* string full of blanks */
  size_t  len;     /* number of blanks      */


 /*
  *  The relative overhead is enormous here, but who cares.
  *  I'm a perfectionist and Usage () doesn't return anyway.
  */
  len = strlen ("Usage :  ") + strlen (prog_name);
  memset (blanks = NewArray (Char, len + 1), ' ', len);
  blanks[len] = '\0';


 /*
  *  This is where the actual work gets done...
  */
  Error0 ("\n");
  Error1 ("Usage : %s ", prog_name);
  Error0 ("[-help][-version][-time mins][-locker locker]\n");
  Error0 (blanks);
  Error0 ("[-notify margin][-notifier notifier][-bell percent]\n");
  Error0 (blanks);
  Error0 ("[-corners xxxx][-cornerdelay secs][-cornerredelay secs]\n");
  Error0 (blanks);
  Error0 ("[-cornersize pixels][-noclose][-nocloseout][-nocloseerr]\n");
  Error0 (blanks);
  Error0 ("[-enable][-disable][-toggle][-idlefile path]\n");

  Error0 ("\n");
  Error0 (" -help               : print this message and exit.\n");
  Error0 (" -version            : print version number and exit.\n");
  Error2 (" -time mins          : time to lock screen [%ld <= mins <= %ld].\n",
                                  MIN_MINUTES, MAX_MINUTES);
  Error0 (" -locker locker      : program used to lock (`NONE' to skip locking).\n");
  Error0 (" -notify margin      : notify this many seconds before locking.\n");
  Error0 (" -notifier notifier  : program used to notify.\n");
  Error0 (" -bell percent       : loudness of notification beeps.\n");
  Error0 (" -corners xxxx       : corner actions (0, +, -) in this order :\n");
  Error0 ("                       topleft topright bottomleft bottomright\n");
  Error0 (" -cornerdelay secs   : time to lock screen in a `+' corner.\n");
  Error0 (" -cornerredelay secs : time to relock screen in a `+' corner.\n");
  Error0 (" -cornersize pixels  : size of corner areas.\n");
  Error0 (" -nocloseout         : do not close stdout.\n");
  Error0 (" -nocloseerr         : do not close stderr.\n");
  Error0 (" -noclose            : close neither stdout nor stderr.\n");
  Error0 (" -enable             : enable a running xautolock.\n");
  Error0 (" -disable            : disable a running xautolock.\n");
  Error0 (" -toggle             : toggle a running xautolock.\n");
  Error0 (" -idlefile path      : set file to stamp with idle time, or `NONE'\n");

  Error0 ("\n");
  Error0 ("Defaults :\n");

  Error0 ("\n");
  Error1 ("  time          : %ld minutes\n" , MINUTES     );
  Error1 ("  locker        : %s\n"          , LOCKER      );
  Error0 ("  notify        : don't notify\n"              );
  Error1 ("  notifier      : %s\n"          , NOTIFIER    );
  Error1 ("  bell          : %d%%\n"        , BELL_PERCENT);
  Error0 ("  corners       : 0000\n"                      );
  Error1 ("  cornerdelay   : %ld seconds\n" , CORNER_DELAY);
  Error1 ("  cornerredelay : %ld seconds\n" , CORNER_DELAY);
  Error1 ("  cornersize    : %d pixels\n"   , CORNER_SIZE );

  Error0 ("\n");
  Error1 ("Patchlevel : %d\n", PATCHLEVEL);

  Error0 ("\n");

  exit (exit_code);
}



/*
 *  Function for processing command line arguments and defaults
 *  -----------------------------------------------------------
 */
static Void  ProcessOpts (d, argc, argv)
Display*  d;       /* display pointer     */
Int       argc;    /* number of arguments */
String    argv[];  /* array of arguments  */

{
  Int                nof_options = sizeof (options) / sizeof (options[0]);
                                /* number of supported options   */
  Int                j;         /* loop counter                  */
  Unsigned           l;         /* temporary storage             */
  Unsigned           max_l;     /* temporary storage             */
  Char*              ptr;       /* temporary storage             */
  Char*              dummy;     /* as it says                    */
  String             fullname;  /* full resource name            */
  String             str;       /* temporary storage             */
  XrmValue           value;     /* resource value container      */
  XrmOptionDescList  xoptions;  /* optionslist in Xlib format    */
  XrmDatabase        resc_db = (XrmDatabase) NULL;
                                /* resource file database        */
  XrmDatabase        cmdl_db = (XrmDatabase) NULL;
                                /* command line options database */


 /*
  *  Beautify argv[0] and remember it for later use.
  */
#ifdef VMS
  if (ptr = strrchr (argv[0], ']'))
  {
    prog_name = ptr + 1;
  }
  else
  {
    prog_name = argv[0];
  }

  if (ptr = strchr (prog_name, '.'))
  {
    *ptr = '\0';
  }
#else /* VMS */
  if (ptr = strrchr (argv[0], '/'))
  {
    prog_name = ptr + 1;
  }
  else
  {
    prog_name = argv[0];
  }
#endif /* VMS */


 /*
  *  Collect defaults from various places except the command line into one
  *  resource database, then parse the command line options into an other.
  *  Both databases are not merged, because we want to know where exactly
  *  each resource value came from.
  *
  *  One day I might extend this stuff to fully cover *all* possible
  *  resource value sources, but... One of the problems is that various
  *  pieces of documentation make conflicting claims with respect to the
  *  proper order in which resource value sources should be accessed.
  */
  if (XResourceManagerString (d) != (String) NULL)
  {
    XrmMergeDatabases (XrmGetStringDatabase (XResourceManagerString (d)),
		       &resc_db);
  }
  else if ((str = getenv ("XENVIRONMENT")) != (String) NULL)
  {
    XrmMergeDatabases (XrmGetFileDatabase (str), &resc_db);
    free (str);
  }

  xoptions = NewArray (XrmOptionDescRec, nof_options);

  for (j = -1, max_l = 0; ++j < nof_options; )
  {
    l = strlen (options[j].name) + 1;
    max_l = Max (max_l, l);

    sprintf (xoptions[j].option = NewArray (Char, l + 1),
	     "-%s", options[j].name);
    sprintf (xoptions[j].specifier = NewArray (Char, l + 1),
             ".%s", options[j].name);
    xoptions[j].argKind = options[j].kind;
    xoptions[j].value = options[j].value;
  }

  XrmParseCommand (&cmdl_db, xoptions, nof_options, prog_name, &argc, argv);

  if (--argc) Usage (PROBLEMS);


 /*
  *  Let's be perfect...
  */
  {
    Unsigned  class_l = strlen (APPLIC_CLASS);  /* temporary storage */
    Unsigned  prog_l  = strlen (prog_name);     /* temporary storage */

    fullname = NewArray (Char, Max (prog_l, class_l) + max_l + 1);
  }


 /*
  *  Call the action functions.
  */
  for (j = -1; ++j < nof_options; )
  {
    sprintf (fullname, "%s%s", prog_name, xoptions[j].specifier);

    if (   XrmGetResource (cmdl_db, fullname, DUMMY_RESOURCE_CLASS,
                           &dummy, &value)
        == True
       )
    {
      if (!(*(options[j].action)) (d, value.addr))
      {
	Usage (PROBLEMS); 
      }
    }
    else if (   XrmGetResource (resc_db, fullname, DUMMY_RESOURCE_CLASS,
                                &dummy, &value)
             == True
            )
    {
      if (!(*(options[j].action)) (d, value.addr))
      {
        Error2 ("Can't interprete \"%s\" for \"%s\", using default.\n", 
                value.addr, fullname);
      }
    }
    else
    {
      sprintf (fullname, "%s%s", APPLIC_CLASS, xoptions[j].specifier);

      if (   (   XrmGetResource (resc_db, fullname, DUMMY_RESOURCE_CLASS,
                                 &dummy, &value)
              == True
             )
          && !(*(options[j].action)) (d, value.addr)
         )
      {
        Error2 ("Can't interprete \"%s\" for \"%s\", using default.\n", 
                value.addr, fullname);
      }
    }
  }



 /*
  *  Call the consistency checkers.
  */
  for (j = -1; ++j < nof_options; )
  {
    if (options[j].checker != (OptChecker) NULL)
    {
      (*(options[j].checker)) (d);
    }
  }

 
 /*
  *  General clean up.
  */
  XrmDestroyDatabase (cmdl_db);
  XrmDestroyDatabase (resc_db);

  for (j = -1; ++j < nof_options; )
  {
    free (xoptions[j].option);
    free (xoptions[j].specifier);
  }

  free (xoptions);
  free (fullname);
}




/*
 *  Functions related to the window queue
 *  =====================================
 *
 *  Function for creating a new queue
 *  ---------------------------------
 */
static Queue  NewQueue ()

{
  Queue  queue;  /* return value */

  queue = New (aQueue);
  queue->tail = New (aQueueItem);
  queue->head = New (aQueueItem);

  queue->tail->next = queue->head;
  queue->head->prev = queue->tail;
  queue->tail->prev = queue->head->next = (QueueItem) NULL;

  return queue;
}



/*
 *  Function for adding an item to a queue
 *  --------------------------------------
 */
static Void  AddToQueue (queue, window)
Queue   queue;   /* as it says */
Window  window;  /* as it says */

{
  QueueItem  new;  /* new item */

  new = New (aQueueItem);

  new->window = window;
  new->creationtime = time ((time_t*) NULL);
  new->next = queue->tail->next;
  new->prev = queue->tail;
  queue->tail->next->prev = new;
  queue->tail->next = new;
}



/*
 *  Function for processing those entries that are old enough
 *  ---------------------------------------------------------
 */
static Void  ProcessQueue (queue, d, age)
Queue     queue;  /* as it says      */
Display*  d;      /* display pointer */
time_t    age;    /* required age    */

{
  QueueItem  current;  /* as it says */
  time_t     now;      /* as it says */

  time (&now);
  current = queue->head->prev;

  while (   current->prev
         && current->creationtime + age < now
        )
  {
    SelectEvents (d, current->window, False);
    current = current->prev;
    free (current->next);
  }

  current->next = queue->head;
  queue->head->prev = current;
}




/*
 *  Functions related to (the lack of) user activity
 *  ================================================
 *
 *  Function for processing the event queue
 *  ---------------------------------------
 */
static Void  ProcessEvents (d, queue)
Display*  d;      /* display pointer */
Queue     queue;  /* as it says      */

{
  XEvent  event;  /* as it says */
  Boolean had_events = FALSE;


 /*
  *  Read whatever is available for reading.
  */
  while (XPending (d))
  {
    if (XCheckMaskEvent (d, SubstructureNotifyMask, &event))
    {
      if (event.type == CreateNotify)
      {
        AddToQueue (queue, event.xcreatewindow.window);
      }
    }
    else
    {
      XNextEvent (d, &event);
    }


   /*
    *  Reset the counter if and only if the event is a KeyPress
    *  event *and* was not generated by XSendEvent ().
    */
    if (   event.type == KeyPress
        && !event.xany.send_event
       )
    {
      SetTrigger (time_limit);
      had_events = TRUE;
    }
  }

  if (had_events)
    IdleFileStamp();

 /*
  *  Check the window queue for entries that are older than
  *  CREATION_DELAY seconds.
  */
  ProcessQueue (queue, d, (time_t) CREATION_DELAY);
}



/*
 *  Function for querying Xidle
 *  ---------------------------
 */
/*ARGSUSED*/
static Void  QueryIdleTime (d)
Display*  d;  /* display pointer */

{
#ifdef HasXidle
  Time  idle_time = 0;  /* milli secs since last input event */

  XGetIdleTime (d, &idle_time);

  if (idle_time < 1000)  
  {
    SetTrigger (time_limit);
    if (use_idlefile)
      IdleFileStamp();
  }
#endif /* HasXidle */
}



/*
 *  Function for monitoring pointer movements
 *  -----------------------------------------
 */
static Void  QueryPointer (d)
Display*  d;  /* display pointer */

{
  Window           dummy_w;            /* as it says                    */
  Int              dummy_c;            /* as it says                    */
  Unsigned         mask;               /* modifier mask                 */
  Int              root_x;             /* as it says                    */
  Int              root_y;             /* as it says                    */
  Int              corner;             /* corner index                  */
  time_t           now;                /* as it says                    */
  time_t           new_trigger;        /* temporary storage             */
  Int              i;                  /* loop counter                  */
  static Window    root;               /* root window the pointer is on */
  static Screen*   screen;             /* screen the pointer is on      */
  static Unsigned  prev_mask = 0;      /* as it says                    */
  static Int       prev_root_x = -1;   /* as it says                    */
  static Int       prev_root_y = -1;   /* as it says                    */
  static Boolean   first_call = TRUE;  /* as it says                    */


 /*
  *  Have a guess...
  */
  if (first_call)
  {
    first_call = FALSE;
    root = DefaultRootWindow (d);
    screen = ScreenOfDisplay (d, DefaultScreen (d));
  }


 /*
  *  Find out whether the pointer has moved. Using XQueryPointer for this
  *  is gross, but it also is the only way never to mess up propagation
  *  of pointer events.
  *
  *  Remark : Unlike XNextEvent(), XPending () doesn't notice if the
  *           connection to the server is lost. For this reason, earlier
  *           versions of xautolock periodically called XNoOp (). But
  *           why not let XQueryPointer () do the job for us, since
  *           we now call that periodically anyway?
  */
  if (!XQueryPointer (d, root, &root, &dummy_w, &root_x, &root_y,
                      &dummy_c, &dummy_c, &mask))
  {
   /*
    *  Pointer has moved to another screen, so let's find out which one.
    */
    for (i = -1; ++i < ScreenCount (d); ) 
    {
      if (root == RootWindow (d, i)) 
      {
        screen = ScreenOfDisplay (d, i);
        break;
      }
    }
  }

  if (   root_x == prev_root_x
      && root_y == prev_root_y
      && mask == prev_mask
     )
  {
   /*
    *  If the pointer has not moved since the previous call and 
    *  is inside one of the 4 corners, we act according to the
    *  contents of the "corners" array.
    *
    *  If root_x and root_y are less than zero, don't lock even if
    *  FORCE_LOCK is set in the upper-left corner. Why? 'cause
    *  on initial server startup, IF the pointer is never moved,
    *  XQueryPointer returns values less than zero (only some
    *  servers, Openwindows 2.0 and 3.0 in particular).
    */
    if (   (corner = 0,
               root_x <= corner_size && root_x >= 0
            && root_y <= corner_size && root_y >= 0
           )
        || (corner++,
               root_x >= WidthOfScreen  (screen) - corner_size - 1
            && root_y <= corner_size
           )
        || (corner++,
               root_x <= corner_size
            && root_y >= HeightOfScreen (screen) - corner_size - 1
           )
        || (corner++,
               root_x >= WidthOfScreen  (screen) - corner_size - 1
            && root_y >= HeightOfScreen (screen) - corner_size - 1
           )
       )
    {
      time (&now);

      switch (corners[corner])
      {
        case FORCE_LOCK :
          new_trigger =   now - 1
                        + (use_redelay ? corner_redelay : corner_delay);

          if (new_trigger < trigger)
          {
            SetTrigger (new_trigger - now);
          }
          break;

        case DONT_LOCK :
          SetTrigger (time_limit);
	  IdleFileStamp();
      }
    }
  }
  else
  {
    use_redelay = FALSE;
    prev_root_x = root_x;
    prev_root_y = root_y;
    prev_mask = mask;
    SetTrigger (time_limit);
    IdleFileStamp();
  }
}



/*
 *  Function for deciding whether to lock
 *  -------------------------------------
 */
/*ARGSUSED*/
static Void  EvaluateCounter (d)
Display*  d;  /* display pointer */

{
  static pid_t   locker_pid = 0;         /* child pid   */
  static time_t  prev_notification = 0;  /* as it says  */
  time_t         now = 0;                /* as it says  */
#ifdef VMS
  Window         r;                      /* root window */


 /*
  *  This is not logically correct, because we are ignoring the 
  *  possibility that the machine has multiple displays. But then
  *  again, it's only a hack needed for another VMS hack so...
  */
  r = RootWindowOfScreen (ScreenOfDisplay (d, 0));
#endif /* VMS */


 /*
  *  Obvious things first.
  *
  *  The trigger is being moved all the time while in disabled
  *  mode in order to make absolutely sure we cannot run into
  *  trouble by an enable signal coming in in an odd moment.
  *  Otherwise we possibly might lock too soon.
  */
  if (disabled)
  {
    SetTrigger (time_limit);
    return;
  }


 /*
  *  Next, wait for the previous locker (if any).
  */
#ifdef VMS
  if (PollSmPauseWindow (d, r))  
  {
#else /* VMS */
  if (locker_pid)
  {
#if !defined (UTEKV) && !defined (SYSV) && !defined(SVR4) && !defined(__osf__)
    union wait  status;  /* childs process status */
#else /* !UTEKV && !SYSV && !SVR4 && !__osf__ */
    int         status;  /* childs process status */
#endif /* !UTEKV && !SYSV && !SVR4 && !__osf__ */


#if !defined (UTEKV) && !defined (SYSV) && !defined(SVR4)
    if (wait3 (&status, WNOHANG, (struct rusage*) NULL))
#else /* !UTEKV && !SYSV && !SVR4 */
    if (waitpid (-1, &status, WNOHANG)) 
#endif /* !UTEKV && !SYSV && !SVR4 */
    {
      use_redelay = TRUE;
      locker_pid = 0;
    }
#endif /* VMS */

    SetTrigger (time_limit);

   /*
    *  No return here! The pointer may be sitting in a corner, while
    *  parameter settings may be such that we need to start another
    *  locker without further delay. If you think this cannot happen,
    *  consider the case in which the locker simply crashed.
    */
  }


 /*
  *  Now trigger the notifier if required. 
  */
  time (&now);
  
  if (   notify_lock
      && now + notify_margin >= trigger
      && prev_notification < now - notify_margin - 1
     )
  {
    if (notifier_specified)
    {
     /*
      *  There is a dirty trick here. On the one hand, we don't want
      *  to block until the notifier returns, but on the other one
      *  we don't want to have it interfere with the wait () stuff we 
      *  do to keep track of the locker. To obtain both, the notifier
      *  command has already been patched by NotifierChecker () so that
      *  it gets backgrounded by the shell started by system ().
      *
      *  For the time being, VMS users are out of luck: their xautolock
      *  will indeed block until the notifier returns.
      */
      system (notifier);
    }
    else
    {
      XBell (d, bell_percent);
      XSync (d, 0);
    }

    prev_notification = now;
  }
  

 /*
  *  Finally fire up the locker if time has come. 
  */
  if (now >= trigger)
  {
#ifdef VMS
    if (!PollSmPauseWindow (d, r))  
#else /* VMS */
    if (!locker_pid)
#endif /* VMS */
    {
      switch (locker_pid = vfork ())
      {
        case -1 :
          locker_pid = 0;
          break;
  
        case 0 :
          close (ConnectionNumber (d));
#ifdef VMS
          execl (locker, (String) NULL);
#ifdef SLOW_VMS
          sleep (SLOW_VMS_DELAY); 
#endif /* SLOW_VMS */
#else /* VMS */
          execl ("/bin/sh", "sh", "-c", locker, (String) NULL); 
#endif /* VMS */
          _exit (PROBLEMS);
  
        default :
         /*
          *  In general, xautolock should keep it's fingers off the real
          *  screen saver because no universally acceptable policy can 
          *  be defined. In no case should it decide to disable or enable 
          *  it all by itself. Setting the screen saver policy is something
          *  the locker should take care of. After all, xautolock is not
          *  supposed to know what the "locker" does and doesn't do. 
          *  People might be using xautolock for totally different
          *  purposes (which, by the way, is why it will accept a
          *  different set of X resources after being renamed).
          *
          *  Nevertheless, simply resetting the screen saver is a
          *  convenience action that aids many xlock users, and doesn't
          *  harm anyone. The problem with xlock is that it can be told 
          *  to replace (= disable) the real screen saver, but that it
          *  forgets to reset that same screen saver if it was already
          *  active at the time xlock starts. I guess xlock wasn't
          *  designed to be run without a user actually typing the
          *  comand ;-).
          */
          XResetScreenSaver(d);
  
          SetTrigger (time_limit);
          XSync (d,0);
      }

      use_redelay = FALSE;
    }
  }
}




/*
 *  Miscellaneous functions
 *  =======================
 *
 *  X Error handler
 *  ---------------
 */
/*ARGSUSED*/
static Int  CatchFalseAlarm (d, event)
Display*     d;      /* display pointer */
XErrorEvent  event;  /* error event     */

{
  return 0;
}



/*
 *  SIGDISABLE signal handler
 *  -------------------------
 */
static SigRet  DisableBySignal ()

{
 /*
  *  The order in which things are done is rather important here.
  */
  SetTrigger (time_limit);
  disabled = TRUE;

  signal (SIGDISABLE, (SigHandler) DisableBySignal);

#ifndef HasVoidSignalReturn 
  return 0;
#endif /* HasVoidSignalReturn */
}



/*
 *  SIGENABLE signal handler
 *  ------------------------
 */
static SigRet  EnableBySignal ()

{
 /*
  *  The order in which things are done is rather important here.
  */
  disabled = FALSE;

  signal (SIGENABLE, (SigHandler) EnableBySignal);

#ifndef HasVoidSignalReturn 
  return 0;
#endif /* HasVoidSignalReturn */
}



/*
 *  SIGTOGGLE signal handler
 *  ------------------------
 */
static SigRet  ToggleBySignal ()

{
 /*
  *  The order in which things are done is rather important here.
  */
  if (disabled = !disabled)  /* = intended */
  {
    SetTrigger (time_limit);
  }

  signal (SIGTOGGLE, (SigHandler) ToggleBySignal);

#ifndef HasVoidSignalReturn 
  return 0;
#endif /* HasVoidSignalReturn */
}



/*
 *  Function for finding out whether another xautolock is already running
 *  ---------------------------------------------------------------------
 */
static Void  CheckConnectionAndSendSignal (d)
Display*  d;  /* display pointer */

{
  pid_t   pid;        /* as it says             */
  Window  r;          /* root window            */
  Atom    property;   /* property atom          */
  Atom    type;       /* property type atom     */
  Int     format;     /* property format        */
  Huge    nof_items;  /* actual number of items */
  Huge    after;      /* dummy                  */
  String  sem;        /* property name          */
  Char*   ptr;        /* iterator               */
  pid_t*  contents;   /* actual property value  */

#define SEM_PID "_SEMAPHORE_PID"  /* for backwards compatibility */
  sem = NewArray (Char, strlen (prog_name) + strlen (SEM_PID) + 1);
  sprintf (sem, "%s%s", prog_name, SEM_PID);
  for (ptr = sem; *ptr; ++ptr) *ptr = (Char) toupper (*ptr);
#undef SEM_PID

  r = RootWindowOfScreen (ScreenOfDisplay (d, 0));
  property = XInternAtom (d, sem, False);
  free (sem);

  XGrabServer (d);
  XGetWindowProperty (d, r, property, 0L, 2L, False, AnyPropertyType,
                      &type, &format, &nof_items, &after,
                      (unsigned char**) &contents);

  if (type == XA_INTEGER)
  {
   /*
    *  This breaks if the other xautolock is not on the same machine.
    */
    if (kill (*contents, 0))
    {
      if (signal_to_send)
      {
        Error1 ("No process with PID %d.\n", *contents);
        exit (PROBLEMS);
      }
    }
    else if (signal_to_send)
    {
      kill (*contents, signal_to_send);
      exit (ALL_OK);
    }
    else
    {
      Error2 ("%s is already running (PID %d).\n", prog_name, *contents);
      exit (PROBLEMS);
    }
  }
  else if (signal_to_send)
  {
    Error1 ("Could not locate a running %s.\n", prog_name);
    exit (PROBLEMS);
  }

  signal (SIGTOGGLE , (SigHandler) ToggleBySignal);
  signal (SIGENABLE , (SigHandler) EnableBySignal);
  signal (SIGDISABLE, (SigHandler) DisableBySignal);

  pid = getpid ();
  XChangeProperty (d, r, property, XA_INTEGER, 8,
                   PropModeReplace, (unsigned char*) &pid, sizeof (pid));
  XUngrabServer (d);

  XFree ((Char*) contents);
}



#ifdef VMS
/*
 *  Function for polling the Session Manager Pause Window on VMS
 *  ------------------------------------------------------------
 */
static Int  PollSmPauseWindow (d, r)
Display* d;
Window   r;

{
  static Window*  w = (Window*) NULL;  /* PauseWindow window ID     */
  Atom            pause;               /* property we're after      */
  Atom            type;                /* real type of the property */
  Int             format;              /* dummy                     */
  Huge            nof_items;           /* dummy                     */
  Huge            bytes;               /* dummy                     */

  if (w) XFree (w);

  pause = XInternAtom (d, "_DEC_SM_PAUSE_WINDOW", True);

  return (    (   XGetWindowProperty (d, r, pause, 0, 1, False, XA_WINDOW,
                                      &type, &format, &nof_items, &bytes,
                                      (unsigned char**) &w)
               == Success
              )
           && type != None
         );
}
#endif /* VMS */



/*
 *  Function for selecting events on a tree of windows
 *  --------------------------------------------------
 */
static Void  SelectEvents (d, window, substructure_only)
Display*  d;                  /* display pointer   */
Window    window;             /* window            */
Boolean   substructure_only;  /* as it says        */

{
  Window             root;              /* root window of this window */
  Window             parent;            /* parent of this window      */
  Window*            children;          /* children of this window    */
  Unsigned           nof_children = 0;  /* number of children         */
  Unsigned           i;                 /* loop counter               */
  XWindowAttributes  attribs;           /* attributes of the window   */


 /*
  *  Start by querying the server about parent and child windows.
  */
  if (!XQueryTree (d, window, &root, &parent, &children, &nof_children))
  {
    return;
  }


 /*
  *  Build the appropriate event mask. The basic idea is that we don't
  *  want to interfere with the normal event propagation mechanism if
  *  we don't have to.
  */
  if (substructure_only)
  {
    XSelectInput (d, window, SubstructureNotifyMask);
  }
  else
  {
    if (parent == None)  /* the *real* rootwindow */
    {
      attribs.all_event_masks = 
      attribs.do_not_propagate_mask = KeyPressMask;
    }
    else if (XGetWindowAttributes (d, window, &attribs) == 0)
    {
      return;
    }

    XSelectInput (d, window,   SubstructureNotifyMask
                             | (  (  attribs.all_event_masks
                                   | attribs.do_not_propagate_mask)
                                & KeyPressMask));
  }


 /*
  *  Now do the same thing for all children.
  */
  for (i = 0; i < nof_children; ++i)
  {
    SelectEvents (d, children[i], substructure_only);
  }

  if (nof_children) XFree ((Char*) children);
}



/*
 *  Main function
 *  -------------
 */
Int  Main (argc, argv)
Int     argc;    /* number of arguments */
String  argv[];  /* array of arguments  */

{
  Display*              d;          /* display pointer  */
  Window                r;          /* root window      */
  Int                   s;          /* screen index     */
  Queue                 queue;      /* as it says       */
  Boolean               use_xidle;  /* as it says       */
  XSetWindowAttributes  attribs;    /* for dummy window */


 /*
  *  Find out whether there actually is a server on the other side...
  */
  if (   (d = XOpenDisplay ((String) NULL))
      == (Display*) NULL
     )
  {
    Error1 ("Couldn't connect to %s\n", XDisplayName ((String) NULL));
    exit (PROBLEMS);
  }


 /*
  *  Some initializations.
  */
  XrmInitialize();

  ProcessOpts (d, argc, argv);

  /*
   * Unless we are running only as an idle timestamp generator
   * make sure we are the only xautolock running
   */
  if (locker)
    CheckConnectionAndSendSignal (d);

  if (close_out) fclose (stdout);
  if (close_err) fclose (stderr);

  XSetErrorHandler ((XErrorHandler) CatchFalseAlarm);

#ifdef HasXidle
  use_xidle = XidleQueryExtension (d, &dummy, &dummy);
#else /* HasXidle */
  use_xidle = FALSE;
#endif /* HasXidle */

  XSync (d, 0);
  sleep (INITIAL_SLEEP);

  if (!use_xidle)
  {
    queue = NewQueue ();
  
    for (s = -1; ++s < ScreenCount (d); )
    {
      AddToQueue (queue, r = RootWindowOfScreen (ScreenOfDisplay (d, s)));
      SelectEvents (d, r, True);
    }
  }

  SetTrigger (time_limit);
  
  
 /*
  *  Get ourselves a dummy window in order to allow display and/or
  *  session managers etc. to use XKillClient() on us (e.g. xdm when
  *  not using XDMCP).
  * 
  *  I'm not sure whether the window needs to be mapped for xdm, but
  *  the default set up Sun uses for OpenWindows and olwm definitely
  *  requires it to be mapped.
  */
  attribs.override_redirect = True;
  XMapWindow (d, XCreateWindow (d, DefaultRootWindow (d), -100, -100, 
                                1, 1, 0, CopyFromParent, InputOnly,
				CopyFromParent, CWOverrideRedirect,
				&attribs));


 /*
  *  Main event loop.
  */
  forever
  {
    if (use_xidle)
    {
      QueryIdleTime (d);
    }
    else
    {
      ProcessEvents (d, queue);
    }

    QueryPointer (d);  /* Overkill if xidle is present, but it works. */

    if (locker)
      EvaluateCounter (d);


   /*
    *  It seems that, on some operating systems (VMS to name just one),
    *  sleep () can be vastly inaccurate: sometimes 60 calls to sleep (1)
    *  add up to only 30 seconds or even less of sleeping. Therefore,
    *  as of patchlevel 9 we no longer rely on it for keeping track of
    *  time. The only reason why we still call it, is to make  xautolock
    *  (which after all uses a busy-form-of-waiting algorithm), less
    *  processor hungry.
    */
    sleep (1);
  }

#ifdef lint
  /*NOTREACHED*/
  return 0;
#endif /* lint */
}
