/***************************************************************************
 *
 * xautolock
 * =========
 *
 * Authors   :  S. De Troch (SDT)
 *              M. Eyckmans (MCE)
 *
 * Date      :  22/07/90
 *
 * Comments  :
 *
 * 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 multiheaded 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 multiheaded
 *                  displays. (Thanks to brian@natinst.com for testing
 *                  the `multiheaded' feature.)
 *                . 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 in stead of using "2>&- 1>&-".
 *                  (Suggested by sinkwitz@ifi.unizh.ch.)
 *                . Added "-noclose" for debugging. 
 *
 * -------------------------------------------------------------------------
 *
 * Please send bug reports to detroch@imec.be or eyckmans@imec.be.
 *
 * -------------------------------------------------------------------------
 *
 * Copyright 1990, 1992 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 : You don't make a profit 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.
 */
#include <stdio.h>
#include <strings.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xresource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>		/* for BSD4_3 definition if BSD 4.3 system */
#include <signal.h>
#include <memory.h>
#include <math.h>

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

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

#if BSD4_3 != 1
#include <stdlib.h>
#endif

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

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

#include "patchlevel.h"




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


#define FALSE                      0       /* as it says                   */
#define TRUE                       1       /* as it says                   */

#define ALL_OK                     0       /* for use by exit ()           */
#define PROBLEMS                   1       /* for use by exit ()           */
#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              20      /* for machines on which the
			 	  	      login sequence takes forever */
#define INCREMENTAL_SLEEP          1       /* time step in seconds         */
#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 LOCKER                     "xlock" /* NEVER use the -root option!  */
#define CLASS                      "Xautolock"
			 	           /* as it says                   */

#if SystemV == YES
#define vfork                      fork
#endif /* SystemV == YES */

#define Main                       main
#define Min(a,b)                   (a < b ? a : b)
#define forever                    for (;;)
#define Output0(str)               (Void) fprintf (stdout, str)
#define Output1(str,arg1)          (Void) fprintf (stdout, str, arg1)
#define Output2(str,arg1,arg2)     (Void) fprintf (stdout, str, arg1, arg2)
#define Error0(str)                (Void) fprintf (stderr, str)
#define Error1(str,arg1)           (Void) fprintf (stderr, str, arg1)
#define Error2(str,arg1,arg2)      (Void) fprintf (stderr, str, arg1, arg2)
#define UpdateTimer(new_val)       if (!sighupped) timer = new_val

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

#define New(type)                  Skeleton (type, sizeof (type))
#define NewArray(type,nof_elems)   Skeleton (type, sizeof (type) * nof_elems)




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

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

#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 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,
						     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                     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) || (BSD4_3 == 1)
extern Void*    malloc             PP((unsigned int));
#endif /* news1800 */
 
static Void     Usage              PP((String, Int));
static Void     Sleep              PP((Void));
static Void     EvaluateCounter    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));
static Void     SelectEvents       PP((Display*, Window, Boolean));
static Void     CheckConnection    PP((Display*, String));
static Int      FetchFalseAlarm    PP((Display*, XEvent));
static Void     ProcessOpts        PP((Display*, Int, String*));
static Boolean  TimeAction         PP((Display*, String, String));
static Boolean  LockerAction       PP((Display*, String, String));
static Boolean  CornersAction      PP((Display*, String, String));
static Boolean  CornerSizeAction   PP((Display*, String, String));
static Boolean  CornerDelayAction  PP((Display*, String, String));
static Boolean  NotifyAction       PP((Display*, String, String));
static Boolean  BellAction         PP((Display*, String, String));
static Boolean  NoCloseAction      PP((Display*, String, String));
static Boolean  HelpAction         PP((Display*, String, String));
static Boolean  VersionAction      PP((Display*, String, String));
static Boolean  GetPositive        PP((String, Int*));
static Void     TimeChecker        PP((Display*));
static Void     NotifyChecker      PP((Display*));
static Void     CornerSizeChecker  PP((Display*));
static Void     BellChecker        PP((Display*));
static SigRet   DisableBySignal    PP((Void));




/*
 *  Global variables
 *  ================
 */
static Time          now = 0;               /* number of sleeps since we
		    			       started (not `Int')        */
static Int           timer = 0;             /* as it says (not `Time')    */
static String        locker = LOCKER;       /* as it says                 */
static Int           time_limit = MINUTES;  /* as it says (not `Time')    */
static Int           notify_margin;         /* as it says (not `Time')    */
static Int           bell_percent = BELL_PERCENT;
					    /* as it says                 */
static Int           corner_size = CORNER_SIZE;
					    /* as it says                 */
static Int           corner_delay = CORNER_DELAY;
					    /* as it says (not `Time')    */
static Boolean       sighupped = FALSE;     /* whether to ignore all
					       time-outs                  */
static Boolean       notify_lock = FALSE;   /* whether to notify the user
				               before locking             */
static CornerAction  corners[4] = { IGNORE, IGNORE, IGNORE, IGNORE };
	                                    /* default CornerActions      */
static Boolean       close_output = TRUE;   /* whether to close stdout
                                               and stderr                 */
static anOpt         options[] = 
     		     {
       		       {"help"       , XrmoptionNoArg   ,
        	        (Caddrt) ""  , HelpAction       , (OptChecker) NULL},
       		       {"version"    , XrmoptionNoArg   ,
        	        (Caddrt) ""  , VersionAction    , (OptChecker) NULL},
       		       {"locker"     , XrmoptionSepArg  ,
        	        (Caddrt) NULL, LockerAction     , (OptChecker) NULL},
       		       {"corners"    , XrmoptionSepArg  ,
        	        (Caddrt) NULL, CornersAction    , (OptChecker) NULL},
       		       {"cornersize" , XrmoptionSepArg  ,
        	        (Caddrt) NULL, CornerSizeAction , CornerSizeChecker},
       		       {"cornerdelay", XrmoptionSepArg  ,
        	        (Caddrt) NULL, CornerDelayAction, (OptChecker) NULL},
       		       {"time"       , XrmoptionSepArg  ,
        	        (Caddrt) NULL, TimeAction       , TimeChecker      },
       		       {"notify"     , XrmoptionSepArg  ,
        	        (Caddrt) NULL, NotifyAction     , NotifyChecker    },
       		       {"bell"       , XrmoptionSepArg  ,
        	        (Caddrt) NULL, BellAction       , BellChecker      },
       		       {"noclose"    , XrmoptionNoArg   ,
        	        (Caddrt) ""  , NoCloseAction    , (OptChecker) NULL},
     		     };                     /* as it says, the order is
					       important                  */




/*
 *  Command line argument 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;
  }
  else
  {
    *pos = old;
    return FALSE;
  }
}



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

{
  Usage (name, ALL_OK);

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


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

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

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


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

{
  return GetPositive (arg, &corner_size);
}


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

{
  return GetPositive (arg, &corner_delay);
}


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

{
  return GetPositive (arg, &time_limit);
}


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

{
  return notify_lock = GetPositive (arg, &notify_margin);
}


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

{
  return GetPositive (arg, &bell_percent);
}


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

{
  close_output = FALSE;
  return TRUE;
}


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

{
  locker = arg;
  return TRUE;
}


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

{
  Int  c;  /* loop counter */


  if (strlen (arg) == 4)
  {
    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;
  }
  else
  {
    return FALSE;
  }
}



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

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

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


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

{
  if (   notify_lock
      && notify_margin >= time_limit / 2
     )
  {
    Error1 ("Notification time set to %d seconds.\n",
            notify_margin = time_limit / 2);
  }
}


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

{
  if (   bell_percent < 1
      || bell_percent > 100
     )
  {
    Error1 ("Bell percentage set 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 set to %d pixels.\n",
	    corner_size = max_corner_size);
  }
}



/*
 *  Function for informing the user about syntax errors
 *  ---------------------------------------------------
 */
static Void  Usage (prog_name, exit_code)
String  prog_name;  /* as it says */
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) + 1;
  (Void) 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 minutes][-locker locker]\n");
  Error0 (blanks);
  Error0 ("[-notify margin][-bell percent][-corners xxxx]\n");
  Error0 (blanks);
  Error0 ("[-cornerdelay secs][-cornersize pixels][-noclose]\n");

  Error0 ("\n");
  Error0 (" -help              : print this message and exit.\n");
  Error0 (" -version           : print version number and exit.\n");
  Error2 (" -time minutes      : time to lock screen [%d < minutes < %d].\n",
	                         MIN_MINUTES, MAX_MINUTES);
  Error0 (" -locker locker     : program used to lock.\n");
  Error0 (" -notify margin     : beep this many seconds before locking.\n");
  Error0 (" -bell percent      : loudness of the beep.\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 (" -cornersize pixels : size of corner areas.\n");
  Error0 (" -noclose           : do not close stdout and stderr.\n");

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

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

  Error0 ("\n");

  exit (exit_code);
}



/*
 *  Function for processing command line arguments
 *  ----------------------------------------------
 */
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 (anOpt);
	                          /* number of supported options   */
  Int                j;           /* loop counter                  */
  Int                l;           /* temporary storage             */
  Char*              ptr;         /* temporary storage             */
  Char               buffer[80];  /* as it says                    */
  Char*              dummy;       /* as it says                    */
  XrmValue           value;       /* resource value container      */
  XrmOptionDescList  xoptions;    /* optionslist in Xlib format    */
  XrmDatabase        db = (XrmDatabase) NULL;
				  /* command line options database */


 /*
  *  Beautify argv[0].
  */
  for (ptr = argv[0] + strlen (argv[0]) - 1; ptr >= argv[0]; ptr--)
  {
    if (*ptr == '/')
    {
      break;
    }
  }

  argv[0] = ptr + 1;


 /*
  *  Calling XGetDefault () on a dummy resource is the easiest 
  *  way to get both Xrm and d->db initialized.
  */
  (Void) XGetDefault (d, argv[0], "dummy");


 /*
  *  Parse the command line options into a resource database. (The
  *  command line database and the resource file database are not
  *  merged, because we want to know where exactly each resource
  *  value came from.)
  */
  xoptions = NewArray (XrmOptionDescRec, nof_options);

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

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

  XrmParseCommand (&db, xoptions, nof_options, argv[0], &argc, argv);

  if (--argc)
  {
    Usage (argv[0], PROBLEMS);
  }

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

  free (xoptions);


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

    if (XrmGetResource (db, buffer, (String) NULL, &dummy, &value))
    {
      if (!(*(options[j].action)) (d, argv[0], value.addr))
      {
        Usage (argv[0], PROBLEMS); 
      }
    }
    else if (XrmGetResource (d->db, buffer, (String) NULL, &dummy, &value))
    {
      if (!(*(options[j].action)) (d, argv[0], value.addr))
      {
        Error2 ("Can't interprete \"%s\" for \"%s\", using default.\n", 
	        value.addr, buffer);
      }
    }
    else
    {
      (Void) sprintf (buffer, "%s%s", CLASS, xoptions[j].specifier);

      if (   XrmGetResource (d->db, buffer, (String) NULL, &dummy, &value)
          && !(*(options[j].action)) (d, argv[0], value.addr)
         )
      {
        Error2 ("Can't interprete \"%s\" for \"%s\", using default.\n", 
	        value.addr, buffer);
      }
    }
  }



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




/*
 *  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 = now;
  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      age;    /* required age    */

{
  QueueItem  current;  /* as it says */


  if (now > age)
  {
    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 */


 /*
  *  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 of one of
    *  the types we are expecting to get *and* was not generated by
    *  XSendEvent ().
    */
    if (   event.type == KeyPress
	&& !event.xany.send_event
       )
    {
      UpdateTimer (0);
    }
  }


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



/*
 *  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                    */
  Mask            dummy_m;            /* as it says                    */
  Int             root_x;             /* as it says                    */
  Int             root_y;             /* as it says                    */
  Int             corner;             /* corner index                  */
  Int             i;                  /* loop counter                  */
  static Window   root;               /* root window the pointer is on */
  static Screen*  screen;             /* screen the pointer is on      */
  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 it every INCREMENTAL_SLEEP seconds anyway?
  */
  if (!XQueryPointer (d, root, &root, &dummy_w, &root_x, &root_y,
		      &dummy_c, &dummy_c, (unsigned int *) &dummy_m))
  {
   /*
    *  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
     )
  {
   /*
    *  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 (   (corner = 0,
	       root_x <= corner_size
	    && root_y <= corner_size
	   )
	|| (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
	   )
       )
    {
      switch (corners[corner])
      {
	case FORCE_LOCK :
	  if (timer < time_limit - corner_delay + 2)
	  {
	    UpdateTimer (time_limit - corner_delay + 2);
	  }
	  break;

	case DONT_LOCK :
	  UpdateTimer (0);
      }
    }
  }
  else
  {
    prev_root_x = root_x;
    prev_root_y = root_y;
    UpdateTimer (0);
  }
}



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

{
  static pid_t  locker_pid = 0;  /* child pid  */
  static Time   prev_bell = 0;   /* as it says */


 /*
  *  Find out whether we should do something special. This can
  *  be one (or more) of the following :
  *
  *   - Wait for the previous locker (if any).
  *   - Ring the bell, if we were asked to and are about to lock.
  *   - Start up a new locker if the time limit has been reached.
  */
  if (locker_pid)
  {
    union wait  status;  /* childs process status */


    if (!wait3 (&status, WNOHANG, (struct rusage*) NULL))
    {
      UpdateTimer (0);
    }
    else
    {
      locker_pid = 0;
    }
  }

  if (   notify_lock
      && timer + notify_margin > time_limit
      && prev_bell < now - notify_margin - 1
     )
  {
    prev_bell = now;
    XBell (d, bell_percent);
    XSync (d, 0);
  }

  if (timer > time_limit)
  {
    if (!locker_pid)
    {
      switch (locker_pid = vfork ())
      {
	case -1 :
	  locker_pid = 0;
	  break;

	case 0 :
	  (Void) close (ConnectionNumber (d));
	  (Void) execl ("/bin/sh", "sh", "-c", locker, (String) NULL);
	  (Void) _exit (PROBLEMS);

	default :
	  UpdateTimer (0);
      }
    }
  }
}




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

{
  return 0;
}



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

{
 /*
  *  The order in which things are done is rather important here.
  */
  UpdateTimer (0);
  sighupped = !sighupped;
  (Void) signal (SIGHUP, (SigHandler) DisableBySignal);

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



/*
 *  Lazy function
 *  -------------
 */
static Void  Sleep ()

{
  Int  i;  /* loop counter */


  for (i = -1; ++i < INCREMENTAL_SLEEP; )
  {
    (Void) sleep (1);
    UpdateTimer (timer + 1);
    now++;
  }
}



/*
 *  Function for finding out whether another xautolock is already running
 *  ---------------------------------------------------------------------
 */
static Void  CheckConnection (d, prog_name)
Display*  d;          /* display pointer */
String    prog_name;  /* as it says      */

{
  pid_t   pid;        /* as it says              */
  Int     kill_val;   /* return value of kill () */
  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                   */
  pid_t*  contents;   /* actual property value   */


  r = RootWindowOfScreen (ScreenOfDisplay (d, 0));
  property = XInternAtom (d, "XAUTOLOCK_SEMAPHORE_PID", False);

  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 
    *  running on the same machine.
    */
    kill_val = kill (*contents, 0);

    if (kill_val == 0)
    {
      Error2 ("%s is already running (PID %d).\n",
	      prog_name, *contents);
      (Void) exit (PROBLEMS);
    }
  }

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

  XFree ((Char*) contents);
}



/*
 *  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 int       nof_children = 0;  /* number of children         */
  Int                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 = -1; ++i < nof_children; )
  {
    SelectEvents (d, children[i], substructure_only);
  }

  if (nof_children != 0)
  {
    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      */


 /*
  *  Find out whether there actually is a server on the other side...
  */
  if (   (d = XOpenDisplay ((String) NULL))
      == (Display*) NULL
     )
  {
    (Void) exit (PROBLEMS);
  }


 /*
  *  Some initializations.
  */
  ProcessOpts (d, argc, argv);

  XSetErrorHandler ((XErrorHandler) FetchFalseAlarm);
  CheckConnection (d, argv[0]);
  (Void) signal (SIGHUP, (SigHandler) DisableBySignal);

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

  queue = NewQueue ();

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

  if (close_output)
  {
    (Void) fclose (stdout);
    (Void) fclose (stderr);
  }


 /*
  *  Main event loop.
  */
  forever
  {
    Sleep ();
    ProcessEvents (d, queue);
    QueryPointer (d);
    EvaluateCounter (d);
  }
}
