/*+++*
 *  title:	xon-xoff
 *  abstract:	check if terminal needs Xon-Xoff synchronization.
 *  author:	T.R.Hageman, Groningen, The Netherlands.
 *  created:	June 1992
 *  modified:	(see Log at end of file)
 *  description:
 *    Usage: xon-xoff [-f] [-s] [pad time in ms (D=500)] [pad char (D=NUL)]
 *    options:
 *	  -f	force check (irrespective of value of XONXOFF).
 *	  -s	output shell command to set XONXOFF environment variable.
 *
 *	This program checks if the terminal can handle output at the current
 *	tty line speed, without having to rely on Xon-Xoff synchronization.
 *	It does so by writing a bunch of pad characters to stderr, then
 *	waiting to see if the terminal generates any ^Q or ^S characters.
 *	Depending on your system, this check can take from half a second to
 *	up to 2 seconds.
 *
 *	If the environment variable XONXOFF exists, and the -f option is
 *	not given, the physical check is bypassed and the value of this
 *	variable is used to determine the result:
 *	    XONXOFF=0	- if no Xon-Xoff synchronisation is needed,
 *	    XONXOFF=1	- if Xon-Xoff synchronisation is mandatory,
 *
 *	If the -s option is given, a shell command to set this environment
 *	variable is generated.  Thus you can use the command:
 *
 *		eval `xon-xoff -s`
 *
 *	in your shell startup file to initialize XONXOFF.
 *	(JOVEs system .joverc checks this variable to set "allow-^Q-and-^S").
 *
 *    exit value:
 *	    EXIT_SUCCESS - if no Xon-Xoff synchronisation is needed,
 *	    EXIT_FAILURE - if Xon-Xoff synchronisation is mandatory,
 *	    2		 - usage error.
 * bugs:
 *	NOT YET TESTED on VMS.
 *---*/

#define NOT_JOVE
#include "tune.h"
#include <stdio.h>

RCS("$Id: xon-xoff.c,v 14.31 1993/02/15 02:01:52 tom Exp tom $")

#ifndef _SYS_TYPES_H
#   if _I_SYS_TYPES - -1
#	include <sys/types.h>
#   else
#	if _I_TYPES
#	    include <types.h>
#	endif
#   endif
#   define _SYS_TYPES_H
#endif

#include "tty.h"	/* for TERMIOS/SGTTY */
#include "process.h"	/* for ALARM_1_SEC, <signal.h> and <errno.h> */

#if _I_FCNTL
#   include <fcntl.h>
#endif

#if !_ANSI_HEADERS_
#   ifndef errno
extern int	errno;
#   endif
#endif

#define OFF	0
#define ON	1

extern char	*getenv __(( const char * ));


int	CharsPerSec;

#if vms
# include <descrip.h>
# include <ssdef.h>
# include <stsdef.h>
# include <iodef.h>
# include <ttdef.h>
# include <tt2def.h>

union u {
	long  l;	unsigned long  ul;
	short w[2];	unsigned short uw[2];
	char  b[4];	unsigned char  ub[4];
};

struct iosb {		/* I/O status block		*/
	union u	u[2];
#	define	i_status	u[0].uw[0]	/* condition code	*/
#	define	i_count		u[0].uw[1]	/* transfer count	*/
#	define	i_ispeed	u[0].ub[2]	/* input speed		*.
#	define	i_ospeed	u[0].ub[3]	/* output speed		*/
#	define	i_terminator	u[1].uw[0]
#	define	i_termsize	u[1].uw[1]
};

struct termchar {	/* Terminal characteristics	*/
	union u u[3];
#	define	t_class		u[0].ub[0]
#	define	t_type		u[0].ub[1]
#	define	t_typeahdcnt	u[0].uw[0]	/* SENSEMODE|TYPEAHDCNT */
#	define	t_width		u[0].uw[1]	/* width in characters	*/
#	define	t_mode		u[1].ul		/* flags (+ page size)	*/
#	define	t_page		u[1].ub[3]	/* page size in lines	*/
#	define	t_xmode		u[2].ul		/* more flags		*/
};

#define SGTTYB	struct termchar

short	_ttchan;

#endif /* vms */

SGTTYB	sg[2];


/* The following is almost literally snarfed from JOVEs tty.c */

#if (O_NDELAY && F_SETFL)
#   define LOOKAHEAD
#endif

/*
 * check if input is available on the keyboard.
 * unfortunately this is very dependent on your UNIX flavour.
 */
#ifdef LOOKAHEAD
static int	lookahead_char = -1;
#endif

int kbhit __(( void ));
int
kbhit()
{
#if unix
#ifdef INWAIT				/* some berkeley systems (??) */
    {
	struct sg_brl gttyBuf;

	gtty(2, (char *) &gttyBuf);
	return (gttyBuf.sg_xflags & INWAIT) != 0;
    }
#endif /* INWAIT */
#if (O_NDELAY && F_SETFL)		/* system V, XENIX, HP-UX... */
    {
	if (lookahead_char < 0) {
		char	c;

		fcntl(2, F_SETFL, O_NDELAY);	/* turn blocking off */
		if (read(2, &c, 1) > 0)		/* anything available? */
			lookahead_char = _UC_(c);
		fcntl(2, F_SETFL, !O_NDELAY);	/* turn blocking on */

		if (lookahead_char < 0)
			return 0;
	}
	return 1;
    }
#endif (O_NDELAY && F_SETFL)
#ifdef FIONREAD				/* BSD (??) */
    {
	long c;

	return (ioctl(2, FIONREAD, (struct sgttyb *) &c) == 0 && c > 0);
    }
#endif /* FIONREAD */
#ifdef TIOCNRD				/* (some versions of??) 7th Ed. */
    {
	int	c;

	return (ioctl(2, TIOCNRD, (struct sgttyb *) &c) == 0 && c > 0);
    }
#endif /* TIOCNRD */
#ifdef c70				/* (??) */
	return !empty(2);
#endif
#endif /* unix */

#if vms
	struct iosb	iosb;
	struct termchar	tc;

	return ((sys$qiow(0, _ttchan, IO$_SENSEMODE|IO$M_TYPEAHDCNT, &iosb,
			  0,0, &tc, sizeof tc, 0,0,0,0) & STS$M_SUCCESS) &&
		(iosb.i_status & STS$M_SUCCESS) &&
		(tc.t_count > 0));
#endif /* vms */

	return -1;	/* if none of the above compiles... */
} /* kbhit */


/* get a single character from stderr. */

int getch __(( void ));
int
getch()
{
	char	c;
#ifdef LOOKAHEAD
	int	ch;

	if ((ch = lookahead_char) >= 0) {
		lookahead_char = -1;
		return ch;
	}
#endif
	if (read(2, &c, 1) <= 0)
		return -1;

	return _UC_(c);
} /* getch */


/* determine terminal speed, in characters per second */

#define CPS(baud)	(((baud) + 5) / 10)

#if (B9600 == 96 && B2400 == 24)	/* Minix (1.5) encoding (baud/100). */
#   define SPEED(ospeed)	((ospeed) ? CPS(ospeed * 100) : CSPEED)
#endif

#ifndef SPEED
static const int speeds[] = {
	CSPEED,
	CPS(50),
	CPS(75),
	CPS(110),
	CPS(134),
	CPS(150),
#if (B200 && !vms)
	CPS(200),
#endif
	CPS(300),
	CPS(600),
#if (B900)
	CPS(900),
#endif
	CPS(1200),
	CPS(1800),
#if (B2000 || vms)
	CPS(2000),
#endif
	CPS(2400),
#if (B3600 || vms)
	CPS(3600),
#endif
	CPS(4800),
#if (B7200 || vms)
	CPS(7200),
#endif
	CPS(9600),
	CPS(19200),
	CPS(38400)
};

#define _SPEED(i) \
	((unsigned)(i) < sizeof(speeds)/sizeof(speeds[0]) ? speeds[i] : CSPEED)

#ifndef B50
#   define SPEED(ospeed)	_SPEED(ospeed)
#else
#   define SPEED(ospeed)	_SPEED((ospeed) - (B50 - 1))
#endif

#endif /* SPEED */

/* Set tty in raw-ish mode. */

void ttinit __(( void ));
void
ttinit()
{
#if unix
#ifdef TERMIOS
	tcgetattr(2, &sg[OFF]);
	sg[ON] = sg[OFF];
	sg[ON].c_iflag &= ~(IXON|IXOFF);
	sg[ON].c_lflag &= ~(ICANON|ECHO);
	sg[ON].c_cc[VMIN] = 1;
	sg[ON].c_cc[VTIME] = 0;
	tcsetattr(2, TCSANOW, &sg[ON]);
	CharsPerSec = SPEED(cfgetospeed(&sg[ON]));
#else
	gtty(2, &sg[OFF]);
	sg[ON] = sg[OFF];
	sg[ON].sg_flags |= RAW;
	stty(2, &sg[ON]);
	CharsPerSec = SPEED(sg[ON].sg_ospeed);
#endif
#endif /* unix */

#if vms
	$DESCRIPTOR(sys_input, "SYS$INPUT:");
	int		st;
	struct	iosb	iosb;

	/* assign a channel for terminal i/o. */
	if (!((st = sys$assign(&sys_input, &_ttchan)) & STS$M_SUCCESS) ||
	    /* get terminal characteristics */
	    !((st = sys$qiow(0,			/* wait on event flag 0	*/
			     _ttchan,		/* channel to terminal	*/
			     IO$_SENSEMODE,	/* command code		*/
			     &iosb,		/* status of operation	*/
			     0,0,		/* no AST		*/
			     &sg[OFF], sizeof sg[OFF]) /* result buffer */
	       ) & STS$M_SUCCESS) ||
	    !((st = iosb.i_status) & STS$M_SUCCESS))
		exit(st);
#   if 0
	if (sg[OFF].t_class != DC$_TERM)
		exit(``SS$_NOTATERM'');
#   endif
	sg[ON] = sg[OFF];
	sg[ON].t_mode |= TT$M_NOECHO;
	sg[ON].t_mode &= ~(TT$M_TTSYNC);
	sg[ON].t_xmode |= TT2$M_PASTHRU|TT2$M_ALTYPEAHD;

	sys$qiow(0, _ttchan, IO$_SETMODE, 0,0,0,
		 &sg[ON], sizeof sg[ON], 0,0,0,0);

	CharsPerSec = SPEED(iosb.i_ospeed);
#endif /* vms */
}

/* Restore original tty modes. */

void ttreset __(( void ));
void
ttreset()
{
#if unix
#ifdef TERMIOS
    tcsetattr(2, TCSANOW, &sg[OFF]);
#else
    stty(2, &sg[OFF]);
#endif
#endif /* unix */

#if vms
	sys$qiow(0, _ttchan, IO$_SETMODE, 0,0,0,
		 &sg[OFF], sizeof sg[OFF], 0,0,0,0);
#endif /* vms */
}

void putstr __(( int _(fd), const char *_(str) ));
void
putstr(fd, str)
int fd;
const char *str;
{
	write(fd, str, strlen(str));
}

void usage __(( void ));
void
usage()
{
	putstr(2, "\
usage: xon-xoff [-f] [-s] [pad time (in ms; D=500)] [pad char (D=NUL)]\n\
options:\n\
	-f	force check (irrespective of value of XONXOFF).\n\
	-s	output shell command to set XONXOFF environment variable.\n\
");
	exit(2);
}

/* Output commands to set XONXOFF environment variable. */

void setenv __(( int _(how) ));
void
setenv(how)
{
#if !vms
	static char template[] = "setenv XONXOFF=0\nexport XONXOFF\n";
				/*01234567890123456 7*/
	register char		*command = template;
	register const char	*sh = getenv("SHELL");

	if (how != EXIT_SUCCESS)
		++command[15];			/* 0 => 1 */

	if (sh == NULL || strcmp(&sh[strlen(sh)-3], "csh") != 0)
		/* Assume Bourne shell. */
		command += 7;		/* skip "setenv " */
	else
		/* Assume C-shell or one of its clones. */
		command[14] = ' ',	/* replace '=' with space */
		command[17] = '\0';	/* chop off "export..." */
#else
	static char command[] = "XONXOFF:==0\n";

	if (how != EXIT_SUCCESS)
		++command[10];			/* 0 => 1 */
#endif /* vms */

	putstr(1, command);
}

/* Dummy SIGALRM handler for time-out. */

sig_tp shoot __(( int _(sig) ));
sig_tp
shoot(sig)
{
}

char	null_buf[3840];

int
main(argc, argv)
int	argc;
char	*argv[];
{
	register char	*s;
	register unsigned ms = 500;
	register char	c = '\0';
	int		force = 0;
	int		set = 0;
	int		result = EXIT_SUCCESS;

	while (argc > 1 && argv[1][0] == '-') {
		switch (argv[1][1]) {
		case 'f':	force++;	break;
		case 's':	set++;		break;
		default:	usage();
		}
		argv++, argc--;
	}

	if (!force)
		if (s = getenv("XONXOFF"))
			return (*s == '0') ? EXIT_SUCCESS : EXIT_FAILURE;

	if (argc > 1) {
		ms = atoi(argv[1]);
		if (ms == 0)
			ms = 500;
		if (ms > 1000)
			ms = 1000;
		if (argc > 2) {
			c = argv[2][0];
			if (c == '^') {
				switch (c = argv[2][1]) {
				case '\0':
					c = '^';
					break;
				case '?':
					c = '\177';
					break;
				default:
					c &= '\037';
					break;
				}
			}
			for (s = &null_buf[(long)CharsPerSec * ms / 1000];
			     s > null_buf; )
				*--s = c;
		}
	}

	if (!isatty(2)) {
		putstr(2, "xon-xoff: stderr should be a tty!\n");
		exit(2);
	}

	ttinit();

	write(2, null_buf, (int)((long) CharsPerSec * ms / 1000));

	/* set timeout in case kbhit() doesn't work. */
	signal(SIGALRM, shoot);
	alarm(ALARM_1_SEC);

	while (kbhit()) {
		switch(getch()) {
		case EOF:
			break;
		case '\021':	/* Ctrl-Q */
		case '\023':	/* Ctrl-S */
			result = EXIT_FAILURE;
		default:
			continue;
		}
		break;
	}

	alarm(0);

	ttreset();

	if (set)
		setenv(result);

	return result;
}

/*======================================================================
 * $Log: xon-xoff.c,v $
 * Revision 14.31  1993/02/15  02:01:52  tom
 * remove (void) casts.
 *
 * Revision 14.30  1993/01/26  18:43:18  tom
 * cleanup whitespace.
 *
 * Revision 14.28  1992/10/23  17:15:59  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 14.26  1992/08/26  23:57:07  tom
 * add RCS directives.
 *
 * Revision 14.25  1992/07/25  01:15:15  tom
 * improve recognition of csh clones.
 */
