/* -*-C-*- */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

#define InputStream PerlIO *

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

 Copyright (C) 1994,1995,1996,1997 Kenneth Albanowski. Unlimited
 distribution and/or modification is allowed as long as this copyright
 notice remains intact.

 Written by Kenneth Albanowski on Thu Oct  6 11:42:20 EDT 1994
 Contact at kjahds@kjahds.com or CIS:70705,126

 Maintained by Jonathan Stowe <jns@gellyfish.com>

 $Id: ReadKey.xs 219 2005-03-24 05:15:07Z mbr $

 Version 2.21, Sun Jul 28 12:57:56 BST 2002
    Fix to improve the chances of automated testing succeeding

 Version 2.20, Tue May 21 07:52:47 BST 2002
    Patch from Autrijus Tang fixing Win32 Breakage with bleadperl
    
 Version 2.19, Thu Mar 21 07:25:31 GMT 2002
    Added check for definedness of $_[0] in comparisons in ReadKey, ReadLine
    after reports of warnings.

 Version 2.18, Sun Feb 10 13:06:57 GMT 2002
    Altered prototyping style after reports of compile failures on
    Windows.

 Version 2.17, Fri Jan 25 06:58:47 GMT 2002
    The '_' macro for non-ANSI compatibility was removed in 5.7.2

 Version 2.16, Thu Nov 29 21:19:03 GMT 2001
    It appears that the genchars.pl bit of the patch didnt apply
    Applied the new ppport.h from Devel::PPPort

 Version 2.15, Sun Nov  4 15:02:37 GMT 2001 (jns)
    Applied the patch in 
    http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2001-01/msg01588.html
    for PerlIO compatibility.

 Version 2.14, Sun Mar 28 23:26:13 EST 1999
    ppport.h 1.007 fixed for 5.005_55.
 
 Version 2.13, Wed Mar 24 20:46:06 EST 1999
 	Adapted to ppport.h 1.006.

 Version 2.12, Wed Jan  7 10:33:11 EST 1998
 	Slightly modified test and error reporting for Win32.
 
 Version 2.11, Sun Dec 14 00:39:12 EST 1997
    First attempt at Win32 support.

 Version 2.10, skipped

 Version 2.09, Tue Oct  7 13:07:43 EDT 1997
    Grr. Added explicit detection of sys/poll.h and poll.h.

 Version 2.08, Mon Oct  6 16:07:44 EDT 1997
    Changed poll.h to sys/poll.h.

 Version 2.07, Sun Jan 26 19:11:56 EST 1997
    Added $VERSION to .pm.

 Version 2.06, Tue Nov 26 01:47:09 EST 1996
    Added PERLIO support and removed duplicate declaration in .pm.

 Version 2.05, Tue Mar 12 19:08:33 EST 1996
 	Changed poll support so it works. Cleaned up .pm a little.
 	
 Version 2.04, Tue Oct 10 05:35:48 EDT 1995
 	Whoops. Changed GetTermSize back so that GSIZE code won't be
 	compiled if GWINSZ is being used. Also took ts_xxx and ts_yyy
 	out of GSIZE.

 Version 2.03, Thu Sep 21 21:53:16 EDT 1995
	Fixed up debugging info in Readkey.pm, and changed TermSizeVIO
	to use _scrsize(). Hopefully this is GO for both Solaris and OS/2.

 Version 2.02, Mon Sep 18 22:17:57 EDT 1995
	Workaround for Solaris bug wasn't sufficient. Modularlized
	GetTermSize into perl code, and added support for the 
	`resize` executable. Hard coded path for Solaris machines.

 Version 2.01, Wed Sep 13 22:22:23 EDT 1995
	Change error reporting around in getscreensize so that if
 	an ioctl fails but getenv succeeds, no warning will be 
	printed. This is an attempt to work around a Solaris bug where
	TIOCGWINSZ fails in telnet sessions.
 
 Version 2.00, Mon Sep  4 06:37:24 EDT 1995
	Added timeouts to select/poll, added USE_STDIO_PTR support
	(required for recent perl revisions), and fixed up compilation
	under OS/2.

 Version 1.99, Fri Aug 11 20:18:11 EDT 1995
	Add file handles to ReadMode.

 Version 1.97, Mon Apr 10 21:41:52 EDT 1995
	Changed mode 5 to disable UC & delays. Added more ECHO flags.
        Tested termio[s] & sgtty.
	Added termoptions so test.pl can give more info.

 Version 1.96,
	Mucked with filehandle selection in ReadKey.pm.

 Version 1.95,
	Cleaning up for distribution.

 Version 1.94,
	Dealt with get/settermsize sillyness.

 Version 1.91, Sat Mar 11 23:47:04 EST 1995:
	Andy's patches, and a bit of termsize finesse.

 Version 1.9, Thu Mar  9 14:11:49 EST 1995:
	Modifying for portability. Prototypes, singed chars, etc.

 Version 1.8, Mon Jan  9 23:18:14 EST 1995:
	Added use of Configure.pm. No changes to ReadKey.

 Version 1.7, Fri Dec 16 13:48:14 EST 1994:
   Getting closer to release. Added new readmode 2. Had to bump up other
   modes, unfortunately. This is the _last_ time I do that. If I have to
   bump up the modes again, I'm switching to a different scheme.

 Version 1.6, Wed Dec 14 17:36:59 EST 1994:
	Completly reorganized the control-char support (twice!) so that
	it is automatically ported by the preproccessor for termio[s], or
	by an included script for sgtty. Logical defaults for sgtty are included
	too. Added Sun TermSize support. (Hope I got it right.)

 Version 1.5, Fri Dec  9 16:07:49 EST 1994:
	Added SetTermSize, GetSpeeds, Get/SetControlChars, PerlIO support.

 Version 1.01, Thu Oct 20 23:32:39 EDT 1994:
	Added Select_fd_set_t casts to select() call.

 Version 1.0: First "real" release. Everything seems cool.


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

/***

 Things to do:

	Make sure the GetSpeed function is doing it's best to separate ispeed
	from ospeed.
	
	Separate the stty stuff from ReadMode, so that stty -a can be easily
	used, among other things.

***/



/* Using these defines, you can elide anything you know 
   won't work properly */

/* Methods of doing non-blocking reads */

/*#define DONT_USE_SELECT*/
/*#define DONT_USE_POLL*/
/*#define DONT_USE_NODELAY*/


/* Terminal I/O packages */

/*#define DONT_USE_TERMIOS*/
/*#define DONT_USE_TERMIO*/
/*#define DONT_USE_SGTTY*/

/* IOCTLs that can be used for GetTerminalSize */

/*#define DONT_USE_GWINSZ*/
/*#define DONT_USE_GSIZE*/

/* IOCTLs that can be used for SetTerminalSize */

/*#define DONT_USE_SWINSZ*/
/*#define DONT_USE_SSIZE*/


/* This bit is for OS/2 */

#ifdef OS2
#       define I_FCNTL
#       define HAS_FCNTL

#       define O_NODELAY O_NDELAY

#       define DONT_USE_SELECT
#       define DONT_USE_POLL

#       define DONT_USE_TERMIOS
#       define DONT_USE_SGTTY
#       define I_TERMIO
#       define CC_TERMIO

/* This flag should be off in the lflags when we enable termio mode */
#      define TRK_IDEFAULT     IDEFAULT

#       define INCL_SUB
#       define INCL_DOS

#       include <os2.h>
#	include <stdlib.h>

#       define VIOMODE
#else
        /* no os2 */
#endif

/* This bit is for Windows 95/NT */

#ifdef WIN32
#		define DONT_USE_TERMIO
#		define DONT_USE_TERMIOS
#		define DONT_USE_SGTTY
#		define DONT_USE_POLL
#		define DONT_USE_SELECT
#		define DONT_USE_NODELAY
#		define USE_WIN32
#		include <io.h>
#		if defined(_get_osfhandle) && (PERL_VERSION == 4) && (PERL_SUBVERSION < 5)
#			undef _get_osfhandle
#			if defined(_MSC_VER)
#				define level _cnt
#			endif
#		endif
#endif

/* This bit for NeXT */

#ifdef _NEXT_SOURCE
  /* fcntl with O_NDELAY (FNDELAY, actually) is broken on NeXT */
# define DONT_USE_NODELAY
#endif

#if !defined(DONT_USE_NODELAY)
# ifdef HAS_FCNTL
#  define Have_nodelay
#  ifdef I_FCNTL
#   include <fcntl.h>
#  endif
#  ifdef I_SYS_FILE
#   include <sys/file.h>
#  endif
#  ifdef I_UNISTD
#   include <unistd.h>
#  endif

/* If any other headers are needed for fcntl or O_NODELAY, they need to get
   included right here */

#  if !defined(O_NODELAY)
#   if !defined(FNDELAY)
#    undef Have_nodelay
#   else
#    define O_NODELAY FNDELAY
#   endif
#  else
#   define O_NODELAY O_NDELAY
#  endif
# endif
#endif

#if !defined(DONT_USE_SELECT)
# ifdef HAS_SELECT
#  ifdef I_SYS_SELECT
#   include <sys/select.h>
#  endif

/* If any other headers are likely to be needed for select, they need to be
   included right here */

#  define Have_select
# endif
#endif

#if !defined(DONT_USE_POLL)
# ifdef HAS_POLL
#  ifdef HAVE_POLL_H
#   include <poll.h>
#   define Have_poll
#  endif
#  ifdef HAVE_SYS_POLL_H
#   include <sys/poll.h>
#   define Have_poll
#  endif
# endif
#endif

#ifdef DONT_USE_TERMIOS
# ifdef I_TERMIOS
#  undef I_TERMIOS
# endif
#endif
#ifdef DONT_USE_TERMIO
# ifdef I_TERMIO
#  undef I_TERMIO
# endif
#endif
#ifdef DONT_USE_SGTTY
# ifdef I_SGTTY
#  undef I_SGTTY
# endif
#endif

/* Pre-POSIX SVR3 systems sometimes define struct winsize in
   sys/ptem.h.  However, sys/ptem.h needs a type mblk_t (?) which
   is defined in <sys/stream.h>.
   No, Configure (dist3.051) doesn't know how to check for this.
*/
#ifdef I_SYS_STREAM
# include <sys/stream.h>
#endif
#ifdef I_SYS_PTEM
# include <sys/ptem.h>
#endif

#ifdef I_TERMIOS
# include <termios.h>
#else
# ifdef I_TERMIO
#  include <termio.h>
# else
#  ifdef I_SGTTY
#   include <sgtty.h>
#  endif
# endif
#endif

#ifdef I_TERMIOS
# define CC_TERMIOS
#else
# ifdef I_TERMIO
#  define CC_TERMIO
# else
#  ifdef I_SGTTY
#   define CC_SGTTY
#  endif
# endif
#endif

#ifndef TRK_IDEFAULT
/* This flag should be off in the lflags when we enable termio mode */
#      define TRK_IDEFAULT     0
#endif

/* Fix up the disappearance of the '_' macro in Perl 5.7.2 */

#ifndef _
#  ifdef CAN_PROTOTYPE
#    define _(args) args
#  else
#    define _(args) ()
#  endif
#endif

#define DisableFlush (1) /* Should flushing mode changes be enabled?
		            I think not for now. */


#define STDIN PerlIO_stdin()

#include "cchars.h"


int GetTermSizeVIO _((PerlIO * file,
	int * retwidth, int * retheight, 
	int * xpix, int * ypix));

int GetTermSizeGWINSZ _((PerlIO * file,
	int * retwidth, int * retheight, 
	int * xpix, int * ypix));

int GetTermSizeGSIZE _((PerlIO * file,
	int * retwidth, int * retheight, 
	int * xpix, int * ypix));

int GetTermSizeWin32 _((PerlIO * file,
	int * retwidth, int * retheight,
	int * xpix, int * ypix));

int SetTerminalSize _((PerlIO * file,
	int width, int height, 
	int xpix, int ypix));

void ReadMode _((PerlIO * file,int mode));

int pollfile _((PerlIO * file, double delay));

int setnodelay _((PerlIO * file, int mode));

int selectfile _((PerlIO * file, double delay));

int Win32PeekChar _((PerlIO * file, double delay, char * key));

int getspeed _((PerlIO * file, I32 *in, I32 * out ));


#ifdef VIOMODE
int GetTermSizeVIO(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	/*int handle=PerlIO_fileno(file);

        static VIOMODEINFO *modeinfo = NULL;

        if (modeinfo == NULL)
                modeinfo = (VIOMODEINFO *)malloc(sizeof(VIOMODEINFO));

        VioGetMode(modeinfo,0);
        *retheight = modeinfo->row ?: 25;
        *retwidth = modeinfo->col ?: 80;*/
	int buf[2];

	_scrsize(&buf[0]);

	*retwidth = buf[0]; *retheight = buf[1];

        *xpix = *ypix = 0;
        return 0;
}
#else
int GetTermSizeVIO(PerlIO *file,int * retwidth,int *retheight, int *xpix,int *ypix)
{
	croak("TermSizeVIO is not implemented on this architecture");
        return 0;
}
#endif


#if defined(TIOCGWINSZ) && !defined(DONT_USE_GWINSZ)
int GetTermSizeGWINSZ(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	int handle=PerlIO_fileno(file);
	struct winsize w;

	if (ioctl (handle, TIOCGWINSZ, &w) == 0) {
		*retwidth=w.ws_col; *retheight=w.ws_row; 
		*xpix=w.ws_xpixel; *ypix=w.ws_ypixel; return 0;
	}
	else {
		return -1; /* failure */
	}

}
#else
int GetTermSizeGWINSZ(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	croak("TermSizeGWINSZ is not implemented on this architecture");
        return 0;
}
#endif

#if (!defined(TIOCGWINSZ) || defined(DONT_USE_GWINSZ)) && (defined(TIOCGSIZE) && !defined(DONT_USE_GSIZE))
int GetTermSizeGSIZE(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	int handle=PerlIO_fileno(file);

	struct ttysize w;

	if (ioctl (handle, TIOCGSIZE, &w) == 0) {
		*retwidth=w.ts_cols; *retheight=w.ts_lines; 
		*xpix=0/*w.ts_xxx*/; *ypix=0/*w.ts_yyy*/; return 0;
	}
	else {
		return -1; /* failure */
	}
}
#else
int GetTermSizeGSIZE(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	croak("TermSizeGSIZE is not implemented on this architecture");
        return 0;
}
#endif

#ifdef USE_WIN32
int GetTermSizeWin32(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	int handle=PerlIO_fileno(file);
	HANDLE whnd = (HANDLE)_get_osfhandle(handle);
	CONSOLE_SCREEN_BUFFER_INFO info;

	if (GetConsoleScreenBufferInfo(whnd, &info)) {
		/* Logic: return maximum possible screen width, but return
		   only currently selected height */
		if (retwidth)
			*retwidth = info.dwMaximumWindowSize.X; 
			/*info.srWindow.Right - info.srWindow.Left;*/
		if (retheight)
			*retheight = info.srWindow.Bottom - info.srWindow.Top;
		if (xpix)
			*xpix = 0;
		if (ypix)
			*ypix = 0;
		return 0;
	} else
		return -1;
}
#else
int GetTermSizeWin32(PerlIO *file,int *retwidth,int *retheight,int *xpix,int *ypix)
{
	croak("TermSizeWin32 is not implemented on this architecture");
        return 0;
}
#endif /* USE_WIN32 */


int termsizeoptions() {
	return	0
#ifdef VIOMODE
		| 1
#endif
#if defined(TIOCGWINSZ) && !defined(DONT_USE_GWINSZ)
		| 2
#endif
#if defined(TIOCGSIZE) && !defined(DONT_USE_GSIZE)
		| 4
#endif
#if defined(USE_WIN32)
		| 8
#endif
		;
}


int SetTerminalSize(PerlIO *file,int width,int height,int xpix,int ypix)
{
	char buffer[10];
	int handle=PerlIO_fileno(file);

#ifdef VIOMODE
        return -1;
#else

#if defined(TIOCSWINSZ) && !defined(DONT_USE_SWINSZ)
	struct winsize w;

	w.ws_col=width;
	w.ws_row=height;
	w.ws_xpixel=xpix;
	w.ws_ypixel=ypix;
	if (ioctl (handle, TIOCSWINSZ, &w) == 0) {
		sprintf(buffer,"%d",width); /* Be polite to our children */
		my_setenv("COLUMNS",buffer);
		sprintf(buffer,"%d",height);
		my_setenv("LINES",buffer);
		return 0;
	}
	else {
		croak("TIOCSWINSZ ioctl call to set terminal size failed: %s",Strerror(errno));
		return -1;
	}
#else
# if defined(TIOCSSIZE) && !defined(DONT_USE_SSIZE)
	struct ttysize w;

	w.ts_lines=height;
	w.ts_cols=width;
	w.ts_xxx=xpix;
	w.ts_yyy=ypix;
	if (ioctl (handle, TIOCSSIZE, &w) == 0) {
		sprintf(buffer,"%d",width);
		my_setenv("COLUMNS",buffer);
		sprintf(buffer,"%d",height);
		my_setenv("LINES",buffer);
		return 0;
	}
	else {
		croak("TIOCSSIZE ioctl call to set terminal size failed: %s",Strerror(errno));
		return -1;
	}
# else
	/*sprintf(buffer,"%d",width)   * Should we could do this and then *
	my_setenv("COLUMNS",buffer)    * said we succeeded?               *
	sprintf(buffer,"%d",height);
	my_setenv("LINES",buffer)*/

	return -1; /* Fail */
# endif
#endif
#endif

}

I32 terminal_speeds[] = {
#ifdef B50
	50, B50,
#endif
#ifdef B75
	75, B75,
#endif
#ifdef B110
	110, B110,
#endif
#ifdef B134
	134, B134,
#endif
#ifdef B150
	150, B150,
#endif
#ifdef B200
	200, B200,
#endif
#ifdef B300
	300, B300,
#endif
#ifdef B600
	600, B600,
#endif
#ifdef B1200
	1200, B1200,
#endif
#ifdef B1800
	1800, B1800,
#endif
#ifdef B2400
	2400, B2400,
#endif
#ifdef B4800
	4800, B4800,
#endif
#ifdef B9600
	9600, B9600,
#endif
#ifdef B19200
	19200, B19200,
#endif
#ifdef B38400
	38400, B38400,
#endif
#ifdef B57600
	57600, B57600,
#endif
#ifdef B115200
	115200, B115200,
#endif
#ifdef EXTA
	19200, EXTA,
#endif
#ifdef EXTB
	38400, EXTB,
#endif
#ifdef B0
	0,  B0,
#endif
	-1,-1
};

int getspeed(PerlIO *file,I32 *in, I32 *out)
{
	int handle=PerlIO_fileno(file);
	int i;
#       ifdef I_TERMIOS
	/* Posixy stuff */

	struct termios buf;
	tcgetattr(handle,&buf);

	*in = *out = -1;
	*in = cfgetispeed(&buf);
	*out = cfgetospeed(&buf);
	for(i=0;terminal_speeds[i]!=-1;i+=2) {
		if(*in == terminal_speeds[i+1])
			{ *in = terminal_speeds[i]; break; }
	}
	for(i=0;terminal_speeds[i]!=-1;i+=2) {
		if(*out == terminal_speeds[i+1])
			{ *out = terminal_speeds[i]; break; }
	}
	return 0;	 	

#       else
#        ifdef I_TERMIO
	 /* SysV stuff */
	 struct termio buf;

	 ioctl(handle,TCGETA,&buf);

	*in=*out=-1;
	for(i=0;terminal_speeds[i]!=-1;i+=2) {
		if((buf.c_cflag & CBAUD) == terminal_speeds[i+1])
			{ *in=*out=terminal_speeds[i]; break; }
	}
	return 0;	 	

#        else
#         ifdef I_SGTTY
	  /* BSD stuff */
	  struct sgttyb buf;

	  ioctl(handle,TIOCGETP,&buf);

	*in=*out=-1;

	for(i=0;terminal_speeds[i]!=-1;i+=2) 
		if(buf.sg_ospeed == terminal_speeds[i+1])
			{ *out = terminal_speeds[i]; break; }

	for(i=0;terminal_speeds[i]!=-1;i+=2) 
		if(buf.sg_ispeed == terminal_speeds[i+1])
			{ *in = terminal_speeds[i]; break; }

	return 0;	 	


#         else

	   /* No termio, termios or sgtty. I suppose we can try stty,
	      but it would be nice if you could get a better OS */

	return -1;

#         endif
#        endif
#       endif
}

#ifdef WIN32
struct tbuffer { DWORD Mode; };
#else
#ifdef I_TERMIOS
#define USE_TERMIOS
#define tbuffer termios
#else
#ifdef I_TERMIO
#define USE_TERMIO
#define tbuffer termio
#else
#ifdef I_SGTTY
#define USE_SGTTY
struct tbuffer {
	  struct sgttyb buf;
#if defined(TIOCGETC)
	  struct tchars tchar;
#endif
#if defined(TIOCGLTC)
	  struct ltchars ltchar;
#endif
#if defined(TIOCLGET)
	  int local;
#endif
};
#else
#define USE_STTY
struct tbuffer {
	int dummy;
};
#endif
#endif
#endif
#endif

HV * filehash; /* Used to store the original terminal settings for each handle*/
HV * modehash; /* Used to record the current terminal "mode" for each handle*/

void ReadMode(PerlIO *file,int mode)
{
	dTHR;
	int handle;
	int firsttime;
	int oldmode;
	struct tbuffer work;
	struct tbuffer	savebuf;

	
	handle=PerlIO_fileno(file);
	
	firsttime=!hv_exists(filehash, (char*)&handle, sizeof(int));


#	ifdef WIN32

	if (!GetConsoleMode((HANDLE)_get_osfhandle(handle), &work.Mode))
	    croak("GetConsoleMode failed, LastError=|%d|",GetLastError());

#	endif /* WIN32 */

#       ifdef USE_TERMIOS
	/* Posixy stuff */
	
	tcgetattr(handle,&work);



#endif
#ifdef USE_TERMIO
	 /* SysV stuff */

	 ioctl(handle,TCGETA,&work);


#endif
#ifdef USE_SGTTY
	  /* BSD stuff */

	  ioctl(handle,TIOCGETP,&work.buf);
# 	  if defined(TIOCGETC)
	   ioctl(handle,TIOCGETC,&work.tchar);
#	  endif
#         if defined(TIOCLGET)
	   ioctl(handle,TIOCLGET,&work.local);
#	  endif
#	  if defined(TIOCGLTC)
	   ioctl(handle,TIOCGLTC,&work.ltchar);
#	  endif


#endif


	if(firsttime) {
		firsttime=0; 
		memcpy((void*)&savebuf,(void*)&work,sizeof(struct tbuffer));
		if(!hv_store(filehash,(char*)&handle,sizeof(int),
			newSVpv((char*)&savebuf,sizeof(struct tbuffer)),0))
			croak("Unable to stash terminal settings.\n");
		if(!hv_store(modehash,(char*)&handle,sizeof(int),newSViv(0),0))
			croak("Unable to stash terminal settings.\n");
	} else {
		SV ** temp;
		if(!(temp=hv_fetch(filehash,(char*)&handle,sizeof(int),0))) 
			croak("Unable to retrieve stashed terminal settings.\n");
		memcpy(&savebuf,SvPV(*temp,PL_na),sizeof(struct tbuffer));
		if(!(temp=hv_fetch(modehash,(char*)&handle,sizeof(int),0))) 
			croak("Unable to retrieve stashed terminal mode.\n");
		oldmode=SvIV(*temp);
	}

#ifdef WIN32

	switch (mode) {
		case 5:
			/* Should 5 disable ENABLE_WRAP_AT_EOL_OUTPUT? */
		case 4:
			work.Mode &= ~(ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_OUTPUT);
			work.Mode |= 0;
			break;
		case 3:
			work.Mode &= ~(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT);
			work.Mode |= ENABLE_PROCESSED_INPUT|ENABLE_PROCESSED_OUTPUT;
			break;
		case 2:
			work.Mode &= ~(ENABLE_ECHO_INPUT);
			work.Mode |= ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT|ENABLE_PROCESSED_OUTPUT;
			break;
		case 1:
			work.Mode &= ~(0);
			work.Mode |= ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT|ENABLE_PROCESSED_OUTPUT;
			break;
		case 0:
			work = savebuf;
			firsttime = 1;
			break;
	}

	if (!SetConsoleMode((HANDLE)_get_osfhandle(handle), work.Mode))
	    croak("SetConsoleMode failed, LastError=|%d|",GetLastError());

#endif /* WIN32 */


#ifdef USE_TERMIOS


/* What, me worry about standards? */

#       if !defined (VMIN)
#		define VMIN VEOF
#       endif

#	if !defined (VTIME)
#		define VTIME VEOL
#	endif

#	if !defined (IXANY)
#		define IXANY (0)
#	endif

#ifndef IEXTEN
#ifdef IDEFAULT
#define IEXTEN IDEFAULT
#endif
#endif

/* XXX Is ONLCR in POSIX?.  The value of '4' seems to be the same for
   both SysV and Sun, so it's probably rather general, and I'm not
   aware of a POSIX way to do this otherwise.
*/
#ifndef ONLCR
# define ONLCR 4
#endif

#ifndef IMAXBEL
#define IMAXBEL 0
#endif
#ifndef ECHOE
#define ECHOE 0
#endif
#ifndef ECHOK
#define ECHOK 0
#endif
#ifndef ECHONL
#define ECHONL 0
#endif 
#ifndef ECHOPRT
#define ECHOPRT 0
#endif
#ifndef FLUSHO
#define FLUSHO 0
#endif
#ifndef PENDIN
#define PENDIN 0
#endif
#ifndef ECHOKE
#define ECHOKE 0
#endif
#ifndef ONLCR
#define ONLCR 0
#endif
#ifndef OCRNL
#define OCRNL 0
#endif
#ifndef ONLRET
#define ONLRET 0
#endif
#ifndef IUCLC
#define IUCLC 0
#endif
#ifndef OPOST
#define OPOST 0
#endif
#ifndef OLCUC
#define OLCUC 0
#endif
#ifndef ECHOCTL
#define ECHOCTL 0
#endif
#ifndef XCASE
#define XCASE 0
#endif
#ifndef BRKINT
#define BRKINT 0
#endif


	if(mode==5) {
		/*\
		 *  Disable everything except parity if needed.
		\*/

		/* Hopefully, this should put the tty into unbuffered mode
		with signals and control characters (both posixy and normal)
		disabled, along with flow control. Echo should be off.
		CR/LF is not translated, along with 8-bit/parity */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_lflag &= ~(ICANON|ISIG|IEXTEN );
		work.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ECHOCTL);
		work.c_lflag &= ~(ECHOPRT|ECHOKE|FLUSHO|PENDIN|XCASE);
		work.c_lflag |= NOFLSH;
        work.c_iflag &= ~(IXOFF|IXON|IXANY|ICRNL|IMAXBEL|BRKINT);

		if(((work.c_iflag & INPCK) != INPCK) ||
                   ((work.c_cflag & PARENB) != PARENB)) {
			work.c_iflag &= ~ISTRIP;
			work.c_iflag |= IGNPAR;
			work.c_iflag &= ~PARMRK;
		} 
		work.c_oflag &= ~(OPOST |ONLCR|OCRNL|ONLRET);

		work.c_cc[VTIME] = 0;
		work.c_cc[VMIN] = 1;
	}
	else if(mode==4) {
		/* Hopefully, this should put the tty into unbuffered mode
		with signals and control characters (both posixy and normal)
		disabled, along with flow control. Echo should be off.
		About the only thing left unchanged is 8-bit/parity */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		/*work.c_iflag = savebuf.c_iflag;*/
		work.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
        work.c_iflag &= ~(IXON | IXANY | BRKINT);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VTIME] = 0;
		work.c_cc[VMIN] = 1;
	}
	else if(mode==3)
	{
		/* This should be an unbuffered mode with signals and control	
		characters enabled, as should be flow control. Echo should
		still be off */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_iflag = savebuf.c_iflag;
		work.c_lflag &= ~(ICANON | ECHO);
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
		work.c_lflag |= ISIG | IEXTEN;
		/*work.c_iflag &= ~(IXON | IXOFF | IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);
		work.c_oflag = savebuf.c_oflag;*/
		work.c_cc[VTIME] = 0;
		work.c_cc[VMIN] = 1;
	}
	else if(mode==2)
	{
		/* This should be an unbuffered mode with signals and control	
		characters enabled, as should be flow control. Echo should
		still be off */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_iflag = savebuf.c_iflag;
		work.c_lflag |= ICANON|ISIG|IEXTEN;
		work.c_lflag &= ~ECHO;
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
		/*work.c_iflag &= ~(IXON |IXOFF|IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VTIME] = savebuf.c_cc[VTIME];
		work.c_cc[VMIN] = savebuf.c_cc[VMIN];*/
	}
	else if(mode==1)
	{
		/* This should be an unbuffered mode with signals and control	
		characters enabled, as should be flow control. Echo should
		still be off */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_iflag = savebuf.c_iflag;
		work.c_lflag |= ICANON|ECHO|ISIG|IEXTEN;
		/*work.c_iflag &= ~(IXON |IXOFF|IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VTIME] = savebuf.c_cc[VTIME];
		work.c_cc[VMIN] = savebuf.c_cc[VMIN];*/
	}
	else if(mode==0){
		/*work.c_lflag &= ~BITMASK; 
		work.c_lflag |= savebuf.c_lflag & BITMASK;
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VTIME] = savebuf.c_cc[VTIME];
		work.c_cc[VMIN] = savebuf.c_cc[VMIN];
		work.c_iflag = savebuf.c_iflag;
		work.c_iflag &= ~(IXON|IXOFF|IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);*/
		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));
		/*Copy(&work,&savebuf,1,sizeof(struct tbuffer));*/

		firsttime=1;
	}	
	else
	{
		croak("ReadMode %d is not implemented on this architecture.",mode);
		return;		
	}


	/* If switching from a "lower power" mode to a higher one, keep the
	data that may be in the queue, as it can easily be type-ahead. On
	switching to a lower mode from a higher one, however, flush the queue
	so that raw keystrokes won't hit an unexpecting program */
	
	if(DisableFlush || oldmode<=mode)
		tcsetattr(handle,TCSANOW,&work);
	else
		tcsetattr(handle,TCSAFLUSH,&work);

	/*tcsetattr(handle,TCSANOW,&work);*/ /* It might be better to FLUSH
					   when changing gears to a lower mode,
					   and only use NOW for higher modes. 
					*/


#endif
#ifdef USE_TERMIO

/* What, me worry about standards? */

#	 if !defined (IXANY)
#                define IXANY (0)
#        endif

#ifndef ECHOE
#define ECHOE 0
#endif
#ifndef ECHOK
#define ECHOK 0
#endif
#ifndef ECHONL
#define ECHONL 0
#endif
#ifndef XCASE
#define XCASE 0
#endif
#ifndef BRKINT
#define BRKINT 0
#endif



	 if(mode==5) {
		/* This mode should be echo disabled, signals disabled,
		flow control disabled, and unbuffered. CR/LF translation 
   	 	is off, and 8 bits if possible */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_lflag &= ~(ECHO | ISIG | ICANON | XCASE);
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL | TRK_IDEFAULT);
		work.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | BRKINT);
		if((work.c_cflag | PARENB)!=PARENB ) {
			work.c_iflag &= ~(ISTRIP|INPCK);
			work.c_iflag |= IGNPAR;
		} 
		work.c_oflag &= ~(OPOST|ONLCR);
		work.c_cc[VMIN] = 1;
		work.c_cc[VTIME] = 1;
	 } 
	 else if(mode==4) {
		/* This mode should be echo disabled, signals disabled,
		flow control disabled, and unbuffered. Parity is not
		touched. */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_lflag &= ~(ECHO | ISIG | ICANON);
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL TRK_IDEFAULT);
		work.c_iflag = savebuf.c_iflag;
		work.c_iflag &= ~(IXON | IXOFF | IXANY | BRKINT);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VMIN] = 1;
		work.c_cc[VTIME] = 1;
	 } 
	 else if(mode==3) {
		/* This mode tries to have echo off, signals enabled,
		flow control as per the original setting, and unbuffered. */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_lflag &= ~(ECHO | ICANON);
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL | TRK_IDEFAULT);
		work.c_lflag |= ISIG;
		work.c_iflag = savebuf.c_iflag;
		work.c_iflag &= ~(IXON | IXOFF | IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VMIN] = 1;
		work.c_cc[VTIME] = 1;
	 }
	 else if(mode==2) {
		/* This mode tries to set echo on, signals on, and buffering
		on, with flow control set to whatever it was originally. */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_lflag |= (ISIG | ICANON);
		work.c_lflag &= ~ECHO;
		work.c_lflag &= ~(ECHOE | ECHOK | ECHONL | TRK_IDEFAULT);
		work.c_iflag = savebuf.c_iflag;
		work.c_iflag &= ~(IXON | IXOFF | IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VMIN] = savebuf.c_cc[VMIN];
		work.c_cc[VTIME] = savebuf.c_cc[VTIME];
		
		/* This assumes turning ECHO and ICANON back on is
		   sufficient to re-enable cooked mode. If this is a 
		   problem, complain to me */

		/* What the heck. We're already saving the entire buf, so
		I'm now going to reset VMIN and VTIME too. Hope this works 
		properly */
		
	 } 
	 else if(mode==1) {
		/* This mode tries to set echo on, signals on, and buffering
		on, with flow control set to whatever it was originally. */

		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));

		work.c_lflag |= (ECHO | ISIG | ICANON);
      work.c_iflag &= ~TRK_IDEFAULT;
		work.c_iflag = savebuf.c_iflag;
		work.c_iflag &= ~(IXON | IXOFF | IXANY);
		work.c_iflag |= savebuf.c_iflag & (IXON|IXOFF|IXANY);
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VMIN] = savebuf.c_cc[VMIN];
		work.c_cc[VTIME] = savebuf.c_cc[VTIME];
		
		/* This assumes turning ECHO and ICANON back on is
		   sufficient to re-enable cooked mode. If this is a 
		   problem, complain to me */

		/* What the heck. We're already saving the entire buf, so
		I'm now going to reset VMIN and VTIME too. Hope this works 
		properly */
	}		
	 else if(mode==0) {
		/* Put things back the way they were */

		/*work.c_lflag = savebuf.c_lflag;
		work.c_iflag = savebuf.c_iflag;
		work.c_oflag = savebuf.c_oflag;
		work.c_cc[VMIN] = savebuf.c_cc[VMIN];
		work.c_cc[VTIME] = savebuf.c_cc[VTIME];*/
		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));
		firsttime=1;
	 }
 	 else
 	 {
		croak("ReadMode %d is not implemented on this architecture.",mode);
		return;		
	 }


	 if(DisableFlush || oldmode<=mode) 
		ioctl(handle,TCSETA,&work);
	 else
		ioctl(handle,TCSETAF,&work);

#endif
#ifdef USE_SGTTY


	  if(mode==5) {
		/* Unbuffered, echo off, signals off, flow control off */
		/* CR-CR/LF mode off too, and 8-bit path enabled. */
#	 	if defined(TIOCLGET) && defined(LPASS8)
		 if((work.buf.sg_flags & (EVENP|ODDP))==0 ||
		    (work.buf.sg_flags & (EVENP|ODDP))==(EVENP|ODDP))
		 	 work.local |= LPASS8; /* If parity isn't being used, use 8 bits */
#		endif
	  	work.buf.sg_flags &= ~(ECHO|CRMOD);
	  	work.buf.sg_flags |= (RAW|CBREAK);
# 	  	if defined(TIOCGETC)
		 work.tchar.t_intrc = -1;
		 work.tchar.t_quitc = -1;
		 work.tchar.t_startc= -1;
		 work.tchar.t_stopc = -1;
		 work.tchar.t_eofc  = -1;
		 work.tchar.t_brkc  = -1;
#		endif
#		if defined(TIOCGLTC)
		 work.ltchar.t_suspc= -1;
		 work.ltchar.t_dsuspc= -1;
		 work.ltchar.t_rprntc= -1;
		 work.ltchar.t_flushc= -1;
		 work.ltchar.t_werasc= -1;
		 work.ltchar.t_lnextc= -1;
#		endif
	  }
	  else if(mode==4) {
		/* Unbuffered, echo off, signals off, flow control off */
	  	work.buf.sg_flags &= ~(ECHO|RAW);
	  	work.buf.sg_flags |= (CBREAK|CRMOD);
#	 	if defined(TIOCLGET)
		 work.local=savebuf.local;
#		endif
# 	  	if defined(TIOCGETC)
		 work.tchar.t_intrc = -1;
		 work.tchar.t_quitc = -1;
		 work.tchar.t_startc= -1;
		 work.tchar.t_stopc = -1;
		 work.tchar.t_eofc  = -1;
		 work.tchar.t_brkc  = -1;
#		endif
#		if defined(TIOCGLTC)
		 work.ltchar.t_suspc= -1;
		 work.ltchar.t_dsuspc= -1;
		 work.ltchar.t_rprntc= -1;
		 work.ltchar.t_flushc= -1;
		 work.ltchar.t_werasc= -1;
		 work.ltchar.t_lnextc= -1;
#		endif
	  }
	  else if(mode==3) {
		/* Unbuffered, echo off, signals on, flow control on */
		work.buf.sg_flags &= ~(RAW|ECHO);
	  	work.buf.sg_flags |= CBREAK|CRMOD;
#	 	if defined(TIOCLGET)
		 work.local=savebuf.local;
#		endif
#		if defined(TIOCGLTC)
		 work.tchar = savebuf.tchar;
#		endif
#		if defined(TIOCGLTC)
		 work.ltchar = savebuf.ltchar;
#		endif
 	  }
	  else if(mode==2) {
		/* Buffered, echo on, signals on, flow control on */
		work.buf.sg_flags &= ~(RAW|CBREAK);
		work.buf.sg_flags |= CRMOD;
		work.buf.sg_flags &= ~ECHO;
#	 	if defined(TIOCLGET)
		 work.local=savebuf.local;
#		endif
#		if defined(TIOCGLTC)
		 work.tchar = savebuf.tchar;
#		endif
#		if defined(TIOCGLTC)
		 work.ltchar = savebuf.ltchar;
#		endif
	  }
	  else if(mode==1) {
		/* Buffered, echo on, signals on, flow control on */
		work.buf.sg_flags &= ~(RAW|CBREAK);
		work.buf.sg_flags |= ECHO|CRMOD;
#	 	if defined(TIOCLGET)
		 work.local=savebuf.local;
#		endif
#		if defined(TIOCGLTC)
		 work.tchar = savebuf.tchar;
#		endif
#		if defined(TIOCGLTC)
		 work.ltchar = savebuf.ltchar;
#		endif
	  }
	  else if(mode==0){
		/* Original settings */
#if 0
		work.buf.sg_flags &= ~(RAW|CBREAK|ECHO|CRMOD);
		work.buf.sg_flags |= savebuf.sg_flags & (RAW|CBREAK|ECHO|CRMOD);
#	 	if defined(TIOCLGET)
		 work.local=savebuf.local;
#		endif
#		if defined(TIOCGLTC)
		 work.tchar = savebuf.tchar;
#		endif
#		if defined(TIOCGLTC)
		 work.ltchar = savebuf.ltchar;
#		endif
#endif
		memcpy((void*)&work,(void*)&savebuf,sizeof(struct tbuffer));
		firsttime=1;
	  }
 	  else
 	  {
		croak("ReadMode %d is not implemented on this architecture.",mode);
		return;		
	  }
#if defined(TIOCLSET)
	  ioctl(handle,TIOCLSET,&work.local);
#endif
#if defined(TIOCSETC)
	  ioctl(handle,TIOCSETC,&work.tchar);
#endif
#	  if defined(TIOCGLTC)
	   ioctl(handle,TIOCSLTC,&work.ltchar);
#	  endif
	  if(DisableFlush || oldmode<=mode)
	  	ioctl(handle,TIOCSETN,&work.buf);
	  else
		ioctl(handle,TIOCSETP,&work.buf);
#endif
#ifdef USE_STTY

	   /* No termio, termios or sgtty. I suppose we can try stty,
	      but it would be nice if you could get a better OS */

	   if(mode==5)
		system("/bin/stty  raw -cbreak -isig -echo -ixon -onlcr -icrnl -brkint");
	   else if(mode==4)
		system("/bin/stty -raw  cbreak -isig -echo -ixon  onlcr  icrnl -brkint");
	   else if(mode==3)
		system("/bin/stty -raw  cbreak  isig -echo  ixon  onlcr  icrnl  brkint");
	   else if(mode==2) 
		system("/bin/stty -raw -cbreak  isig  echo  ixon  onlcr  icrnl  brkint");
	   else if(mode==1)
		system("/bin/stty -raw -cbreak  isig -echo  ixon  onlcr  icrnl  brkint");
	   else if(mode==0)
		system("/bin/stty -raw -cbreak  isig  echo  ixon  onlcr  icrnl  brkint");

	   /* Those probably won't work, but they couldn't hurt 
              at this point */

#endif

	/*warn("Mode set to %d.\n",mode);*/

	if( firsttime ) {
		hv_delete(filehash,(char*)&handle,sizeof(int),0);
		hv_delete(modehash,(char*)&handle,sizeof(int),0);
	} else {
		if(!hv_store(modehash,(char*)&handle,sizeof(int),
			newSViv(mode),0))
			croak("Unable to stash terminal settings.\n");
	}

}

#ifdef USE_PERLIO

/* Make use of a recent addition to Perl, if possible */
# define FCOUNT(f) PerlIO_get_cnt(f)
#else

 /* Make use of a recent addition to Configure, if possible */
# ifdef USE_STDIO_PTR
#  define FCOUNT(f) PerlIO_get_cnt(f)
# else
  /* This bit borrowed from pp_sys.c. Complain to Larry if it's broken. */
  /* If any of this works PerlIO_get_cnt() will too ... NI-S */
#  if defined(USE_STD_STDIO) || defined(atarist) /* this will work with atariST */
#   define FBASE(f) ((f)->_base)
#   define FSIZE(f) ((f)->_cnt + ((f)->_ptr - (f)->_base))
#   define FPTR(f) ((f)->_ptr)
#   define FCOUNT(f) ((f)->_cnt)
#  else
#   if defined(USE_LINUX_STDIO)
#     define FBASE(f) ((f)->_IO_read_base)
#     define FSIZE(f) ((f)->_IO_read_end - FBASE(f))
#     define FPTR(f) ((f)->_IO_read_ptr)
#     define FCOUNT(f) ((f)->_IO_read_end - FPTR(f))
#   endif
#  endif
# endif
#endif

/* This is for the best, I'm afraid. */
#if !defined(FCOUNT)
# ifdef Have_select
#  undef Have_select
# endif
# ifdef Have_poll
#  undef Have_poll
# endif
#endif

/* Note! If your machine has a bolixed up select() call that doesn't
understand this syntax, either fix the checkwaiting call below, or define
DONT_USE_SELECT. */

#ifdef Have_select
int selectfile(PerlIO *file,double delay)
{
	struct timeval t;
	int handle=PerlIO_fileno(file);

	/*char buf[32];    
	Select_fd_set_t fd=(Select_fd_set_t)&buf[0];*/

	fd_set fd;
	if (PerlIO_fast_gets(file) && PerlIO_get_cnt(file) > 0)
		return 1;

	/*t.tv_sec=t.tv_usec=0;*/

        if (delay < 0.0)
            delay = 0.0;
        t.tv_sec = (long)delay;
        delay -= (double)t.tv_sec;
        t.tv_usec = (long)(delay * 1000000.0);

	FD_ZERO(&fd);
	FD_SET(handle,&fd);
	if(select(handle+1,(Select_fd_set_t)&fd,
			   (Select_fd_set_t)0,
			   (Select_fd_set_t)&fd, &t)) return -1; 
	else return 0;
}

#else
int selectfile(PerlIO *file, double delay)
{
	croak("select is not supported on this architecture");
	return 0;
}
#endif

#ifdef Have_nodelay
int setnodelay(PerlIO *file, int mode)
{
	int handle=PerlIO_fileno(file);
	int flags;
	flags=fcntl(handle,F_GETFL,0);
	if(mode)
		flags|=O_NODELAY;
	else
		flags&=~O_NODELAY;
	fcntl(handle,F_SETFL,flags);
	return 0;
}

#else
int setnodelay(PerlIO *file, int mode) 
{
	croak("setnodelay is not supported on this architecture");
	return 0;
}
#endif

#ifdef Have_poll
int pollfile(PerlIO *file,double delay)
{
	int handle=PerlIO_fileno(file);
	struct pollfd fds;
	if (PerlIO_fast_gets(f) && PerlIO_get_cnt(f) > 0)
		return 1;
	if(delay<0.0) delay = 0.0;
	fds.fd=handle;
	fds.events=POLLIN;
	fds.revents=0;
	return (poll(&fds,1,(long)(delay * 1000.0))>0);
} 
#else
int pollfile(PerlIO *file,double delay) 
{
	croak("pollfile is not supported on this architecture");
	return 0;
}
#endif

#ifdef WIN32

/*

 This portion of the Win32 code is partially borrowed from a version of PDCurses.

*/

int Win32PeekChar(PerlIO *file,double delay,char *key)
{
	int handle;
	HANDLE whnd;
	INPUT_RECORD record;
	DWORD readRecords;

	static int keyCount = 0;
	static char lastKey = 0;

	file = STDIN;

	handle = PerlIO_fileno(file);
	whnd = /*GetStdHandle(STD_INPUT_HANDLE)*/(HANDLE)_get_osfhandle(handle);


again:
	if (keyCount > 0) {
		keyCount--;
		*key = lastKey;
	    return TRUE;
	}

	if (delay > 0) {
		if (WaitForSingleObject(whnd, delay * 1000.0) != WAIT_OBJECT_0)
		{
			return FALSE;
		}
	}

	if (delay != 0) {
		PeekConsoleInput(whnd, &record, 1, &readRecords);
		if (readRecords == 0)
			return(FALSE);
	}

	ReadConsoleInput(whnd, &record, 1, &readRecords);
	switch(record.EventType)
   {
    case KEY_EVENT:
		/*printf("\nkeyDown = %d, repeat = %d, vKey = %d, vScan = %d, ASCII = %d, Control = %d\n",
			record.Event.KeyEvent.bKeyDown,
			record.Event.KeyEvent.wRepeatCount,
			record.Event.KeyEvent.wVirtualKeyCode,
			record.Event.KeyEvent.wVirtualScanCode,
			record.Event.KeyEvent.uChar.AsciiChar,
			record.Event.KeyEvent.dwControlKeyState);*/

         if (record.Event.KeyEvent.bKeyDown == FALSE)
            goto again;                        /* throw away KeyUp events */
         if (record.Event.KeyEvent.wVirtualKeyCode == 16
         ||  record.Event.KeyEvent.wVirtualKeyCode == 17
         ||  record.Event.KeyEvent.wVirtualKeyCode == 18
         ||  record.Event.KeyEvent.wVirtualKeyCode == 20
         ||  record.Event.KeyEvent.wVirtualKeyCode == 144
         ||  record.Event.KeyEvent.wVirtualKeyCode == 145)
            goto again;  /* throw away shift/alt/ctrl key only key events */
         keyCount = record.Event.KeyEvent.wRepeatCount;
		 break;
    default:
         keyCount = 0;
         goto again;
         break;
   }

 *key = lastKey = record.Event.KeyEvent.uChar.AsciiChar; 
 keyCount--;
 
 return(TRUE);

 /* again:
	return (FALSE);
	*/


} 
#else
int Win32PeekChar(PerlIO *file, double delay,char *key) 
{
	croak("Win32PeekChar is not supported on this architecture");
	return 0;
}
#endif


int blockoptions() {
	return	0
#ifdef Have_nodelay
		| 1
#endif
#ifdef Have_poll
		| 2
#endif
#ifdef Have_select
		| 4
#endif
#ifdef USE_WIN32
		| 8
#endif
		;
}

int termoptions() {
	int i=0;
#ifdef USE_TERMIOS
	i=1;		
#endif
#ifdef USE_TERMIO
	i=2;
#endif
#ifdef USE_SGTTY
	i=3;
#endif
#ifdef USE_STTY
	i=4;
#endif
#ifdef USE_WIN32
	i=5;
#endif
	return i;
}



MODULE = Term::ReadKey		PACKAGE = Term::ReadKey

int
selectfile(file,delay)
	InputStream	file
	double	delay

# Clever, eh?
void
SetReadMode(mode,file=STDIN)
	int	mode
	InputStream	file
	CODE:
	{
		ReadMode(file,mode);
	}

int
setnodelay(file,mode)
	InputStream	file
	int	mode

int
pollfile(file,delay)
	InputStream	file
	double	delay

SV *
Win32PeekChar(file, delay)
	InputStream	file
	double	delay
	CODE:
	{
		char key;
		if (Win32PeekChar(file, delay, &key))
			RETVAL = newSVpv(&key, 1);
		else
			RETVAL = newSVsv(&PL_sv_undef);
	}
	OUTPUT:
	RETVAL

int
blockoptions()

int
termoptions()

int
termsizeoptions()

void
GetTermSizeWin32(file=STDIN)
	InputStream	file
	PPCODE:
	{
		int x,y,xpix,ypix;
		if( GetTermSizeWin32(file,&x,&y,&xpix,&ypix)==0)
		{
			EXTEND(sp, 4);
			PUSHs(sv_2mortal(newSViv((IV)x)));
			PUSHs(sv_2mortal(newSViv((IV)y)));
			PUSHs(sv_2mortal(newSViv((IV)xpix)));
			PUSHs(sv_2mortal(newSViv((IV)ypix)));
		}
		else
		{
			ST(0) = sv_newmortal();
		}
	}

void
GetTermSizeVIO(file=STDIN)
	InputStream	file
	PPCODE:
	{
		int x,y,xpix,ypix;
		if( GetTermSizeVIO(file,&x,&y,&xpix,&ypix)==0)
		{
			EXTEND(sp, 4);
			PUSHs(sv_2mortal(newSViv((IV)x)));
			PUSHs(sv_2mortal(newSViv((IV)y)));
			PUSHs(sv_2mortal(newSViv((IV)xpix)));
			PUSHs(sv_2mortal(newSViv((IV)ypix)));
		}
		else
		{
			ST(0) = sv_newmortal();
		}
	}

void
GetTermSizeGWINSZ(file=STDIN)
	InputStream	file
	PPCODE:
	{
		int x,y,xpix,ypix;
		if( GetTermSizeGWINSZ(file,&x,&y,&xpix,&ypix)==0)
		{
			EXTEND(sp, 4);
			PUSHs(sv_2mortal(newSViv((IV)x)));
			PUSHs(sv_2mortal(newSViv((IV)y)));
			PUSHs(sv_2mortal(newSViv((IV)xpix)));
			PUSHs(sv_2mortal(newSViv((IV)ypix)));
		}
		else
		{
			ST(0) = sv_newmortal();
		}
	}

void
GetTermSizeGSIZE(file=STDIN)
	InputStream	file
	PPCODE:
	{
		int x,y,xpix,ypix;
		if( GetTermSizeGSIZE(file,&x,&y,&xpix,&ypix)==0)
		{
			EXTEND(sp, 4);
			PUSHs(sv_2mortal(newSViv((IV)x)));
			PUSHs(sv_2mortal(newSViv((IV)y)));
			PUSHs(sv_2mortal(newSViv((IV)xpix)));
			PUSHs(sv_2mortal(newSViv((IV)ypix)));
		}
		else
		{
			ST(0) = sv_newmortal();
		}
	}

int
SetTerminalSize(width,height,xpix,ypix,file=STDIN)
	int	width
	int	height
	int	xpix
	int	ypix
	InputStream	file
	CODE:
	{
		RETVAL=SetTerminalSize(file,width,height,xpix,ypix);
	}

void
GetSpeed(file=STDIN)
	InputStream	file
	PPCODE:
	{
		I32 in,out;
		if(items!=0) {
			croak("Usage: Term::ReadKey::GetSpeed()");
		}
		if(getspeed(file,&in,&out)) {
			/* Failure */
			ST( 0) = sv_newmortal();
		} else {
			EXTEND(sp, 2);
			PUSHs(sv_2mortal(newSViv((IV)in)));
			PUSHs(sv_2mortal(newSViv((IV)out)));
		}
	}



BOOT: 
newXS("Term::ReadKey::GetControlChars", XS_Term__ReadKey_GetControlChars, file);
newXS("Term::ReadKey::SetControlChars", XS_Term__ReadKey_SetControlChars, file);
filehash=newHV();
modehash=newHV();
