#!/bin/sh
#Here it is...  I don't use this, so ask tsf@CS.CMU.EDU (Tim Freeman) for info
#if you need any.
: 'to disassemble this file, read it with sh.'
echo xidle.1 1>&2
sed -e 's/^X//' > xidle.1 << 'EOF'
X.TH XIDLE 1
X.SH NAME
Xxidle \- allow finger to figure out your idle time
X.SH SYNOPSIS
Xxidle [-display dpy] [-name name] [-console file] [-poll n] [-error n] [-sync n] [-wcheck n]
X.SH DESCRIPTION
X.I xidle
Xallows finger(1) to have the correct idle time for you when you're running X.
XIt listens to keyboard events in those windows
Xwhere other applications are also listening for keyboard events
Xand updates your idle time if you type at any X application.
XUnlike making your /etc/utmp writable,
Xxidle will cause your idle time to be correctly reported even if
Xyou're typing away in a non-pty based window (e.g., gnu-emacs).
XCaveat:  if you use an application that only listens to the mouse
X(ignoring modifiers),
X.I xidle
Xwill fail to update your idle time.
X
XThe
X.B -console
Xflag specifies another file to use instead of /dev/console.
XThis should be the file that finger examines for your idle time.
X
XThe
X.B -poll
Xflag specifies the timeout period for polling in seconds.
XThis is the upper bound on how frequently the other times are updated.
XBy default this is 60 seconds.
X
XThe
X.B -error
Xflag specifies the maximum error allowed in the idle time reflected by your
X.I console
Xfile in seconds.
XIncreasing this value lowers the number of syscalls that interact with
Xthe file system --
X.I xidle
Xwill queue up closely spaced keystrokes without changing the idle time.
XBy default this is 5 seconds.
X
XThe
X.B -sync
Xflag specifies the number of seconds between resynchronization.
X.I xidle
Xuses the timer provided by the events sent by your X server and
Xperiodically checks the
Xcorrespondence between this time and the real time.
XThe default is 5 minutes (300 seconds).
X
XThe
X.B -wcheck
Xflag specifies the period between checking for new windows.
X.I xidle
Xwill not see keyboard events in newly created windows until this
Xtimer expires.
XThe default is 60 seconds.
X
X.I xidle
Xhas three major states:
X.IR  normal ,
X.IR  hide ,
Xand
X.IR busy .
XIn normal mode,
X.I xidle
Xupdates your idle times normally.
XIn hide mode,
X.I xidle
Xstops updating your idle time;
Xthis is useful if you want to hide from those people who seems to send you a
Xtell(1)/talk(1) message immediately after you touch your keyboard.
XIn busy mode,
X.I xidle
Xupdates your idle time whether you're actually typing at your keyboard or not;
Xthis is useful if you want to pretend (to your advisor)
Xthat you're busily working,
Xor if you're working at your desk (but not using your workstation)
Xand want your friends to know that you're around.
XTo switch
X.I xidle
Xamong its modes, send it the signals SIGTSTP, SIGCONT, and SIGUSR1
Xto cause it to go to hide mode, normal mode, and busy mode respectively.
XA good way to start up
X.I xidle
Xfrom your
X.I .xinitrc
Xfile is
X.nf
X	sh -c 'echo $$ > $HOME/.xidlepid; exec xidle' &
X.fi
Xwhich permits you to have the commands
X(possibly invoked from a window manager menu)
X.nf
X	kill -TSTP `cat $HOME/.xidlepid`
X	kill -CONT `cat $HOME/.xidlepid`
X	kill -USR1 `cat $HOME/.xidlepid`
X.fi
Xcontrol your xidle state.
X
X.SH RESOURCES
X.TP 8
X.B "console (\fPclass\fB Console)"
XSpecifies the name of your console file (default /dev/console).
XThis should be the file that finger(1) uses to compute your idle time.
X.TP 8
X.B "poll (\fPclass\fB Poll)"
XSpecifies polling period.
X.TP 8
X.B "error (\fPclass\fB Error)"
XSpecifies maximum error in idle time reflected by your console file.
X.TP 8
X.B "sync (\fPclass\fB Sync)"
XSpecifies the clock synchronization period.
X.TP 8
X.B "wcheck (\fPclass\fP Wcheck)"
XSpecifies the new-window check period.
X
X.SH SEE ALSO
Xfinger(1), thumb(1Xtra), pinky(1bsy), boff(1bsy), push(1bsy), utmp(5)
X
X.SH Author
XBennet Yee (bsy@play.mach.cs.cmu.edu)
X
X.SH Bugs
XOnly works with keyboard activity.
XIf you use an application where all you do is move the mouse and press mouse
Xbuttons,
X.I xidle
Xwill not update your idle time correctly.
EOF
echo xidle.c 1>&2
sed -e 's/^X//' > xidle.c << 'EOF'
X/*
X * Copyright (C) 1990, Bennet Yee.  You may freely distribute this code as long
X * as this notice is intact.  If you make any modifications in your
X * distribution, you must clearly document them.  If you distribute this code
X * or any derivative thereof, it must be given at no charge to all parties
X * on terms identical to those prescribed herein.  All distributions must
X * contain source code.
X *
X * This software is provided with absolutely no warranty.
X *
X * If you have questions, you can contact me at bsy@cs.cmu.edu
X */
X#ifndef lint
X	static char copyright[] = "\n\
XCopyright (C) 1990, Bennet Yee.  You may freely distribute this code as long\n\
Xas this notice is intact.  If you make any modifications in your\n\
Xdistribution, you must clearly document them.  If you distribute this code\n\
Xor any derivative thereof, it must be provided at no charge to all parties\n\
Xon terms identical to those prescribed herein.  All distributions must\n\
Xcontain source code.\n\
X\n\
XThis software is provided with absolutely no warranty.\n\
X";
X#endif /* lint */
X
X#include <X11/Xlib.h>
X#include <X11/Xatom.h>
X#include <X11/Xresource.h>
X#include <X11/Xutil.h>
X#include <X11/cursorfont.h>
X#include <stdio.h>
X#include <ctype.h>
X#include <signal.h>
X#include <errno.h>
X#include <sys/param.h>
X#include <sys/types.h>
X#include <sys/file.h>
X#include <sys/time.h>
X
Xextern int	errno;
X
X#ifdef	DEBUG
X#define	D(x)	fprintf x
X#define	D2(x)	fprintf x
X#define	D3(x)	fprintf x
X#else
X#define	D(x)
X#define	D2(x)
X#define	D3(x)
X#endif
X
X#define	MAXDISPLAY		1024
X#define	MAXNAME			1024
X#define	MAXRESOURCE		1024
X
X#define	HIDE_POLL		50	/* < one minute */
X#define	POLL			60
X#define	MAX_ELAPSED_MSEC	(5*1000)
X#define	UPDATE_CLOCK_IVL	(5*60*1000)
X#define	UPDATE_WIN_IVL		(60*1000)
X
Xlong	poll = POLL;
XTime	max_elapsed_msec = MAX_ELAPSED_MSEC,
X	update_clock_ivl = UPDATE_CLOCK_IVL,
X	update_win_ivl = UPDATE_WIN_IVL;
X
XXrmOptionDescRec tbl[] = {
X{"-display",".display",XrmoptionSepArg, (caddr_t) ""},
X{"-name",".name",XrmoptionSepArg, (caddr_t) "xidle"},
X{"-console",".console",XrmoptionSepArg, (caddr_t) "/dev/console"},
X{"-poll",".poll",XrmoptionSepArg, (caddr_t) NULL},
X{"-error",".error",XrmoptionSepArg, (caddr_t) NULL},
X{"-sync",".sync",XrmoptionSepArg, (caddr_t) NULL},
X{"-wcheck",".win",XrmoptionSepArg, (caddr_t) NULL},
X{"-xrm",NULL,XrmoptionResArg, (caddr_t) NULL},
X};
X
Xchar console[MAXPATHLEN] = "/dev/console";
X
Xusage()
X{
X	fprintf(stderr,"Usage:  xidle [-display dpy] [-name n] [-xrm rm] [-poll N] [-error N] [-sync N] [-wcheck N]\n");
X}
X
X#define	STATE_NORM	0
X#define	STATE_HIDE	1
X#define	STATE_BUSY	2
X
Xint	state = STATE_NORM;
X
Xhide(){	state = STATE_HIDE;}
Xcont(){	state = STATE_NORM;}
Xfake(){ state = STATE_BUSY;}
X
Xmain(ac,av)
Xint	ac;
Xchar	**av;
X{
X	Display			*dpy;
X	XEvent			ev;
X	XKeyPressedEvent	*kev = (XKeyPressedEvent *) &ev;
X	XrmDatabase		db, rmdb;
X	char			*type, display[MAXDISPLAY], name[MAXNAME];
X	XrmValue		val;
X	char			*tmp_string(), *Tmp_string();
X	Atom			typeA;
X	int			format;
X	unsigned long		nitems, bytes;
X	char			*prop;
X	fd_set			in_fds, in_fd_init;
X	int			Xfd;
X	struct timeval		time_out, time_of_server_base, keytime;
X	int			get_time, time_of_server_base_valid,
X				orig_timeout;
X	Time			current_time, key, server_base, elapsed,
X				elapsed_mseconds, update_clock_mseconds,
X				update_win_mseconds;
X	int			ignore(), (*signal())();
X
XD3((stderr,"initializing RM\n"));
X	XrmInitialize();
X	db = (XrmDatabase) 0;
XD3((stderr,"parsing cmds\n"));
X	XrmParseCommand(&db,tbl,sizeof tbl/sizeof tbl[0],"xidle",&ac,av);
XD3((stderr,"db points to %X\n",db));
X	if (ac != 1) {
X		fprintf(stderr,"Option not understood: %s\n",av[1]);
X		usage();
X		exit(1);
X	}
X	strcpy(name,"xidle");
X	if (XrmGetResource(db,tmp_string(name,".name"),Tmp_string(name,".Name"),&type,&val) && !strcmp(type,"String")) {
X		strcpy(name,(char *)val.addr);
X	}
X	locase(name);
X	strcpy(display,"");
X	if (XrmGetResource(db,tmp_string(name,".display"),Tmp_string(name,".Display"),&type,&val) && !strcmp(type,"String")) {
X		strcpy(display,(char *)val.addr);
X	}
X	dpy = XOpenDisplay(display);
X	if (!dpy) {
X		fprintf(stderr,"No display\n");
X		exit(1);
X	}
X
X	/*
X	 * now that the display is open, we can merge in the
X	 * XA_RESOURCE_MANAGER rdb entry
X	 */
X	if (XGetWindowProperty(dpy,RootWindow(dpy,DefaultScreen(dpy)),XA_RESOURCE_MANAGER,0,(int)(((unsigned)~0)>>1),0,XA_STRING,&typeA,&format,&nitems,&bytes,&prop) == Success && typeA != None) {
X		if (format != 8) {
X			fprintf(stderr,"xidle: warning: your RESOURCE MANAGER property is not an 8-bit string!\n");
X			goto cont_xdefault;
X		}
X		rmdb = XrmGetStringDatabase(prop);
X		XrmMergeDatabases(db,&rmdb);
X		XFree(prop);
X		db = rmdb;
X	}
Xcont_xdefault:
X	{
X		char		*getenv();
X		char		*home = getenv("HOME");
X		char		buf[MAXPATHLEN];
X		XrmDatabase	xdefaults;
X		if (home) {
X			strcpy(buf,home);
X			strcat(buf,"/.Xdefaults");
X			xdefaults = XrmGetFileDatabase(buf);
X			if (xdefaults) {
X				XrmMergeDatabases(db,&xdefaults);
X				db = xdefaults;
X			}
X		}
X	}
X	if (XrmGetResource(db,tmp_string(name,".console"),Tmp_string(name,".Console"),&type,&val) && !strcmp(type,"String")) {
X		strcpy(console,(char *)val.addr);
X	}
X	if (XrmGetResource(db,tmp_string(name,".poll"),Tmp_string(name,".Poll"),&type,&val) && !strcmp(type,"String")) {
X		poll = atoi((char *)val.addr);
X	}
X	if (XrmGetResource(db,tmp_string(name,".error"),Tmp_string(name,".Error"),&type,&val) && !strcmp(type,"String")) {
X		max_elapsed_msec = 1000 * atoi((char *)val.addr);
X	}
X	if (XrmGetResource(db,tmp_string(name,".sync"),Tmp_string(name,".Sync"),&type,&val) && !strcmp(type,"String")) {
X		update_clock_ivl = 1000 * atoi((char *)val.addr);
X	}
X	if (XrmGetResource(db,tmp_string(name,".wcheck"),Tmp_string(name,".Wcheck"),&type,&val) && !strcmp(type,"String")) {
X		update_win_ivl = 1000 * atoi((char *)val.addr);
X	}
X
X	XSetErrorHandler(ignore);
X
X	FD_ZERO(&in_fd_init);
X	Xfd = ConnectionNumber(dpy);
X	FD_SET(Xfd,&in_fd_init);
X	recurse_win(dpy,RootWindow(dpy,DefaultScreen(dpy)));
XD((stderr,"looping\n"));
X	elapsed_mseconds = 0;
X	update_clock_mseconds = 0;
X	update_win_mseconds = 0;
X	get_time = 1;
X	time_of_server_base_valid = 0;
X	(void) signal(SIGTSTP,hide);
X	(void) signal(SIGCONT,cont);
X	(void) signal(SIGUSR1,fake);
X	for (;;) {
X		in_fds = in_fd_init;
X		if (state != STATE_HIDE) time_out.tv_sec = poll;
X		else time_out.tv_sec = HIDE_POLL;
X		time_out.tv_usec = 0;
X		orig_timeout = time_out.tv_sec;
X		errno = 0;
X		if (select(Xfd+1,&in_fds,(fd_set *) 0, (fd_set *) 0,&time_out) == -1 && errno != EINTR) {
X			perror("xidle");
X			fprintf(stderr,"xidle: select failed\n");
X			exit(1);
X		}
X		if (errno == EINTR) {
X			get_time = 1;
X			/*
X			 * Kernel may or may not modify the time_out value,
X			 * so we could lose time here.  This only happens
X			 * when we get a signal, however, so is not a big deal.
X			 */
X			elapsed = 1000 * (orig_timeout - time_out.tv_sec);
X			current_time += elapsed;
X		} else if (FD_ISSET(Xfd,&in_fds)) {
XD((stderr,"normal ev proc\n"));
X			XNextEvent(dpy,&ev);
X			/* check for more */
X			while (XPending(dpy)) {
X				XNextEvent(dpy,&ev);
X			}
X			key = kev->time;
X			if (get_time) {
X				get_time = 0;
X				(void) gettimeofday(&time_of_server_base,(struct timezone *) 0);
X				elapsed_mseconds = max_elapsed_msec + 1;
X				/* force an update */
X				current_time = key;
X				server_base = current_time;
X				time_of_server_base_valid = 1;
X			}
X			elapsed = key - current_time;
X			current_time = key;
X		} else {
X			elapsed = 1000 * orig_timeout;
X			current_time += elapsed;
X		}
X		/*
X		 * current_time == key iff got an keypress (poll>0)
X		 * otherwise, current_time > key
X		 */
X
X		/* update timers */
X		elapsed_mseconds += elapsed;
X		update_clock_mseconds += elapsed;
X		update_win_mseconds += elapsed;
X
X		/* check expiration */
X		if (update_win_mseconds > update_win_ivl) {
X			update_win_mseconds = 0;
X			recurse_win(dpy,RootWindow(dpy,DefaultScreen(dpy)));
X		}
X		if (update_clock_mseconds > update_clock_ivl) {
X			update_clock_mseconds = 0;
X			get_time = 1;
X		}
X		if (elapsed_mseconds > max_elapsed_msec && time_of_server_base_valid) {
X			elapsed_mseconds = 0;
X			switch (state) {
X			case STATE_NORM:
X				if (current_time == key) {
X					keytime.tv_sec = (key - server_base)/1000 + time_of_server_base.tv_sec;
X					keytime.tv_usec = 0;	/* ignore it */
X					update(console,&keytime);
X				}
X				break;
X			case STATE_BUSY:
X				/*
X				 * Presumably, we will not get any events
X				 * from the server, so we can not periodically
X				 * resynchronize our timers.  They still run
X				 * and give us relative time, ....
X				 */
X				(void) gettimeofday(&keytime.tv_sec,(struct timezone *) 0);
X				keytime.tv_usec = 0;	/* ignore it */
X				update(console,&keytime);
X				break;
X			case STATE_HIDE:
X				break;
X			}
X		}
X	}
X}
X
X/* since utimes has to go to the fs, we want to avoid it if poss. */
X
Xupdate(file,now)
Xchar		*file;
Xstruct timeval	*now;
X{
X	struct timeval	t[2];
X	t[1] = t[0] = *now;
X	(void) utimes(file,t);
X}
X
Xrecurse_win(dpy,w)
XDisplay	*dpy;
XWindow	w;
X{
X	Window			*child_list, root, parent;
X	int			nchild;
X	XWindowAttributes	attr;
X
X	/*
X	 * if nobody else is interested in keypresses,
X	 * this may be a subwindow the keypresses of which should
X	 * propagate to a parent window where it is handled.
X	 */
X	if (XGetWindowAttributes(dpy,w,&attr)) {
X		if (attr.all_event_masks & KeyPressMask) {
X			XSelectInput(dpy,w,KeyPressMask);
X		} else D((stderr,"nobody interested in %d %X\n",w,w));
X		if (XQueryTree(dpy,w,&root,&parent,&child_list,&nchild)) {
X			while (--nchild >= 0) {
X				recurse_win(dpy,child_list[nchild]);
X			}
X			XFree((char *) child_list);
X		} else D((stderr,"can not query %d %X\n",w,w));
X	}
X	else D((stderr,"No attr for window %d %X\n",w,w));
X}
X
Xignore(dpy,err_ev)
XDisplay		*dpy;
XXErrorEvent	*err_ev;
X{
X	;
X}
X
Xlocase(str)
Xchar	*str;
X{
X	char	ch;
X	while (ch = *str) {
X		if (isupper(ch)) *str = ch + ('a'-(int)'A');
X		++str;
X	}
X}
X
Xchar *tmp_string(name,prop)
Xchar	*name,*prop;
X{
X	static char	buf[MAXRESOURCE];
X
X	strcpy(buf,name);
X	strcat(buf,prop);
X	return buf;
X}
X
Xchar *Tmp_string(name,prop)
Xchar	*name,*prop;
X{
X	static char	buf[MAXRESOURCE];
X
X	strcpy(buf,name);
X	strcat(buf,prop);
X	buf[0] += 'A' - (int) 'a';
X	return buf;
X}
EOF
echo Makefile 1>&2
sed -e 's/^X//' > Makefile << 'EOF'
XLIBES=-lX11
XBINDIR=$$HOME/bin
XMANDIR=$$HOME/man
X
Xxidle:	xidle.o
X	$(CC) $(LDFLAGS) -o xidle xidle.o $(LIBES)
X
Xinstall:	xidle xidle.1
X	rm -f $(BINDIR)/xidle
X	mv xidle $(BINDIR)/xidle
X	rm -f $(MANDIR)/man1/xidle.1
X	cp xidle.1 $(MANDIR)/man1/xidle.1
X
Xclean:
X	-rm -f xidle
X	-rm -f *.o
EOF



