/*
 * Getkey:
 *
 * A machine dependent function to prompt for and read a keystring from stdin
 *
 * Input:
 *   prompt - A string output to display prior to requesting input
 *   str    - where to put the characters
 *   size   - the number of storage locations reserved for the key - 1
 *    
 * Returns: 
 *    The number of characters read (not counting terminating '\n')
 *
 * Display a prompt on the screen (not stdout!) before entry. 
 * Reads in upto <size> characters from the terminal (not stdin!).  
 * Terminated by EOF or '\n'. 
 * The trailing terminator is removed if present.  
 * String will be '\0' terminated if its less than size bytes.   
 * The characters are not echoed as they are typed.
 */
#if !defined(__msdos) && !defined(__MSDOS__) && !defined(__BSD) /* { */
#define _POSIX_SOURCE
#include <unistd.h>	/* read write getpid */
#include <termios.h>	/* termio stuff */
#include <signal.h>	/* signal stuff */
#include <string.h>	/* strlen */
#include <fcntl.h>	/* O_RDWR */
#include <stdio.h>	/* ctermid on dec alpha */
/*
 * The type of a signal handler is declared inconsistantly in the header files.
 * Extremely picky compilers may require adjustment here; most don't complain.
 * The problem is what the type of signal's argument really is.  POSIX doesn't
 * state this precisely; what is the prototype of the signal handler's
 * declaration supposed to look like exactly?  Like Sun, I would guess 
 *     void handler(int,...) 
 * but most vendors use void handler(). Sigh.
 */
#if defined(_HPUX_SOURCE) && defined(__cplusplus)
typedef void (*pfv)(int);			/* HPUX 9.0 C++ */
#else
typedef void (*pfv)();				/* POSIX (normal case) */
/* typedef void (*pfv)(int, ...);		/* sunOS 4.1.2 C++ */
#endif

/*
 * Signal with posix signal semantics
 */
static pfv psignal(sig, handler)
   int sig;
   pfv handler;
{
   struct sigaction sact, oact;
   int res;

   sact.sa_handler = handler; /* some compilers break here; see typedef pfv */
   sact.sa_flags   = 0;
   sigemptyset(&sact.sa_mask);
   res = sigaction(sig, &sact, &oact);
   if (res < 0) {
      return (pfv) -1;
   }
   return (pfv) oact.sa_handler;
}

/*
 * Set tty echo on(1) or off(0); returns previous setting or -1
 */
static int setTtyEcho(fd, echo)
   int fd;
   int echo;
{
   register int res, oldecho;
   struct termios modes;

   res = tcgetattr(fd, &modes);
   if (res < 0) return -1;
    
   oldecho = ((modes.c_lflag & ECHO) != 0);
   if (echo) {
      modes.c_lflag |= ECHO;
   } else {
      modes.c_lflag &= ~ECHO;
   }

   res = tcsetattr(fd, TCSANOW, &modes);
   if (res < 0) return -2;

   return oldecho;
}

static int oldEcho = 1;		/* previous tty echo state */
static int ttyfd   = -1;	/* controlling terminal */
pfv    	   oldsigint;		/* previous handler */

static void restoreEcho(sig) 
   int sig;
{
   if (ttyfd >= 0) {
      setTtyEcho(ttyfd, oldEcho);
      close(ttyfd);
   }
   (void) psignal(SIGINT, oldsigint);	/* restore old handler */
   kill(getpid(), SIGINT);		/* invoke old handler on return */
}

/*
 * Display prompt to the screen, read from the keyboard without echo until CR.
 */
int getkey(prompt, str, size)
   char *prompt;
   char *str;
   register unsigned size;
{
   unsigned len = strlen(prompt);
   register int count;
   register char *chp = str;
   register int res = -1;
   char ch; 

   ttyfd = open(ctermid((char *) 0), O_RDWR);
   if (ttyfd < 0) return -1;

   oldsigint = psignal(SIGINT, restoreEcho);
   oldEcho   = setTtyEcho(ttyfd, 0);

   count = write(ttyfd, prompt, len);
   if (count != len) return -1;

   if (size != 0) do {
      count = read(ttyfd, &ch, 1);
      if (count == 0) break;
      if (count != 1) break;
      if (ch == '\n') break;
      *chp++ = ch;
   } while (--size != 0);

   if (count >= 0) {
      res =  chp - str;
   }
   *chp = '\0';
   count = write(ttyfd, "\n", 1);			/* goto next line */

   oldEcho = setTtyEcho(ttyfd, oldEcho);
   psignal(SIGINT, oldsigint);
   close(ttyfd);
   ttyfd = -1;
   return res;
}
#endif		/* } POSIX */

#if defined(__msdos) || defined(__MSDOS__)	/* { */
#include <dos.h>

/*
 * Output a character to the console independent of I/O redirection
 */
void conOut(ch)
   int ch;
{
   union REGS regs;

   regs.h.ah = 14 ;     /* INT 10h, function 0EH, for video driver */
   regs.h.al = (unsigned char) ch;
   int86(16, &regs, &regs) ;
}


/*
 * Input a character from the console independent of I/O redirection (no echo)
 */
char conIn()
{
   union REGS regs;

   regs.h.ah = 0 ; /* INT 16h, function 00H, get char from keyboard */
   int86(22, &regs, &regs) ;
   return (char) regs.h.al;
}

int getkey(prompt, str, size)
   register char *prompt;
   char *str;
   register unsigned size;
{
   register int ch;
   register char *chp = str;;

   while (*prompt != '\0') {
      conOut(*prompt++);                /* write char to console */
   }
   if (size != 0) do {
      ch = conIn();             /* read char from console without echo */
      if (ch == '\r') break;    /* carriage return */
      *chp++ = ch;
   } while (--size != 0);

   if (size != 0) {
      *chp = '\0';
   }
   conOut('\r');
   conOut('\n');
   return chp - str;
}
#endif 	/* } MSDOS */
#if defined(__BSD)		/* Are you *sure* you don't have Posix? { */
/*
 * This code taken from ciper's original release.  I don't mess with 
 * BSD-specific stuff anymore since POSIX now works almost everywhere
 * (including every BSD machine I can find these days).
 *
 * Tested on SunOS 4.1 on SPARC IPC (sun4c/40) 28-Mar-94 by Dave Barrett
 * (That OS *does* have POSIX; I just wanted to make *sure* BSD worked)
 */
#include <string.h>
#include <fcntl.h>
#if defined(_LINUX_SOURCE)	/* or whatever linux cpp sets automagically */
#include <bsd/sgtty.h>
#else
#include <sgtty.h>		/* *my* BSD manual says; works on Sun SPARC  */
#endif
#include <signal.h>

int setEcho(fd, echo)
   int fd;
   int echo;
{  
   struct sgttyb modes;
   int res, oldecho;

   res = ioctl(fd, TIOCGETP, &modes);
   oldecho = modes.sg_flags & ECHO;
   if (echo >= 0) {
      if (echo) {
	 modes.sg_flags |= ECHO;
      } else {
	 modes.sg_flags &= ~ECHO;
      }
      res = ioctl(fd, TIOCSETN, &modes);
   }
   return oldecho;
}

static void (*oldSigInt)();
static int  oldEcho = 1;			/* assume echo was on */
static int  fd = -1;

void intHandler()
{
   setEcho(fd, oldEcho);			/* restore echoing */
   close(fd);					/* close /dev/tty */
   (void) signal(SIGINT, oldSigInt);		/* restore old handler */
   kill(getpid(), SIGINT);			/* invoke old handler */
}

/*
 * A machine dependent function to prompt for and read a keystring from stdin
 *
 * Input:
 *   prompt - A string output to display prior to requesting input
 *   str    - where to put the characters
 *   size   - the number of storage locations reserved for the key
 *    
 * Returns: 
 *    The number of characters read (not counting terminating '\n')
 *
 * Reads in upto <size> characters from the terminal.  Terminated by EOF
 * or '\n'.  String will be '\0' terminated if its less than size bytes.
 */
int getkey(prompt, str, size)
   char *prompt;
   char *str;
   register unsigned size;
{
   int count, len;
   char buf[1], *chp = str;;

   fd = open("/dev/tty", O_RDWR);
   if (fd < 0) return fd;
   oldSigInt = signal(SIGINT, intHandler);
   oldEcho = setEcho(fd, 0);		/* disable printing of input */

   len   = strlen(prompt);
   count = write(fd, prompt, len);

   if (size != 0) do {
      count = read(fd, buf, 1);
      if (count != 1) break;
      if (buf[0] == '\n') break;
      *chp++ = buf[0];
   } while (--size != 0);
 
   if (size != 0) {
      *chp = '\0';
   }
#if !defined(_LINUX_SOURCE)
   /*
    * This code is definitely correct on Sun BSD machines.  I'd like to
    * know why it's wrong on some others.  Please send me mail if you disagree.
    * Dave Barrett <barrett@asgard.cs.Colorado.EDU>
    */
   write(fd, "\n", 1);			/* goto next line */
#endif
   setEcho(fd, oldEcho);		/* restore echo to previous state */
   (void) signal(SIGINT, oldSigInt);
   close(fd);
   return chp - str;
}
#endif /* } __BSD but not POSIX (must be an OLD machine) */
