/*----------------------------------------------------------------------

            T H E    P I N E    M A I L   S Y S T E M

   Laurence Lundblade and Mike Seibel
   Networks and Distributed Computing
   Computing and Communications
   University of Washington
   Administration Building, AG-44
   Seattle, Washington, 98195, USA
   Internet: lgl@CAC.Washington.EDU
             mikes@CAC.Washington.EDU

   Please address all bugs and comments to "pine-bugs@cac.washington.edu"

   Copyright 1989, 1990, 1991, 1992  University of Washington

    Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee to the University of
   Washington is hereby granted, provided that the above copyright notice
   appears in all copies and that both the above copyright notice and this
   permission notice appear in supporting documentation, and that the name
   of the University of Washington not be used in advertising or publicity
   pertaining to distribution of the software without specific, written
   prior permission.  This software is made available "as is", and
   THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
   WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
   NO EVENT SHALL THE UNIVERSITY OF WASHINGTON 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, TORT
   (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
   WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  

   Pine is in part based on The Elm Mail System:
    ***********************************************************************
    *  The Elm Mail System  -  $Revision: 2.13 $   $State: Exp $          *
    *                                                                     *
    * 			Copyright (c) 1986, 1987 Dave Taylor              *
    * 			Copyright (c) 1988, 1989 USENET Community Trust   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
       ttyin.c
       Things having to do with reading from the tty driver and keyboard
          - initialize tty driver and reset tty driver
          - read a character from terminal with keyboard escape seqence mapping
          - initialize keyboard (keypad or such) and reset keyboard
          - prompt user for a line of input
          - read a command from keyboard with timeouts.

 ====*/

#include "headers.h"

#ifndef DOS  /* Beginning of giant switch between UNIX and DOS input driver */

#ifdef POSIX_SIGNALS
#include <signal.h>
#endif

#ifdef USE_POLL
#include <stropts.h>
#include <poll.h>
#endif

#ifdef LINUX
#define HAVE_TERMIO
#endif /* LINUX */

#ifdef HAVE_TERMIOS
#include <termios.h>

struct termios _raw_tty, _original_tty;

#else
#ifdef HAVE_TERMIO
#include <termio.h>

static struct termio _raw_tty, _original_tty;


#else /* HAVE_TERMIO */

#include <sgtty.h>

static struct sgttyb  _raw_tty,     _original_tty;
static struct ltchars _raw_ltchars, _original_ltchars;
static struct tchars  _raw_tchars,  _original_tchars;
static int            _raw_lmode,   _original_lmode;
#endif /* HAVE_TERMIO  */
#endif /* HAVE_TERMIOS */

#define STDIN_FD 0

/*----------------------------------------------------------------------
    Initialize the tty driver to do single char I/O and whatever else  (UNIX)

   Args:  struct pine

 Result: tty driver is put in raw mode so characters can be read one
         at a time. Returns -1 if unsuccessful, 0 if successful.

Some file descriptor voodoo to allow for pipes across vforks. See 
open_mailer for details.
  ----------------------------------------------------------------------*/
init_tty_driver(ps)
     struct pine *ps;
{
    ps = ps; /* Get rid of unused parameter warning */

    return(Raw(1));
}



/*----------------------------------------------------------------------
       End use of the tty, put it back into it's normal mode     (UNIX)

   Args: ps --  struct pine

 Result: tty driver mode change. 
  ----------------------------------------------------------------------*/
void
end_tty_driver(ps)
     struct pine *ps;
{
    ps = ps; /* get rid of unused parameter warning */

    fflush(stdout);
    dprint(2, (debugfile, "about to end_tty_driver\n"));

    Raw(0);
}



/*----------------------------------------------------------------------
    Actually set up the tty driver                             (UNIX)

   Args: state -- which state to put it in. 1 means go into raw, 0 out of

  Result: returns 0 if successful and -1 if not.
  ----*/

Raw(state)
int state;
{
    static int _inraw = 0;
    /** state is either ON or OFF, as indicated by call **/
    /* Check return code only on first call. If it fails we're done for and
       if it goes OK the other will probably go OK too. */

    if (state == 0 && _inraw) {
        /*----- restore state to original -----*/
#ifdef HAVE_TERMIOS
	if (tcsetattr (STDIN_FD, TCSADRAIN, &_original_tty) < 0)
		return -1;
#else  /* HAVE_TERMIOS */
#ifdef HAVE_TERMIO
        if(ioctl(STDIN_FD, TCSETAW, &_original_tty) < 0)
          return(-1);
#else
	if(ioctl(STDIN_FD, TIOCSETP, &_original_tty) < 0)
          return(-1);
	(void) ioctl(STDIN_FD, TIOCSLTC, &_original_ltchars);
	(void) ioctl(STDIN_FD, TIOCSETC, &_original_tchars);
        (void) ioctl(STDIN_FD, TIOCLSET, &_original_lmode);
#endif /* HAVE_TERMIO */
#endif /* HAVE_TERMIOS */
        _inraw = 0;
    } else if (state == 1 && ! _inraw) {
        /*----- Go into raw mode (cbreak actually) ----*/

#ifdef HAVE_TERMIOS
	if (tcgetattr (STDIN_FD, &_original_tty) < 0)
		return -1;
	tcgetattr (STDIN_FD, &_raw_tty);
	_raw_tty.c_lflag &= ~(ICANON | ECHO);	/* noecho raw mode  */

 	_raw_tty.c_lflag &= ~ISIG;            /* disable signals */
 	_raw_tty.c_iflag &= ~ICRNL;           /* turn off CR->NL on input */
 	_raw_tty.c_oflag &= ~ONLCR;           /* turn off NL->CR on output */
 	_raw_tty.c_oflag &= ~OPOST;
	_raw_tty.c_cc[VMIN]  = '\01'; /* minimum # of chars to queue  */
	_raw_tty.c_cc[VTIME] = '\0';  /* minimum time to wait for input */
        ps_global->low_speed = (_raw_tty.c_cflag & CBAUD) < B4800;
	tcsetattr (STDIN_FD, TCSADRAIN, &_raw_tty);

#else
#ifdef HAVE_TERMIO
        if(ioctl(STDIN_FD, TCGETA, &_original_tty) < 0)
          return(-1);
	(void) ioctl(STDIN_FD, TCGETA, &_raw_tty);    /** again! **/

	_raw_tty.c_lflag &= ~(ICANON | ECHO);	/* noecho raw mode  */

 	_raw_tty.c_lflag &= ~ISIG;            /* disable signals */
 	_raw_tty.c_iflag &= ~ICRNL;           /* turn off CR->NL on input */
 	_raw_tty.c_oflag &= ~ONLCR;           /* turn off NL->CR on output */
 	_raw_tty.c_oflag &= ~OPOST;
	_raw_tty.c_cc[VMIN]  = 1;  /* minimum # of chars to queue  */
	_raw_tty.c_cc[VTIME] = 0;  /* minimum time to wait for input */
        ps_global->low_speed = (_raw_tty.c_cflag & CBAUD) < B4800;
	(void) ioctl(STDIN_FD, TCSETAW, &_raw_tty);

#else/* HAVE_TERMIO */
        if(ioctl(STDIN_FD, TIOCGETP, &_original_tty) < 0)
          return(-1);
	(void) ioctl(STDIN_FD, TIOCGETP, &_raw_tty);   
        (void) ioctl(STDIN_FD, TIOCGETC, &_original_tchars);
	(void) ioctl(STDIN_FD, TIOCGETC, &_raw_tchars);
	(void) ioctl(STDIN_FD, TIOCGLTC, &_original_ltchars);
	(void) ioctl(STDIN_FD, TIOCGLTC, &_raw_ltchars);
        (void) ioctl(STDIN_FD, TIOCLGET, &_original_lmode);
        (void) ioctl(STDIN_FD, TIOCLGET, &_raw_lmode);

	_raw_tty.sg_flags &= ~(ECHO);	/* echo off */
	_raw_tty.sg_flags |= CBREAK;	/* raw on    */
        _raw_tty.sg_flags &= ~CRMOD;    /* Turn off CR -> LF mapping */

	_raw_tchars.t_intrc = -1; /* Turn off ^C and ^D */
	_raw_tchars.t_eofc  = -1;

	_raw_ltchars.t_lnextc = -1;   /* Turn off ^V so we can use it */
	_raw_ltchars.t_dsuspc = -1;   /* Turn off ^Y so we can use it */
	_raw_ltchars.t_suspc  = -1;   /* Turn off ^Z; we just read 'em */
	_raw_ltchars.t_werasc = -1;   /* Turn off ^w word erase */
	_raw_ltchars.t_rprntc = -1;   /* Turn off ^R reprint line */
        _raw_ltchars.t_flushc = -1;   /* Turn off ^O output flush */

        if((ps_global->VAR_CHAR_SET != NULL &&
            strlen(ps_global->VAR_CHAR_SET) &&
            strucmp(ps_global->VAR_CHAR_SET, "us-ascii")) ||
           ps_global->show_all_characters) {
            /* Only go into 8 bit mode if we are doing something other
               than plain ASCII. This will save the folks that have
               their parity on their serial lines wrong thr trouble of
               getting it right
              */
           _raw_lmode |= LPASS8;
#ifdef NXT  /* Hope there aren't many specific like this! */
            _raw_lmode |= LPASS8OUT;
#endif
       }
            
	(void) ioctl(STDIN_FD, TIOCSETP, &_raw_tty);
	(void) ioctl(STDIN_FD, TIOCSLTC, &_raw_ltchars);
        (void) ioctl(STDIN_FD, TIOCSETC, &_raw_tchars);
        (void) ioctl(STDIN_FD, TIOCLSET, &_raw_lmode);
        ps_global->low_speed =  _raw_tty.sg_ispeed < B4800;

#endif
#endif
         _inraw = 1;
    }
    return(0);
}



jmp_buf winch_state;
int     winch_occured = 0;
int     ready_for_winch = 0;

/*----------------------------------------------------------------------
     Lowest level read command. This reads one character with timeout. (UNIX)

    Args:  time_out --  number of seconds before read will timeout

  Result: Returns a single character read or a NO_OP_COMMAND if the
          timeout expired, or a KEY_RESIZE if a resize even occured.

  ----*/
static int
read_with_timeout(time_out)
     int time_out;
{
#ifdef USE_POLL
     struct pollfd pollfd;
     int    res;
#else
     struct timeval tmo;
     int            readfds, res;
#endif
     unsigned char  c;

     if(winch_occured) {
         winch_occured = 0;
         return(KEY_RESIZE);
     }


     if(setjmp(winch_state) != 0) {
         ready_for_winch = 0;
         winch_occured   = 0;
#ifdef POSIX_SIGNALS
	 /* Need to reset signal after longjmp from handler. */
	 sigrelse (SIGWINCH);
#endif
         return((int)KEY_RESIZE);
     } else {
         ready_for_winch = 1;
     }

     if(time_out > 0) {
         /* Check to see if there's bytes to read with a timeout */
#ifdef USE_POLL
	pollfd.fd = STDIN_FD;
	pollfd.events = POLLIN;
	dprint(9,(debugfile,"poll event %d, timeout %d\n", pollfd.events, time_out));
	res = poll (&pollfd, 1, time_out * 1000);
	dprint(9, (debugfile, "poll on tty returned %d, events %d\n", res, pollfd.revents));
#else
         if(STDIN_FD == 0)
           readfds    = 1;
         else
           readfds    = 1 << STDIN_FD; /* First bit for file desc 0 */
         tmo.tv_sec  = time_out;
         tmo.tv_usec = 0; 

         dprint(9,(debugfile,"Select readfds:%d timeval:%d,%d\n",readfds, tmo.tv_sec,tmo.tv_usec));

         res = select(STDIN_FD+1, &readfds, 0, &readfds, &tmo);

         dprint(9, (debugfile, "Select on tty returned %d\n", res));
#endif

         if(res < 0) {
             if(errno == EINTR) {
                 ready_for_winch = 0;
                 return(NO_OP_COMMAND);
             }
             panic1("Select error: %s\n", error_description(errno));
         }

         if(res == 0) { /* the select timed out */
             ready_for_winch = 0;
             return(time_out > 25 ? NO_OP_IDLE: NO_OP_COMMAND);
         }
     }

     res = read(STDIN_FD, &c, 1);
     dprint(9, (debugfile, "Read returned %d\n", res));

     ready_for_winch = 0;

     if(res <= 0) {
         /* Got an error reading from the terminal. Treat this like
            a SIGHUP: clean up and exit. */
         dprint(1, (debugfile, "\n\n** Error reading from tty : %s\n\n",
                error_description(errno)));

         if(errno == EINTR)
           return(NO_OP_COMMAND);

         if(ps_global->inbox_stream != NULL){
             if(ps_global->inbox_stream == ps_global->mail_stream)
               ps_global->mail_stream = NULL; 
             mail_close(ps_global->inbox_stream);
         }
         if(ps_global->mail_stream != NULL &&
            ps_global->mail_stream != ps_global->inbox_stream)
           mail_close(ps_global->mail_stream);
     
         MoveCursor(ps_global->ttyo->screen_rows -1, 0);
         NewLine();
         end_keyboard(ps_global->use_fkeys);
         end_tty_driver(ps_global);
         printf("\n\n\nPine finished. Error reading from terminal: %s\n",
           error_description(errno));
         exit(0);
     }

     return((int)c);
     
}
 


/*----------------------------------------------------------------------
  Read input characters with lots of processing for arrow keys and such  (UNIX)

 Args:  time_out -- The timeout to for the reads 

 Result: returns the character read. Possible special chars defined h file

    This deals with function and arrow keys as well. 

  The idea is that this routine handles all escape codes so it done in
  only one place. Especially so the back arrow key can work when entering
  things on a line. Also so all function keys can be disabled and not
  cause weird things to happen.


  Assume here that any chars making up an escape sequence will be close 
  together over time. It's possible for a timeout to occur waiting for rest
  of escape sequence if it takes more than 30 seconds to type the 
  escape sequence. The timeout will effectively cancel the escape sequence.
  ---*/

int
read_char(time_out)
     int time_out;
{
    register int  rx, ch, num_keys;

    rx = 0; /* avoid ref before set errors */
    ch = read_with_timeout(time_out);
    if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND || ch == KEY_RESIZE) goto done;
    ch &= 0x7f;
    switch(ch) {
    
      case '\033':
        ch = read_with_timeout(time_out);
        if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND || ch == KEY_RESIZE)
          goto done;
        ch &= 0x7f;
        if(ch == 'O') {
            /* For DEC terminals, vt100s */
            ch = read_with_timeout(time_out);
            if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND || ch == KEY_RESIZE)
              goto done;
            ch &= 0x7f;
            switch(ch) {
              case 'P': return(PF1);
              case 'Q': return(PF2);
              case 'R': return(PF3);
              case 'S': return(PF4);
              case 'p': return(PF5);
              case 'q': return(PF6);
              case 'r': return(PF7);
              case 's': return(PF8);
              case 't': return(PF9);
              case 'u': return(PF10);
              case 'v': return(PF11);
              case 'w': return(PF12);
              case 'A': return(KEY_UP);
              case 'B': return(KEY_DOWN);
              case 'C': return(KEY_RIGHT);
              case 'D': return(KEY_LEFT);
              default: return(KEY_JUNK);
            }
        } else if(ch == '[') {
            /* For dec terminals, vt200s, and some weird Sun stuff */
            ch = read_with_timeout(time_out);
            if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND || ch == KEY_RESIZE)
              goto done;
            ch &= 0x7f;
            switch(ch) {
              case 'A': return(KEY_UP);
              case 'B': return(KEY_DOWN);
              case 'C': return(KEY_RIGHT);
              case 'D': return(KEY_LEFT);
    
              case '=': /* ansi terminal function keys */
                ch = read_with_timeout(time_out);
                if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND || ch == KEY_RESIZE)
                  goto done;
                ch &= 0x7f;
                switch(ch) {
                  case 'a': return(PF1);
                  case 'b': return(PF2);
                  case 'c': return(PF3);
                  case 'd': return(PF4);
                  case 'e': return(PF5);
                  case 'f': return(PF6);
                  case 'g': return(PF7);
                  case 'h': return(PF8);
                  case 'i': return(PF9);
                  case 'j': return(PF10);
                  case 'k': return(PF11);
                  case 'l': return(PF12);
                  default: return(KEY_JUNK);
                }
              case '1': /* Sun keys */
                  rx = KEY_JUNK; goto swallow_till_z;
    
              case '2': /* Sun keys */
                  ch = read_with_timeout(time_out);
                  if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND ||
                     ch == KEY_RESIZE)
                    goto done;
                  ch &= 0x7f;
                  if(ch == '1') {
                      ch = read_with_timeout(time_out);
                      if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND ||
                         ch == KEY_RESIZE) goto done;
                      ch &= 0x7f;
                      switch (ch) {
                        case '5':  rx = KEY_UP; break;
                        case '7':  rx = KEY_LEFT; break;
                        case '9':  rx = KEY_RIGHT; break;
                        default:   rx = KEY_JUNK;
                      }
                  } else if (ch == '2') {
                      ch = read_with_timeout(time_out);
                      if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND ||
                         ch == KEY_RESIZE) goto done;
                      ch &= 0x7f;
                      if(ch == '1')
                        rx = KEY_DOWN;
                      else
                        rx = KEY_JUNK;
                  }
                    swallow_till_z:		  
		  num_keys = 0;
                  do {
                      ch = read_with_timeout(time_out);
                      if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND ||
                         ch == KEY_RESIZE) goto done;
                      ch &= 0x7f;
                  } while (num_keys++ < 4 && ch != 'z');
                  return(rx);			 
                      
                         
              default:
                /* DEC function keys */
                num_keys = 0;
                do {
                    ch = read_with_timeout(time_out);
                    if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND ||
                       ch == KEY_RESIZE) goto done;
                    ch &= 0x7f;
                } while (num_keys++ < 6 && ch != '~');
                return(KEY_JUNK);
            }   
        } else if(ch == '?') {
             /* DEC vt52 application keys, and some Zenith 19 */
             ch = read_with_timeout(time_out);
             if(ch == NO_OP_IDLE || ch == NO_OP_COMMAND ||
                ch == KEY_RESIZE) goto done;
             ch &= 0x7f;
             switch(ch) {
               case 'r': return(KEY_DOWN);
               case 't': return(KEY_LEFT);
               case 'v': return(KEY_RIGHT);
               case 'x': return(KEY_UP);
               default: return(KEY_JUNK);
             }    
        } else {
             /* This gets most Z19 codes, and some VT52 modes */
             switch(ch) {
               case 'A': return(KEY_UP);
               case 'B': return(KEY_DOWN);
               case 'C': return(KEY_RIGHT);
               case 'D': return(KEY_LEFT);
               default:  return(KEY_JUNK);
             }    
        }

     default:
     done:
        dprint(9, (debugfile, "Read char returning: %d %s\n",
                   ch, pretty_command(ch)));
        return(ch);
    }
}



/*----------------------------------------------------------------------
         Highest level read for reading Pine commands.    (UNIX)

   Args:  none

 Result: Retuns key press read. (keyboard escape sequences decoded)

 Calculates the timeout for the read, and does a few other house keeping 
things.
  ----*/
int
read_command()
{
    int ch, tm;

    fflush(stdout);
    tm = timeout > 0 ? messages_queued() ? messages_queued() : timeout : 0;
    ch = read_char(tm);
    dprint(9, (debugfile, "Read command returning: %d %s\n", ch,
              pretty_command(ch)));
    if(ch != NO_OP_COMMAND && ch != NO_OP_IDLE && ch != KEY_RESIZE)
      zero_new_mail_count();
    return(ch);
}



extern char termname[]; /* termname from ttyout.c-- affect keyboard*/
/* -------------------------------------------------------------------
     Set up the keyboard -- usually enable some function keys  (UNIX)

    Args: struct pine 

So far all we do here is turn on keypad mode for certain terminals

Hack for NCSA telnet on an IBM PC to put the keypad in the right mode.
This is the same for a vtXXX terminal or [zh][12]9's which we have 
a lot of at UW
  ----*/
void
init_keyboard(use_fkeys)
     int use_fkeys;
{
    if(use_fkeys && (!strucmp(termname,"vt102") || !strucmp(termname,"vt100")))
      printf("\033\133\071\071\150");

}



/*----------------------------------------------------------------------
     Clear keyboard, usually disable some function keys           (UNIX)

   Args:  pine state (terminal type)

 Result: keyboard state reset
  ----*/
void
end_keyboard(use_fkeys)
     int use_fkeys;
{
    if(use_fkeys && (!strcmp(termname, "vt102") || !strcmp(termname, "vt100")))
      printf("\033\133\071\071\154");

}

    
#else /* DOS  Middle of giant siwtch UNX and DOS input drivers */



/* 
   This code compiled once, but it out of date with respect to the rest of
  Pine. Substantial work is needed to make it run
 */



/*----------------------------------------------------------------------
    Initialize the tty driver to do single char I/O and whatever else  (DOS)

 Input:  struct pine

 Result: tty driver is put in raw mode
  ----------------------------------------------------------------------*/
void
init_tty_driver(pine)
     struct pine *pine;
{
}



/*----------------------------------------------------------------------
       End use of the tty, put it back into it's normal mode          (DOS)

 Input:  struct pine

 Result: tty driver mode change
  ----------------------------------------------------------------------*/
void
end_tty_driver(pine)
     struct pine *pine;
{
    dprint(2, (debugfile, "about to end_tty_driver\n"));
}

/*----------------------------------------------------------------------
   translate IBM Keyboard Extended Functions to things pine understands.
   More work can be done to make things like Home, PageUp and PageDown work. 
  ----------------------------------------------------------------------*/
static unsigned char key_ext_fun[] = {
    NO_OP_COMMAND, /* 0 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 10 */
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    KEY_RIGHT,     /* 15 */
    NO_OP_COMMAND,  /* Alt Q, W..... */
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 20 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 30 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 40 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 50 */  /* Alt ...... M */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    PF1,           /* 59 */ /* F1 */
    PF2,
    PF3,
    PF4,
    PF5,
    PF6,
    PF7,
    PF8,
    PF9,          
    PF10,          /* 68 */
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 70 */
    NO_OP_COMMAND, /* Home */
    KEY_UP, 
    NO_OP_COMMAND, /* Page Up and Home Cursor */
    NO_OP_COMMAND, 
    KEY_LEFT, 
    NO_OP_COMMAND,
    KEY_RIGHT,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* End */
    KEY_DOWN,      /* 80 */
    NO_OP_COMMAND, /* Page Down and Home Cursor */
    NO_OP_COMMAND, /* Insert */
    NO_OP_COMMAND, /* Delete */
    PF11,          /* F11 */
    PF12,          /* F12 */
    NO_OP_COMMAND, /* F13....  */
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 90 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 100 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 110 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND,  /* ..... F40 */
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 120 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, /* 130 */
    NO_OP_COMMAND,
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND, 
    NO_OP_COMMAND};





/*----------------------------------------------------------------------
   Read input characters with lots of processing for arrow keys and such (DOS)

 Input:  none

 Result: returns the character read. Possible special chars defined h file


    This deals with function and arrow keys as well. 
  It returns ^T for up , ^U for down, ^V for forward and ^W for back.
  These are just sort of arbitrarily picked and might be changed.
  They are defined in defs.h. Didn't want to use 8 bit chars because
  the values are signed chars, though it ought to work with negative 
  values. 

  The idea is that this routine handles all escape codes so it done in
  only one place. Especially so the back arrow key can work when entering
  things on a line. Also so all function keys can be broken and not
  cause weird things to happen.
----------------------------------------------------------------------*/

int
read_char()
{
    /** read a character with Raw mode set! **/

    register int result, rx;
    int8         ch;

    ch = bioskey(0);

    dprint(9, (debugfile, "read : %d\n", ch));

    if(ch & 0xff == 0) 
        return(key_ext_fun[ch >> 8]);
    else 
        return(ch);
}


/* -------------------------------------------------------------------
     Set up the keyboard -- usually enable some function keys     (DOS)

  Input: struct pine (terminal type)

  Result: keyboard set up

-----------------------------------------------------------------------*/
init_keyboard(pine)
     struct pine *pine;
{
}



/*----------------------------------------------------------------------
     Clear keyboard, usually disable some function keys            (DOS)

 Input:  pine state (terminal type)

 Result: keyboard state reset
  ----------------------------------------------------------------------*/
/* BUG shouldn't have to check for pine != NULL */
end_keyboard(pine)
     struct pine *pine;
{

}




/*----------------------------------------------------------------------
        Read a character from keyboard with timeout               (DOS)
 Input:  none

 Result: Returns command read via readch_arar 
         Times out and returns a null command every so often

  The duration of the timeout is set in pine.c.
  ----------------------------------------------------------------------*/
int
read_command()
{
    int ch, i , tm;
    
    if (timeout > 0) {
       tm = messages_queued() ? STATUS_MESSAGE_TIME : timeout;
       for(i = 0; i < timeout * 10; i++) {
           delay(100); /* 100 mS, check 10 times a second */
           if(kbhit())
             break;
       }
       if(i == 10 * timeout) {
           ch = NO_OP_COMMAND;
       } else {
           ch = read_char();
       }
    }
    return(ch);
}

#endif /* DOS End of giant switch between UNX and DOS input drivers */






static struct key_menu oe_keymenu =
  {0,{
     {"^G","Help",0},     {"RETURN","Enter",0},     {NULL,NULL,0},
     {"^T","xxx",0},      {NULL,NULL,0},            {NULL,NULL,0},
     {NULL,NULL,0},       {NULL,NULL,0},            {NULL,NULL,0},
     {NULL,NULL,0},       {NULL,NULL,0},            {NULL,NULL,0}}};


/*---------------------------------------------------------------------- 
       Prompt user for a string in status line with various options

  Args: string -- the buffer result is returned in, and original string (if 
                   any) is passed in.
        y_base -- y position on screen to start on. 0,0 is upper left
                    negative numbers start from bottom
        x_base -- column position on screen to start on. 0,0 is upper left
        field_len -- Maximum length of string to accept
        append_current -- flag indicating string should not be truncated before
                          accepting input
        passwd -- a pass word is being fetch. Don't echo on screen
        prompt -- The string to prompt with
        escape_label -- Label for ^T on keymap at bottom of screen
        help   -- Arrary of strings for help text in bottom screen lines
        disallow_abort -- flag indicating ^C for abort is not allowed

  Result:  editing input string
            returns -1 unexpected errors
            returns 0  normal entry typed (editing and return or PF2)
            returns 1  typed ^C or PF3 (abort)
            returns 2  typed ^T or PF4 (special escape)
            returns 3  typed ^G or PF1 (help)
            returns 4  typed ^L for a screen redraw

   Tabs can't be entered here.
This allows near full weemacs style editing in the line
   ^A beginning of line
   ^E End of line
   ^R Redraw line
   ^G Help
   ^F forward
   ^B backward
   ^D delete
----------------------------------------------------------------------*/

optionally_enter(string, y_base, x_base, field_len, append_current, passwd,
                 prompt, escape_label, help, disallow_abort)
     char        *string, *prompt, *escape_label, **help;
     int          x_base, y_base, field_len, append_current, passwd,
                  disallow_abort;
{
    register char *s2;
    register int   field_pos;
    int            j, return_v, cols, ch, use_fkeys, prompt_len, didnt_delete,
                   too_thin, real_y_base, cursor_moved;
    char          *saved_original, *k, *kb;
    char          *kill_buffer = NULL;

    dprint(5, (debugfile, "=== optionally_enter called ===\n"));
    dprint(9, (debugfile, "string:\"%s\"  y:%d  x:%d  length: %d append: %d\n",
               string, x_base, y_base, field_len, append_current));
    dprint(9, (debugfile, "passwd:%d   prompt:\"%s\"   label:\"%s\"\n",
               passwd, prompt, escape_label == NULL ? "" : escape_label));

    use_fkeys  = ps_global->use_fkeys; 
    cols       = ps_global->ttyo->screen_cols;
    prompt_len = strlen(prompt);
    too_thin   = 0;
    if(y_base > 0) {
        real_y_base = y_base;
    } else {
        real_y_base=  y_base + ps_global->ttyo->screen_rows;
        if(real_y_base < 2)
          real_y_base = ps_global->ttyo->screen_rows;
    }

    mark_status_dirty();

    if(append_current) {
        /*---- save a copy in case of abort -----*/
	saved_original = fs_get(strlen(string) + 1);
	strcpy(saved_original, string);
    } else {
        /*---- zero the string passed in ------*/
	string[0]      = '\0';
	saved_original = NULL;
    }


    if(help != NULL) {
        /*---- Show help text -----*/
        MoveCursor(real_y_base + 1, x_base);
        CleartoEOLN();
        PutLine0(real_y_base+1, x_base, help[0]);
        MoveCursor(real_y_base + 2, x_base);
        CleartoEOLN();
        if(help[1] != NULL) 
          PutLine0(real_y_base + 2, x_base, help[1]);
          
    } else {
        /*---- Show the usual possible keys ----*/
        if(escape_label != NULL) {
            oe_keymenu.keys[3].label = escape_label;
            oe_keymenu.keys[3].name  = "^T";
        } else {
            oe_keymenu.keys[3].name = NULL;
        }
        if(disallow_abort) {
            oe_keymenu.keys[2].name  = NULL;
        } else {
            oe_keymenu.keys[2].label = "Abort";
            oe_keymenu.keys[2].name  = "^C";
        }
        format_keymenu(&oe_keymenu, cols);
        output_keymenu(&oe_keymenu, -2, 0);
    }
    
    
    StartInverse();  /* Always in inverse  */


    if(x_base + field_len + prompt_len > cols)
      field_len = cols - x_base - prompt_len - 1; /* Truncate if won't fit */
    else
      field_len--; /* One less so we have room for terminating NULL! */


    PutLine0(real_y_base, x_base, prompt);
    field_pos = 0;
    if(append_current) {
        for(; field_pos < field_len && string[field_pos] != '\0' ; field_pos++)
	  Writechar(string[field_pos], 0);
        if(string[field_pos]) {
	    /* Have to truncate the string */ 
  	    string[field_pos] = '\0';
        }
    }

    /*----- Fill out the rest of the line as blanks --------------*/
    s2 = &string[field_pos];
    for(j = x_base + prompt_len + field_pos; j < cols; j++) {
        Writechar(' ', 0);
        if(s2 <= &string[field_len])
          *s2++ = '\0';
    }

    MoveCursor(real_y_base, x_base + prompt_len + field_pos);


    /*----------------------------------------------------------------------
      The main loop
   
    here field_pos is the position in the string.
    s always points to where we are in the string.
    loops until someone sets the return_v.
      ----------------------------------------------------------------------*/
    return_v = -10;

    while(return_v == -10) {
	fflush(stdout);

        ch = read_char(600); /* Timeout 5 min to keep imap mail stream alive */

        if(too_thin && ch != KEY_RESIZE && ch != ctrl('Z'))
          goto bleep;


	switch(ch) {

	    /*--------------- KEY RIGHT ---------------*/
          case ctrl('F'):  
	  case KEY_RIGHT:
	    if(field_pos >= field_len || string[field_pos] == '\0')
              goto bleep;
	    if(!passwd)
	      Writechar(string[field_pos], 0);
	    field_pos++;
	    break;

	    /*--------------- KEY LEFT ---------------*/
          case ctrl('B'):
	  case KEY_LEFT:
	    if(field_pos <= 0)
	      goto bleep;
	    if(!passwd)
	      Writechar(BACKSPACE, 0); /* Bug. this should come from termcap */
	    field_pos--;
	    break;

	  /* --------------- Special escape ---------------*/
	  case ctrl('T'): 
	    if(use_fkeys) goto bleep;
	    goto spl;
	  case PF4:
	    if(!(ps_global->use_fkeys)) goto bleep;
	  spl:
	    if(escape_label == NULL) goto bleep;
	    return_v = 2;
	    break;

          /*--------------------  RETURN --------------------*/
	  case PF2:
	    if(!use_fkeys) goto bleep;
	  case ctrl('J'): 
	  case ctrl('M'): 
	    return_v = 0;
	    break;

#ifdef DEBUG
          case ctrl('X'):
            if(debug < 9)
              goto bleep;
            printf("field_pos: %d \n", field_pos);
	    for(s2 = string; s2 < &string[field_len] ; s2++) 
	      printf("%s%d --%c--\n", s2 == &string[field_pos] ?"-->" :"   ",
                     *s2, *s2);
	    fflush(stdout);
            break;
#endif
	    

          /*-------------------- Destructive backspace --------------------*/
	  case '\177': /* DEL */
	  case ctrl('H'):
            /*   Try and do this with by telling the terminal to delete a
                 a character. If that fails, then repaint the rest of the
                 line, acheiving the same much less efficiently
             */
	    if(field_pos <= 0) goto bleep;
	    didnt_delete = 0;
	    if(!passwd) {
  	        Writechar(BACKSPACE, 0);
	        if(DeleteChar(1) < 0){
		    /* terminal can't delete */
		    didnt_delete = 1;
	        } 
	    }
	    field_pos--; /* Need to pull line back */
	    for(s2 = &string[field_pos]; *s2 != '\0'; s2++) {
	        if(!passwd && didnt_delete && *(s2 + 1))
                  Writechar(*(s2 + 1), 0);
		*s2 = *(s2 + 1);
	    }
	    *s2 = *(s2 + 1); /* Copy last NULL */
	    if(!passwd) {
   		/* -- Tack blank on end keeping whole thing in reverse-- */
                if(didnt_delete) {
                    Writechar(' ', 0);
                } else {
                    MoveCursor(real_y_base, cols - 1);
	            Writechar(' ', 0);
	        }
                MoveCursor(real_y_base, x_base + prompt_len + field_pos);
            }
	    break;


          /*-------------------- Delete char --------------------*/
	  case ctrl('D'): 
	    didnt_delete = 0;
            if(field_pos >= field_len || !string[field_pos]) goto bleep;
            if(!passwd) {
	        if(DeleteChar(1) < 0){
		    /* terminal can't delete */
  		    didnt_delete = 1;
	        } 	    }
	    for(s2 = &string[field_pos]; *s2 != '\0'; s2++) {
		if(didnt_delete && !passwd && *(s2 + 1))
                  Writechar(*(s2 + 1), 0);
		*s2 = *(s2 + 1);
            }
	    *s2 = *(s2 + 1); /* Copy last NULL */
	    if(!passwd) {
		/* -- Tack blank on end keeping whole thing in reverse -- */
                if(didnt_delete) {
                    Writechar(' ', 0);
                } else {
                    MoveCursor(real_y_base, cols - 1);
		    Writechar(' ', 0);
	        }
		MoveCursor(real_y_base, x_base + prompt_len + field_pos);
	    }   
	    break;


            /*--------------- Kill line -----------------*/
          case ctrl('K'):
            if(kill_buffer != NULL)
              fs_give((void **)&kill_buffer);
            MoveCursor(real_y_base, x_base + prompt_len);
            for(j = 0; j < field_len; j++)
              Writechar(' ', 0);
            kill_buffer = cpystr(string);
            string[0] = '\0';
            field_pos = 0;
            MoveCursor(real_y_base, x_base + prompt_len + field_pos); 
            break;

            /*------------------- Undelete line --------------------*/
          case ctrl('U'):
            if(kill_buffer == NULL)
              goto bleep;
            /* Make string so it will fit */
            kb = cpystr(kill_buffer);
            dprint(2, (debugfile, "Undelete: %d %d\n", strlen(string), field_len));
            if(strlen(kb) + strlen(string) > field_len) 
                kb[field_len - strlen(string)] = '\0';
            dprint(2, (debugfile, "Undelete: %d %d\n", field_len - strlen(string),
                       strlen(kb)));
            if(string[field_pos] == '\0') {
                /*--- adding to the end of the string ----*/
                for(k = kb; *k; k++) {
                    if(!passwd)
                      Writechar(*k, 0);
                    string[field_pos] = *k;
                    field_pos++;
                }
                string[field_pos] = '\0';
            } else {
                goto bleep;
                /* To lazy to do insert in middle of string now */
            }
            fs_give((void **)&kb);
            break;
            

	    /*-------------------- Interrupt --------------------*/
	  case ctrl('C'): /* ^C */ 
	    if(use_fkeys || disallow_abort) goto bleep;
	    goto abort;
	  case PF3:
	    if(!use_fkeys || disallow_abort) goto bleep;
	  abort:
	    return_v = 1;
	    if(saved_original != NULL)
	      strcpy(string, saved_original);
	    break;
	    

          case ctrl('R'):
            /*----------- Repaint current line --------------*/
            PutLine0(real_y_base, x_base, prompt);
	    if(!passwd) {
	        MoveCursor(real_y_base, x_base + prompt_len);
	        for(s2 = string; *s2 != '\0'; s2++)
	          Writechar(*s2, 0);
                for(j = x_base + prompt_len + s2 - string; j < cols; j++)
                  Writechar(' ', 0);
		MoveCursor(real_y_base, x_base + prompt_len + field_pos);
	    }
	    break;

          case ctrl('A'):
            /*-------------------- Start of line -------------*/
            field_pos = 0;
            MoveCursor(real_y_base, x_base + prompt_len);
            break;


          case ctrl('E'):
            /*-------------------- End of line ---------------*/
            while(string[field_pos]) {
                field_pos++;
            }
            MoveCursor(real_y_base, x_base + prompt_len + field_pos);
            break;


          case NO_OP_COMMAND:
          case NO_OP_IDLE:
            new_mail(&cursor_moved, 0, 2); /* Keep mail stream alive */
            if(!cursor_moved) /* In case checkpoint happened */
              break;
            /* Else fall into redraw */

	    
	    /*-------------------- Redraw --------------------*/
	  case ctrl('L'):
            real_y_base= y_base > 0 ? y_base :
              y_base + ps_global->ttyo->screen_rows;
            EndInverse();
            ClearScreen();
            redraw_titlebar();
            if(ps_global->redrawer != NULL)
              (*ps_global->redrawer)();
            redraw_keymenu();
            StartInverse();
            goto rs;
	    

	    /*-------------------- Help --------------------*/
	  case ctrl('G') : 
	  case PF1:
	    return_v = 3;
	    break;

	  case  '\t':
	    goto bleep;

            /*---------------- re size ----------------*/
          case KEY_RESIZE:
          rs:
            real_y_base= y_base > 0 ? y_base :
              y_base + ps_global->ttyo->screen_rows;
            EndInverse();
            ClearScreen();
            redraw_titlebar();
            if(ps_global->redrawer != (void (*)())NULL)
              (*ps_global->redrawer)();
            redraw_keymenu();

            StartInverse();
            
            PutLine0(real_y_base, x_base, prompt);
            cols     =  ps_global->ttyo->screen_cols;
            too_thin = 0;
            if(cols < x_base + prompt_len + 4) {
                PutLine0(real_y_base, 0, "\007Screen's too thin. Ouch!");
                too_thin = 1;
            } else {
                if(!passwd) {
                    for(j = x_base + prompt_len,s2 = string;
                        *s2 != '\0' && j < cols; s2++,j++)
                      Writechar(*s2, 0);
                    *s2 = '\0';
                    if(field_pos + x_base + prompt_len > cols)
                      field_pos = cols - x_base - prompt_len;
                    while(j < cols) {
                        Writechar(' ', 0);
                        j++;
                    }
                    MoveCursor(real_y_base, x_base + prompt_len + field_pos);
                }
            }
            dprint(9, (debugfile,
                    "optionally_enter  RESIZE new_cols:%d  too_thin: %d\n",
                       cols, too_thin));
            fflush(stdout);
            break;

          case ctrl('Z'):
            if(!have_job_control() || passwd)
              goto bleep;
            if(ps_global->can_suspend) {
                EndInverse();
                Writechar('\n', 0);
                Writechar('\n', 0);
                do_suspend(ps_global);
                return_v = 4;
            } else {
                goto bleep;
            }

  
          default:
	    if(!isprint(ch)) {
       bleep:
		putc('\007', stdout);
		continue;
	    }

	    /*--- Insert a character -----*/
	    if(field_pos >= field_len)
	      goto bleep;

	    if(string[field_pos] == '\0'){
                /*---- extending the length of the string ---*/
	        string[field_pos] = ch;
		string[field_pos+1] = '\0';
		if(!passwd)
	          Writechar(ch, 0);
    	        field_pos++;
	    } else {
		if(string[field_len - 1] != '\0')
		  goto bleep;
		for(s2 = &string[field_pos]; *s2 != '\0'; s2++);/* to end of string */
		for(; s2 != &string[field_pos]; s2--) {
		    *s2 = *(s2-1);
		}
		string[field_pos] = ch;
		if(!passwd) {
		    if(InsertChar(ch) < 0){
			for(s2 = &string[field_pos]; *s2 != '\0'; s2++)
			  Writechar(*s2, 0);
		    }
		}
		field_pos++;
		if(!passwd)
	          MoveCursor(real_y_base, x_base + prompt_len + field_pos);

	    }
		    
	}   /*---- End of switch on char ----*/
    }
    if(append_current) 
	fs_give((void *)&saved_original); 

    removing_trailing_white_space(string);
    EndInverse();
    MoveCursor(real_y_base, x_base); /* Move the cursor to show we're done here */
    fflush(stdout);             
    return(return_v);
}




/*----------------------------------------------------------------------
    Check to see if the given command is reasonably valid
  
  Args:  ch -- the character to check

 Result:  A valid command is returned, or a well know bad command is returned.
 
 ---*/
validatekeys(ch)
     int  ch;
{
    if(ps_global->use_fkeys) {
	if(ch >= 'a' && ch <= 'z')
	  return(KEY_JUNK);
    } else {
	if(ch >= PF1 && ch <= PF12)
	  return(KEY_JUNK);
    }
    return(ch);
}
