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