/***********************************************************************

This package  implements a  simple keyboard  input interface  to  permit
character-at-a-time  input,  plus  optional  suppression  of   character
translation.

This is one of the areas where operating systems (even variants of Unix)
are horribly  non-standard,  and the  problem  is not  really  addressed
satisfactorily by either the emerging draft ANSI C Standard (01-Oct-86),
or the IEEE Trial Use  Standard: Portable Operating System for  Computer
Environments (POSIX), IEEE Std 1003.1, (14-Apr-86).

This package is intentionally simple--only one keyboard input device  is
supported (NOT Unix stdin!), and where possible, it is implemented  with
rather ordinary Unix library calls.  The functionality provided is open,
close, flush,  get, unget,  set-mode, and  inquire-mode.  All  functions
(except kbfile()) use the stdio.h EOF value to signal an error, and none
set the global  errno flag (that  is not the  prerogative of  non-system
software).

User programs  need  only use  a  '#include "key.h"'  statement  to  get
definitions of all visible flags, functions, and types in this  package.
Global data  which is  private to  this package  is declared  static  to
ensure just that.

The mode parameter set in calls  to kbopen() and kbmode(), and  returned
by  them,  is  simply  a  collection  of  bit  flags,  but  for   future
expandability, it has a special type  KEYMODE, instead of being an  int.
The mode flags require some explanation.  They are based on the terminal
I/O model  in  Berkeley  Unix.  Essentially  three  terminal  modes  are
supported: normal,  cbreak, and  raw.

In normal mode,  keyboard input is  handled like fopen()  / getchar()  /
fclose(), so that input editing characters are supported (and handled by
the terminal driver, not this package!).  Input may be available  before
a <CR> is typed  (e.g. 4.3BSD, TOPS-20, MS-DOS),  or may not (System  V,
VMS).

In cbreak mode, input editing characters should still work, but input is
guaranteed to be  available before a  <CR> is typed,  provided that  the
operating  system  supports  that  (CDC  and  IBM  do  not).   Interrupt
characters (CTL-C,  CTL-Z, et  al) are  not readable--they  still  cause
interrupts which are  not handled here.   Other control characters  (HT,
ESC, et al) must be readable.

In raw mode, all characters in the host character set may be input,  and
no character-initiated interrupts are possible.

These three modes  are selected  by OR'ing into  the mode  value one  of
KB_CBREAK, KB_NORMAL, or  KB_RAW; they  are mutually  exclusive, and  an
error is raised  if more  than one is  specified.

The current mode may be  saved (internally) with KB_SAVE, restored  with
KB_RESTORE, or inquired  with KB_INQUIRE;  any other  KB_xxxx flags  set
with these are ignored, and the  current mode is not changed.  Only  one
level of save/restore is  provided; successive restore requests  without
an intervening save are permissible, and simply restore the saved  mode.
The intent is that save/restore normally only be done once at job  start
and end.

Normally, input  characters  are not  echoed  to the  terminal;  if  the
side-effect of echoing is wanted, it must be requested by KB_ECHO.

The effect  of  these  modes  on  input  (and  output)  via  other  file
descriptors is operating-system dependent.  On Unix, the terminal  modes
are job-wide, and persist after a program terminates.  On MS-DOS and VAX
VMS, they affect terminal I/O, but only for the duration of the job, and
only for  that file  descriptor.   On TOPS-20,  some  of the  modes  can
persist after the job (e.g.  terminal width and pause-on-end-of-page).

All functions return the EOF error indicator if the keyboard file is not
open.  This  can  be done  with  an  initial call  to  kbopen(mode);  on
success, it returns the argument mode value.

When processing is complete, kbclose() should be called.  Further  calls
of the  kbxxxx() functions  will return  error codes  until kbopen()  is
called again.  kbclose() must restore the  terminal to the state it  was
in when  kbopen() was  called;  kbmode() does  not necessarily  do  that
(since the default mode for kbopen() is no echo).

kbget()  and  kbunget(c)   provide  for   single-character  input,   and
single-character push-back.   kbget()  returns  a  non-negative  ordinal
value on success, and EOF on error.  The pushed-back character given  to
kbunget(c) need  not be  one obtained  from the  last call  to  kbget().
Attempts to push back more than one character are ignored, and result in
an EOF  error return.	Depending  on the  mode (raw,  cbreak,  normal),
kbget() may not be able to return an end-of-file indicator; it cannot in
raw mode, but for  portability, EOF returns from  kbget() should not  be
expected.

kbflush() simply flushes typeahead.  It returns EOF only if the keyboard
file is not open; otherwise it returns 0.

In many applications, it  is often important to  have the capability  of
determining whether or nor  input is available.   For example, a  screen
display algorithm probably should suspend  display if input is  awaiting
that is  likely  to change  the  state  of the  screen.   The  kbinput()
function is therefore  an important  one; it returns  0 if  no input  is
available to be  read by kbget(),  returns EOF on  error, and  otherwise
returns a  positive number  which  in some  implementations may  be  the
number of unread input characters, and in others may be arbitrary.  This
function is  one  which is  difficult  to implement  on  some  operating
systems, with Unix System III and V being the worst.  The implementation
below for the latter  is borrowed from  Marc Rochkind's excellent  book,
"Advanced Unix Programming", Prentice-Hall (1985).

There is one  other function  provided, kbfile(); it  returns the  FILE*
pointer for the  keyboard file, or  (FILE*)NULL is none  is open, or  if
this package is not implemented with  standard C I/O routines.  Its  use
is strongly discouraged,  but the author's  long programming  experience
has shown that on rare occasions, it may be useful.  Character push-back
with direct  calls  to  ungetc(c,kbfile()) will  probably  not  work  as
expected, but other stdio functions should.  fopen() and fclose() should
not be called with the kbfile() value; that would corrupt internal  data
structures here.

Most  functions   are  necessarily   internally  system-dependent,   and
preprocessor conditionals  encapsulate the  gory details.   Preprocessor
flags are  as  follows;  at  least  one  implementation  flag,  and  one
compatible operating system flag, must be chosen:

C Implementations:
	ATT		-- AT&T Unix (System III, V)
	BSD		-- Berkeley 4.xBSD
	HPUX		-- HP 9000 series Unix (System V based)
	IBM_PC_MICROSOFT-- IBM PC Microsoft Version 3.x or later C compiler
	KCC_20		-- SRI's KCC Compiler on TOPS-20
	PCC_20		-- Portable C Compiler on TOPS-20

Operating systems:
	OS_PCDOS	-- IBM (and clones) PC DOS and MS DOS
	OS_TOPS20	-- DEC-20 TOPS-20
	OS_UNIX		-- Unix (almost any variant)
	OS_VAXVMS	-- VAX VMS

Testing of this package has been carried on TOPS-20 (KCC-20 and  PCC-20)
on DEC-20/60, , VAX VMS (CC) on VAX 8600, 4.3BSD Unix on VAX 8600,  HPUX
on HP 9000, and PC DOS (IBM  PC XT).  It appears to work  satisfactorily
on all of these, with the single exception that CTL-C handling on PC DOS
is not restored  after a switch  from raw mode;  under all modes,  CTL-C
still echoes  as  ^C<CR><LF>.	Using  DOS  interrupt  0x33  instead  of
signal() to save and  restore the ctl-break status  does not provide  an
acceptable alternative; CTL-C always terminates execution.  Further work
is necessary to resolve this.

[08-Apr-87]
***********************************************************************/

#include <stdio.h>
#include "keydef.h"

#ifndef OS_PCDOS
#define OS_PCDOS 0
#endif

#ifndef IBM_PC_MICROSOFT
#define IBM_PC_MICROSOFT 0
#endif

#ifndef OS_TOPS20
#define OS_TOPS20 0
#endif

#ifndef KCC_20
#define KCC_20 0
#endif

#ifndef PCC_20
#define PCC_20 0
#endif

#ifndef OS_UNIX
#define OS_UNIX 0
#endif

#ifndef ATT
#define ATT 0
#endif

#ifndef BSD
#define BSD 0
#endif

#ifndef HPUX
#define HPUX 0
#endif

#ifndef OS_VAXVMS
#define OS_VAXVMS 0
#endif

#if    OS_TOPS20
#if    (KCC_20 | PCC_20)
#else
#undef PCC_20
#define PCC_20 1			/* PCC-20 is default for Tops-20 */
#endif
#endif

#if    (OS_PCDOS | OS_TOPS20 | OS_UNIX | OS_VAXVMS)
#else
#undef OS_UNIX
#define OS_UNIX 1			/* Unix is default operating system */
#endif

#if    (ATT | BSD | HPUX)
#else
#undef BSD
#define BSD 1				/* BSD is default Unix variant */
#endif

#if    OS_PCDOS
#include <conio.h>
#include <signal.h>

#define TTYOPENFLAGS "rb"
#define TTYNAME "con"

#define interrupt catch_int /* MSC Version 5.0 raises syntax error otherwise */
static int (*signal_status)();
static void interrupt();
#endif /* OS_PCDOS */

#if    OS_TOPS20
#define TTYNAME "TTY:"
static int w_rfmod;			/* terminal mode word */
static int w_rpcap;			/* process capability word */
static int w_rtiw;			/* terminal interrupt word */
static int w_morxo;			/* end-of-page mode */
static int kbrestore();			/* restore terminal settings */
/*
** KCC-20 and PCC-20 have  similar enough JSYS  interfaces that we  just
** use KCC symbolic names,  and then redefine  them for PCC-20.   PCC-20
** has a working ioctl()  that could be  used for Unix-style  processing
** too, although it has a bug in that it resets the terminal width to 0,
** and disables pause at end of page.  It therefore seems easier to  use
** direct Monitor calls both both KCC-20 and PCC-20.
*/

#if    KCC_20
#include <jsys.h>
#include <monsym.h>
#define jfnof(x)	_CTTRM		/* we use direct JSYS'es for now */
#define TTYASCMOD	FLD(_TTASC,TT_DAM)
#define TTYBINMOD	FLD(_TTBIN,TT_DAM)
#define TTYDAM		FLD(-1,TT_DAM)
#define TTYECHO		FLD(-1,TT_ECO)
#define TTYOPENFLAGS	"rb8"
#define TTYPGM		FLD(-1,TT_PGM)
#endif /* KCC_20 */

#if    PCC_20
#include <file.h>
#include <monsym.h>
#undef jfnof
#define jfnof(x) _CTTRM			/* we use direct JSYS'es for now */
/* Convert names in better KCC-20 style to PCC-20 style */
#define BIN		JSbin
#define DOBE		JSdobe
#define MTOPR		JSmtopr
#define RFMOD		JSrfmod
#define RPCAP		JSrpcap
#define RTIW		JSrtiw
#define SC_CTC		makefield(SC_ctc,-1)	/* WARNING: PCC bug--see below */
#define SFMOD		JSsfmod
#define SIBE		JSsibe
#define STIW		JSstiw
#define STPAR		JSstpar
#define TTYASCMOD	makefield(TT_dam,TTasc)
#define TTYBINMOD	makefield(TT_dam,TTbin)
#define TTYDAM		makefield(TT_dam,-1)
#define TTYPGM		makefield(TT_pgm,-1)
#define TTYECHO		makefield(TT_eco,-1)
#define TTYOPENFLAGS	"rb"
#define _CTTRM		CTtrm
#define _FHJOB		0777773
#define _FHSLF		FHslf
#define _MOXOF		MOxof
#define _MOOFF		MOoff
#define _MORXO		MOrxo
#endif /* PCC_20 */

#endif /* OS_TOPS20 */

#if    OS_UNIX

#if    (ATT | HPUX)
typedef int BOOLEAN;
#define FALSE 0
#define TRUE 1

#include <fcntl.h>
#include <termio.h>

static struct termio tty;
static struct termio ttysave;
static int setblock();
#endif

#if    BSD
#include <sgtty.h>
#include <sys/ioctl.h>

static struct sgttyb tty;
static struct sgttyb ttysave;
#endif

#define TTYOPENFLAGS "r"
#define TTYNAME "/dev/tty"
#endif /* OS_UNIX */

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

#define TTYOPENFLAGS "rb"
#define TTYNAME ctermid((char*)NULL)
static int status;			/* system service status */
static int tt_channel = -1;		/* terminal channel for image QIO's */
static int iomask;			/* QIO flag mask */
static $DESCRIPTOR(sys_in,"TT:");	/* terminal descriptor */

static struct
{
    unsigned char class;
    unsigned char type;
    unsigned short buffer_size;
    unsigned long tt;
    unsigned long tt2;
} mode_buf,mode_save;

static struct
{
    unsigned short typeahead_count;
    unsigned char first_char;
    unsigned char reserved[5];
} typeahead_buf;

#define FAILED(status) (~(status) & 1)	/* failure if LSB is 0 */

#endif /* OS_VAXVMS */

/*====================================================================*/

#define BADMODE ((KEYMODE)(EOF))	/* unset mode value */
#define EMPTY 32767			/* flag for empty pushback buffer */

static KEYMODE kbcurrmode = BADMODE;	/* current mode */
static KEYMODE kbsavedmode = BADMODE;	/* saved mode */
static int kbpushback = EMPTY;		/* character pushback buffer */
static FILE* kbfp = (FILE*)NULL;	/* keyboard file pointer */
static int kbinit = 0;			/* initialization flag */

#if    OS_PCDOS
/*====================================================================*/

static void
interrupt()
{
    if (kbpushback == EMPTY)
	kbpushback = 3;			/* CTL-C */
}
#endif /* OS_PCDOS */

/*====================================================================*/

int
kbclose()				/* close the keyboard file */
{					/* return EOF on error, 0 on success */
    if (!kbinit)
	return (EOF);
    kbinit = 0;

#if    OS_PCDOS
    (void)signal(SIGINT,signal_status);
    return (0);
#endif /* OS_PCDOS */

#if    OS_TOPS20
    return (kbrestore());
#endif /* OS_TOPS20 */


#if    OS_UNIX
#if    (ATT | HPUX)
    if (ioctl(fileno(kbfp),TCSETAF,&ttysave) == -1)
	return (EOF);
#endif /* (ATT | HPUX) */
#if    BSD
    if (ioctl(fileno(kbfp),TIOCSETP,&ttysave) == -1)
	return (EOF);
#endif /* BSD */

    return (kbfp == (FILE *)NULL ? EOF : fclose(kbfp));
#endif /* OS_UNIX */

#if    OS_VAXVMS
    status = sys$qiow(0,tt_channel,IO$_SETMODE,0,0,0, &mode_save,12,0,0,0,0);
    if (FAILED(status))
	return (EOF);
    return (0);
#endif /* OS_VAXVMS */
}

/*====================================================================*/

FILE*
kbfile()				/* return keyboard file pointer */
{
    return (kbinit ? kbfp : (FILE*)NULL);
}

/*====================================================================*/

int
kbflush()				/* flush current input buffer */
{
    if (!kbinit)
	return (EOF);
    while (kbinput() > 0)
	(void)kbget();
    return (0);
}


/*====================================================================*/

int
kbget()					/* blocking read of character */
{			/* return character, or EOF on end-of-file or error */
    int c;

    if (!kbinit)
	return (EOF);
    else if (kbpushback == EMPTY)
    {

#if    OS_PCDOS
	c = (kbcurrmode & KB_ECHO) ? getche() : getch();
#endif /* OS_PCDOS */

#if    OS_TOPS20
	ac1 = _CTTRM;
	c = ((jsys(BIN,acs) == JSok) ? ac2 : EOF);
#endif /* OS_TOPS20 */


#if    OS_UNIX
#if    (ATT | HPUX)
	if (setblock(fileno(kbfp),TRUE) == EOF)
	    return (EOF);
	if (read(fileno(kbfp),&c,1) <= 0)
	    c = EOF;
#endif /* (ATT | HPUX) */
#if    BSD
	c = getc(kbfp);
#endif /* BSD */
#endif /* OS_UNIX */

#if    OS_VAXVMS
	status = sys$qiow(0,tt_channel,iomask,0,0,0,&c,1,0,0,0,0);
	if (FAILED(status))
	    c = EOF;
#endif /* OS_VAXVMS */

    }
    else
    {
	c = kbpushback;
	kbpushback = EMPTY;
    }
    return (c == EOF ? c : (int)(0377 & c));	/* make sure it is 8-bit value */
}

/*====================================================================*/

int
kbinput()				/* input available? */
{ /* return count of available input characters, 0 if none, or EOF on error */

    if (!kbinit)
	return (EOF);
    else if (kbpushback != EMPTY)
	return (1);
    else
    {
#if    OS_PCDOS
	return (kbhit() ? 1 : 0);	/* otherwise returns 255 */
#endif /* OS_PCDOS */

#if    OS_TOPS20
	ac1 = jfnof(fileno(kbfp));
	if (jsys(SIBE,acs) == JSok)
	    return (0);			/* input buffer empty */
	else
	    return (ac2);		/* input character count */
#endif /* OS_TOPS20 */

#if    OS_UNIX
#if    (ATT | HPUX)
	{
	    char c;

	    if (setblock(fileno(kbfp),FALSE) == EOF)
		return (EOF);
	    switch (read(fileno(kbfp),&c,1))
	    {
	    case -1:
		return (EOF);

	    case 0:
		return (0);

	    default:
		kbpushback = (int)c;
		return (1);
	    }
	}
#endif /* (ATT | HPUX) */
#if    BSD
	{
	    int count;

	    return ((ioctl(fileno(kbfp), FIONREAD, &count) == -1) ? EOF : count);
	}
#endif /* BSD */
#endif /* OS_UNIX */

#if    OS_VAXVMS
	status = sys$qiow(0,tt_channel,IO$_SENSEMODE | IO$M_TYPEAHDCNT,
	    0,0,0,&typeahead_buf,8,0,0,0,0);
	return (FAILED(status) ? EOF : typeahead_buf.typeahead_count);
#endif /* OS_VAXVMS */
    }
}

/*====================================================================*/

KEYMODE
kbmode(mode)				/* keyboard mode */
KEYMODE mode;			/* return new mode on success, EOF on error */
{
    if (!kbinit)
	return (EOF);

    switch (mode & (KB_INQUIRE | KB_RESTORE | KB_SAVE))
    {
    case 0:
	break;				/* must be a set-mode operation  */

    case KB_INQUIRE:
	return (kbcurrmode);

    case KB_RESTORE:
	if (kbsavedmode = BADMODE)
	    return (BADMODE);
	else
	{
	    kbcurrmode = kbsavedmode;
	    kbcurrmode &= ~(KB_INQUIRE | KB_RESTORE | KB_SAVE);
	    return (kbmode(kbcurrmode));
	}

    case KB_SAVE:
	kbsavedmode = kbcurrmode;
	return (kbcurrmode);

    default:				/* do not allow mixed mode flags */
	return (BADMODE);
    }

#if    OS_TOPS20
    if (kbrestore() == EOF)		/* restore terminal settings */
	return (BADMODE);
#endif /* OS_TOPS20 */

    switch (mode & (KB_CBREAK | KB_NORMAL | KB_RAW))	/* set-mode request */
    {
#if    OS_PCDOS
    case KB_CBREAK:
    case KB_NORMAL:
	(void)signal(SIGINT,signal_status);
	break;

    case KB_RAW:
	(void)signal(SIGINT,interrupt);
	break;
#endif /* OS_PCDOS */

#if    OS_TOPS20
    case KB_CBREAK:
    case KB_NORMAL:
	ac1 = jfnof(fileno(kbfp));
	ac2 = w_rfmod & ~TTYECHO;	/* clear TT%ECO field (want no echo) */
	if (jsys(SFMOD,acs) != JSok)
	    return (BADMODE);
	if (jsys(STPAR,acs) != JSok)
	    return (BADMODE);
	break;

    case KB_RAW:
	/* Wait for output to flush; if the screen is at end-of-page, we
	are waiting for the user to type CTL-Q, which will have no
	effect once we enter raw mode, and the terminal will be hung! */
	ac1 = _CTTRM;
	if (jsys(DOBE,acs) != JSok)	/* dismiss until output buffer empty */
	    return (BADMODE);

	ac1 = jfnof(fileno(kbfp));
	ac2 = w_rfmod;
	ac2 &= ~(TTYECHO | TTYDAM | TTYPGM);	/* clear fields */
	ac2 |= TTYBINMOD;		/* set binary mode */
	if (jsys(SFMOD,acs) != JSok)
	    return (BADMODE);
	if (jsys(STPAR,acs) != JSok)
	    return (BADMODE);

	ac1 = _FHJOB;

#if    KCC_20
	if (w_rpcap & SC_CTC)		/* then have CTL-C capability */
#endif /* KCC_20 */
#if    PCC_20
	ac3 = 0600000000000;		/* PCC BUG: 0400000000000 becomes 0! */
	ac3 &=0200000000000;		/* so make it on-the-fly */
	if (w_rpcap & ac3)
#endif /* PCC_20 */
	    ac2 &= 077;			/* read all ctl-chars */
	else
	    ac2 &= 0040000000077;	/* read all ctl-chars but CTL-C */
	if (jsys(STIW,acs) != JSok)	/* reset terminal interrupt word */
	    return (BADMODE);

	ac1 = _CTTRM;
	ac2 = _MOXOF;
	ac3 = _MOOFF;			/* disable pause at end-of-page */
	if (jsys(MTOPR,acs) != JSok)	/* otherwise we can hang waiting for */
	    return (BADMODE);		/* a CTL-Q which is eaten as input! */

	break;
#endif /* OS_TOPS20 */

#if    OS_UNIX
#if    (ATT | HPUX)
    case KB_CBREAK:
	tty = ttysave;
	tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | ISTRIP | IXON | BRKINT);
	tty.c_lflag &= ~(ECHO | ICANON);
	tty.c_cc[4] = 5;		/* MIN */
	tty.c_cc[5] = 2;		/* TIME */
	if (ioctl(fileno(kbfp),TCSETAF,&tty) == -1)
	    return (BADMODE);
	break;

    case KB_NORMAL:
	tty = ttysave;
	tty.c_lflag &= ~ECHO;
	tty.c_cc[4] = 5;		/* MIN */
	tty.c_cc[5] = 2;		/* TIME */
	if (ioctl(fileno(kbfp),TCSETAF,&tty) == -1)
	    return (BADMODE);
	break;

    case KB_RAW:
	tty = ttysave;
	tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | ISTRIP | IXON | BRKINT);
	tty.c_lflag &= ~(ICANON | ISIG | ECHO);
	tty.c_cc[4] = 5;		/* MIN */
	tty.c_cc[5] = 2;		/* TIME */
	if (ioctl(fileno(kbfp),TCSETAF,&tty) == -1)
	    return (BADMODE);
	break;
#endif /* (ATT | HPUX) */

#if    BSD
    case KB_CBREAK:
	tty = ttysave;
	tty.sg_flags &= ~(CRMOD | ECHO | LCASE);
	tty.sg_flags |= CBREAK;
	if (ioctl(fileno(kbfp),TIOCSETP,&tty) == -1)
	    return (BADMODE);
	break;

    case KB_NORMAL:
	tty = ttysave;
	tty.sg_flags &= ~(CBREAK | CRMOD | ECHO | LCASE | RAW);
	if (ioctl(fileno(kbfp),TIOCSETP,&tty) == -1)
	    return (BADMODE);
	break;

    case KB_RAW:
	tty = ttysave;
	tty.sg_flags &= ~(CRMOD | ECHO | LCASE | TANDEM);
	tty.sg_flags |= (CBREAK | RAW);
	if (ioctl(fileno(kbfp),TIOCSETP,&tty) == -1)
	    return (BADMODE);

	break;
#endif /* BSD */
#endif /* OS_UNIX */

#if    OS_VAXVMS
    case KB_CBREAK:
    case KB_NORMAL:
	mode_buf = mode_save;
	mode_buf.tt &= ~TT$M_WRAP;
	status = sys$qiow(0,tt_channel,IO$_SETMODE,0,0,0,&mode_buf,12,0,0,0,0);
	if (FAILED(status))
	    return (EOF);
	iomask = IO$_TTYREADALL | IO$M_NOECHO;
	break;

    case KB_RAW:
	mode_buf = mode_save;
	mode_buf.tt &= ~(TT$M_TTSYNC | TT$M_WRAP);
	mode_buf.tt2 |= TT2$M_PASTHRU;
	mode_buf.tt2 &= ~TT2$M_XON;
	status = sys$qiow(0,tt_channel,IO$_SETMODE,0,0,0,&mode_buf,12,0,0,0,0);
	if (FAILED(status))
	    return (EOF);
	iomask = IO$_TTYREADALL | IO$M_NOECHO;
	break;
#endif /* OS_VAXVMS */

    default:				/* do not allow mixed mode flags */
	return (BADMODE);
    }
    kbcurrmode = mode;
    kbcurrmode &= ~(KB_INQUIRE | KB_RESTORE | KB_SAVE);

    if (mode & KB_ECHO)
    {
#if    OS_PCDOS
	/* NO-OP */ ;
#endif /* OS_PCDOS */

#if    OS_TOPS20
	ac1 = jfnof(fileno(kbfp));
	ac2 = w_rfmod | TTYECHO;	/* set echo mode */
	if (jsys(SFMOD,acs) != JSok)
	    return (BADMODE);
#endif /* OS_TOPS20 */


#if    OS_UNIX
#if    (ATT | HPUX)
	/* ??? do not yet know how to handle this ??? */
#endif /* (ATT | HPUX) */
#if    BSD
	if (ioctl(fileno(kbfp),TIOCGETP,&tty) == -1)
	    return (BADMODE);
	tty.sg_flags |= ECHO;
	if (ioctl(fileno(kbfp),TIOCSETP,&tty) == -1)
	    return (BADMODE);
#endif /* BSD */
#endif /* OS_UNIX */

#if    OS_VAXVMS
	iomask &= ~IO$M_NOECHO;		/* turn off no-echo flag */
#endif /* OS_VAXVMS */
    }
    return (kbcurrmode);
}

/*====================================================================*/

int
kbopen(mode)			/* open keyboard with mode (KB_xxxx flags) */
int mode;			/* return 0 on success, EOF on error */
{
    if (kbinit)				/* ignore if already open */
	return (EOF);

#if    OS_PCDOS
    kbfp = (FILE*)NULL;			/* we use direct console input */
    signal_status = signal(SIGINT,SIG_IGN);
    (void)signal(SIGINT,signal_status);
#endif /* OS_PCDOS */

#if    OS_TOPS20
    kbfp = (FILE*)NULL;			/* we use direct JSYS'es */

    /* save terminal characteristics for later modification and restoration */
    ac1 = _CTTRM;
    if (jsys(RFMOD,acs) != JSok)	/* request terminal mode word */
	return (EOF);
    w_rfmod = ac2;

    ac1 = _FHSLF;
    if (jsys(RPCAP,acs) != JSok)	/* request process capabilities */
	return (EOF);
    w_rpcap = ac2;

    ac1 = _FHJOB;
    if (jsys(RTIW,acs) != JSok)		/* request terminal interrupt word */
	return (EOF);
    w_rtiw = ac2;

    ac1 = _CTTRM;
    ac2 = _MORXO;
    if (jsys(MTOPR,acs) != JSok)	/* request end-of-page mode */
	return (EOF);
    w_morxo = ac3;
#endif /* OS_TOPS20 */

#if    OS_UNIX
    if ((kbfp = fopen(TTYNAME,TTYOPENFLAGS)) == (FILE*)NULL)
	return (EOF);

#if    (ATT | HPUX)
    if (ioctl(fileno(kbfp),TCGETA,&ttysave) == -1)
	return (EOF);
#endif

#if    BSD
    if (ioctl(fileno(kbfp),TIOCGETP,&ttysave) == -1)
	return (EOF);
#endif /* BSD */
#endif /* OS_UNIX */

#if    OS_VAXVMS
    kbfp = (FILE*)NULL;			/* we use direct system calls */

    status = sys$assign(&sys_in,&tt_channel,0,0);
    if (FAILED(status))
	return (EOF);
    status = sys$qiow(0,tt_channel,IO$_SENSEMODE,0,0,0, &mode_save,12,0,0,0,0);
    if (FAILED(status))
	return (EOF);
#endif /* OS_VAXVMS */

    kbinit = 1;
    if (kbmode(mode) == (KEYMODE)EOF)
    {
	if (kbfp != (FILE*)NULL)
	    (void)fclose(kbfp);
	kbinit = 0;
	kbfp = (FILE*)NULL;
	return (EOF);
    }

    return (0);
}

#if    OS_TOPS20
/*====================================================================*/
static int
kbrestore()				/* restore terminal settings */
{					/* return 0 on success, EOF on error */
    ac1 = jfnof(fileno(kbfp));
    ac2 = w_rfmod;
    if (jsys(SFMOD,acs) != JSok)	/* set program-related modes */
	return (EOF);
    if (jsys(STPAR,acs) != JSok)	/* set device-related modes */
	return (EOF);

    ac1 = _FHJOB;
    ac2 = w_rtiw;
    if (jsys(STIW,acs) != JSok)		/* set terminal interrupt word */
	return (EOF);

    ac1 = _CTTRM;
    ac2 = _MOXOF;
    ac3 = w_morxo;
    if (jsys(MTOPR,acs) != JSok)	/* restore end-of-page mode */
	return (EOF);

    return (0);
}

#endif /* OS_TOPS20 */

/*====================================================================*/

int
kbunget(c)	/* unget keyboard character (1-level pushback) */
char c;		/* return c, or EOF on error (already pushed back character) */
{
    if (!kbinit)
	return (EOF);
    else if (kbpushback != EMPTY)
	return (EOF);
    else
    {
	kbpushback = (int)c;
	return (kbpushback);
    }
}


#if    OS_UNIX
#if    (ATT | HPUX)
static int
setblock(fd,on)		/* turn blocking on or off */
			/* return EOF on error, 0 on success */
int fd;			/* file descriptor */
BOOLEAN on;		/* FALSE == turn off, TRUE == turn on */
{
    static int blockf;
    static int nonblockf;
    static BOOLEAN first = TRUE;
    int flags;

    if (first)
    {
	first = FALSE;
	if ((flags = fcntl(fd,F_GETFL,0)) == -1)
	    return (EOF);
	blockf = flags & ~O_NDELAY;	/* turn off O_NDELAY */
	nonblockf = flags | O_NDELAY;	/* turn on O_NDELAY */
    }
    if (fcntl(fd,F_SETFL,on ? blockf : nonblockf) == -1)
	return (EOF);
    return (0);
}
#endif /* (ATT | HPUX) */
#endif /* OS_UNIX */