/**********************************************************************/
/*                                                                    */
/*	CRISP - Programmable editor                                   */
/*	===========================                                   */
/*                                                                    */
/*  File:          getkey.c                                           */
/*  Author:        P. D. Fox                                          */
/*  Created:       10 Jun 1991                     		      */
/*                                                                    */
/*  Copyright (c) 1990, 1991 Paul Fox                                 */
/*                All Rights Reserved.                                */
/*                                                                    */
/*                                                                    */
/*--------------------------------------------------------------------*/
/*  Description:  Low level routines to read keystrokes               */
/*                                                                    */
/*   The  routines  in this file hide the complexity of dealing with  */
/*   the  different  operating systems and windowing systems and how  */
/*   to read keystrokes together with a timeout.		      */
/**********************************************************************/

/* static char sccs_id[] = "%Z% %M% %R%.%L%";*/

# include	"list.h"
# include	"clk.h"
# include	"tty.h"
# include	<errno.h>

# if	CR_DOS
# define INCL_BASE
# define INCL_NOPM
# include "os2.h" 
# endif

int select();
time_t	time();
# if defined(HAVE_MOUSE)
int	mouse_poll PROTO((fd_set *));
# endif
int	wait_state = 0;
extern long interval;
extern	int	display_ctrl;
time_t	next_idle_time;
time_t	time_last_minute;
static time_t time_now;

time_t	time_last_poll;

/**********************************************************************/
/*   This  function  is called by getkey() to actually read from the  */
/*   keyboard  if  getkey()  couldn't  satisfy  the request from the  */
/*   various  internal  buffers.  'state'  indicates  what  sort  of  */
/*   thing  we  are  waiting  for,  and  buf  receives  the  data we  */
/*   actually read. 						      */
/*                  						      */
/*   tmo  indicates  a  timeout.  If zero, then the calling function  */
/*   only  wants  to timeout on the designated event. Otherwise, tmo  */
/*   may give us a very short time span in which to timeout.	      */
/**********************************************************************/
int
read_char_timeout(state, buf, timeo)
int	state;
int	*buf;
long	timeo;
{	int	event = 0;
	long	tmo = 60 SECONDS;

	/**********************************************************
	 *   Max  time  to  wait  is  60 seconds, because after   *
	 *   that time clock needs updating.			  *
	 **********************************************************/
	if (state & WAIT_STATE_1) {
		event = WAIT_MINUTE;
		}
		
	/**********************************************************
	 *   If idle timer is less than 60 SECONDS long, then	  *
	 *   use this value.					  *
	 **********************************************************/
	if (state & WAIT_IDLE && (next_idle_time - time_now) < tmo) {
		event = WAIT_IDLE;
		tmo = (next_idle_time - time_now) * 1 SECOND;
		}
		
	/**********************************************************
	 *   If pty is active, then set timer down to one second. *
	 **********************************************************/
	if (state & WAIT_PTY && tmo > 1 SECOND) {
		event = WAIT_PTY;
		tmo = 1 SECOND;
		}

# if defined(HAVE_MOUSE) && !defined(SELECT)
	if (mouse_active())
		tmo = 100 MILLISECONDS;
# endif
		
	/**********************************************************
	 *   If we want second character of a function key, make  *
	 *   timer less than a second.				  *
	 **********************************************************/
	if (state & WAIT_STATE_2) {
		event = WAIT_STATE_2;
		tmo = 500 MILLISECONDS;
		}
		
	/**********************************************************
	 *   If we want to grab character only if available, set  *
	 *   to zero -- used for typeahead only.		  *
	 **********************************************************/
	if (state & WAIT_STATE_0) {
		event = WAIT_STATE_0;
		tmo = 0;
		}

	/***********************************************/
	/*   If  timeo  is  shorter  than  the  timer  */
	/*   value   we  have  here,  then  use  that  */
	/*   instead.				       */
	/***********************************************/
	if (timeo && timeo < tmo) {
		tmo = timeo;
		event = WAIT_TIMEOUT;
		}


# if defined(SELECT)
	{
	static struct timeval timeout = { 0 };
	int	nfds;
	extern fd_set sel_bits;
	fd_set	readbits;
	
	readbits = sel_bits;
	
	timeout.tv_sec = tmo / 1000;
	timeout.tv_usec = (tmo % 1000) * 1000;
	
	if (scrfn.scr_select == NULL)
		scrfn.scr_select = select;
	nfds = (*scrfn.scr_select)(32, &readbits, (fd_set *) NULL, (fd_set *) NULL, &timeout);
	if (nfds < 0) {
# if defined(SYSV)
		if (errno != EINTR)
			return sys_getchar(TTY_FD, buf, tmo) == 1 
				? 0 : event;
# endif
		return event;
		}
# if defined(HAVE_MOUSE)
	mouse_poll(&readbits);
# endif

	/***********************************************/
	/*   If  we  get a character on the keyboard,  */
	/*   then return this for processing.	       */
	/***********************************************/
	if (FD_ISSET(TTY_FD, &readbits)) {
		if (scrfn.scr_read) {
			*buf = (*scrfn.scr_read)();
			}
		else {
			char	ch;
			read(TTY_FD, &ch, 1);
			buf[0] = ch & 0xff;
			}
		return 0;
		}
		
	/***********************************************/
	/*   If  we  have  nothing  on  the keyboard,  */
	/*   then  either  the  timer  expired, or we  */
	/*   got  something  on  a  pty.  If  we  got  */
	/*   something  on  pty  and  we  are waiting  */
	/*   for  subsequent  function key character,  */
	/*   then   ignore   the   timeout   on   the  */
	/*   function key for now.		       */
	/***********************************************/
	if (nfds != 0)
		return WAIT_PTY;
	return event;
	}
# endif

# if !defined(SELECT) && !CR_DOS && !CR_VMS
	{
	return sys_getchar(TTY_FD, buf, tmo) == 1 ? 0 : event;
	}
# endif

# if CR_DOS
	{int	r;
	r = main_thread_read_kbd(tmo);	
	if (r == -1)
		return event;
	*buf = r;
	return 0;
	}
# endif

# if CR_VMS
	sys_timeout(state & WAIT_STATE_2);
	return sys_getchar(TTY_FD, buf, (long) 0L);
# endif
}

/**********************************************************************/
/*   Read  a  key  from  the  keyboard, with a timeout. If tmo == 0,  */
/*   then  wait  forever  for  a  key. If tmo == -1, then poll, i.e.  */
/*   dont   wait  for  a  key  stroke.  Otherwise  tmo  is  time  in  */
/*   milliseconds  to  wait  for  a  keystroke.  We also do a lot of  */
/*   things  inside  here  like  check  to  see if we can update the  */
/*   clock  on  the  status  line,  or  poll the process buffers and  */
/*   update   their   windows.  Also  we  handle  any  pushed-backed  */
/*   characters and other keystrokes buffered up.		      */
/**********************************************************************/
KEY
getkey(tmo)
long	tmo;
{
	int	ch16, ret;
	extern long time_last_key_pressed;
	KEY	key;
	static unsigned short buf[32];	/* Buffer for current key being read in */
	static unsigned short *bufptr = NULL;
	unsigned short	*cp = buf;
	extern int trigger_level;
	long	time_when_timeout = 0;
	int	state;
	int	event;
	int	multikey;
	int	flag16;
		
	/***********************************************/
	/*   If  user  wants  to poll, then check for  */
	/*   typeahead.   If  there  isn't  any  then  */
	/*   return  straight  away.  If  we  do have  */
	/*   type   ahead,   the  remainder  of  this  */
	/*   function will handle that case.	       */
	/***********************************************/
	if (tmo == -1L) {
		if (typeahead() == 0)
			return -1;
		}

	/***********************************************/
	/*   State  is  set  to  the  bits indicating  */
	/*   the  major  event(s) we are waiting for.  */
	/*   The   WAIT_KEYS   bits  are  masked  off  */
	/*   because   they   indicate  that  we  are  */
	/*   waiting for some part of a key-press.     */
	/***********************************************/
	state = (wait_state & ~WAIT_KEYS) | WAIT_STATE_1;
	
	/***********************************************/
	/*   If  idle  timer running, then one of the  */
	/*   things  we  need  to  timeout  on is the  */
	/*   timer expiring.			       */
	/***********************************************/
	if (interval)
		state |= WAIT_IDLE;
			
	while (1) {
		extern int child_sig;

		/***********************************************/
		/*   If  SIGCLD  gone  off  then we should be  */
		/*   safe here to go and process it.	       */
		/***********************************************/
		if (child_sig)
			proc_wait(-1);
			
		/***********************************************/
		/*   If  theres  a character in the typeahead  */
		/*   buffer, use that.			       */
		/***********************************************/
		if (bufptr) {
			ch16 = (int) *bufptr++;
			if (*bufptr == NULL)
				bufptr = NULL;
			}
		else if ((ch16 = get_typeahead(&flag16)) >= 0) {
			if (flag16)
				return ch16;
			}
		else {
			time_now = time((time_t *) NULL);
			/***********************************************/
			/*   Compute  absolute  time when the timeout  */
			/*   will expire.			       */
			/***********************************************/
			if (time_when_timeout == 0 && tmo)
				time_when_timeout = time_now + (tmo / 1000) + 1;
			if (next_idle_time == 0)
				next_idle_time = time((time_t *) NULL) + interval;
			if (time_now >= next_idle_time && interval) {
				if (trigger_level == 0)
					reg_idle();
				next_idle_time = time_now + interval;
				/***********************************************/
				/*   Calling    reg_idle()   causes   us   to  */
				/*   refresh   screen  which  in  turn  calls  */
				/*   typeahead()  for  keyboard  input. If it  */
				/*   gets  something  in  typeahead() we must  */
				/*   process the characters in sequence.       */
				/***********************************************/
				continue;
				}
# if !defined(SELECT)
			/***********************************************/
			/*   If  we  don't  have  select then we have  */
			/*   to poll the pty's once a second.	       */
			/***********************************************/
			if (time_now >= time_last_poll+1) {
				time_last_poll = time_now;
				p_poll();
				continue;
				}
# endif
			/***********************************************/
			/*   Call    the    REG_KEYBOARD   registered  */
			/*   macro.  If  we  actually called a macro,  */
			/*   then  we  need  to  check  the push back  */
			/*   buffers, etc.			       */
			/***********************************************/
			ret = -1;
			if (trigger_level == 0 && (state & WAIT_STATE_1) &&
			   (ret = trigger(REG_KEYBOARD)) > 0)
				continue;
			if (ret == 0) {
				if ((ch16 = get_typeahead(&flag16)) >= 0) {
					if (flag16)
						return ch16;
					goto handle_ch;
					}
				}
			event = read_char_timeout(state, &ch16, tmo);
			/***********************************************/
			/*   If  users  timeout  expired, then return  */
			/*   that.				       */
			/***********************************************/
			if (event & WAIT_TIMEOUT) {
				return -1;
				}

			if (event & (WAIT_PTY | WAIT_MINUTE | WAIT_IDLE)) {
				if (event & WAIT_PTY)
					p_poll();
				time_now = time((time_t *) NULL);
				if (time_now >= time_last_minute + 60) {
					time_last_minute = time_now;
					line_col(FALSE);
					set_cursor();
					}
				/***********************************************/
				/*   If  timeout  specified  and  we've  gone  */
				/*   past  the  time,  then  return  a no key  */
				/*   indication.   This   handles   the  case  */
				/*   where  the  timeout  is  more  than  the  */
				/*   interval or clock update time.	       */
				/***********************************************/
				if (time_when_timeout && time_now >= time_when_timeout)
					return -1;
				continue;
				}
			if (event) {
				/***********************************************/
				/*   If  we  got  a  character  and  we timed  */
				/*   out,   it   may  have  been  due  to  an  */
				/*   ambiguous  sequence,  eg  ABC & ABCD. We  */
				/*   may  have  got  ABC. So now check to see  */
				/*   if it corresponds to a key sequence.      */
				/***********************************************/
				if (state & WAIT_STATE_2 &&
				   (key = check_key(buf, &multikey, TRUE)) > 0) {
					store_char(key);
					return key;
					}
				break;
				}
			}
handle_ch:			
	 /*	printf("ch16: %02x ", ch16);	*/
		*cp++ = (unsigned short) ch16;
		*cp = NULL;
		time_last_key_pressed = time((long *) 0);
		next_idle_time = 0;
		/***********************************************/
		/*   If  we  got  a function key of some sort  */
		/*   we can stop reading from the keyboard.    */
		/***********************************************/
		if (ch16 & ~0xff/* || display_ctrl & DC_WINDOW*/)
			break;
		/***********************************************/
		/*   Check  for  a  completed function key or  */
		/*   maybe part of a multikey sequence.	       */
		/***********************************************/
		if ((key = check_key(buf, &multikey, state & WAIT_STATE_2)) == 0)
			break;
		if (key > 0) {
			store_char(key);
			return key;
			}
		/***********************************************/
		/*   If  it  doesn't  look  like a multi-key,  */
		/*   shorten time for next character.	       */
		/***********************************************/
		if (!multikey)
			state = (state & ~WAIT_KEYS) | WAIT_STATE_2;
		}

	store_char (buf[0]);
	if (buf[1]) {
		bufptr = &buf[1];
		return buf[0];
		}
	return buf[0];
}
/**********************************************************************/
/*   Function  called  when  we  return from a shell escape to clear  */
/*   the  idle  timer  to  avoid  having an autosave or screen blank  */
/*   happen immediately we return from shell mode.		      */
/**********************************************************************/
void
clear_timers()
{
	next_idle_time = 0;
}
/**********************************************************************/
/*   Function  to  read a raw keystroke from the keyboard. Called by  */
/*   read_char() and echo line handler.				      */
/**********************************************************************/
int
get_rawkey(tmo)
long	tmo;
{	int	flag16;
	int	ret;
	int	ch = 0;
	
	/***********************************************/
	/*   Check  for  typeahead  and  other  input  */
	/*   sources before going to the keyboard.     */
	/***********************************************/
	if ((ret = get_typeahead(&flag16)) >= 0)
		return ret;
	while (1) {
		/***********************************************/
		/*   Now  go  for  the keyboard. Under X11 we  */
		/*   may   return   with  no  character,  but  */
		/*   something in the push back buffer.	       */
		/***********************************************/
		if (read_char_timeout(0, &ch, tmo) == 0)
			return ch;
		if ((ret = get_typeahead(&flag16)) >= 0)
			return ret;
		/***********************************************/
		/*   We  didnt  get  anything.  If user wants  */
		/*   to  timeout  then  assume  we  must have  */
		/*   done.  (We  may not have, for example if  */
		/*   we  are  running  under X11, we may have  */
		/*   had  expose  events  which  caused us to  */
		/*   wake up instead).			       */
		/***********************************************/
		if (tmo)
			return 0;
		}
}
