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