screen.c revision 170967
1/* $FreeBSD: head/contrib/less/screen.c 170967 2007-06-21 10:44:50Z delphij $ */
2/*
3 * Copyright (C) 1984-2007  Mark Nudelman
4 *
5 * You may distribute under the terms of either the GNU General Public
6 * License or the Less License, as specified in the README file.
7 *
8 * For more information about less, or for information on how to
9 * contact the author, see the README file.
10 */
11
12
13/*
14 * Routines which deal with the characteristics of the terminal.
15 * Uses termcap to be as terminal-independent as possible.
16 */
17
18#include "less.h"
19#include "cmd.h"
20
21#if MSDOS_COMPILER
22#include "pckeys.h"
23#if MSDOS_COMPILER==MSOFTC
24#include <graph.h>
25#else
26#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
27#include <conio.h>
28#if MSDOS_COMPILER==DJGPPC
29#include <pc.h>
30extern int fd0;
31#endif
32#else
33#if MSDOS_COMPILER==WIN32C
34#include <windows.h>
35#endif
36#endif
37#endif
38#include <time.h>
39
40#else
41
42#if HAVE_SYS_IOCTL_H
43#include <sys/ioctl.h>
44#endif
45
46#if HAVE_TERMIOS_H && HAVE_TERMIOS_FUNCS
47#include <termios.h>
48#else
49#if HAVE_TERMIO_H
50#include <termio.h>
51#else
52#if HAVE_SGSTAT_H
53#include <sgstat.h>
54#else
55#include <sgtty.h>
56#endif
57#endif
58#endif
59
60#if HAVE_TERMCAP_H
61#include <termcap.h>
62#endif
63#ifdef _OSK
64#include <signal.h>
65#endif
66#if OS2
67#include <sys/signal.h>
68#include "pckeys.h"
69#endif
70#if HAVE_SYS_STREAM_H
71#include <sys/stream.h>
72#endif
73#if HAVE_SYS_PTEM_H
74#include <sys/ptem.h>
75#endif
76
77#endif /* MSDOS_COMPILER */
78
79/*
80 * Check for broken termios package that forces you to manually
81 * set the line discipline.
82 */
83#ifdef __ultrix__
84#define MUST_SET_LINE_DISCIPLINE 1
85#else
86#define MUST_SET_LINE_DISCIPLINE 0
87#endif
88
89#if OS2
90#define	DEFAULT_TERM		"ansi"
91static char *windowid;
92#else
93#define	DEFAULT_TERM		"unknown"
94#endif
95
96#if MSDOS_COMPILER==MSOFTC
97static int videopages;
98static long msec_loops;
99static int flash_created = 0;
100#define	SETCOLORS(fg,bg)	{ _settextcolor(fg); _setbkcolor(bg); }
101#endif
102
103#if MSDOS_COMPILER==BORLANDC
104static unsigned short *whitescreen;
105static int flash_created = 0;
106#endif
107#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
108#define _settextposition(y,x)   gotoxy(x,y)
109#define _clearscreen(m)         clrscr()
110#define _outtext(s)             cputs(s)
111#define	SETCOLORS(fg,bg)	{ textcolor(fg); textbackground(bg); }
112extern int sc_height;
113#endif
114
115#if MSDOS_COMPILER==WIN32C
116struct keyRecord
117{
118	int ascii;
119	int scan;
120} currentKey;
121
122static int keyCount = 0;
123static WORD curr_attr;
124static int pending_scancode = 0;
125static WORD *whitescreen;
126
127static HANDLE con_out_save = INVALID_HANDLE_VALUE; /* previous console */
128static HANDLE con_out_ours = INVALID_HANDLE_VALUE; /* our own */
129HANDLE con_out = INVALID_HANDLE_VALUE;             /* current console */
130
131extern int quitting;
132static void win32_init_term();
133static void win32_deinit_term();
134
135#define FG_COLORS       (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)
136#define BG_COLORS       (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY)
137#define	MAKEATTR(fg,bg)		((WORD)((fg)|((bg)<<4)))
138#define	SETCOLORS(fg,bg)	{ curr_attr = MAKEATTR(fg,bg); \
139				if (SetConsoleTextAttribute(con_out, curr_attr) == 0) \
140				error("SETCOLORS failed"); }
141#endif
142
143#if MSDOS_COMPILER
144public int nm_fg_color;		/* Color of normal text */
145public int nm_bg_color;
146public int bo_fg_color;		/* Color of bold text */
147public int bo_bg_color;
148public int ul_fg_color;		/* Color of underlined text */
149public int ul_bg_color;
150public int so_fg_color;		/* Color of standout text */
151public int so_bg_color;
152public int bl_fg_color;		/* Color of blinking text */
153public int bl_bg_color;
154static int sy_fg_color;		/* Color of system text (before less) */
155static int sy_bg_color;
156
157#else
158
159/*
160 * Strings passed to tputs() to do various terminal functions.
161 */
162static char
163	*sc_pad,		/* Pad string */
164	*sc_home,		/* Cursor home */
165	*sc_addline,		/* Add line, scroll down following lines */
166	*sc_lower_left,		/* Cursor to last line, first column */
167	*sc_return,		/* Cursor to beginning of current line */
168	*sc_move,		/* General cursor positioning */
169	*sc_clear,		/* Clear screen */
170	*sc_eol_clear,		/* Clear to end of line */
171	*sc_eos_clear,		/* Clear to end of screen */
172	*sc_s_in,		/* Enter standout (highlighted) mode */
173	*sc_s_out,		/* Exit standout mode */
174	*sc_u_in,		/* Enter underline mode */
175	*sc_u_out,		/* Exit underline mode */
176	*sc_b_in,		/* Enter bold mode */
177	*sc_b_out,		/* Exit bold mode */
178	*sc_bl_in,		/* Enter blink mode */
179	*sc_bl_out,		/* Exit blink mode */
180	*sc_visual_bell,	/* Visual bell (flash screen) sequence */
181	*sc_backspace,		/* Backspace cursor */
182	*sc_s_keypad,		/* Start keypad mode */
183	*sc_e_keypad,		/* End keypad mode */
184	*sc_init,		/* Startup terminal initialization */
185	*sc_deinit;		/* Exit terminal de-initialization */
186#endif
187
188static int init_done = 0;
189
190public int auto_wrap;		/* Terminal does \r\n when write past margin */
191public int ignaw;		/* Terminal ignores \n immediately after wrap */
192public int erase_char;		/* The user's erase char */
193public int erase2_char;		/* The user's other erase char */
194public int kill_char;		/* The user's line-kill char */
195public int werase_char;		/* The user's word-erase char */
196public int sc_width, sc_height;	/* Height & width of screen */
197public int bo_s_width, bo_e_width;	/* Printing width of boldface seq */
198public int ul_s_width, ul_e_width;	/* Printing width of underline seq */
199public int so_s_width, so_e_width;	/* Printing width of standout seq */
200public int bl_s_width, bl_e_width;	/* Printing width of blink seq */
201public int above_mem, below_mem;	/* Memory retained above/below screen */
202public int can_goto_line;		/* Can move cursor to any line */
203public int clear_bg;		/* Clear fills with background color */
204public int missing_cap = 0;	/* Some capability is missing */
205
206static int attrmode = AT_NORMAL;
207extern int binattr;
208
209#if !MSDOS_COMPILER
210static char *cheaper();
211static void tmodes();
212#endif
213
214/*
215 * These two variables are sometimes defined in,
216 * and needed by, the termcap library.
217 */
218#if MUST_DEFINE_OSPEED
219extern short ospeed;	/* Terminal output baud rate */
220extern char PC;		/* Pad character */
221#endif
222#ifdef _OSK
223short ospeed;
224char PC_, *UP, *BC;
225#endif
226
227extern int quiet;		/* If VERY_QUIET, use visual bell for bell */
228extern int no_back_scroll;
229extern int swindow;
230extern int no_init;
231extern int quit_at_eof;
232extern int less_is_more;
233extern int no_keypad;
234extern int sigs;
235extern int wscroll;
236extern int screen_trashed;
237extern int tty;
238extern int top_scroll;
239extern int oldbot;
240#if HILITE_SEARCH
241extern int hilite_search;
242#endif
243
244extern char *tgetstr();
245extern char *tgoto();
246
247
248/*
249 * Change terminal to "raw mode", or restore to "normal" mode.
250 * "Raw mode" means
251 *	1. An outstanding read will complete on receipt of a single keystroke.
252 *	2. Input is not echoed.
253 *	3. On output, \n is mapped to \r\n.
254 *	4. \t is NOT expanded into spaces.
255 *	5. Signal-causing characters such as ctrl-C (interrupt),
256 *	   etc. are NOT disabled.
257 * It doesn't matter whether an input \n is mapped to \r, or vice versa.
258 */
259	public void
260raw_mode(on)
261	int on;
262{
263	static int curr_on = 0;
264
265	if (on == curr_on)
266		return;
267	erase2_char = '\b'; /* in case OS doesn't know about erase2 */
268#if HAVE_TERMIOS_H && HAVE_TERMIOS_FUNCS
269    {
270	struct termios s;
271	static struct termios save_term;
272	static int saved_term = 0;
273
274	if (on)
275	{
276		/*
277		 * Get terminal modes.
278		 */
279		tcgetattr(tty, &s);
280
281		/*
282		 * Save modes and set certain variables dependent on modes.
283		 */
284		if (!saved_term)
285		{
286			save_term = s;
287			saved_term = 1;
288		}
289#if HAVE_OSPEED
290		switch (cfgetospeed(&s))
291		{
292#ifdef B0
293		case B0: ospeed = 0; break;
294#endif
295#ifdef B50
296		case B50: ospeed = 1; break;
297#endif
298#ifdef B75
299		case B75: ospeed = 2; break;
300#endif
301#ifdef B110
302		case B110: ospeed = 3; break;
303#endif
304#ifdef B134
305		case B134: ospeed = 4; break;
306#endif
307#ifdef B150
308		case B150: ospeed = 5; break;
309#endif
310#ifdef B200
311		case B200: ospeed = 6; break;
312#endif
313#ifdef B300
314		case B300: ospeed = 7; break;
315#endif
316#ifdef B600
317		case B600: ospeed = 8; break;
318#endif
319#ifdef B1200
320		case B1200: ospeed = 9; break;
321#endif
322#ifdef B1800
323		case B1800: ospeed = 10; break;
324#endif
325#ifdef B2400
326		case B2400: ospeed = 11; break;
327#endif
328#ifdef B4800
329		case B4800: ospeed = 12; break;
330#endif
331#ifdef B9600
332		case B9600: ospeed = 13; break;
333#endif
334#ifdef EXTA
335		case EXTA: ospeed = 14; break;
336#endif
337#ifdef EXTB
338		case EXTB: ospeed = 15; break;
339#endif
340#ifdef B57600
341		case B57600: ospeed = 16; break;
342#endif
343#ifdef B115200
344		case B115200: ospeed = 17; break;
345#endif
346		default: ;
347		}
348#endif
349		erase_char = s.c_cc[VERASE];
350#ifdef VERASE2
351		erase2_char = s.c_cc[VERASE2];
352#endif
353		kill_char = s.c_cc[VKILL];
354#ifdef VWERASE
355		werase_char = s.c_cc[VWERASE];
356#else
357		werase_char = CONTROL('W');
358#endif
359
360		/*
361		 * Set the modes to the way we want them.
362		 */
363		s.c_lflag &= ~(0
364#ifdef ICANON
365			| ICANON
366#endif
367#ifdef ECHO
368			| ECHO
369#endif
370#ifdef ECHOE
371			| ECHOE
372#endif
373#ifdef ECHOK
374			| ECHOK
375#endif
376#if ECHONL
377			| ECHONL
378#endif
379		);
380
381		s.c_oflag |= (0
382#ifdef OXTABS
383			| OXTABS
384#else
385#ifdef TAB3
386			| TAB3
387#else
388#ifdef XTABS
389			| XTABS
390#endif
391#endif
392#endif
393#ifdef OPOST
394			| OPOST
395#endif
396#ifdef ONLCR
397			| ONLCR
398#endif
399		);
400
401		s.c_oflag &= ~(0
402#ifdef ONOEOT
403			| ONOEOT
404#endif
405#ifdef OCRNL
406			| OCRNL
407#endif
408#ifdef ONOCR
409			| ONOCR
410#endif
411#ifdef ONLRET
412			| ONLRET
413#endif
414		);
415		s.c_cc[VMIN] = 1;
416		s.c_cc[VTIME] = 0;
417#ifdef VLNEXT
418		s.c_cc[VLNEXT] = 0;
419#endif
420#ifdef VDSUSP
421		s.c_cc[VDSUSP] = 0;
422#endif
423#if MUST_SET_LINE_DISCIPLINE
424		/*
425		 * System's termios is broken; need to explicitly
426		 * request TERMIODISC line discipline.
427		 */
428		s.c_line = TERMIODISC;
429#endif
430	} else
431	{
432		/*
433		 * Restore saved modes.
434		 */
435		s = save_term;
436	}
437#if HAVE_FSYNC
438	fsync(tty);
439#endif
440	tcsetattr(tty, TCSADRAIN, &s);
441#if MUST_SET_LINE_DISCIPLINE
442	if (!on)
443	{
444		/*
445		 * Broken termios *ignores* any line discipline
446		 * except TERMIODISC.  A different old line discipline
447		 * is therefore not restored, yet.  Restore the old
448		 * line discipline by hand.
449		 */
450		ioctl(tty, TIOCSETD, &save_term.c_line);
451	}
452#endif
453    }
454#else
455#ifdef TCGETA
456    {
457	struct termio s;
458	static struct termio save_term;
459	static int saved_term = 0;
460
461	if (on)
462	{
463		/*
464		 * Get terminal modes.
465		 */
466		ioctl(tty, TCGETA, &s);
467
468		/*
469		 * Save modes and set certain variables dependent on modes.
470		 */
471		if (!saved_term)
472		{
473			save_term = s;
474			saved_term = 1;
475		}
476#if HAVE_OSPEED
477		ospeed = s.c_cflag & CBAUD;
478#endif
479		erase_char = s.c_cc[VERASE];
480		kill_char = s.c_cc[VKILL];
481#ifdef VWERASE
482		werase_char = s.c_cc[VWERASE];
483#else
484		werase_char = CONTROL('W');
485#endif
486
487		/*
488		 * Set the modes to the way we want them.
489		 */
490		s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
491		s.c_oflag |=  (OPOST|ONLCR|TAB3);
492		s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
493		s.c_cc[VMIN] = 1;
494		s.c_cc[VTIME] = 0;
495	} else
496	{
497		/*
498		 * Restore saved modes.
499		 */
500		s = save_term;
501	}
502	ioctl(tty, TCSETAW, &s);
503    }
504#else
505#ifdef TIOCGETP
506    {
507	struct sgttyb s;
508	static struct sgttyb save_term;
509	static int saved_term = 0;
510
511	if (on)
512	{
513		/*
514		 * Get terminal modes.
515		 */
516		ioctl(tty, TIOCGETP, &s);
517
518		/*
519		 * Save modes and set certain variables dependent on modes.
520		 */
521		if (!saved_term)
522		{
523			save_term = s;
524			saved_term = 1;
525		}
526#if HAVE_OSPEED
527		ospeed = s.sg_ospeed;
528#endif
529		erase_char = s.sg_erase;
530		kill_char = s.sg_kill;
531		werase_char = CONTROL('W');
532
533		/*
534		 * Set the modes to the way we want them.
535		 */
536		s.sg_flags |= CBREAK;
537		s.sg_flags &= ~(ECHO|XTABS);
538	} else
539	{
540		/*
541		 * Restore saved modes.
542		 */
543		s = save_term;
544	}
545	ioctl(tty, TIOCSETN, &s);
546    }
547#else
548#ifdef _OSK
549    {
550	struct sgbuf s;
551	static struct sgbuf save_term;
552	static int saved_term = 0;
553
554	if (on)
555	{
556		/*
557		 * Get terminal modes.
558		 */
559		_gs_opt(tty, &s);
560
561		/*
562		 * Save modes and set certain variables dependent on modes.
563		 */
564		if (!saved_term)
565		{
566			save_term = s;
567			saved_term = 1;
568		}
569		erase_char = s.sg_bspch;
570		kill_char = s.sg_dlnch;
571		werase_char = CONTROL('W');
572
573		/*
574		 * Set the modes to the way we want them.
575		 */
576		s.sg_echo = 0;
577		s.sg_eofch = 0;
578		s.sg_pause = 0;
579		s.sg_psch = 0;
580	} else
581	{
582		/*
583		 * Restore saved modes.
584		 */
585		s = save_term;
586	}
587	_ss_opt(tty, &s);
588    }
589#else
590	/* MS-DOS, Windows, or OS2 */
591#if OS2
592	/* OS2 */
593	LSIGNAL(SIGINT, SIG_IGN);
594#endif
595	erase_char = '\b';
596#if MSDOS_COMPILER==DJGPPC
597	kill_char = CONTROL('U');
598	/*
599	 * So that when we shell out or run another program, its
600	 * stdin is in cooked mode.  We do not switch stdin to binary
601	 * mode if fd0 is zero, since that means we were called before
602	 * tty was reopened in open_getchr, in which case we would be
603	 * changing the original stdin device outside less.
604	 */
605	if (fd0 != 0)
606		setmode(0, on ? O_BINARY : O_TEXT);
607#else
608	kill_char = ESC;
609#endif
610	werase_char = CONTROL('W');
611#endif
612#endif
613#endif
614#endif
615	curr_on = on;
616}
617
618#if !MSDOS_COMPILER
619/*
620 * Some glue to prevent calling termcap functions if tgetent() failed.
621 */
622static int hardcopy;
623
624	static char *
625ltget_env(capname)
626	char *capname;
627{
628	char name[16];
629	char *s;
630
631	s = lgetenv("LESS_TERMCAP_DEBUG");
632	if (s != NULL && *s != '\0')
633	{
634		struct env { struct env *next; char *name; char *value; };
635		static struct env *envs = NULL;
636		struct env *p;
637		for (p = envs;  p != NULL;  p = p->next)
638			if (strcmp(p->name, capname) == 0)
639				return p->value;
640		p = (struct env *) ecalloc(1, sizeof(struct env));
641		p->name = save(capname);
642		p->value = (char *) ecalloc(strlen(capname)+3, sizeof(char));
643		sprintf(p->value, "<%s>", capname);
644		p->next = envs;
645		envs = p;
646		return p->value;
647	}
648	strcpy(name, "LESS_TERMCAP_");
649	strcat(name, capname);
650	return (lgetenv(name));
651}
652
653	static int
654ltgetflag(capname)
655	char *capname;
656{
657	char *s;
658
659	if ((s = ltget_env(capname)) != NULL)
660		return (*s != '\0' && *s != '0');
661	if (hardcopy)
662		return (0);
663	return (tgetflag(capname));
664}
665
666	static int
667ltgetnum(capname)
668	char *capname;
669{
670	char *s;
671
672	if ((s = ltget_env(capname)) != NULL)
673		return (atoi(s));
674	if (hardcopy)
675		return (-1);
676	return (tgetnum(capname));
677}
678
679	static char *
680ltgetstr(capname, pp)
681	char *capname;
682	char **pp;
683{
684	char *s;
685
686	if ((s = ltget_env(capname)) != NULL)
687		return (s);
688	if (hardcopy)
689		return (NULL);
690	return (tgetstr(capname, pp));
691}
692#endif /* MSDOS_COMPILER */
693
694/*
695 * Get size of the output screen.
696 */
697	public void
698scrsize()
699{
700	register char *s;
701	int sys_height;
702	int sys_width;
703#if !MSDOS_COMPILER
704	int n;
705#endif
706
707#define	DEF_SC_WIDTH	80
708#if MSDOS_COMPILER
709#define	DEF_SC_HEIGHT	25
710#else
711#define	DEF_SC_HEIGHT	24
712#endif
713
714
715	sys_width = sys_height = 0;
716
717#if MSDOS_COMPILER==MSOFTC
718	{
719		struct videoconfig w;
720		_getvideoconfig(&w);
721		sys_height = w.numtextrows;
722		sys_width = w.numtextcols;
723	}
724#else
725#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
726	{
727		struct text_info w;
728		gettextinfo(&w);
729		sys_height = w.screenheight;
730		sys_width = w.screenwidth;
731	}
732#else
733#if MSDOS_COMPILER==WIN32C
734	{
735		CONSOLE_SCREEN_BUFFER_INFO scr;
736		GetConsoleScreenBufferInfo(con_out, &scr);
737		sys_height = scr.srWindow.Bottom - scr.srWindow.Top + 1;
738		sys_width = scr.srWindow.Right - scr.srWindow.Left + 1;
739	}
740#else
741#if OS2
742	{
743		int s[2];
744		_scrsize(s);
745		sys_width = s[0];
746		sys_height = s[1];
747		/*
748		 * When using terminal emulators for XFree86/OS2, the
749		 * _scrsize function does not work well.
750		 * Call the scrsize.exe program to get the window size.
751		 */
752		windowid = getenv("WINDOWID");
753		if (windowid != NULL)
754		{
755			FILE *fd = popen("scrsize", "rt");
756			if (fd != NULL)
757			{
758				int w, h;
759				fscanf(fd, "%i %i", &w, &h);
760				if (w > 0 && h > 0)
761				{
762					sys_width = w;
763					sys_height = h;
764				}
765				pclose(fd);
766			}
767		}
768	}
769#else
770#ifdef TIOCGWINSZ
771	{
772		struct winsize w;
773		if (ioctl(2, TIOCGWINSZ, &w) == 0)
774		{
775			if (w.ws_row > 0)
776				sys_height = w.ws_row;
777			if (w.ws_col > 0)
778				sys_width = w.ws_col;
779		}
780	}
781#else
782#ifdef WIOCGETD
783	{
784		struct uwdata w;
785		if (ioctl(2, WIOCGETD, &w) == 0)
786		{
787			if (w.uw_height > 0)
788				sys_height = w.uw_height / w.uw_vs;
789			if (w.uw_width > 0)
790				sys_width = w.uw_width / w.uw_hs;
791		}
792	}
793#endif
794#endif
795#endif
796#endif
797#endif
798#endif
799
800	if (sys_height > 0)
801		sc_height = sys_height;
802	else if ((s = lgetenv("LINES")) != NULL)
803		sc_height = atoi(s);
804#if !MSDOS_COMPILER
805	else if ((n = ltgetnum("li")) > 0)
806 		sc_height = n;
807#endif
808	else
809		sc_height = DEF_SC_HEIGHT;
810
811	if (sys_width > 0)
812		sc_width = sys_width;
813	else if ((s = lgetenv("COLUMNS")) != NULL)
814		sc_width = atoi(s);
815#if !MSDOS_COMPILER
816	else if ((n = ltgetnum("co")) > 0)
817 		sc_width = n;
818#endif
819	else
820		sc_width = DEF_SC_WIDTH;
821}
822
823#if MSDOS_COMPILER==MSOFTC
824/*
825 * Figure out how many empty loops it takes to delay a millisecond.
826 */
827	static void
828get_clock()
829{
830	clock_t start;
831
832	/*
833	 * Get synchronized at the start of a tick.
834	 */
835	start = clock();
836	while (clock() == start)
837		;
838	/*
839	 * Now count loops till the next tick.
840	 */
841	start = clock();
842	msec_loops = 0;
843	while (clock() == start)
844		msec_loops++;
845	/*
846	 * Convert from (loops per clock) to (loops per millisecond).
847	 */
848	msec_loops *= CLOCKS_PER_SEC;
849	msec_loops /= 1000;
850}
851
852/*
853 * Delay for a specified number of milliseconds.
854 */
855	static void
856dummy_func()
857{
858	static long delay_dummy = 0;
859	delay_dummy++;
860}
861
862	static void
863delay(msec)
864	int msec;
865{
866	long i;
867
868	while (msec-- > 0)
869	{
870		for (i = 0;  i < msec_loops;  i++)
871		{
872			/*
873			 * Make it look like we're doing something here,
874			 * so the optimizer doesn't remove the whole loop.
875			 */
876			dummy_func();
877		}
878	}
879}
880#endif
881
882/*
883 * Return the characters actually input by a "special" key.
884 */
885	public char *
886special_key_str(key)
887	int key;
888{
889	static char tbuf[40];
890	char *s;
891#if MSDOS_COMPILER || OS2
892	static char k_right[]		= { '\340', PCK_RIGHT, 0 };
893	static char k_left[]		= { '\340', PCK_LEFT, 0  };
894	static char k_ctl_right[]	= { '\340', PCK_CTL_RIGHT, 0  };
895	static char k_ctl_left[]	= { '\340', PCK_CTL_LEFT, 0  };
896	static char k_insert[]		= { '\340', PCK_INSERT, 0  };
897	static char k_delete[]		= { '\340', PCK_DELETE, 0  };
898	static char k_ctl_delete[]	= { '\340', PCK_CTL_DELETE, 0  };
899	static char k_ctl_backspace[]	= { '\177', 0 };
900	static char k_home[]		= { '\340', PCK_HOME, 0 };
901	static char k_end[]		= { '\340', PCK_END, 0 };
902	static char k_up[]		= { '\340', PCK_UP, 0 };
903	static char k_down[]		= { '\340', PCK_DOWN, 0 };
904	static char k_backtab[]		= { '\340', PCK_SHIFT_TAB, 0 };
905	static char k_pagedown[]	= { '\340', PCK_PAGEDOWN, 0 };
906	static char k_pageup[]		= { '\340', PCK_PAGEUP, 0 };
907	static char k_f1[]		= { '\340', PCK_F1, 0 };
908#endif
909#if !MSDOS_COMPILER
910	char *sp = tbuf;
911#endif
912
913	switch (key)
914	{
915#if OS2
916	/*
917	 * If windowid is not NULL, assume less is executed in
918	 * the XFree86 environment.
919	 */
920	case SK_RIGHT_ARROW:
921		s = windowid ? ltgetstr("kr", &sp) : k_right;
922		break;
923	case SK_LEFT_ARROW:
924		s = windowid ? ltgetstr("kl", &sp) : k_left;
925		break;
926	case SK_UP_ARROW:
927		s = windowid ? ltgetstr("ku", &sp) : k_up;
928		break;
929	case SK_DOWN_ARROW:
930		s = windowid ? ltgetstr("kd", &sp) : k_down;
931		break;
932	case SK_PAGE_UP:
933		s = windowid ? ltgetstr("kP", &sp) : k_pageup;
934		break;
935	case SK_PAGE_DOWN:
936		s = windowid ? ltgetstr("kN", &sp) : k_pagedown;
937		break;
938	case SK_HOME:
939		s = windowid ? ltgetstr("kh", &sp) : k_home;
940		break;
941	case SK_END:
942		s = windowid ? ltgetstr("@7", &sp) : k_end;
943		break;
944	case SK_DELETE:
945		if (windowid)
946		{
947			s = ltgetstr("kD", &sp);
948			if (s == NULL)
949			{
950				tbuf[0] = '\177';
951				tbuf[1] = '\0';
952				s = tbuf;
953			}
954		} else
955			s = k_delete;
956		break;
957#endif
958#if MSDOS_COMPILER
959	case SK_RIGHT_ARROW:
960		s = k_right;
961		break;
962	case SK_LEFT_ARROW:
963		s = k_left;
964		break;
965	case SK_UP_ARROW:
966		s = k_up;
967		break;
968	case SK_DOWN_ARROW:
969		s = k_down;
970		break;
971	case SK_PAGE_UP:
972		s = k_pageup;
973		break;
974	case SK_PAGE_DOWN:
975		s = k_pagedown;
976		break;
977	case SK_HOME:
978		s = k_home;
979		break;
980	case SK_END:
981		s = k_end;
982		break;
983	case SK_DELETE:
984		s = k_delete;
985		break;
986#endif
987#if MSDOS_COMPILER || OS2
988	case SK_INSERT:
989		s = k_insert;
990		break;
991	case SK_CTL_LEFT_ARROW:
992		s = k_ctl_left;
993		break;
994	case SK_CTL_RIGHT_ARROW:
995		s = k_ctl_right;
996		break;
997	case SK_CTL_BACKSPACE:
998		s = k_ctl_backspace;
999		break;
1000	case SK_CTL_DELETE:
1001		s = k_ctl_delete;
1002		break;
1003	case SK_F1:
1004		s = k_f1;
1005		break;
1006	case SK_BACKTAB:
1007		s = k_backtab;
1008		break;
1009#else
1010	case SK_RIGHT_ARROW:
1011		s = ltgetstr("kr", &sp);
1012		break;
1013	case SK_LEFT_ARROW:
1014		s = ltgetstr("kl", &sp);
1015		break;
1016	case SK_UP_ARROW:
1017		s = ltgetstr("ku", &sp);
1018		break;
1019	case SK_DOWN_ARROW:
1020		s = ltgetstr("kd", &sp);
1021		break;
1022	case SK_PAGE_UP:
1023		s = ltgetstr("kP", &sp);
1024		break;
1025	case SK_PAGE_DOWN:
1026		s = ltgetstr("kN", &sp);
1027		break;
1028	case SK_HOME:
1029		s = ltgetstr("kh", &sp);
1030		break;
1031	case SK_END:
1032		s = ltgetstr("@7", &sp);
1033		break;
1034	case SK_DELETE:
1035		s = ltgetstr("kD", &sp);
1036		if (s == NULL)
1037		{
1038			tbuf[0] = '\177';
1039			tbuf[1] = '\0';
1040			s = tbuf;
1041		}
1042		break;
1043#endif
1044	case SK_CONTROL_K:
1045		tbuf[0] = CONTROL('K');
1046		tbuf[1] = '\0';
1047		s = tbuf;
1048		break;
1049	default:
1050		return (NULL);
1051	}
1052	return (s);
1053}
1054
1055/*
1056 * Get terminal capabilities via termcap.
1057 */
1058	public void
1059get_term()
1060{
1061#if MSDOS_COMPILER
1062	auto_wrap = 1;
1063	ignaw = 0;
1064	can_goto_line = 1;
1065	clear_bg = 1;
1066	/*
1067	 * Set up default colors.
1068	 * The xx_s_width and xx_e_width vars are already initialized to 0.
1069	 */
1070#if MSDOS_COMPILER==MSOFTC
1071	sy_bg_color = _getbkcolor();
1072	sy_fg_color = _gettextcolor();
1073	get_clock();
1074#else
1075#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
1076    {
1077	struct text_info w;
1078	gettextinfo(&w);
1079	sy_bg_color = (w.attribute >> 4) & 0x0F;
1080	sy_fg_color = (w.attribute >> 0) & 0x0F;
1081    }
1082#else
1083#if MSDOS_COMPILER==WIN32C
1084    {
1085	DWORD nread;
1086	CONSOLE_SCREEN_BUFFER_INFO scr;
1087
1088	con_out_save = con_out = GetStdHandle(STD_OUTPUT_HANDLE);
1089	/*
1090	 * Always open stdin in binary. Note this *must* be done
1091	 * before any file operations have been done on fd0.
1092	 */
1093	SET_BINARY(0);
1094	GetConsoleScreenBufferInfo(con_out, &scr);
1095	ReadConsoleOutputAttribute(con_out, &curr_attr,
1096					1, scr.dwCursorPosition, &nread);
1097	sy_bg_color = (curr_attr & BG_COLORS) >> 4; /* normalize */
1098	sy_fg_color = curr_attr & FG_COLORS;
1099    }
1100#endif
1101#endif
1102#endif
1103	nm_fg_color = sy_fg_color;
1104	nm_bg_color = sy_bg_color;
1105	bo_fg_color = 11;
1106	bo_bg_color = 0;
1107	ul_fg_color = 9;
1108	ul_bg_color = 0;
1109	so_fg_color = 15;
1110	so_bg_color = 9;
1111	bl_fg_color = 15;
1112	bl_bg_color = 0;
1113
1114	/*
1115	 * Get size of the screen.
1116	 */
1117	scrsize();
1118	pos_init();
1119
1120
1121#else /* !MSDOS_COMPILER */
1122
1123	char *sp;
1124	register char *t1, *t2;
1125	char *term;
1126	char termbuf[TERMBUF_SIZE];
1127
1128	static char sbuf[TERMSBUF_SIZE];
1129
1130#if OS2
1131	/*
1132	 * Make sure the termcap database is available.
1133	 */
1134	sp = lgetenv("TERMCAP");
1135	if (sp == NULL || *sp == '\0')
1136	{
1137		char *termcap;
1138		if ((sp = homefile("termcap.dat")) != NULL)
1139		{
1140			termcap = (char *) ecalloc(strlen(sp)+9, sizeof(char));
1141			sprintf(termcap, "TERMCAP=%s", sp);
1142			free(sp);
1143			putenv(termcap);
1144		}
1145	}
1146#endif
1147	/*
1148	 * Find out what kind of terminal this is.
1149	 */
1150 	if ((term = lgetenv("TERM")) == NULL)
1151 		term = DEFAULT_TERM;
1152	hardcopy = 0;
1153 	if (tgetent(termbuf, term) != TGETENT_OK)
1154 		hardcopy = 1;
1155 	if (ltgetflag("hc"))
1156		hardcopy = 1;
1157
1158	/*
1159	 * Get size of the screen.
1160	 */
1161	scrsize();
1162	pos_init();
1163
1164	auto_wrap = ltgetflag("am");
1165	ignaw = ltgetflag("xn");
1166	above_mem = ltgetflag("da");
1167	below_mem = ltgetflag("db");
1168	clear_bg = ltgetflag("ut");
1169
1170	/*
1171	 * Assumes termcap variable "sg" is the printing width of:
1172	 * the standout sequence, the end standout sequence,
1173	 * the underline sequence, the end underline sequence,
1174	 * the boldface sequence, and the end boldface sequence.
1175	 */
1176	if ((so_s_width = ltgetnum("sg")) < 0)
1177		so_s_width = 0;
1178	so_e_width = so_s_width;
1179
1180	bo_s_width = bo_e_width = so_s_width;
1181	ul_s_width = ul_e_width = so_s_width;
1182	bl_s_width = bl_e_width = so_s_width;
1183
1184#if HILITE_SEARCH
1185	if (so_s_width > 0 || so_e_width > 0)
1186		/*
1187		 * Disable highlighting by default on magic cookie terminals.
1188		 * Turning on highlighting might change the displayed width
1189		 * of a line, causing the display to get messed up.
1190		 * The user can turn it back on with -g,
1191		 * but she won't like the results.
1192		 */
1193		hilite_search = 0;
1194#endif
1195
1196	/*
1197	 * Get various string-valued capabilities.
1198	 */
1199	sp = sbuf;
1200
1201#if HAVE_OSPEED
1202	sc_pad = ltgetstr("pc", &sp);
1203	if (sc_pad != NULL)
1204		PC = *sc_pad;
1205#endif
1206
1207	sc_s_keypad = ltgetstr("ks", &sp);
1208	if (sc_s_keypad == NULL)
1209		sc_s_keypad = "";
1210	sc_e_keypad = ltgetstr("ke", &sp);
1211	if (sc_e_keypad == NULL)
1212		sc_e_keypad = "";
1213
1214	/*
1215	 * This loses for terminals with termcap entries with ti/te strings
1216	 * that switch to/from an alternate screen, and we're in quit_at_eof
1217	 * (eg, more(1)).
1218 	 */
1219	if (!quit_at_eof && !less_is_more) {
1220		sc_init = ltgetstr("ti", &sp);
1221		sc_deinit = ltgetstr("te", &sp);
1222	}
1223
1224	if (sc_init == NULL)
1225		sc_init = "";
1226
1227	if (sc_deinit == NULL)
1228		sc_deinit = "";
1229
1230	sc_eol_clear = ltgetstr("ce", &sp);
1231	if (sc_eol_clear == NULL || *sc_eol_clear == '\0')
1232	{
1233		missing_cap = 1;
1234		sc_eol_clear = "";
1235	}
1236
1237	sc_eos_clear = ltgetstr("cd", &sp);
1238	if (below_mem && (sc_eos_clear == NULL || *sc_eos_clear == '\0'))
1239	{
1240		missing_cap = 1;
1241		sc_eos_clear = "";
1242	}
1243
1244	sc_clear = ltgetstr("cl", &sp);
1245	if (sc_clear == NULL || *sc_clear == '\0')
1246	{
1247		missing_cap = 1;
1248		sc_clear = "\n\n";
1249	}
1250
1251	sc_move = ltgetstr("cm", &sp);
1252	if (sc_move == NULL || *sc_move == '\0')
1253	{
1254		/*
1255		 * This is not an error here, because we don't
1256		 * always need sc_move.
1257		 * We need it only if we don't have home or lower-left.
1258		 */
1259		sc_move = "";
1260		can_goto_line = 0;
1261	} else
1262		can_goto_line = 1;
1263
1264	tmodes("so", "se", &sc_s_in, &sc_s_out, "", "", &sp);
1265	tmodes("us", "ue", &sc_u_in, &sc_u_out, sc_s_in, sc_s_out, &sp);
1266	tmodes("md", "me", &sc_b_in, &sc_b_out, sc_s_in, sc_s_out, &sp);
1267	tmodes("mb", "me", &sc_bl_in, &sc_bl_out, sc_s_in, sc_s_out, &sp);
1268
1269	sc_visual_bell = ltgetstr("vb", &sp);
1270	if (sc_visual_bell == NULL)
1271		sc_visual_bell = "";
1272
1273	if (ltgetflag("bs"))
1274		sc_backspace = "\b";
1275	else
1276	{
1277		sc_backspace = ltgetstr("bc", &sp);
1278		if (sc_backspace == NULL || *sc_backspace == '\0')
1279			sc_backspace = "\b";
1280	}
1281
1282	/*
1283	 * Choose between using "ho" and "cm" ("home" and "cursor move")
1284	 * to move the cursor to the upper left corner of the screen.
1285	 */
1286	t1 = ltgetstr("ho", &sp);
1287	if (t1 == NULL)
1288		t1 = "";
1289	if (*sc_move == '\0')
1290		t2 = "";
1291	else
1292	{
1293		strcpy(sp, tgoto(sc_move, 0, 0));
1294		t2 = sp;
1295		sp += strlen(sp) + 1;
1296	}
1297	sc_home = cheaper(t1, t2, "|\b^");
1298
1299	/*
1300	 * Choose between using "ll" and "cm"  ("lower left" and "cursor move")
1301	 * to move the cursor to the lower left corner of the screen.
1302	 */
1303	t1 = ltgetstr("ll", &sp);
1304	if (t1 == NULL)
1305		t1 = "";
1306	if (*sc_move == '\0')
1307		t2 = "";
1308	else
1309	{
1310		strcpy(sp, tgoto(sc_move, 0, sc_height-1));
1311		t2 = sp;
1312		sp += strlen(sp) + 1;
1313	}
1314	sc_lower_left = cheaper(t1, t2, "\r");
1315
1316	/*
1317	 * Get carriage return string.
1318	 */
1319	sc_return = ltgetstr("cr", &sp);
1320	if (sc_return == NULL)
1321		sc_return = "\r";
1322
1323	/*
1324	 * Choose between using "al" or "sr" ("add line" or "scroll reverse")
1325	 * to add a line at the top of the screen.
1326	 */
1327	t1 = ltgetstr("al", &sp);
1328	if (t1 == NULL)
1329		t1 = "";
1330	t2 = ltgetstr("sr", &sp);
1331	if (t2 == NULL)
1332		t2 = "";
1333#if OS2
1334	if (*t1 == '\0' && *t2 == '\0')
1335		sc_addline = "";
1336	else
1337#endif
1338	if (above_mem)
1339		sc_addline = t1;
1340	else
1341		sc_addline = cheaper(t1, t2, "");
1342	if (*sc_addline == '\0')
1343	{
1344		/*
1345		 * Force repaint on any backward movement.
1346		 */
1347		no_back_scroll = 1;
1348	}
1349#endif /* MSDOS_COMPILER */
1350}
1351
1352#if !MSDOS_COMPILER
1353/*
1354 * Return the cost of displaying a termcap string.
1355 * We use the trick of calling tputs, but as a char printing function
1356 * we give it inc_costcount, which just increments "costcount".
1357 * This tells us how many chars would be printed by using this string.
1358 * {{ Couldn't we just use strlen? }}
1359 */
1360static int costcount;
1361
1362/*ARGSUSED*/
1363	static int
1364inc_costcount(c)
1365	int c;
1366{
1367	costcount++;
1368	return (c);
1369}
1370
1371	static int
1372cost(t)
1373	char *t;
1374{
1375	costcount = 0;
1376	tputs(t, sc_height, inc_costcount);
1377	return (costcount);
1378}
1379
1380/*
1381 * Return the "best" of the two given termcap strings.
1382 * The best, if both exist, is the one with the lower
1383 * cost (see cost() function).
1384 */
1385	static char *
1386cheaper(t1, t2, def)
1387	char *t1, *t2;
1388	char *def;
1389{
1390	if (*t1 == '\0' && *t2 == '\0')
1391	{
1392		missing_cap = 1;
1393		return (def);
1394	}
1395	if (*t1 == '\0')
1396		return (t2);
1397	if (*t2 == '\0')
1398		return (t1);
1399	if (cost(t1) < cost(t2))
1400		return (t1);
1401	return (t2);
1402}
1403
1404	static void
1405tmodes(incap, outcap, instr, outstr, def_instr, def_outstr, spp)
1406	char *incap;
1407	char *outcap;
1408	char **instr;
1409	char **outstr;
1410	char *def_instr;
1411	char *def_outstr;
1412	char **spp;
1413{
1414	*instr = ltgetstr(incap, spp);
1415	if (*instr == NULL)
1416	{
1417		/* Use defaults. */
1418		*instr = def_instr;
1419		*outstr = def_outstr;
1420		return;
1421	}
1422
1423	*outstr = ltgetstr(outcap, spp);
1424	if (*outstr == NULL)
1425		/* No specific out capability; use "me". */
1426		*outstr = ltgetstr("me", spp);
1427	if (*outstr == NULL)
1428		/* Don't even have "me"; use a null string. */
1429		*outstr = "";
1430}
1431
1432#endif /* MSDOS_COMPILER */
1433
1434
1435/*
1436 * Below are the functions which perform all the
1437 * terminal-specific screen manipulation.
1438 */
1439
1440
1441#if MSDOS_COMPILER
1442
1443#if MSDOS_COMPILER==WIN32C
1444	static void
1445_settextposition(int row, int col)
1446{
1447	COORD cpos;
1448	CONSOLE_SCREEN_BUFFER_INFO csbi;
1449
1450	GetConsoleScreenBufferInfo(con_out, &csbi);
1451	cpos.X = csbi.srWindow.Left + (col - 1);
1452	cpos.Y = csbi.srWindow.Top + (row - 1);
1453	SetConsoleCursorPosition(con_out, cpos);
1454}
1455#endif
1456
1457/*
1458 * Initialize the screen to the correct color at startup.
1459 */
1460	static void
1461initcolor()
1462{
1463	SETCOLORS(nm_fg_color, nm_bg_color);
1464#if 0
1465	/*
1466	 * This clears the screen at startup.  This is different from
1467	 * the behavior of other versions of less.  Disable it for now.
1468	 */
1469	char *blanks;
1470	int row;
1471	int col;
1472
1473	/*
1474	 * Create a complete, blank screen using "normal" colors.
1475	 */
1476	SETCOLORS(nm_fg_color, nm_bg_color);
1477	blanks = (char *) ecalloc(width+1, sizeof(char));
1478	for (col = 0;  col < sc_width;  col++)
1479		blanks[col] = ' ';
1480	blanks[sc_width] = '\0';
1481	for (row = 0;  row < sc_height;  row++)
1482		_outtext(blanks);
1483	free(blanks);
1484#endif
1485}
1486#endif
1487
1488#if MSDOS_COMPILER==WIN32C
1489
1490/*
1491 * Termcap-like init with a private win32 console.
1492 */
1493	static void
1494win32_init_term()
1495{
1496	CONSOLE_SCREEN_BUFFER_INFO scr;
1497	COORD size;
1498
1499	if (con_out_save == INVALID_HANDLE_VALUE)
1500		return;
1501
1502	GetConsoleScreenBufferInfo(con_out_save, &scr);
1503
1504	if (con_out_ours == INVALID_HANDLE_VALUE)
1505	{
1506		/*
1507		 * Create our own screen buffer, so that we
1508		 * may restore the original when done.
1509		 */
1510		con_out_ours = CreateConsoleScreenBuffer(
1511			GENERIC_WRITE | GENERIC_READ,
1512			FILE_SHARE_WRITE | FILE_SHARE_READ,
1513			(LPSECURITY_ATTRIBUTES) NULL,
1514			CONSOLE_TEXTMODE_BUFFER,
1515			(LPVOID) NULL);
1516	}
1517
1518	size.X = scr.srWindow.Right - scr.srWindow.Left + 1;
1519	size.Y = scr.srWindow.Bottom - scr.srWindow.Top + 1;
1520	SetConsoleScreenBufferSize(con_out_ours, size);
1521	SetConsoleActiveScreenBuffer(con_out_ours);
1522	con_out = con_out_ours;
1523}
1524
1525/*
1526 * Restore the startup console.
1527 */
1528static void
1529win32_deinit_term()
1530{
1531	if (con_out_save == INVALID_HANDLE_VALUE)
1532		return;
1533	if (quitting)
1534		(void) CloseHandle(con_out_ours);
1535	SetConsoleActiveScreenBuffer(con_out_save);
1536	con_out = con_out_save;
1537}
1538
1539#endif
1540
1541/*
1542 * Initialize terminal
1543 */
1544	public void
1545init()
1546{
1547#if !MSDOS_COMPILER
1548	if (!no_init)
1549		tputs(sc_init, sc_height, putchr);
1550	if (!no_keypad)
1551		tputs(sc_s_keypad, sc_height, putchr);
1552	if (top_scroll)
1553	{
1554		int i;
1555
1556		/*
1557		 * This is nice to terminals with no alternate screen,
1558		 * but with saved scrolled-off-the-top lines.  This way,
1559		 * no previous line is lost, but we start with a whole
1560		 * screen to ourself.
1561		 */
1562		for (i = 1; i < sc_height; i++)
1563			putchr('\n');
1564	}
1565#else
1566#if MSDOS_COMPILER==WIN32C
1567	if (!no_init)
1568		win32_init_term();
1569#endif
1570	initcolor();
1571	flush();
1572#endif
1573	init_done = 1;
1574}
1575
1576/*
1577 * Deinitialize terminal
1578 */
1579	public void
1580deinit()
1581{
1582	if (!init_done)
1583		return;
1584#if !MSDOS_COMPILER
1585	if (!no_keypad)
1586		tputs(sc_e_keypad, sc_height, putchr);
1587	if (!no_init)
1588		tputs(sc_deinit, sc_height, putchr);
1589#else
1590	/* Restore system colors. */
1591	SETCOLORS(sy_fg_color, sy_bg_color);
1592#if MSDOS_COMPILER==WIN32C
1593	if (!no_init)
1594		win32_deinit_term();
1595#else
1596	/* Need clreol to make SETCOLORS take effect. */
1597	clreol();
1598#endif
1599#endif
1600	init_done = 0;
1601}
1602
1603/*
1604 * Home cursor (move to upper left corner of screen).
1605 */
1606	public void
1607home()
1608{
1609#if !MSDOS_COMPILER
1610	tputs(sc_home, 1, putchr);
1611#else
1612	flush();
1613	_settextposition(1,1);
1614#endif
1615}
1616
1617/*
1618 * Add a blank line (called with cursor at home).
1619 * Should scroll the display down.
1620 */
1621	public void
1622add_line()
1623{
1624#if !MSDOS_COMPILER
1625	tputs(sc_addline, sc_height, putchr);
1626#else
1627	flush();
1628#if MSDOS_COMPILER==MSOFTC
1629	_scrolltextwindow(_GSCROLLDOWN);
1630	_settextposition(1,1);
1631#else
1632#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
1633	movetext(1,1, sc_width,sc_height-1, 1,2);
1634	gotoxy(1,1);
1635	clreol();
1636#else
1637#if MSDOS_COMPILER==WIN32C
1638    {
1639	CHAR_INFO fillchar;
1640	SMALL_RECT rcSrc, rcClip;
1641	COORD new_org;
1642	CONSOLE_SCREEN_BUFFER_INFO csbi;
1643
1644	GetConsoleScreenBufferInfo(con_out,&csbi);
1645
1646	/* The clip rectangle is the entire visible screen. */
1647	rcClip.Left = csbi.srWindow.Left;
1648	rcClip.Top = csbi.srWindow.Top;
1649	rcClip.Right = csbi.srWindow.Right;
1650	rcClip.Bottom = csbi.srWindow.Bottom;
1651
1652	/* The source rectangle is the visible screen minus the last line. */
1653	rcSrc = rcClip;
1654	rcSrc.Bottom--;
1655
1656	/* Move the top left corner of the source window down one row. */
1657	new_org.X = rcSrc.Left;
1658	new_org.Y = rcSrc.Top + 1;
1659
1660	/* Fill the right character and attributes. */
1661	fillchar.Char.AsciiChar = ' ';
1662	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
1663	fillchar.Attributes = curr_attr;
1664	ScrollConsoleScreenBuffer(con_out, &rcSrc, &rcClip, new_org, &fillchar);
1665	_settextposition(1,1);
1666    }
1667#endif
1668#endif
1669#endif
1670#endif
1671}
1672
1673#if 0
1674/*
1675 * Remove the n topmost lines and scroll everything below it in the
1676 * window upward.  This is needed to stop leaking the topmost line
1677 * into the scrollback buffer when we go down-one-line (in WIN32).
1678 */
1679	public void
1680remove_top(n)
1681	int n;
1682{
1683#if MSDOS_COMPILER==WIN32C
1684	SMALL_RECT rcSrc, rcClip;
1685	CHAR_INFO fillchar;
1686	COORD new_org;
1687	CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */
1688
1689	if (n >= sc_height - 1)
1690	{
1691		clear();
1692		home();
1693		return;
1694	}
1695
1696	flush();
1697
1698	GetConsoleScreenBufferInfo(con_out, &csbi);
1699
1700	/* Get the extent of all-visible-rows-but-the-last. */
1701	rcSrc.Left    = csbi.srWindow.Left;
1702	rcSrc.Top     = csbi.srWindow.Top + n;
1703	rcSrc.Right   = csbi.srWindow.Right;
1704	rcSrc.Bottom  = csbi.srWindow.Bottom;
1705
1706	/* Get the clip rectangle. */
1707	rcClip.Left   = rcSrc.Left;
1708	rcClip.Top    = csbi.srWindow.Top;
1709	rcClip.Right  = rcSrc.Right;
1710	rcClip.Bottom = rcSrc.Bottom ;
1711
1712	/* Move the source window up n rows. */
1713	new_org.X = rcSrc.Left;
1714	new_org.Y = rcSrc.Top - n;
1715
1716	/* Fill the right character and attributes. */
1717	fillchar.Char.AsciiChar = ' ';
1718	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
1719	fillchar.Attributes = curr_attr;
1720
1721	ScrollConsoleScreenBuffer(con_out, &rcSrc, &rcClip, new_org, &fillchar);
1722
1723	/* Position cursor on first blank line. */
1724	goto_line(sc_height - n - 1);
1725#endif
1726}
1727#endif
1728
1729#if MSDOS_COMPILER==WIN32C
1730/*
1731 * Clear the screen.
1732 */
1733	static void
1734win32_clear()
1735{
1736	/*
1737	 * This will clear only the currently visible rows of the NT
1738	 * console buffer, which means none of the precious scrollback
1739	 * rows are touched making for faster scrolling.  Note that, if
1740	 * the window has fewer columns than the console buffer (i.e.
1741	 * there is a horizontal scrollbar as well), the entire width
1742	 * of the visible rows will be cleared.
1743	 */
1744	COORD topleft;
1745	DWORD nchars;
1746	DWORD winsz;
1747	CONSOLE_SCREEN_BUFFER_INFO csbi;
1748
1749	/* get the number of cells in the current buffer */
1750	GetConsoleScreenBufferInfo(con_out, &csbi);
1751	winsz = csbi.dwSize.X * (csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
1752	topleft.X = 0;
1753	topleft.Y = csbi.srWindow.Top;
1754
1755	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
1756	FillConsoleOutputCharacter(con_out, ' ', winsz, topleft, &nchars);
1757	FillConsoleOutputAttribute(con_out, curr_attr, winsz, topleft, &nchars);
1758}
1759
1760/*
1761 * Remove the n topmost lines and scroll everything below it in the
1762 * window upward.
1763 */
1764	public void
1765win32_scroll_up(n)
1766	int n;
1767{
1768	SMALL_RECT rcSrc, rcClip;
1769	CHAR_INFO fillchar;
1770	COORD topleft;
1771	COORD new_org;
1772	DWORD nchars;
1773	DWORD size;
1774	CONSOLE_SCREEN_BUFFER_INFO csbi;
1775
1776	if (n <= 0)
1777		return;
1778
1779	if (n >= sc_height - 1)
1780	{
1781		win32_clear();
1782		_settextposition(1,1);
1783		return;
1784	}
1785
1786	/* Get the extent of what will remain visible after scrolling. */
1787	GetConsoleScreenBufferInfo(con_out, &csbi);
1788	rcSrc.Left    = csbi.srWindow.Left;
1789	rcSrc.Top     = csbi.srWindow.Top + n;
1790	rcSrc.Right   = csbi.srWindow.Right;
1791	rcSrc.Bottom  = csbi.srWindow.Bottom;
1792
1793	/* Get the clip rectangle. */
1794	rcClip.Left   = rcSrc.Left;
1795	rcClip.Top    = csbi.srWindow.Top;
1796	rcClip.Right  = rcSrc.Right;
1797	rcClip.Bottom = rcSrc.Bottom ;
1798
1799	/* Move the source text to the top of the screen. */
1800	new_org.X = rcSrc.Left;
1801	new_org.Y = 0;
1802
1803	/* Fill the right character and attributes. */
1804	fillchar.Char.AsciiChar = ' ';
1805	fillchar.Attributes = MAKEATTR(nm_fg_color, nm_bg_color);
1806
1807	/* Scroll the window. */
1808	SetConsoleTextAttribute(con_out, fillchar.Attributes);
1809	ScrollConsoleScreenBuffer(con_out, &rcSrc, &rcClip, new_org, &fillchar);
1810
1811	/* Clear remaining lines at bottom. */
1812	topleft.X = csbi.dwCursorPosition.X;
1813	topleft.Y = rcSrc.Bottom - n;
1814	size = (n * csbi.dwSize.X) + (rcSrc.Right - topleft.X);
1815	FillConsoleOutputCharacter(con_out, ' ', size, topleft,
1816		&nchars);
1817	FillConsoleOutputAttribute(con_out, fillchar.Attributes, size, topleft,
1818		&nchars);
1819	SetConsoleTextAttribute(con_out, curr_attr);
1820
1821	/* Move cursor n lines up from where it was. */
1822	csbi.dwCursorPosition.Y -= n;
1823	SetConsoleCursorPosition(con_out, csbi.dwCursorPosition);
1824}
1825#endif
1826
1827/*
1828 * Move cursor to lower left corner of screen.
1829 */
1830	public void
1831lower_left()
1832{
1833#if !MSDOS_COMPILER
1834	tputs(sc_lower_left, 1, putchr);
1835#else
1836	flush();
1837	_settextposition(sc_height, 1);
1838#endif
1839}
1840
1841/*
1842 * Move cursor to left position of current line.
1843 */
1844	public void
1845line_left()
1846{
1847#if !MSDOS_COMPILER
1848	tputs(sc_return, 1, putchr);
1849#else
1850	int row;
1851	flush();
1852#if MSDOS_COMPILER==WIN32C
1853	{
1854		CONSOLE_SCREEN_BUFFER_INFO scr;
1855		GetConsoleScreenBufferInfo(con_out, &scr);
1856		row = scr.dwCursorPosition.Y - scr.srWindow.Top + 1;
1857	}
1858#else
1859	{
1860		struct rccoord tpos = _gettextposition();
1861		row = tpos.row;
1862	}
1863#endif
1864	_settextposition(row, 1);
1865#endif
1866}
1867
1868/*
1869 * Check if the console size has changed and reset internals
1870 * (in lieu of SIGWINCH for WIN32).
1871 */
1872	public void
1873check_winch()
1874{
1875#if MSDOS_COMPILER==WIN32C
1876	CONSOLE_SCREEN_BUFFER_INFO scr;
1877	COORD size;
1878
1879	if (con_out == INVALID_HANDLE_VALUE)
1880		return;
1881
1882	flush();
1883	GetConsoleScreenBufferInfo(con_out, &scr);
1884	size.Y = scr.srWindow.Bottom - scr.srWindow.Top + 1;
1885	size.X = scr.srWindow.Right - scr.srWindow.Left + 1;
1886	if (size.Y != sc_height || size.X != sc_width)
1887	{
1888		sc_height = size.Y;
1889		sc_width = size.X;
1890		if (!no_init && con_out_ours == con_out)
1891			SetConsoleScreenBufferSize(con_out, size);
1892		pos_init();
1893		wscroll = (sc_height + 1) / 2;
1894		screen_trashed = 1;
1895	}
1896#endif
1897}
1898
1899/*
1900 * Goto a specific line on the screen.
1901 */
1902	public void
1903goto_line(slinenum)
1904	int slinenum;
1905{
1906#if !MSDOS_COMPILER
1907	tputs(tgoto(sc_move, 0, slinenum), 1, putchr);
1908#else
1909	flush();
1910	_settextposition(slinenum+1, 1);
1911#endif
1912}
1913
1914#if MSDOS_COMPILER==MSOFTC || MSDOS_COMPILER==BORLANDC
1915/*
1916 * Create an alternate screen which is all white.
1917 * This screen is used to create a "flash" effect, by displaying it
1918 * briefly and then switching back to the normal screen.
1919 * {{ Yuck!  There must be a better way to get a visual bell. }}
1920 */
1921	static void
1922create_flash()
1923{
1924#if MSDOS_COMPILER==MSOFTC
1925	struct videoconfig w;
1926	char *blanks;
1927	int row, col;
1928
1929	_getvideoconfig(&w);
1930	videopages = w.numvideopages;
1931	if (videopages < 2)
1932	{
1933		at_enter(AT_STANDOUT);
1934		at_exit();
1935	} else
1936	{
1937		_setactivepage(1);
1938		at_enter(AT_STANDOUT);
1939		blanks = (char *) ecalloc(w.numtextcols, sizeof(char));
1940		for (col = 0;  col < w.numtextcols;  col++)
1941			blanks[col] = ' ';
1942		for (row = w.numtextrows;  row > 0;  row--)
1943			_outmem(blanks, w.numtextcols);
1944		_setactivepage(0);
1945		_setvisualpage(0);
1946		free(blanks);
1947		at_exit();
1948	}
1949#else
1950#if MSDOS_COMPILER==BORLANDC
1951	register int n;
1952
1953	whitescreen = (unsigned short *)
1954		malloc(sc_width * sc_height * sizeof(short));
1955	if (whitescreen == NULL)
1956		return;
1957	for (n = 0;  n < sc_width * sc_height;  n++)
1958		whitescreen[n] = 0x7020;
1959#else
1960#if MSDOS_COMPILER==WIN32C
1961	register int n;
1962
1963	whitescreen = (WORD *)
1964		malloc(sc_height * sc_width * sizeof(WORD));
1965	if (whitescreen == NULL)
1966		return;
1967	/* Invert the standard colors. */
1968	for (n = 0;  n < sc_width * sc_height;  n++)
1969		whitescreen[n] = (WORD)((nm_fg_color << 4) | nm_bg_color);
1970#endif
1971#endif
1972#endif
1973	flash_created = 1;
1974}
1975#endif /* MSDOS_COMPILER */
1976
1977/*
1978 * Output the "visual bell", if there is one.
1979 */
1980	public void
1981vbell()
1982{
1983#if !MSDOS_COMPILER
1984	if (*sc_visual_bell == '\0')
1985		return;
1986	tputs(sc_visual_bell, sc_height, putchr);
1987#else
1988#if MSDOS_COMPILER==DJGPPC
1989	ScreenVisualBell();
1990#else
1991#if MSDOS_COMPILER==MSOFTC
1992	/*
1993	 * Create a flash screen on the second video page.
1994	 * Switch to that page, then switch back.
1995	 */
1996	if (!flash_created)
1997		create_flash();
1998	if (videopages < 2)
1999		return;
2000	_setvisualpage(1);
2001	delay(100);
2002	_setvisualpage(0);
2003#else
2004#if MSDOS_COMPILER==BORLANDC
2005	unsigned short *currscreen;
2006
2007	/*
2008	 * Get a copy of the current screen.
2009	 * Display the flash screen.
2010	 * Then restore the old screen.
2011	 */
2012	if (!flash_created)
2013		create_flash();
2014	if (whitescreen == NULL)
2015		return;
2016	currscreen = (unsigned short *)
2017		malloc(sc_width * sc_height * sizeof(short));
2018	if (currscreen == NULL) return;
2019	gettext(1, 1, sc_width, sc_height, currscreen);
2020	puttext(1, 1, sc_width, sc_height, whitescreen);
2021	delay(100);
2022	puttext(1, 1, sc_width, sc_height, currscreen);
2023	free(currscreen);
2024#else
2025#if MSDOS_COMPILER==WIN32C
2026	/* paint screen with an inverse color */
2027	clear();
2028
2029	/* leave it displayed for 100 msec. */
2030	Sleep(100);
2031
2032	/* restore with a redraw */
2033	repaint();
2034#endif
2035#endif
2036#endif
2037#endif
2038#endif
2039}
2040
2041/*
2042 * Make a noise.
2043 */
2044	static void
2045beep()
2046{
2047#if !MSDOS_COMPILER
2048	putchr(CONTROL('G'));
2049#else
2050#if MSDOS_COMPILER==WIN32C
2051	MessageBeep(0);
2052#else
2053	write(1, "\7", 1);
2054#endif
2055#endif
2056}
2057
2058/*
2059 * Ring the terminal bell.
2060 */
2061	public void
2062bell()
2063{
2064	if (quiet == VERY_QUIET)
2065		vbell();
2066	else
2067		beep();
2068}
2069
2070/*
2071 * Clear the screen.
2072 */
2073	public void
2074clear()
2075{
2076#if !MSDOS_COMPILER
2077	tputs(sc_clear, sc_height, putchr);
2078#else
2079	flush();
2080#if MSDOS_COMPILER==WIN32C
2081	win32_clear();
2082#else
2083	_clearscreen(_GCLEARSCREEN);
2084#endif
2085#endif
2086}
2087
2088/*
2089 * Clear from the cursor to the end of the cursor's line.
2090 * {{ This must not move the cursor. }}
2091 */
2092	public void
2093clear_eol()
2094{
2095#if !MSDOS_COMPILER
2096	tputs(sc_eol_clear, 1, putchr);
2097#else
2098#if MSDOS_COMPILER==MSOFTC
2099	short top, left;
2100	short bot, right;
2101	struct rccoord tpos;
2102
2103	flush();
2104	/*
2105	 * Save current state.
2106	 */
2107	tpos = _gettextposition();
2108	_gettextwindow(&top, &left, &bot, &right);
2109	/*
2110	 * Set a temporary window to the current line,
2111	 * from the cursor's position to the right edge of the screen.
2112	 * Then clear that window.
2113	 */
2114	_settextwindow(tpos.row, tpos.col, tpos.row, sc_width);
2115	_clearscreen(_GWINDOW);
2116	/*
2117	 * Restore state.
2118	 */
2119	_settextwindow(top, left, bot, right);
2120	_settextposition(tpos.row, tpos.col);
2121#else
2122#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
2123	flush();
2124	clreol();
2125#else
2126#if MSDOS_COMPILER==WIN32C
2127	DWORD           nchars;
2128	COORD           cpos;
2129	CONSOLE_SCREEN_BUFFER_INFO scr;
2130
2131	flush();
2132	memset(&scr, 0, sizeof(scr));
2133	GetConsoleScreenBufferInfo(con_out, &scr);
2134	cpos.X = scr.dwCursorPosition.X;
2135	cpos.Y = scr.dwCursorPosition.Y;
2136	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
2137	FillConsoleOutputAttribute(con_out, curr_attr,
2138		scr.dwSize.X - cpos.X, cpos, &nchars);
2139	FillConsoleOutputCharacter(con_out, ' ',
2140		scr.dwSize.X - cpos.X, cpos, &nchars);
2141#endif
2142#endif
2143#endif
2144#endif
2145}
2146
2147/*
2148 * Clear the current line.
2149 * Clear the screen if there's off-screen memory below the display.
2150 */
2151	static void
2152clear_eol_bot()
2153{
2154#if MSDOS_COMPILER
2155	clear_eol();
2156#else
2157	if (below_mem)
2158		tputs(sc_eos_clear, 1, putchr);
2159	else
2160		tputs(sc_eol_clear, 1, putchr);
2161#endif
2162}
2163
2164/*
2165 * Clear the bottom line of the display.
2166 * Leave the cursor at the beginning of the bottom line.
2167 */
2168	public void
2169clear_bot()
2170{
2171	/*
2172	 * If we're in a non-normal attribute mode, temporarily exit
2173	 * the mode while we do the clear.  Some terminals fill the
2174	 * cleared area with the current attribute.
2175	 */
2176	if (oldbot)
2177		lower_left();
2178	else
2179		line_left();
2180
2181	if (attrmode == AT_NORMAL)
2182		clear_eol_bot();
2183	else
2184	{
2185		int saved_attrmode = attrmode;
2186
2187		at_exit();
2188		clear_eol_bot();
2189		at_enter(saved_attrmode);
2190	}
2191}
2192
2193	public void
2194at_enter(attr)
2195	int attr;
2196{
2197	attr = apply_at_specials(attr);
2198
2199#if !MSDOS_COMPILER
2200	/* The one with the most priority is last.  */
2201	if (attr & AT_UNDERLINE)
2202		tputs(sc_u_in, 1, putchr);
2203	if (attr & AT_BOLD)
2204		tputs(sc_b_in, 1, putchr);
2205	if (attr & AT_BLINK)
2206		tputs(sc_bl_in, 1, putchr);
2207	if (attr & AT_STANDOUT)
2208		tputs(sc_s_in, 1, putchr);
2209#else
2210	flush();
2211	/* The one with the most priority is first.  */
2212	if (attr & AT_STANDOUT)
2213	{
2214		SETCOLORS(so_fg_color, so_bg_color);
2215	} else if (attr & AT_BLINK)
2216	{
2217		SETCOLORS(bl_fg_color, bl_bg_color);
2218	}
2219	else if (attr & AT_BOLD)
2220	{
2221		SETCOLORS(bo_fg_color, bo_bg_color);
2222	}
2223	else if (attr & AT_UNDERLINE)
2224	{
2225		SETCOLORS(ul_fg_color, ul_bg_color);
2226	}
2227#endif
2228
2229	attrmode = attr;
2230}
2231
2232	public void
2233at_exit()
2234{
2235#if !MSDOS_COMPILER
2236	/* Undo things in the reverse order we did them.  */
2237	if (attrmode & AT_STANDOUT)
2238		tputs(sc_s_out, 1, putchr);
2239	if (attrmode & AT_BLINK)
2240		tputs(sc_bl_out, 1, putchr);
2241	if (attrmode & AT_BOLD)
2242		tputs(sc_b_out, 1, putchr);
2243	if (attrmode & AT_UNDERLINE)
2244		tputs(sc_u_out, 1, putchr);
2245#else
2246	flush();
2247	SETCOLORS(nm_fg_color, nm_bg_color);
2248#endif
2249
2250	attrmode = AT_NORMAL;
2251}
2252
2253	public void
2254at_switch(attr)
2255	int attr;
2256{
2257	int new_attrmode = apply_at_specials(attr);
2258	int ignore_modes = AT_ANSI;
2259
2260	if ((new_attrmode & ~ignore_modes) != (attrmode & ~ignore_modes))
2261	{
2262		at_exit();
2263		at_enter(attr);
2264	}
2265}
2266
2267	public int
2268is_at_equiv(attr1, attr2)
2269	int attr1;
2270	int attr2;
2271{
2272	attr1 = apply_at_specials(attr1);
2273	attr2 = apply_at_specials(attr2);
2274
2275	return (attr1 == attr2);
2276}
2277
2278	public int
2279apply_at_specials(attr)
2280	int attr;
2281{
2282	if (attr & AT_BINARY)
2283		attr |= binattr;
2284	if (attr & AT_HILITE)
2285		attr |= AT_STANDOUT;
2286	attr &= ~(AT_BINARY|AT_HILITE);
2287
2288	return attr;
2289}
2290
2291#if 0 /* No longer used */
2292/*
2293 * Erase the character to the left of the cursor
2294 * and move the cursor left.
2295 */
2296	public void
2297backspace()
2298{
2299#if !MSDOS_COMPILER
2300	/*
2301	 * Erase the previous character by overstriking with a space.
2302	 */
2303	tputs(sc_backspace, 1, putchr);
2304	putchr(' ');
2305	tputs(sc_backspace, 1, putchr);
2306#else
2307#if MSDOS_COMPILER==MSOFTC
2308	struct rccoord tpos;
2309
2310	flush();
2311	tpos = _gettextposition();
2312	if (tpos.col <= 1)
2313		return;
2314	_settextposition(tpos.row, tpos.col-1);
2315	_outtext(" ");
2316	_settextposition(tpos.row, tpos.col-1);
2317#else
2318#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
2319	cputs("\b");
2320#else
2321#if MSDOS_COMPILER==WIN32C
2322	COORD cpos;
2323	DWORD cChars;
2324	CONSOLE_SCREEN_BUFFER_INFO scr;
2325
2326	flush();
2327	GetConsoleScreenBufferInfo(con_out, &scr);
2328	cpos = scr.dwCursorPosition;
2329	if (cpos.X <= 0)
2330		return;
2331	cpos.X--;
2332	SetConsoleCursorPosition(con_out, cpos);
2333	FillConsoleOutputCharacter(con_out, (TCHAR)' ', 1, cpos, &cChars);
2334	SetConsoleCursorPosition(con_out, cpos);
2335#endif
2336#endif
2337#endif
2338#endif
2339}
2340#endif /* 0 */
2341
2342/*
2343 * Output a plain backspace, without erasing the previous char.
2344 */
2345	public void
2346putbs()
2347{
2348#if !MSDOS_COMPILER
2349	tputs(sc_backspace, 1, putchr);
2350#else
2351	int row, col;
2352
2353	flush();
2354	{
2355#if MSDOS_COMPILER==MSOFTC
2356		struct rccoord tpos;
2357		tpos = _gettextposition();
2358		row = tpos.row;
2359		col = tpos.col;
2360#else
2361#if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
2362		row = wherey();
2363		col = wherex();
2364#else
2365#if MSDOS_COMPILER==WIN32C
2366		CONSOLE_SCREEN_BUFFER_INFO scr;
2367		GetConsoleScreenBufferInfo(con_out, &scr);
2368		row = scr.dwCursorPosition.Y - scr.srWindow.Top + 1;
2369		col = scr.dwCursorPosition.X - scr.srWindow.Left + 1;
2370#endif
2371#endif
2372#endif
2373	}
2374	if (col <= 1)
2375		return;
2376	_settextposition(row, col-1);
2377#endif /* MSDOS_COMPILER */
2378}
2379
2380#if MSDOS_COMPILER==WIN32C
2381/*
2382 * Determine whether an input character is waiting to be read.
2383 */
2384	static int
2385win32_kbhit(tty)
2386	HANDLE tty;
2387{
2388	INPUT_RECORD ip;
2389	DWORD read;
2390
2391	if (keyCount > 0)
2392		return (TRUE);
2393
2394	currentKey.ascii = 0;
2395	currentKey.scan = 0;
2396
2397	/*
2398	 * Wait for a real key-down event, but
2399	 * ignore SHIFT and CONTROL key events.
2400	 */
2401	do
2402	{
2403		PeekConsoleInput(tty, &ip, 1, &read);
2404		if (read == 0)
2405			return (FALSE);
2406		ReadConsoleInput(tty, &ip, 1, &read);
2407	} while (ip.EventType != KEY_EVENT ||
2408		ip.Event.KeyEvent.bKeyDown != TRUE ||
2409		ip.Event.KeyEvent.wVirtualScanCode == 0 ||
2410		ip.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT ||
2411		ip.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL ||
2412		ip.Event.KeyEvent.wVirtualKeyCode == VK_MENU);
2413
2414	currentKey.ascii = ip.Event.KeyEvent.uChar.AsciiChar;
2415	currentKey.scan = ip.Event.KeyEvent.wVirtualScanCode;
2416	keyCount = ip.Event.KeyEvent.wRepeatCount;
2417
2418	if (ip.Event.KeyEvent.dwControlKeyState &
2419		(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
2420	{
2421		switch (currentKey.scan)
2422		{
2423		case PCK_ALT_E:     /* letter 'E' */
2424			currentKey.ascii = 0;
2425			break;
2426		}
2427	} else if (ip.Event.KeyEvent.dwControlKeyState &
2428		(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
2429	{
2430		switch (currentKey.scan)
2431		{
2432		case PCK_RIGHT: /* right arrow */
2433			currentKey.scan = PCK_CTL_RIGHT;
2434			break;
2435		case PCK_LEFT: /* left arrow */
2436			currentKey.scan = PCK_CTL_LEFT;
2437			break;
2438		case PCK_DELETE: /* delete */
2439			currentKey.scan = PCK_CTL_DELETE;
2440			break;
2441		}
2442	}
2443	return (TRUE);
2444}
2445
2446/*
2447 * Read a character from the keyboard.
2448 */
2449	public char
2450WIN32getch(tty)
2451	int tty;
2452{
2453	int ascii;
2454
2455	if (pending_scancode)
2456	{
2457		pending_scancode = 0;
2458		return ((char)(currentKey.scan & 0x00FF));
2459	}
2460
2461	while (win32_kbhit((HANDLE)tty) == FALSE)
2462	{
2463		Sleep(20);
2464		if (ABORT_SIGS())
2465			return ('\003');
2466		continue;
2467	}
2468	keyCount --;
2469	ascii = currentKey.ascii;
2470	/*
2471	 * On PC's, the extended keys return a 2 byte sequence beginning
2472	 * with '00', so if the ascii code is 00, the next byte will be
2473	 * the lsb of the scan code.
2474	 */
2475	pending_scancode = (ascii == 0x00);
2476	return ((char)ascii);
2477}
2478#endif
2479