text: #if !defined(lint) && !defined(DOS)
  static char rcsid[] = "$Id: getkey.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
  #endif
  
  /*
   * ========================================================================
   * Copyright 2006-2007 University of Washington
   * Copyright 2013-2017 Eduardo Chappa
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * ========================================================================
   */
  
  #include <system.h>
  #include <general.h>
  
  #include "../estruct.h"
  #include "../mode.h"
  #include "../pico.h"
  #include "../edef.h"
  #include "../efunc.h"
  #include "../keydefs.h"
  
  #include "tty.h"
  #include "getkey.h"
  #include "read.h"
  #include "mouse.h"
  
  #ifdef _WINDOWS
  #include "mswin.h"
  static int	MapMSKEYtoPK(int c);
  #endif /* _WINDOWS */
  
  
  /* internal declarations */
  static int timeo = 0;
  
  /* these next two are declared in pith/conf.h */
  int
  set_input_timeout(int t)
  {
      int oldtimeo = timeo;
  
      timeo = t;
      return(oldtimeo);
  }
  
  int
  get_input_timeout(void)
  {
      return(timeo);
  }
  
  
  #ifndef _WINDOWS
  
  
  /* internal prototypes */
  void		bail(void);
  int		ReadyForKey(int);
  
  
  
  void
  bail(void)
  {
      sleep(30);				/* see if os receives SIGHUP */
      kill(getpid(), SIGHUP);			/* eof or bad error */
  }
  
  
  #if	TYPEAH
  /* 
   * typahead - Check to see if any characters are already in the
   *	      keyboard buffer
   */
  int
  typahead(void)
  {
      int x;	/* holds # of pending chars */
  
      return((ioctl(0,FIONREAD,&x) < 0) ? 0 : x);
  }
  #endif /* TYPEAH */
  
  
  
  /*
   * ReadyForKey - return true if there's no timeout or we're told input
   *		 is available...
   */
  int
  ReadyForKey(int timeout)
  {
      switch(input_ready(timeout)){
        case READY_TO_READ:
  	return(1);
  	break;
  
        case NO_OP_COMMAND:
        case NO_OP_IDLE:
        case READ_INTR:
  	return(0);
  
        case BAIL_OUT:
        case PANIC_NOW:
  	emlwwrite(_("Problem reading from keyboard!"), NULL);
  	kill(getpid(), SIGHUP);	/* Bomb out (saving our work)! */
  	/* no return */
      }
  
      /* can't happen */
      return(0);
  }
  
  
  
  /*
   * GetKey - Read in a key.
   * Do the standard keyboard preprocessing. Convert the keys to the internal
   * character set.  Resolves escape sequences and returns no-op if global
   * timeout value exceeded.
   */
  UCS
  GetKey(void)
  {
      UCS ch, status, cc;
  
      if(!ReadyForKey(FUDGE-5))
        return(NODATA);
  
      switch(status = kbseq(simple_ttgetc, NULL, bail, input_cs, &ch)){
        case 0: 	/* regular character */
  	break;
  
        case KEY_DOUBLE_ESC:
  	/*
  	 * Special hack to get around comm devices eating control characters.
  	 */
  	if(!ReadyForKey(5))
  	  return(BADESC);		/* user typed ESC ESC, then stopped */
  	else
  	    switch(status = kbseq(simple_ttgetc, NULL, bail, input_cs, &ch)){
  	      case  KEY_UP		:
  	      case  KEY_DOWN		:
  	      case  KEY_RIGHT		:
  	      case  KEY_LEFT		:
  	      case  KEY_PGUP		:
  	      case  KEY_PGDN		:
  	      case  KEY_HOME		:
  	      case  KEY_END		:
  	      case  KEY_DEL		:
  	      case F1  :
  	      case F2  :
  	      case F3  :
  	      case F4  :
  	      case F5  :
  	      case F6  :
  	      case F7  :
  	      case F8  :
  	      case F9  :
  	      case F10 :
  	      case F11 :
  	      case F12 :
  		return(CTRL | status);
  		break;
  
  	      case 0: 	/* regular character */
  		break;
  
  	      default:				/* punt the whole thing	*/
  		(*term.t_beep)();
  		return(BADESC);
  		break;
  	    }
  
  	ch &= 0x7f;
  	if(isdigit((unsigned char)ch)){
  	    int n = 0, i = ch - '0';
  
  	    if(i < 0 || i > 2)
  	      return(BADESC);		/* bogus literal char value */
  
  	    while(n++ < 2){
  		if(!ReadyForKey(5)
  		   || (!isdigit((unsigned char) (ch =
  				   (*term.t_getchar)(NODATA, NULL, bail)))
  		       || (n == 1 && i == 2 && ch > '5')
  		       || (n == 2 && i == 25 && ch > '5'))){
  		    return(BADESC);
  		}
  
  		i = (i * 10) + (ch - '0');
  	    }
  
  	    ch = i;
  	}
  	else{
  	    if(islower((unsigned char)ch))	/* canonicalize if alpha */
  	      ch = toupper((unsigned char)ch);
  
  	    return((isalpha((unsigned char)ch) || ch == '@'
  		    || (ch >= '[' && ch <= '_'))
  		    ? (CTRL | ch) : ((ch == ' ') ? (CTRL | '@') : ch));
  	}
  
  	break;
  
  #ifdef MOUSE
        case KEY_XTERM_MOUSE:
  	{
  	    /*
  	     * Special hack to get mouse events from an xterm.
  	     * Get the details, then pass it past the keymenu event
  	     * handler, and then to the installed handler if there
  	     * is one...
  	     */
  	    static int    down = 0;
  	    int           x, y, button;
  	    unsigned long cmd;
  
  	    button = (*term.t_getchar)(NODATA, NULL, bail) & 0x03;
  
  	    x = (*term.t_getchar)(NODATA, NULL, bail) - '!';
  	    y = (*term.t_getchar)(NODATA, NULL, bail) - '!';
  
  	    if(button == 0){
  		down = 1;
  		if(checkmouse(&cmd, 1, x, y))
  		  return((UCS) cmd);
  	    }
  	    else if(down && button == 3){
  		down = 0;
  		if(checkmouse(&cmd, 0, x, y))
  		  return((UCS) cmd);
  	    }
  
  	    return(NODATA);
  	}
  
  	break;
  #endif /* MOUSE */
  
        case  KEY_UP		:
        case  KEY_DOWN		:
        case  KEY_RIGHT		:
        case  KEY_LEFT		:
        case  KEY_PGUP		:
        case  KEY_PGDN		:
        case  KEY_HOME		:
        case  KEY_END		:
        case  KEY_DEL		:
        case F1  :
        case F2  :
        case F3  :
        case F4  :
        case F5  :
        case F6  :
        case F7  :
        case F8  :
        case F9  :
        case F10 :
        case F11 :
        case F12 :
  	return(status);
  
        case  CTRL_KEY_UP		:
  	return(CTRL | KEY_UP);
        case  CTRL_KEY_DOWN	:
  	return(CTRL | KEY_DOWN);
        case  CTRL_KEY_RIGHT	:
  	return(CTRL | KEY_RIGHT);
        case  CTRL_KEY_LEFT	:
  	return(CTRL | KEY_LEFT);
  
        case KEY_SWALLOW_Z:
  	status = BADESC;
        case KEY_SWAL_UP:
        case KEY_SWAL_DOWN:
        case KEY_SWAL_LEFT:
        case KEY_SWAL_RIGHT:
  	do
  	  if(!ReadyForKey(2)){
  	      status = BADESC;
  	      break;
  	  }
  	while(!strchr("~qz", (*term.t_getchar)(NODATA, NULL, bail)));
  
  	return((status == BADESC)
  		? status
  		: status - (KEY_SWAL_UP - KEY_UP));
  	break;
  
        case KEY_KERMIT:
  	do{
  	    cc = ch;
  	    if(!ReadyForKey(2))
  	      return(BADESC);
  	    else
  	      ch = (*term.t_getchar)(NODATA, NULL, bail) & 0x7f;
  	}while(cc != '\033' && ch != '\\');
  
  	ch = NODATA;
  	break;
  
        case BADESC:
  	(*term.t_beep)();
  	return(status);
  
        default:				/* punt the whole thing	*/
  	(*term.t_beep)();
  	break;
      }
  
      if (ch >= 0x00 && ch <= 0x1F)       /* C0 control -> C-     */
        ch = CTRL | (ch+'@');
  
      return(ch);
  }
  
  
  /* 
   * kbseq - looks at an escape sequence coming from the keyboard and 
   *         compares it to a trie of known keyboard escape sequences, and
   *         returns the function bound to the escape sequence.
   * 
   *     Args: getcfunc     -- Function to get a single character from stdin,
   *                           called with the next two arguments to this
   *                           function as its arguments.
   *           recorder     -- If non-NULL, function used to record keystroke.
   *           bail_handler -- Function used to bail out on read error.
   *           c            -- Pointer to returned character.
   *
   *  Returns: BADESC
   *           The escaped function.
   *           0 if a regular char with char stuffed in location c.
   */
  UCS
  kbseq(int (*getcfunc)(int (*recorder)(int ), void (*bail_handler)(void )),
        int (*recorder)(int),
        void (*bail_handler)(void),
        void *data,
        UCS *ch)
  {
      unsigned char c;
      int      first = 1;
      KBESC_T *current;
  
      current = kbesc;
      if(current == NULL)				/* bag it */
        return(BADESC);
  
      while(1){
  	c = (*getcfunc)(recorder, bail_handler);
  
  	while(current->value != c){
  	    if(current->left == NULL){		/* NO MATCH */
  		if(first){
  		    unsigned long octets_so_far, remaining_octets;
  		    unsigned char *inputp;
  		    UCS ucs;
  		    unsigned char inputbuf[20];
  
  		    /*
  		     * Regular character.
  		     * Read enough bytes to make up a character and convert it to UCS-4.
  		     */
  		    memset(inputbuf, 0, sizeof(inputbuf));
  		    inputbuf[0] = c;
  		    octets_so_far = 1;
  		    for(;;){
  			remaining_octets = octets_so_far;
  			inputp = inputbuf;
  			ucs = mbtow(data, &inputp, &remaining_octets);
  			switch(ucs){
  			  case CCONV_BADCHAR:
  			    /*
  			     * Not really a BADESC but that ought to
  			     * be sufficient. We can add another type if
  			     * we need to.
  			     */
  			    return(BADESC);
  			  
  			  case CCONV_NEEDMORE:
  			    if(octets_so_far >= sizeof(inputbuf))
  			      return(BADESC);
  
  			    c = (*getcfunc)(recorder, bail_handler);
  			    inputbuf[octets_so_far++] = c;
  			    break;
  
  			  default:
  			    /* got a good UCS-4 character */
  			    *ch = ucs;
  			    return(0);
  			}
  		    }
  
  		    /* NOTREACHED */
  		    return(0);
  		}
  		else
  		  return(BADESC);
  	    }
  	    current = current->left;
  	}
  
  	if(current->down == NULL)		/* match!!!*/
  	  return(current->func);
  	else
  	  current = current->down;
  
  	first = 0;
      }
  }
  
  
  #define	newnode()	(KBESC_T *)malloc(sizeof(KBESC_T))
  
  /*
   * kpinsert - insert a keystroke escape sequence into the global search
   *	      structure.
   */
  void
  kpinsert(char *kstr, int kval, int termcap_wins)
  {
      register	char	*buf;
      register	KBESC_T *temp;
      register	KBESC_T *trail;
  
      if(kstr == NULL)
        return;
  
      /*
       * Don't allow escape sequences that don't start with ESC unless
       * termcap_wins.  This is to protect against mistakes in termcap files.
       */
      if(!termcap_wins && *kstr != '\033')
        return;
  
      temp = trail = kbesc;
      buf = kstr;
  
      for(;;){
  	if(temp == NULL){
  	    temp = newnode();
  	    temp->value = *buf;
  	    temp->func = 0;
  	    temp->left = NULL;
  	    temp->down = NULL;
  	    if(kbesc == NULL)
  	      kbesc = temp;
  	    else
  	      trail->down = temp;
  	}
  	else{				/* first entry */
  	    while((temp != NULL) && (temp->value != *buf)){
  		trail = temp;
  		temp = temp->left;
  	    }
  
  	    if(temp == NULL){   /* add new val */
  		temp = newnode();
  		temp->value = *buf;
  		temp->func = 0;
  		temp->left = NULL;
  		temp->down = NULL;
  		trail->left = temp;
  	    }
  	}
  
  	if(*(++buf) == '\0')
  	  break;
  	else{
  	    /*
  	     * Ignore attempt to overwrite shorter existing escape sequence.
  	     * That means that sequences with higher priority should be
  	     * set up first, so if we want termcap sequences to override
  	     * hardwired sequences, put the kpinsert calls for the
  	     * termcap sequences first.  (That's what you get if you define
  	     * TERMCAP_WINS.)
  	     */
  	    if(temp->func != 0)
  	      return;
  
  	    trail = temp;
  	    temp = temp->down;
  	}
      }
      
      /*
       * Ignore attempt to overwrite longer sequences we are a prefix
       * of (down != NULL) and exact same sequence (func != 0).
       */
      if(temp != NULL && temp->down == NULL && temp->func == 0)
        temp->func = kval;
  }
  
  
  
  /*
   * kbdestroy() - kills the key pad function key search tree
   *		 and frees all lines associated with it
   *
   *               Should be called with arg kbesc, the top of the tree.
   */
  void
  kbdestroy(KBESC_T *kb)
  {
      if(kb){
  	kbdestroy(kb->left);
  	kbdestroy(kb->down);
  	free((char *)kb);
  	kb = NULL;
      }
  }
  
  #else /* _WINDOWS */
  
  /*
   * Read in a key.
   * Do the standard keyboard preprocessing. Convert the keys to the internal
   * character set.  Resolves escape sequences and returns no-op if global
   * timeout value exceeded.
   */
  UCS
  GetKey(void)
  {
      UCS			ch = 0;
      long		timein;
      
  
      ch = NODATA;
      timein = time(0L);
  
      /*
       * Main character processing loop.
       */
      while(!mswin_charavail()) {
  
  #ifdef MOUSE
  	/* Check Mouse.  If we get a mouse event, convert to char
  	 * event and return that. */
  	if (checkmouse (&ch,0,0,0)) {
  	    curwp->w_flag |= WFHARD;
  	    return (ch);
  	}
  #endif /* MOUSE */
  
  
  	/* Check Timeout. */
  	if(time(0L) >= timein+(FUDGE-10)) 
  	    return(NODATA);
      }
  
      
      return (mswin_getc_fast());
  }
  
  #endif /* _WINDOWS */
  