xref: /freebsd/contrib/less/screen.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
1 /*
2  * Copyright (C) 1984-2026  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>
28 extern 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_TERMINFO && !USE_TERMCAP
72 #define USE_TERMINFO 1
73 #endif
74 
75 #if USE_TERMINFO
76 #include <curses.h>
77 #include <term.h>
78 #if HAVE_TPARM2
79 #define LTPARM2(s,p1,p2) tparm(s, p1, p2)
80 #else
81 #if HAVE_TPARM8
82 #define LTPARM2(s,p1,p2) tparm(s, p1, p2, 0, 0, 0, 0, 0, 0)
83 #else
84 #if HAVE_TPARM9
85 #define LTPARM2(s,p1,p2) tparm(s, p1, p2, 0, 0, 0, 0, 0, 0, 0)
86 #else
87 #endif
88 #endif
89 #endif
90 #else
91 #if HAVE_NCURSESW_TERMCAP_H
92 #include <ncursesw/termcap.h>
93 #else
94 #if HAVE_NCURSES_TERMCAP_H
95 #include <ncurses/termcap.h>
96 #else
97 #if HAVE_TERMCAP_H
98 #include <termcap.h>
99 #endif
100 #endif
101 #endif
102 #endif
103 #ifdef _OSK
104 #include <signal.h>
105 #endif
106 #ifdef __APPLE__
107 #pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
108 #endif
109 #if OS2
110 #include <sys/signal.h>
111 #include "pckeys.h"
112 #endif
113 #if HAVE_SYS_STREAM_H
114 #include <sys/stream.h>
115 #endif
116 #if HAVE_SYS_PTEM_H
117 #include <sys/ptem.h>
118 #endif
119 
120 #endif /* MSDOS_COMPILER */
121 
122 /*
123  * Check for broken termios package that forces you to manually
124  * set the line discipline.
125  */
126 #ifdef __ultrix__
127 #define MUST_SET_LINE_DISCIPLINE 1
128 #else
129 #define MUST_SET_LINE_DISCIPLINE 0
130 #endif
131 
132 #if OS2
133 #define DEFAULT_TERM            "ansi"
134 static char *windowid;
135 #else
136 #define DEFAULT_TERM            "unknown"
137 #endif
138 
139 #if MSDOS_COMPILER==MSOFTC
140 static int videopages;
141 static long msec_loops;
142 static int flash_created = 0;
143 #define SET_FG_COLOR(fg)        _settextcolor(fg)
144 #define SET_BG_COLOR(bg)        _setbkcolor(bg)
145 #define SETCOLORS(fg,bg)        { SET_FG_COLOR(fg); SET_BG_COLOR(bg); }
146 #endif
147 
148 #if MSDOS_COMPILER==BORLANDC
149 static unsigned short *whitescreen;
150 static int flash_created = 0;
151 #endif
152 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
153 #define _settextposition(y,x)   gotoxy(x,y)
154 #define _clearscreen(m)         clrscr()
155 #define _outtext(s)             cputs(s)
156 #define SET_FG_COLOR(fg)        textcolor(fg)
157 #define SET_BG_COLOR(bg)        textbackground(bg)
158 #define SETCOLORS(fg,bg)        { SET_FG_COLOR(fg); SET_BG_COLOR(bg); }
159 extern int sc_height;
160 #endif
161 
162 #if MSDOS_COMPILER==WIN32C
163 #define UTF8_MAX_LENGTH 4
164 
165 static WORD curr_attr;
166 
167 static HANDLE con_out_save = INVALID_HANDLE_VALUE; /* previous console */
168 static HANDLE con_out_ours = INVALID_HANDLE_VALUE; /* our own */
169 HANDLE con_out = INVALID_HANDLE_VALUE;             /* current console */
170 
171 extern int utf_mode;
172 extern lbool quitting;
173 static void win32_init_term();
174 static void win32_deinit_term();
175 
176 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
177 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4
178 #endif
179 
180 #define FG_COLORS       (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)
181 #define BG_COLORS       (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY)
182 #define MAKEATTR(fg,bg)         ((WORD)((fg)|((bg)<<4)))
183 #define APPLY_COLORS()          { if (SetConsoleTextAttribute(con_out, curr_attr) == 0) \
184                                   error("SETCOLORS failed", NULL_PARG); }
185 #define SET_FG_COLOR(fg)        { curr_attr &= ~0x0f; curr_attr |= (fg); APPLY_COLORS(); }
186 #define SET_BG_COLOR(bg)        { curr_attr &= ~0xf0; curr_attr |= ((bg)<<4); APPLY_COLORS(); }
187 #define SETCOLORS(fg,bg)        { curr_attr = MAKEATTR(fg,bg); APPLY_COLORS(); }
188 #endif
189 
190 #if MSDOS_COMPILER
191 public int nm_fg_color = CV_ERROR; /* Color of normal text */
192 public int nm_bg_color = CV_ERROR;
193 public int nm_attr = 0;
194 public int bo_fg_color = CV_ERROR; /* Color of bold text */
195 public int bo_bg_color = CV_ERROR;
196 public int bo_attr = 0;
197 public int ul_fg_color = CV_ERROR; /* Color of underlined text */
198 public int ul_bg_color = CV_ERROR;
199 public int ul_attr = 0;
200 public int so_fg_color = CV_ERROR; /* Color of standout text */
201 public int so_bg_color = CV_ERROR;
202 public int so_attr = 0;
203 public int bl_fg_color = CV_ERROR; /* Color of blinking text */
204 public int bl_bg_color = CV_ERROR;
205 public int bl_attr = 0;
206 static int sy_fg_color;            /* Color of system text (before less) */
207 static int sy_bg_color;
208 public int sgr_mode;            /* Honor ANSI sequences rather than using above */
209 #if MSDOS_COMPILER==WIN32C
210 static DWORD init_console_output_mode;
211 extern DWORD init_console_input_mode;
212 extern DWORD curr_console_input_mode;
213 extern DWORD base_console_input_mode;
214 extern DWORD mouse_console_input_mode;
215 public int vt_enabled = -1;     /* Is virtual terminal processing available? */
216 #endif
217 #else
218 
219 /*
220  * These two variables are sometimes defined in,
221  * and needed by, the termcap library.
222  */
223 #if USE_TERMINFO
224 #undef HAVE_OSPEED
225 #else
226 #if MUST_DEFINE_OSPEED
227 extern short ospeed;    /* Terminal output baud rate */
228 extern char PC;         /* Pad character */
229 #endif
230 #ifdef _OSK
231 short ospeed;
232 char PC_, *UP, *BC;
233 #endif
234 #endif
235 
236 /*
237  * Strings passed to tputs() to do various terminal functions.
238  */
239 static constant char
240 #if HAVE_OSPEED
241 	*sc_pad,                /* Pad string */
242 #endif
243 	*sc_home,               /* Cursor home */
244 	*sc_addline,            /* Add line, scroll down following lines */
245 	*sc_lower_left,         /* Cursor to last line, first column */
246 	*sc_ll,                 /* Raw lower-left cap (but we may use sc_move for sc_lower_left) */
247 	*sc_return,             /* Cursor to beginning of current line */
248 	*sc_move,               /* General cursor positioning */
249 	*sc_clear,              /* Clear screen */
250 	*sc_eol_clear,          /* Clear to end of line */
251 	*sc_eos_clear,          /* Clear to end of screen */
252 	*sc_s_in,               /* Enter standout (highlighted) mode */
253 	*sc_s_out,              /* Exit standout mode */
254 	*sc_u_in,               /* Enter underline mode */
255 	*sc_u_out,              /* Exit underline mode */
256 	*sc_b_in,               /* Enter bold mode */
257 	*sc_b_out,              /* Exit bold mode */
258 	*sc_bl_in,              /* Enter blink mode */
259 	*sc_bl_out,             /* Exit blink mode */
260 	*sc_visual_bell,        /* Visual bell (flash screen) sequence */
261 	*sc_backspace,          /* Backspace cursor */
262 	*sc_s_keypad,           /* Start keypad mode */
263 	*sc_e_keypad,           /* End keypad mode */
264 	*sc_s_mousecap,         /* Start mouse capture mode */
265 	*sc_e_mousecap,         /* End mouse capture mode */
266 	*sc_s_bracketed_paste,  /* Start bracketed paste mode */
267 	*sc_e_bracketed_paste,  /* End bracketed paste mode */
268 	*sc_suspend,            /* Suspend screen updates */
269 	*sc_resume,             /* Resume screen updates */
270 	*sc_init,               /* Startup terminal initialization */
271 	*sc_deinit;             /* Exit terminal de-initialization */
272 
273 /* attrbits is the current attribute mode of the terminal
274  * (AT_UNDERLINE, AT_BOLD, AT_STANDOUT, AT_BLINK).
275  * attrmode is the user-defined mode, which may include AT_COLOR values,
276  * and the mapping of the AT_COLOR value may include the 4 attribute bits. */
277 static int attrbits = 0;
278 static int attrcolor = -1;
279 static int termcap_debug;
280 static int no_alt_screen;               /* sc_init does not switch to alt screen */
281 static int above_mem, below_mem;        /* Memory retained above/below screen */
282 #endif
283 
284 /* term_init has been called; terminal is ready for use by less */
285 static lbool term_init_done = FALSE;
286 public lbool term_init_ever = FALSE;
287 
288 public int auto_wrap;           /* Terminal does \r\n when write past margin */
289 public int defer_wrap;          /* After printing char in last column, doesn't wrap until next char */
290 public int erase_char;          /* The user's erase char */
291 public int erase2_char;         /* The user's other erase char */
292 public int kill_char;           /* The user's line-kill char */
293 public int werase_char;         /* The user's word-erase char */
294 public int sc_width, sc_height; /* Height & width of screen */
295 public int bo_s_width, bo_e_width;      /* Printing width of boldface seq */
296 public int ul_s_width, ul_e_width;      /* Printing width of underline seq */
297 public int so_s_width, so_e_width;      /* Printing width of standout seq */
298 public int bl_s_width, bl_e_width;      /* Printing width of blink seq */
299 public lbool can_goto_line;             /* Can move cursor to any line */
300 public lbool missing_cap = FALSE;       /* Some capability is missing */
301 public constant char *kent = NULL;      /* Keypad ENTER sequence */
302 public lbool kent_mapped = FALSE;       /* Keypad ENTER is mapped to a command */
303 public lbool term_addrs = FALSE;        /* "ti" has been sent to terminal */
304 public lbool full_screen = TRUE;        /* We're using all lines of terminal */
305 
306 static int attrmode = AT_NORMAL; /* current attributes (AT_* bits) */
307 extern int binattr;
308 extern lbool one_screen;
309 extern int shell_lines;
310 
311 extern int quiet;               /* If VERY_QUIET, use visual bell for bell */
312 extern int no_vbell;
313 extern lbool no_back_scroll;
314 extern int no_init;
315 extern int no_keypad;
316 extern int sigs;
317 extern int top_scroll;
318 extern int quit_if_one_screen;
319 extern int oldbot;
320 extern int emouse;
321 extern lbool is_tty;
322 extern int use_color;
323 extern int no_paste;
324 extern int wscroll;
325 #if HILITE_SEARCH
326 extern int hilite_search;
327 #endif
328 #if MSDOS_COMPILER==WIN32C
329 extern HANDLE tty;
330 #else
331 extern int tty;
332 #endif
333 
334 #if (HAVE_TERMIOS_H && HAVE_TERMIOS_FUNCS) || defined(TCGETA)
335 /*
336  * Set termio flags for use by less.
337  */
338 static void set_termio_flags(
339 #if HAVE_TERMIOS_H && HAVE_TERMIOS_FUNCS
340 	struct termios *s
341 #else
342 	struct termio *s
343 #endif
344 	)
345 {
346 	s->c_lflag &= ~(0
347 #ifdef ICANON
348 		| ICANON
349 #endif
350 #ifdef ECHO
351 		| ECHO
352 #endif
353 #ifdef ECHOE
354 		| ECHOE
355 #endif
356 #ifdef ECHOK
357 		| ECHOK
358 #endif
359 #ifdef ECHONL
360 		| ECHONL
361 #endif
362 	);
363 
364 	s->c_oflag |= (0
365 #ifdef OPOST
366 		| OPOST
367 #endif
368 #ifdef ONLCR
369 		| ONLCR
370 #endif
371 	);
372 
373 	s->c_oflag &= ~(0
374 #ifdef ONOEOT
375 		| ONOEOT
376 #endif
377 #ifdef OCRNL
378 		| OCRNL
379 #endif
380 #ifdef ONOCR
381 		| ONOCR
382 #endif
383 #ifdef ONLRET
384 		| ONLRET
385 #endif
386 	);
387 
388 	s->c_iflag &= ~(0
389 #ifdef ICRNL
390 		| ICRNL
391 #endif
392 #ifdef INLCR
393 		| INLCR
394 #endif
395 	);
396 }
397 #endif
398 
399 /*
400  * Change terminal to "raw mode", or restore to "normal" mode.
401  * "Raw mode" means
402  *      1. An outstanding read will complete on receipt of a single keystroke.
403  *      2. Input is not echoed.
404  *      3. On output, \n is mapped to \r\n.
405  *      4. \t is NOT expanded into spaces.
406  *      5. Signal-causing characters such as ctrl-C (interrupt),
407  *         etc. are NOT disabled.
408  *      6. Input \r is not mapped to \n, nor vice versa.
409  */
410 public void raw_mode(lbool on)
411 {
412 	static lbool curr_on = FALSE;
413 
414 	if (on == curr_on)
415 			return;
416 	erase2_char = '\b'; /* in case OS doesn't know about erase2 */
417 #if LESSTEST
418 	if (is_lesstest())
419 	{
420 		/* {{ For consistent conditions when running tests. }} */
421 		erase_char = '\b';
422 		kill_char = CONTROL('U');
423 		werase_char = CONTROL('W');
424 	} else
425 #endif /*LESSTEST*/
426 #if HAVE_TERMIOS_H && HAVE_TERMIOS_FUNCS
427     {
428 	struct termios s;
429 	static struct termios save_term;
430 	static lbool saved_term = FALSE;
431 
432 	if (on)
433 	{
434 		/*
435 		 * Get terminal modes.
436 		 */
437 		if (tcgetattr(tty, &s) < 0)
438 		{
439 			erase_char = '\b';
440 			kill_char = CONTROL('U');
441 			werase_char = CONTROL('W');
442 		} else
443 		{
444 			/*
445 			 * Save modes and set certain variables dependent on modes.
446 			 */
447 			if (!saved_term)
448 			{
449 				save_term = s;
450 				saved_term = TRUE;
451 			}
452 #if HAVE_OSPEED
453 			switch (cfgetospeed(&s))
454 			{
455 #ifdef B0
456 			case B0: ospeed = 0; break;
457 #endif
458 #ifdef B50
459 			case B50: ospeed = 1; break;
460 #endif
461 #ifdef B75
462 			case B75: ospeed = 2; break;
463 #endif
464 #ifdef B110
465 			case B110: ospeed = 3; break;
466 #endif
467 #ifdef B134
468 			case B134: ospeed = 4; break;
469 #endif
470 #ifdef B150
471 			case B150: ospeed = 5; break;
472 #endif
473 #ifdef B200
474 			case B200: ospeed = 6; break;
475 #endif
476 #ifdef B300
477 			case B300: ospeed = 7; break;
478 #endif
479 #ifdef B600
480 			case B600: ospeed = 8; break;
481 #endif
482 #ifdef B1200
483 			case B1200: ospeed = 9; break;
484 #endif
485 #ifdef B1800
486 			case B1800: ospeed = 10; break;
487 #endif
488 #ifdef B2400
489 			case B2400: ospeed = 11; break;
490 #endif
491 #ifdef B4800
492 			case B4800: ospeed = 12; break;
493 #endif
494 #ifdef B9600
495 			case B9600: ospeed = 13; break;
496 #endif
497 #ifdef EXTA
498 			case EXTA: ospeed = 14; break;
499 #endif
500 #ifdef EXTB
501 			case EXTB: ospeed = 15; break;
502 #endif
503 #ifdef B57600
504 			case B57600: ospeed = 16; break;
505 #endif
506 #ifdef B115200
507 			case B115200: ospeed = 17; break;
508 #endif
509 			default: ;
510 			}
511 #endif
512 			erase_char = s.c_cc[VERASE];
513 #ifdef VERASE2
514 			erase2_char = s.c_cc[VERASE2];
515 #endif
516 			kill_char = s.c_cc[VKILL];
517 #ifdef VWERASE
518 			werase_char = s.c_cc[VWERASE];
519 #else
520 			werase_char = CONTROL('W');
521 #endif
522 
523 			/*
524 			 * Set the modes to the way we want them.
525 			 */
526 			set_termio_flags(&s);
527 			s.c_cc[VMIN] = 1;
528 			s.c_cc[VTIME] = 0;
529 #ifdef VLNEXT
530 			s.c_cc[VLNEXT] = 0;
531 #endif
532 #ifdef VDSUSP
533 			s.c_cc[VDSUSP] = 0;
534 #endif
535 #ifdef VSTOP
536 			s.c_cc[VSTOP] = 0;
537 #endif
538 #ifdef VSTART
539 			s.c_cc[VSTART] = 0;
540 #endif
541 #ifdef VDISCARD
542 			s.c_cc[VDISCARD] = 0;
543 #endif
544 #if MUST_SET_LINE_DISCIPLINE
545 			/*
546 			 * System's termios is broken; need to explicitly
547 			 * request TERMIODISC line discipline.
548 			 */
549 			s.c_line = TERMIODISC;
550 #endif
551 		}
552 	} else
553 	{
554 		/*
555 		 * Restore saved modes.
556 		 */
557 		s = save_term;
558 	}
559 #if HAVE_FSYNC
560 	fsync(tty);
561 #endif
562 	tcsetattr(tty, TCSADRAIN, &s);
563 #if MUST_SET_LINE_DISCIPLINE
564 	if (!on)
565 	{
566 		/*
567 		 * Broken termios *ignores* any line discipline
568 		 * except TERMIODISC.  A different old line discipline
569 		 * is therefore not restored, yet.  Restore the old
570 		 * line discipline by hand.
571 		 */
572 		ioctl(tty, TIOCSETD, &save_term.c_line);
573 	}
574 #endif
575     }
576 #else
577 #ifdef TCGETA
578     {
579 	struct termio s;
580 	static struct termio save_term;
581 	static lbool saved_term = FALSE;
582 
583 	if (on)
584 	{
585 		/*
586 		 * Get terminal modes.
587 		 */
588 		ioctl(tty, TCGETA, &s);
589 
590 		/*
591 		 * Save modes and set certain variables dependent on modes.
592 		 */
593 		if (!saved_term)
594 		{
595 			save_term = s;
596 			saved_term = TRUE;
597 		}
598 #if HAVE_OSPEED
599 		ospeed = s.c_cflag & CBAUD;
600 #endif
601 		erase_char = s.c_cc[VERASE];
602 		kill_char = s.c_cc[VKILL];
603 #ifdef VWERASE
604 		werase_char = s.c_cc[VWERASE];
605 #else
606 		werase_char = CONTROL('W');
607 #endif
608 
609 		/*
610 		 * Set the modes to the way we want them.
611 		 */
612 		set_termio_flags(&s);
613 		s.c_cc[VMIN] = 1;
614 		s.c_cc[VTIME] = 0;
615 #ifdef VSTOP
616 		s.c_cc[VSTOP] = 0;
617 #endif
618 #ifdef VSTART
619 		s.c_cc[VSTART] = 0;
620 #endif
621 	} else
622 	{
623 		/*
624 		 * Restore saved modes.
625 		 */
626 		s = save_term;
627 	}
628 	ioctl(tty, TCSETAW, &s);
629     }
630 #else
631 #ifdef TIOCGETP
632     {
633 	struct sgttyb s;
634 	static struct sgttyb save_term;
635 	static lbool saved_term = FALSE;
636 
637 	if (on)
638 	{
639 		/*
640 		 * Get terminal modes.
641 		 */
642 		ioctl(tty, TIOCGETP, &s);
643 
644 		/*
645 		 * Save modes and set certain variables dependent on modes.
646 		 */
647 		if (!saved_term)
648 		{
649 			save_term = s;
650 			saved_term = TRUE;
651 		}
652 #if HAVE_OSPEED
653 		ospeed = s.sg_ospeed;
654 #endif
655 		erase_char = s.sg_erase;
656 		kill_char = s.sg_kill;
657 		werase_char = CONTROL('W');
658 
659 		/*
660 		 * Set the modes to the way we want them.
661 		 */
662 		s.sg_flags |= CBREAK;
663 		s.sg_flags &= ~(ECHO);
664 	} else
665 	{
666 		/*
667 		 * Restore saved modes.
668 		 */
669 		s = save_term;
670 	}
671 	ioctl(tty, TIOCSETN, &s);
672     }
673 #else
674 #ifdef _OSK
675     {
676 	struct sgbuf s;
677 	static struct sgbuf save_term;
678 	static lbool saved_term = FALSE;
679 
680 	if (on)
681 	{
682 		/*
683 		 * Get terminal modes.
684 		 */
685 		_gs_opt(tty, &s);
686 
687 		/*
688 		 * Save modes and set certain variables dependent on modes.
689 		 */
690 		if (!saved_term)
691 		{
692 			save_term = s;
693 			saved_term = TRUE;
694 		}
695 		erase_char = s.sg_bspch;
696 		kill_char = s.sg_dlnch;
697 		werase_char = CONTROL('W');
698 
699 		/*
700 		 * Set the modes to the way we want them.
701 		 */
702 		s.sg_echo = 0;
703 		s.sg_eofch = 0;
704 		s.sg_pause = 0;
705 		s.sg_psch = 0;
706 	} else
707 	{
708 		/*
709 		 * Restore saved modes.
710 		 */
711 		s = save_term;
712 	}
713 	_ss_opt(tty, &s);
714     }
715 #else
716 	/* MS-DOS, Windows, or OS2 */
717 #if OS2
718 	/* OS2 */
719 	LSIGNAL(SIGINT, SIG_IGN);
720 #endif
721 	erase_char = '\b';
722 #if MSDOS_COMPILER==DJGPPC
723 	kill_char = CONTROL('U');
724 	/*
725 	 * So that when we shell out or run another program, its
726 	 * stdin is in cooked mode.  We do not switch stdin to binary
727 	 * mode if fd0 is zero, since that means we were called before
728 	 * tty was reopened in open_getchr, in which case we would be
729 	 * changing the original stdin device outside less.
730 	 */
731 	if (fd0 != 0)
732 		setmode(0, on ? O_BINARY : O_TEXT);
733 #else
734 	kill_char = ESC;
735 #endif
736 	werase_char = CONTROL('W');
737 #endif
738 #endif
739 #endif
740 #endif
741 	curr_on = on;
742 }
743 
744 #if !MSDOS_COMPILER
745 
746 /*
747  * Some glue to prevent calling termcap functions if tgetent() failed.
748  */
749 static lbool hardcopy;
750 
751 static constant char * ltget_env(constant char *tiname, constant char *tcname)
752 {
753 	char envname[64];
754 	constant char *s;
755 
756 	if (termcap_debug)
757 	{
758 		struct env { struct env *next; char *name; char *value; };
759 		constant char *capname = tcname != NULL ? tcname : tiname;
760 		static struct env *envs = NULL;
761 		struct env *p;
762 		for (p = envs;  p != NULL;  p = p->next)
763 			if (strcmp(p->name, capname) == 0)
764 				return p->value;
765 		p = (struct env *) ecalloc(1, sizeof(struct env));
766 		p->name = save(capname);
767 		p->value = (char *) ecalloc(strlen(capname)+3, sizeof(char));
768 		sprintf(p->value, "<%s>", capname);
769 		p->next = envs;
770 		envs = p;
771 		return p->value;
772 	}
773 	if (tiname != NULL)
774 	{
775 		SNPRINTF1(envname, sizeof(envname), "LESS_TERMINFO_%s", tiname);
776 		s = lgetenv(envname);
777 		if (!isnullenv(s))
778 			return s;
779 	}
780 	if (tcname != NULL)
781 	{
782 		SNPRINTF1(envname, sizeof(envname), "LESS_TERMCAP_%s", tcname);
783 		s = lgetenv(envname);
784 		if (!isnullenv(s))
785 			return s;
786 	}
787 	return NULL;
788 }
789 
790 static int ltgetflag(constant char *tiname, constant char *tcname)
791 {
792 	constant char *s;
793 
794 	if ((s = ltget_env(tiname, tcname)) != NULL)
795 		return (*s != '\0' && *s != '0');
796 	if (hardcopy)
797 		return (0);
798 #if USE_TERMINFO
799 	return tiname == NULL ? 0 : tigetflag(tiname);
800 #else
801 	return tcname == NULL ? 0 : tgetflag(tcname);
802 #endif
803 }
804 
805 static int ltgetnum(constant char *tiname, constant char *tcname)
806 {
807 	constant char *s;
808 
809 	if ((s = ltget_env(tiname, tcname)) != NULL)
810 		return (atoi(s));
811 	if (hardcopy)
812 		return (-1);
813 #if USE_TERMINFO
814 	return tiname == NULL ? -1 : tigetnum(tiname);
815 #else
816 	return tcname == NULL ? -1 : tgetnum(tcname);
817 #endif
818 }
819 
820 static constant char * ltgetstr(constant char *tiname, constant char *tcname, char **pp)
821 {
822 	constant char *s;
823 
824 	if ((s = ltget_env(tiname, tcname)) != NULL)
825 		return (s);
826 	if (hardcopy)
827 		return (NULL);
828 #if USE_TERMINFO
829 	(void) pp;
830 	if (tiname == NULL)
831 		return (NULL);
832 	s = tigetstr(tiname);
833 	if (s == (constant char *)-1)
834 		s = NULL;
835 	return (s);
836 #else
837 	if (tcname == NULL)
838 		return (NULL);
839 	return tgetstr(tcname, pp);
840 #endif
841 }
842 #endif /* MSDOS_COMPILER */
843 
844 /*
845  * Get size of the output screen.
846  */
847 public void scrsize(void)
848 {
849 	constant char *s;
850 	int sys_height;
851 	int sys_width;
852 #if !MSDOS_COMPILER
853 	int n;
854 #endif
855 
856 #define DEF_SC_WIDTH    80
857 #if MSDOS_COMPILER
858 #define DEF_SC_HEIGHT   25
859 #else
860 #define DEF_SC_HEIGHT   24
861 #endif
862 
863 	sys_width = sys_height = 0;
864 
865 #if LESSTEST
866 	if (0) /* can't use is_lesstest(): ttyin_name may not be set by scan_option yet */
867 #endif /*LESSTEST*/
868 	{
869 #if MSDOS_COMPILER==MSOFTC
870 	{
871 		struct videoconfig w;
872 		_getvideoconfig(&w);
873 		sys_height = w.numtextrows;
874 		sys_width = w.numtextcols;
875 	}
876 #else
877 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
878 	{
879 		struct text_info w;
880 		gettextinfo(&w);
881 		sys_height = w.screenheight;
882 		sys_width = w.screenwidth;
883 	}
884 #else
885 #if MSDOS_COMPILER==WIN32C
886 	{
887 		CONSOLE_SCREEN_BUFFER_INFO scr;
888 		GetConsoleScreenBufferInfo(con_out, &scr);
889 		sys_height = scr.srWindow.Bottom - scr.srWindow.Top + 1;
890 		sys_width = scr.srWindow.Right - scr.srWindow.Left + 1;
891 	}
892 #else
893 #if OS2
894 	{
895 		int s[2];
896 		_scrsize(s);
897 		sys_width = s[0];
898 		sys_height = s[1];
899 		/*
900 		 * When using terminal emulators for XFree86/OS2, the
901 		 * _scrsize function does not work well.
902 		 * Call the scrsize.exe program to get the window size.
903 		 */
904 		windowid = getenv("WINDOWID");
905 		if (windowid != NULL)
906 		{
907 			FILE *fd = popen("scrsize", "rt");
908 			if (fd != NULL)
909 			{
910 				int w, h;
911 				fscanf(fd, "%i %i", &w, &h);
912 				if (w > 0 && h > 0)
913 				{
914 					sys_width = w;
915 					sys_height = h;
916 				}
917 				pclose(fd);
918 			}
919 		}
920 	}
921 #else
922 #ifdef TIOCGWINSZ
923 	{
924 		struct winsize w;
925 		if (ioctl(2, TIOCGWINSZ, &w) == 0 || ioctl(1, TIOCGWINSZ, &w) == 0)
926 		{
927 			if (w.ws_row > 0)
928 				sys_height = w.ws_row;
929 			if (w.ws_col > 0)
930 				sys_width = w.ws_col;
931 		}
932 	}
933 #else
934 #ifdef WIOCGETD
935 	{
936 		struct uwdata w;
937 		if (ioctl(2, WIOCGETD, &w) == 0 || ioctl(1, WIOCGETD, &w) == 0)
938 		{
939 			if (w.uw_height > 0)
940 				sys_height = w.uw_height / w.uw_vs;
941 			if (w.uw_width > 0)
942 				sys_width = w.uw_width / w.uw_hs;
943 		}
944 	}
945 #endif
946 #endif
947 #endif
948 #endif
949 #endif
950 #endif
951 	}
952 
953 	if (sys_height > 0)
954 		sc_height = sys_height;
955 	else if ((s = lgetenv("LINES")) != NULL)
956 		sc_height = atoi(s);
957 #if !MSDOS_COMPILER
958 	else if ((n = ltgetnum("lines", "li")) > 0)
959 		sc_height = n;
960 #endif
961 	if ((s = lgetenv("LESS_LINES")) != NULL)
962 	{
963 		int height = atoi(s);
964 		sc_height = (height < 0) ? sc_height + height : height;
965 		full_screen = FALSE;
966 	}
967 	if (sc_height <= 0)
968 		sc_height = DEF_SC_HEIGHT;
969 
970 	if (sys_width > 0)
971 		sc_width = sys_width;
972 	else if ((s = lgetenv("COLUMNS")) != NULL)
973 		sc_width = atoi(s);
974 #if !MSDOS_COMPILER
975 	else if ((n = ltgetnum("cols", "co")) > 0)
976 		sc_width = n;
977 #endif
978 	if ((s = lgetenv("LESS_COLUMNS")) != NULL)
979 	{
980 		int width = atoi(s);
981 		sc_width = (width < 0) ? sc_width + width : width;
982 	}
983 	if (sc_width <= 0)
984 		sc_width = DEF_SC_WIDTH;
985 	screen_size_changed();
986 }
987 
988 /*
989  * Recalculate things that depend on the screen size.
990  */
991 public void screen_size_changed(void)
992 {
993 	constant char *env = lgetenv("LESS_SHELL_LINES");
994 	shell_lines = isnullenv(env) ? 1 : atoi(env);
995 	if (shell_lines >= sc_height)
996 		shell_lines = sc_height - 1;
997 	wscroll = (sc_height + 1) / 2;
998 	calc_jump_sline();
999 	calc_shift_count();
1000 	calc_match_shift();
1001 }
1002 
1003 #if MSDOS_COMPILER==MSOFTC
1004 /*
1005  * Figure out how many empty loops it takes to delay a millisecond.
1006  */
1007 static void get_clock(void)
1008 {
1009 	clock_t start;
1010 
1011 	/*
1012 	 * Get synchronized at the start of a tick.
1013 	 */
1014 	start = clock();
1015 	while (clock() == start)
1016 		;
1017 	/*
1018 	 * Now count loops till the next tick.
1019 	 */
1020 	start = clock();
1021 	msec_loops = 0;
1022 	while (clock() == start)
1023 		msec_loops++;
1024 	/*
1025 	 * Convert from (loops per clock) to (loops per millisecond).
1026 	 */
1027 	msec_loops *= CLOCKS_PER_SEC;
1028 	msec_loops /= 1000;
1029 }
1030 
1031 /*
1032  * Delay for a specified number of milliseconds.
1033  */
1034 static void delay(int msec)
1035 {
1036 	long i;
1037 
1038 	while (msec-- > 0)
1039 	{
1040 		for (i = 0;  i < msec_loops;  i++)
1041 			(void) clock();
1042 	}
1043 }
1044 #endif
1045 
1046 /*
1047  * Return the characters actually input by a "special" key.
1048  */
1049 public constant char * special_key_str(int key)
1050 {
1051 	static char tbuf[40];
1052 	constant char *s;
1053 #if MSDOS_COMPILER || OS2
1054 	static char k_right[]           = { '\340', PCK_RIGHT, 0 };
1055 	static char k_left[]            = { '\340', PCK_LEFT, 0  };
1056 	static char k_ctl_right[]       = { '\340', PCK_CTL_RIGHT, 0  };
1057 	static char k_ctl_left[]        = { '\340', PCK_CTL_LEFT, 0  };
1058 	static char k_ctl_up[]          = { '\340', PCK_CTL_UP, 0  };
1059 	static char k_ctl_down[]        = { '\340', PCK_CTL_DOWN, 0  };
1060 	static char k_shift_right[]     = { '\340', PCK_SHIFT_RIGHT, 0  };
1061 	static char k_shift_left[]      = { '\340', PCK_SHIFT_LEFT, 0  };
1062 	static char k_shift_up[]        = { '\340', PCK_SHIFT_UP, 0  };
1063 	static char k_shift_down[]      = { '\340', PCK_SHIFT_DOWN, 0  };
1064 	static char k_insert[]          = { '\340', PCK_INSERT, 0  };
1065 	static char k_delete[]          = { '\340', PCK_DELETE, 0  };
1066 	static char k_ctl_delete[]      = { '\340', PCK_CTL_DELETE, 0  };
1067 	static char k_shift_delete[]    = { '\340', PCK_SHIFT_DELETE, 0  };
1068 	static char k_ctl_backspace[]   = { '\177', 0 };
1069 	static char k_backspace[]       = { '\b', 0 };
1070 	static char k_home[]            = { '\340', PCK_HOME, 0 };
1071 	static char k_end[]             = { '\340', PCK_END, 0 };
1072 	static char k_up[]              = { '\340', PCK_UP, 0 };
1073 	static char k_down[]            = { '\340', PCK_DOWN, 0 };
1074 	static char k_backtab[]         = { '\340', PCK_SHIFT_TAB, 0 };
1075 	static char k_pagedown[]        = { '\340', PCK_PAGEDOWN, 0 };
1076 	static char k_shift_pagedown[]  = { '\340', PCK_SHIFT_PAGEDOWN, 0 };
1077 	static char k_ctl_pagedown[]    = { '\340', PCK_CTL_PAGEDOWN, 0 };
1078 	static char k_pageup[]          = { '\340', PCK_PAGEUP, 0 };
1079 	static char k_shift_pageup[]    = { '\340', PCK_SHIFT_PAGEUP, 0 };
1080 	static char k_ctl_pageup[]      = { '\340', PCK_CTL_PAGEUP, 0 };
1081 	static char k_ctl_home[]        = { '\340', PCK_CTL_HOME, 0 };
1082 	static char k_ctl_end[]         = { '\340', PCK_CTL_END, 0 };
1083 	static char k_shift_home[]      = { '\340', PCK_SHIFT_HOME, 0 };
1084 	static char k_shift_end[]       = { '\340', PCK_SHIFT_END, 0 };
1085 	static char k_f1[]              = { '\340', PCK_F1, 0 };
1086 #endif
1087 #if !MSDOS_COMPILER
1088 	char *sp = tbuf;
1089 #endif
1090 
1091 	switch (key)
1092 	{
1093 #if OS2
1094 	/*
1095 	 * If windowid is not NULL, assume less is executed in
1096 	 * the XFree86 environment.
1097 	 */
1098 	case SK_RIGHT_ARROW:
1099 		s = windowid ? ltgetstr("kcuf1", "kr", &sp) : k_right;
1100 		break;
1101 	case SK_LEFT_ARROW:
1102 		s = windowid ? ltgetstr("kcub1", "kl", &sp) : k_left;
1103 		break;
1104 	case SK_UP_ARROW:
1105 		s = windowid ? ltgetstr("kcuu1", "ku", &sp) : k_up;
1106 		break;
1107 	case SK_DOWN_ARROW:
1108 		s = windowid ? ltgetstr("kcud1", "kd", &sp) : k_down;
1109 		break;
1110 	case SK_PAGE_UP:
1111 		s = windowid ? ltgetstr("kpp", "kP", &sp) : k_pageup;
1112 		break;
1113 	case SK_PAGE_DOWN:
1114 		s = windowid ? ltgetstr("knp", "kN", &sp) : k_pagedown;
1115 		break;
1116 	case SK_HOME:
1117 		s = windowid ? ltgetstr("khome", "kh", &sp) : k_home;
1118 		break;
1119 	case SK_END:
1120 		s = windowid ? ltgetstr("kend", "@7", &sp) : k_end;
1121 		break;
1122 	case SK_F1:
1123 		s = windowid ? ltgetstr("kf1", "k1", &sp) : k_f1;
1124 		break;
1125 	case SK_DELETE:
1126 		s = windowid ? ltgetstr("kdch1", "kD", &sp) : k_delete;
1127 		if (s == NULL)
1128 		{
1129 				tbuf[0] = '\177';
1130 				tbuf[1] = '\0';
1131 				s = tbuf;
1132 		}
1133 		break;
1134 #endif
1135 #if MSDOS_COMPILER
1136 	case SK_RIGHT_ARROW:
1137 		s = k_right;
1138 		break;
1139 	case SK_LEFT_ARROW:
1140 		s = k_left;
1141 		break;
1142 	case SK_UP_ARROW:
1143 		s = k_up;
1144 		break;
1145 	case SK_DOWN_ARROW:
1146 		s = k_down;
1147 		break;
1148 	case SK_PAGE_UP:
1149 		s = k_pageup;
1150 		break;
1151 	case SK_SHIFT_PAGE_UP:
1152 		s = k_shift_pageup;
1153 		break;
1154 	case SK_CTL_PAGE_UP:
1155 		s = k_ctl_pageup;
1156 		break;
1157 	case SK_PAGE_DOWN:
1158 		s = k_pagedown;
1159 		break;
1160 	case SK_SHIFT_PAGE_DOWN:
1161 		s = k_shift_pagedown;
1162 		break;
1163 	case SK_CTL_PAGE_DOWN:
1164 		s = k_ctl_pagedown;
1165 		break;
1166 	case SK_HOME:
1167 		s = k_home;
1168 		break;
1169 	case SK_END:
1170 		s = k_end;
1171 		break;
1172 	case SK_DELETE:
1173 		s = k_delete;
1174 		break;
1175 #endif
1176 #if MSDOS_COMPILER || OS2
1177 	case SK_INSERT:
1178 		s = k_insert;
1179 		break;
1180 	case SK_CTL_LEFT_ARROW:
1181 		s = k_ctl_left;
1182 		break;
1183 	case SK_CTL_RIGHT_ARROW:
1184 		s = k_ctl_right;
1185 		break;
1186 	case SK_CTL_UP_ARROW:
1187 		s = k_ctl_up;
1188 		break;
1189 	case SK_CTL_DOWN_ARROW:
1190 		s = k_ctl_down;
1191 		break;
1192 	case SK_SHIFT_LEFT_ARROW:
1193 		s = k_shift_left;
1194 		break;
1195 	case SK_SHIFT_RIGHT_ARROW:
1196 		s = k_shift_right;
1197 		break;
1198 	case SK_SHIFT_UP_ARROW:
1199 		s = k_shift_up;
1200 		break;
1201 	case SK_SHIFT_DOWN_ARROW:
1202 		s = k_shift_down;
1203 		break;
1204 	case SK_CTL_BACKSPACE:
1205 		s = k_ctl_backspace;
1206 		break;
1207 	case SK_CTL_DELETE:
1208 		s = k_ctl_delete;
1209 		break;
1210 	case SK_SHIFT_DELETE:
1211 		s = k_shift_delete;
1212 		break;
1213 	case SK_BACKSPACE:
1214 		s = k_backspace;
1215 		break;
1216 	case SK_F1:
1217 		s = k_f1;
1218 		break;
1219 	case SK_BACKTAB:
1220 		s = k_backtab;
1221 		break;
1222 	case SK_SHIFT_HOME:
1223 		s = k_shift_home;
1224 		break;
1225 	case SK_CTL_HOME:
1226 		s = k_ctl_home;
1227 		break;
1228 	case SK_SHIFT_END:
1229 		s = k_shift_end;
1230 		break;
1231 	case SK_CTL_END:
1232 		s = k_ctl_end;
1233 		break;
1234 #else
1235 	case SK_RIGHT_ARROW:
1236 		s = ltgetstr("kcuf1", "kr", &sp);
1237 		break;
1238 	case SK_LEFT_ARROW:
1239 		s = ltgetstr("kcub1", "kl", &sp);
1240 		break;
1241 	case SK_UP_ARROW:
1242 		s = ltgetstr("kcuu1", "ku", &sp);
1243 		break;
1244 	case SK_DOWN_ARROW:
1245 		s = ltgetstr("kcud1", "kd", &sp);
1246 		break;
1247 	case SK_PAGE_UP:
1248 		s = ltgetstr("kpp", "kP", &sp);
1249 		break;
1250 	case SK_SHIFT_PAGE_UP:
1251 		s = ltgetstr("kPRV", NULL, &sp);
1252 		break;
1253 	case SK_CTL_PAGE_UP:
1254 		s = ltgetstr("kPRV5", NULL, &sp);
1255 		break;
1256 	case SK_PAGE_DOWN:
1257 		s = ltgetstr("knp", "kN", &sp);
1258 		break;
1259 	case SK_SHIFT_PAGE_DOWN:
1260 		s = ltgetstr("kNXT", NULL, &sp);
1261 		break;
1262 	case SK_CTL_PAGE_DOWN:
1263 		s = ltgetstr("kNXT5", NULL, &sp);
1264 		break;
1265 	case SK_HOME:
1266 		s = ltgetstr("khome", "kh", &sp);
1267 		break;
1268 	case SK_SHIFT_HOME:
1269 		s = ltgetstr("kHOM", "#2", &sp);
1270 		break;
1271 	case SK_CTL_HOME:
1272 		s = ltgetstr("kHOM5", NULL, &sp);
1273 		break;
1274 	case SK_END:
1275 		s = ltgetstr("kend", "@7", &sp);
1276 		break;
1277 	case SK_SHIFT_END:
1278 		s = ltgetstr("kEND", "#7", &sp);
1279 		break;
1280 	case SK_CTL_END:
1281 		s = ltgetstr("kEND5", NULL, &sp);
1282 		break;
1283 	case SK_INSERT:
1284 		s = ltgetstr("kich1", "kI", &sp);
1285 		break;
1286 	case SK_BACKTAB:
1287 		s = ltgetstr("kcbt", "kB", &sp);
1288 		break;
1289 	case SK_SHIFT_RIGHT_ARROW:
1290 		s = ltgetstr("kRIT", NULL, &sp);
1291 		break;
1292 	case SK_SHIFT_LEFT_ARROW:
1293 		s = ltgetstr("kLFT", NULL, &sp);
1294 		break;
1295 	case SK_SHIFT_UP_ARROW:
1296 		s = ltgetstr("kUP", NULL, &sp);
1297 		break;
1298 	case SK_SHIFT_DOWN_ARROW:
1299 		s = ltgetstr("kDN", NULL, &sp);
1300 		break;
1301 	case SK_CTL_RIGHT_ARROW:
1302 		s = ltgetstr("kRIT5", NULL, &sp);
1303 		break;
1304 	case SK_CTL_LEFT_ARROW:
1305 		s = ltgetstr("kLFT5", NULL, &sp);
1306 		break;
1307 	case SK_CTL_UP_ARROW:
1308 		s = ltgetstr("kUP5", NULL, &sp);
1309 		break;
1310 	case SK_CTL_DOWN_ARROW:
1311 		s = ltgetstr("kDN5", NULL, &sp);
1312 		break;
1313 	case SK_F1:
1314 		s = ltgetstr("kf1", "k1", &sp);
1315 		break;
1316 	case SK_PAD_UL:
1317 		s = ltgetstr("ka1", "K1", &sp);
1318 		break;
1319 	case SK_PAD_U:
1320 		s = ltgetstr("ka2", NULL, &sp);
1321 		break;
1322 	case SK_PAD_UR:
1323 		s = ltgetstr("ka3", "K3", &sp);
1324 		break;
1325 	case SK_PAD_L:
1326 		s = ltgetstr("kb1", NULL, &sp);
1327 		break;
1328 	case SK_PAD_CENTER:
1329 		s = ltgetstr("kb2", "K2", &sp);
1330 		break;
1331 	case SK_PAD_R:
1332 		s = ltgetstr("kb3", NULL, &sp);
1333 		break;
1334 	case SK_PAD_DL:
1335 		s = ltgetstr("kc1", "K4", &sp);
1336 		break;
1337 	case SK_PAD_D:
1338 		s = ltgetstr("kc2", NULL, &sp);
1339 		break;
1340 	case SK_PAD_DR:
1341 		s = ltgetstr("kc3", "K5", &sp);
1342 		break;
1343 	case SK_PAD_STAR:
1344 		s = ltgetstr("kpMUL", NULL, &sp);
1345 		break;
1346 	case SK_PAD_SLASH:
1347 		s = ltgetstr("kpDIV", NULL, &sp);
1348 		break;
1349 	case SK_PAD_DASH:
1350 		s = ltgetstr("kpSUB", NULL, &sp);
1351 		break;
1352 	case SK_PAD_PLUS:
1353 		s = ltgetstr("kpADD", NULL, &sp);
1354 		break;
1355 	case SK_PAD_DOT:
1356 		s = ltgetstr("kpDOT", NULL, &sp);
1357 		break;
1358 	case SK_PAD_COMMA:
1359 		s = ltgetstr("kpCMA", NULL, &sp);
1360 		break;
1361 	case SK_PAD_ZERO:
1362 		s = ltgetstr("kpZRO", NULL, &sp);
1363 		break;
1364 	case SK_PAD_ENTER:
1365 		s = kent;
1366 		kent_mapped = TRUE;
1367 		break;
1368 	case SK_DELETE:
1369 		s = ltgetstr("kdch1", "kD", &sp);
1370 		if (s == NULL)
1371 		{
1372 				tbuf[0] = '\177';
1373 				tbuf[1] = '\0';
1374 				s = tbuf;
1375 		}
1376 		break;
1377 	case SK_CTL_DELETE:
1378 		s = ltgetstr("kDC5", NULL, &sp);
1379 		break;
1380 	case SK_SHIFT_DELETE:
1381 		s = ltgetstr("kDC", NULL, &sp);
1382 		break;
1383 	case SK_BACKSPACE:
1384 		s = ltgetstr("kbs", "kb", &sp);
1385 		if (s == NULL)
1386 		{
1387 				tbuf[0] = '\b';
1388 				tbuf[1] = '\0';
1389 				s = tbuf;
1390 		}
1391 		break;
1392 #endif
1393 	case SK_CONTROL_K:
1394 		tbuf[0] = CONTROL('K');
1395 		tbuf[1] = '\0';
1396 		s = tbuf;
1397 		break;
1398 	default:
1399 		return (NULL);
1400 	}
1401 	if (s != NULL && *s == '\0')
1402 		s = NULL;
1403 	return (s);
1404 }
1405 
1406 #if !MSDOS_COMPILER
1407 static constant char *ltgoto(constant char *cap, int col, int line)
1408 {
1409 #if USE_TERMINFO
1410 	return LTPARM2(cap, line, col);
1411 #else
1412 	return tgoto(cap, col, line);
1413 #endif
1414 }
1415 #endif /* MSDOS_COMPILER */
1416 
1417 #if MSDOS_COMPILER
1418 public void init_win_colors(void)
1419 {
1420 	if (nm_fg_color == CV_ERROR || nm_fg_color == CV_NOCHANGE) nm_fg_color = sy_fg_color;
1421 	if (nm_bg_color == CV_ERROR || nm_bg_color == CV_NOCHANGE) nm_bg_color = sy_bg_color;
1422 	if (bo_fg_color == CV_NOCHANGE) bo_fg_color = sy_fg_color; else if (bo_fg_color == CV_ERROR) bo_fg_color = sy_fg_color | 8;
1423 	if (bo_bg_color == CV_NOCHANGE) bo_bg_color = sy_bg_color; else if (bo_bg_color == CV_ERROR) bo_bg_color = sy_bg_color;
1424 	if (ul_fg_color == CV_NOCHANGE) ul_fg_color = sy_fg_color; else if (ul_fg_color == CV_ERROR) ul_fg_color = (sy_bg_color == 3 || sy_bg_color == 11) ? 0 : 11;
1425 	if (ul_bg_color == CV_NOCHANGE) ul_bg_color = sy_bg_color; else if (ul_bg_color == CV_ERROR) ul_bg_color = sy_bg_color;
1426 	if (so_fg_color == CV_NOCHANGE) so_fg_color = sy_fg_color; else if (so_fg_color == CV_ERROR) so_fg_color = sy_bg_color;
1427 	if (so_bg_color == CV_NOCHANGE) so_bg_color = sy_bg_color; else if (so_bg_color == CV_ERROR) so_bg_color = sy_fg_color;
1428 	if (bl_fg_color == CV_NOCHANGE) bl_fg_color = sy_fg_color; else if (bl_fg_color == CV_ERROR) bl_fg_color = ul_bg_color;
1429 	if (bl_bg_color == CV_NOCHANGE) bl_bg_color = sy_bg_color; else if (bl_bg_color == CV_ERROR) bl_bg_color = ul_fg_color;
1430 	nm_fg_color |= nm_attr;
1431 	bo_fg_color |= bo_attr;
1432 	ul_fg_color |= ul_attr;
1433 	so_fg_color |= so_attr;
1434 	bl_fg_color |= bl_attr;
1435 }
1436 #endif /* MSDOS_COMPILER */
1437 
1438 #if !MSDOS_COMPILER
1439 /*
1440  * Return the cost of displaying a termcap string.
1441  * We use the trick of calling tputs, but as a char printing function
1442  * we give it inc_costcount, which just increments "costcount".
1443  * This tells us how many chars would be printed by using this string.
1444  */
1445 static int costcount;
1446 
1447 /*ARGSUSED*/
1448 static int inc_costcount(int c)
1449 {
1450 	costcount++;
1451 	return (c);
1452 }
1453 
1454 static int cost(constant char *t)
1455 {
1456 	costcount = 0;
1457 	tputs(t, sc_height, inc_costcount);
1458 	return (costcount);
1459 }
1460 
1461 /*
1462  * Return the "best" of the two given termcap strings.
1463  * The best, if both exist, is the one with the lower
1464  * cost (see cost() function).
1465  */
1466 static constant char * cheaper(constant char *t1, constant char *t2, constant char *def)
1467 {
1468 	if (*t1 == '\0' && *t2 == '\0')
1469 	{
1470 		missing_cap = TRUE;
1471 		return (def);
1472 	}
1473 	if (*t1 == '\0')
1474 		return (t2);
1475 	if (*t2 == '\0')
1476 		return (t1);
1477 	if (cost(t1) < cost(t2))
1478 		return (t1);
1479 	return (t2);
1480 }
1481 
1482 static void tmodes(constant char *inti, constant char *outti, constant char *intc, constant char *outtc, constant char **instr, constant char **outstr, constant char *def_instr, constant char *def_outstr, char **spp)
1483 {
1484 	*instr = ltgetstr(inti, intc, spp);
1485 	if (*instr == NULL)
1486 	{
1487 		/* Use defaults. */
1488 		*instr = def_instr;
1489 		*outstr = def_outstr;
1490 		return;
1491 	}
1492 
1493 	*outstr = ltgetstr(outti, outtc, spp);
1494 	if (*outstr == NULL)
1495 		/* No specific out capability; use "me". */
1496 		*outstr = ltgetstr("sgr0", "me", spp);
1497 	if (*outstr == NULL)
1498 		/* Don't even have "me"; use a null string. */
1499 		*outstr = "";
1500 }
1501 
1502 /*
1503  * Get terminal capabilities from termcap or terminfo.
1504  */
1505 static void get_term_info(void)
1506 {
1507 	constant char *t1;
1508 	constant char *t2;
1509 	constant char *term;
1510 	char *sp;
1511 	/*
1512 	 * Some termcap libraries assume termbuf is static
1513 	 * (accessible after tgetent returns).
1514 	 */
1515 #if USE_TERMINFO
1516 	int err;
1517 #else
1518 	static char termbuf[TERMBUF_SIZE];
1519 #endif
1520 	static char sbuf[TERMSBUF_SIZE];
1521 
1522 #if OS2
1523 	/*
1524 	 * Make sure the termcap database is available.
1525 	 */
1526 	constant char *cp = lgetenv("TERMCAP");
1527 	if (isnullenv(cp))
1528 	{
1529 		char *termcap;
1530 		if ((sp = homefile("termcap.dat")) != NULL)
1531 		{
1532 			termcap = (char *) ecalloc(strlen(sp)+9, sizeof(char));
1533 			sprintf(termcap, "TERMCAP=%s", sp);
1534 			free(sp);
1535 			putenv(termcap);
1536 		}
1537 	}
1538 #endif
1539 	/*
1540 	 * Find out what kind of terminal this is.
1541 	 */
1542 	if ((term = lgetenv("TERM")) == NULL && (term = getenv("TERM")) == NULL)
1543 		term = DEFAULT_TERM;
1544 	hardcopy = FALSE;
1545 #if USE_TERMINFO
1546 	if (setupterm(term, -1, &err) != OK || err != 1)
1547 		hardcopy = TRUE;
1548 #else
1549 	if (tgetent(termbuf, term) != TGETENT_OK)
1550 		hardcopy = TRUE;
1551 #endif
1552 	if (!hardcopy && ltgetflag("hc", "hc"))
1553 		hardcopy = TRUE;
1554 
1555 	auto_wrap = ltgetflag("am", "am");
1556 	defer_wrap = ltgetflag("xenl", "xn");
1557 	above_mem = ltgetflag("da", "da");
1558 	below_mem = ltgetflag("db", "db");
1559 	no_alt_screen = ltgetflag("nrrmc", "NR");
1560 
1561 	/*
1562 	 * Assumes termcap variable "sg" is the printing width of:
1563 	 * the standout sequence, the end standout sequence,
1564 	 * the underline sequence, the end underline sequence,
1565 	 * the boldface sequence, and the end boldface sequence.
1566 	 */
1567 	if ((so_s_width = ltgetnum("xmc", "sg")) < 0)
1568 		so_s_width = 0;
1569 	so_e_width = so_s_width;
1570 
1571 	bo_s_width = bo_e_width = so_s_width;
1572 	ul_s_width = ul_e_width = so_s_width;
1573 	bl_s_width = bl_e_width = so_s_width;
1574 
1575 #if HILITE_SEARCH
1576 	if (so_s_width > 0 || so_e_width > 0)
1577 		/*
1578 		 * Disable highlighting by default on magic cookie terminals.
1579 		 * Turning on highlighting might change the displayed width
1580 		 * of a line, causing the display to get messed up.
1581 		 * The user can turn it back on with -g,
1582 		 * but she won't like the results.
1583 		 */
1584 		hilite_search = 0;
1585 #endif
1586 
1587 	/*
1588 	 * Get various string-valued capabilities.
1589 	 */
1590 	sp = sbuf;
1591 
1592 #if HAVE_OSPEED
1593 	sc_pad = ltgetstr("pad", "pc", &sp);
1594 	if (sc_pad != NULL)
1595 		PC = *sc_pad;
1596 #endif
1597 
1598 	sc_s_keypad = ltgetstr("smkx", "ks", &sp);
1599 	if (sc_s_keypad == NULL)
1600 		sc_s_keypad = "";
1601 	sc_e_keypad = ltgetstr("rmkx", "ke", &sp);
1602 	if (sc_e_keypad == NULL)
1603 		sc_e_keypad = "";
1604 	kent = ltgetstr("kent", "@8", &sp);
1605 
1606 	sc_s_mousecap = ltgetstr("MOUSE_START", "MOUSE_START", &sp);
1607 	if (sc_s_mousecap == NULL)
1608 		sc_s_mousecap = ESCS "[?1000h" ESCS "[?1002h" ESCS "[?1006h";
1609 	sc_e_mousecap = ltgetstr("MOUSE_END", "MOUSE_END", &sp);
1610 	if (sc_e_mousecap == NULL)
1611 		sc_e_mousecap = ESCS "[?1006l" ESCS "[?1002l" ESCS "[?1000l";
1612 
1613 	sc_s_bracketed_paste = ltgetstr("BRACKETED_PASTE_START", "BRACKETED_PASTE_START", &sp);
1614 	if (sc_s_bracketed_paste == NULL)
1615 		sc_s_bracketed_paste = ESCS"[?2004h";
1616 	sc_e_bracketed_paste = ltgetstr("BRACKETED_PASTE_END", "BRACKETED_PASTE_END", &sp);
1617 	if (sc_e_bracketed_paste == NULL)
1618 		sc_e_bracketed_paste = ESCS"[?2004l";
1619 
1620 	sc_suspend = ltgetstr("SUSPEND", "SUSPEND", &sp);
1621 	if (sc_suspend == NULL)
1622 		sc_suspend = "";
1623 	sc_resume = ltgetstr("RESUME", "RESUME", &sp);
1624 	if (sc_resume == NULL)
1625 		sc_resume = "";
1626 
1627 	sc_init = ltgetstr("smcup", "ti", &sp);
1628 	if (sc_init == NULL)
1629 		sc_init = "";
1630 
1631 	sc_deinit= ltgetstr("rmcup", "te", &sp);
1632 	if (sc_deinit == NULL)
1633 		sc_deinit = "";
1634 
1635 	sc_eol_clear = ltgetstr("el", "ce", &sp);
1636 	if (sc_eol_clear == NULL || *sc_eol_clear == '\0')
1637 	{
1638 		missing_cap = TRUE;
1639 		sc_eol_clear = "";
1640 	}
1641 
1642 	sc_eos_clear = ltgetstr("ed", "cd", &sp);
1643 	if (below_mem && (sc_eos_clear == NULL || *sc_eos_clear == '\0'))
1644 	{
1645 		missing_cap = TRUE;
1646 		sc_eos_clear = "";
1647 	}
1648 
1649 	sc_clear = ltgetstr("clear", "cl", &sp);
1650 	if (sc_clear == NULL || *sc_clear == '\0')
1651 	{
1652 		missing_cap = TRUE;
1653 		sc_clear = "\n\n";
1654 	}
1655 
1656 	sc_move = ltgetstr("cup", "cm", &sp);
1657 	if (sc_move == NULL || *sc_move == '\0')
1658 	{
1659 		/*
1660 		 * This is not an error here, because we don't
1661 		 * always need sc_move.
1662 		 * We need it only if we don't have home or lower-left.
1663 		 */
1664 		sc_move = "";
1665 		can_goto_line = FALSE;
1666 	} else
1667 		can_goto_line = TRUE;
1668 
1669 	tmodes("smso", "rmso", "so", "se", &sc_s_in, &sc_s_out, "", "", &sp);
1670 	tmodes("smul", "rmul", "us", "ue", &sc_u_in, &sc_u_out, sc_s_in, sc_s_out, &sp);
1671 	tmodes("bold", "sgr0", "md", "me", &sc_b_in, &sc_b_out, sc_s_in, sc_s_out, &sp);
1672 	tmodes("blink", "sgr0", "mb", "me", &sc_bl_in, &sc_bl_out, sc_s_in, sc_s_out, &sp);
1673 
1674 	sc_visual_bell = ltgetstr("flash", "vb", &sp);
1675 	if (sc_visual_bell == NULL)
1676 		sc_visual_bell = "";
1677 
1678 	if (ltgetflag(NULL, "bs"))
1679 		sc_backspace = "\b";
1680 	else
1681 	{
1682 		sc_backspace = ltgetstr("OTbc", "bc", &sp);
1683 		if (sc_backspace == NULL || *sc_backspace == '\0')
1684 			sc_backspace = "\b";
1685 	}
1686 
1687 	/*
1688 	 * Choose between using "ho" and "cm" ("home" and "cursor move")
1689 	 * to move the cursor to the upper left corner of the screen.
1690 	 */
1691 	t1 = ltgetstr("home", "ho", &sp);
1692 	if (t1 == NULL)
1693 		t1 = "";
1694 	if (*sc_move == '\0')
1695 		t2 = "";
1696 	else
1697 	{
1698 		strcpy(sp, ltgoto(sc_move, 0, 0));
1699 		t2 = sp;
1700 		sp += strlen(sp) + 1;
1701 	}
1702 	sc_home = cheaper(t1, t2, "|\b^");
1703 
1704 	sc_ll = ltgetstr("ll", "ll", &sp);
1705 
1706 	/*
1707 	 * Get carriage return string.
1708 	 */
1709 	sc_return = ltgetstr("cr", "cr", &sp);
1710 	if (sc_return == NULL)
1711 		sc_return = "\r";
1712 
1713 	/*
1714 	 * Choose between using "al" or "sr" ("add line" or "scroll reverse")
1715 	 * to add a line at the top of the screen.
1716 	 */
1717 	t1 = ltgetstr("ill", "al", &sp);
1718 	if (t1 == NULL)
1719 		t1 = "";
1720 	t2 = ltgetstr("ri", "sr", &sp);
1721 	if (t2 == NULL)
1722 		t2 = "";
1723 	if (*t1 == '\0' && *t2 == '\0')
1724 		sc_addline = "";
1725 	else if (above_mem)
1726 		sc_addline = t1;
1727 	else
1728 		sc_addline = cheaper(t1, t2, "");
1729 	if (*sc_addline == '\0')
1730 	{
1731 		/*
1732 		 * Force repaint on any backward movement.
1733 		 */
1734 		no_back_scroll = TRUE;
1735 	}
1736 }
1737 
1738 /*
1739  * Update terminal capabilities after screen size has changed.
1740  */
1741 static void reget_term_info(void)
1742 {
1743 	constant char *t1;
1744 	constant char *t2;
1745 	static char sbuf[64];
1746 
1747 	/*
1748 	 * Choose between using "ll" and "cm"  ("lower left" and "cursor move")
1749 	 * to move the cursor to the lower left corner of the screen.
1750 	 */
1751 	t1 = sc_ll;
1752 	if (t1 == NULL || !full_screen)
1753 		t1 = "";
1754 	if (*sc_move == '\0')
1755 		t2 = "";
1756 	else
1757 	{
1758 		SNPRINTF1(sbuf, sizeof(sbuf), "%s", ltgoto(sc_move, 0, sc_height-1));
1759 		t2 = sbuf;
1760 	}
1761 	sc_lower_left = cheaper(t1, t2, "\r");
1762 }
1763 #endif /* !MSDOS_COMPILER */
1764 
1765 /*
1766  * Get permanent terminal characteristics.
1767  * Called once at startup.
1768  */
1769 public void get_term(void)
1770 {
1771 #if MSDOS_COMPILER
1772 	auto_wrap = 1;
1773 	defer_wrap = 0;
1774 	can_goto_line = TRUE;
1775 	/*
1776 	 * Set up default colors.
1777 	 * The xx_s_width and xx_e_width vars are already initialized to 0.
1778 	 */
1779 #if MSDOS_COMPILER==MSOFTC
1780 	sy_bg_color = _getbkcolor();
1781 	sy_fg_color = _gettextcolor();
1782 	get_clock();
1783 #else
1784 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
1785     {
1786 	struct text_info w;
1787 	gettextinfo(&w);
1788 	sy_bg_color = (w.attribute >> 4) & 0x0F;
1789 	sy_fg_color = (w.attribute >> 0) & 0x0F;
1790     }
1791 #else
1792 #if MSDOS_COMPILER==WIN32C
1793     {
1794 	CONSOLE_SCREEN_BUFFER_INFO scr;
1795 
1796 	con_out_save = con_out = GetStdHandle(STD_OUTPUT_HANDLE);
1797 	/*
1798 	 * Always open stdin in binary. Note this *must* be done
1799 	 * before any file operations have been done on fd0.
1800 	 */
1801 	SET_BINARY(0);
1802 	GetConsoleMode(con_out, &init_console_output_mode);
1803 	GetConsoleScreenBufferInfo(con_out, &scr);
1804 	curr_attr = scr.wAttributes;
1805 	sy_bg_color = (curr_attr & BG_COLORS) >> 4; /* normalize */
1806 	sy_fg_color = curr_attr & FG_COLORS;
1807     }
1808 #endif
1809 #endif
1810 #endif
1811 	init_win_colors();
1812 #else
1813 	termcap_debug = !isnullenv(lgetenv("LESS_TERMCAP_DEBUG"));
1814 	get_term_info();
1815 #endif /* MSDOS_COMPILER */
1816 	update_term();
1817 }
1818 
1819 /*
1820  * Get terminal characteristics which may change during execution (size, etc).
1821  * May be called multiple times.
1822  */
1823 public void update_term(void)
1824 {
1825 	/* Get size of the screen. */
1826 	scrsize();
1827 	pos_init();
1828 #if !MSDOS_COMPILER
1829 	reget_term_info();
1830 #endif
1831 }
1832 
1833 
1834 /*
1835  * Below are the functions which perform all the
1836  * terminal-specific screen manipulation.
1837  */
1838 
1839 
1840 #if MSDOS_COMPILER
1841 
1842 #if MSDOS_COMPILER==WIN32C
1843 static void _settextposition(int row, int col)
1844 {
1845 	COORD cpos;
1846 	CONSOLE_SCREEN_BUFFER_INFO csbi;
1847 
1848 	GetConsoleScreenBufferInfo(con_out, &csbi);
1849 	cpos.X = csbi.srWindow.Left + (col - 1);
1850 	cpos.Y = csbi.srWindow.Top + (row - 1);
1851 	SetConsoleCursorPosition(con_out, cpos);
1852 }
1853 #endif
1854 
1855 /*
1856  * Initialize the screen to the correct color at startup.
1857  */
1858 static void initcolor(void)
1859 {
1860 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
1861 	intensevideo();
1862 #endif
1863 	SETCOLORS(nm_fg_color, nm_bg_color);
1864 #if 0
1865 	/*
1866 	 * This clears the screen at startup.  This is different from
1867 	 * the behavior of other versions of less.  Disable it for now.
1868 	 */
1869 	char *blanks;
1870 	int row;
1871 	int col;
1872 
1873 	/*
1874 	 * Create a complete, blank screen using "normal" colors.
1875 	 */
1876 	SETCOLORS(nm_fg_color, nm_bg_color);
1877 	blanks = (char *) ecalloc(width+1, sizeof(char));
1878 	for (col = 0;  col < sc_width;  col++)
1879 		blanks[col] = ' ';
1880 	blanks[sc_width] = '\0';
1881 	for (row = 0;  row < sc_height;  row++)
1882 		_outtext(blanks);
1883 	free(blanks);
1884 #endif
1885 }
1886 #endif
1887 
1888 #if MSDOS_COMPILER==WIN32C
1889 
1890 /*
1891  * Enable virtual terminal processing, if available.
1892  */
1893 static void win32_init_vt_term(void)
1894 {
1895 	if (vt_enabled == 0 || (vt_enabled == 1 && con_out == con_out_ours))
1896 		return;  /* already initialized */
1897 
1898 	/* don't care about the initial mode, and win VT hard-enables am+xn */
1899 	vt_enabled = SetConsoleMode(con_out, ENABLE_PROCESSED_OUTPUT |
1900 	                                     ENABLE_VIRTUAL_TERMINAL_PROCESSING |
1901 	                                     ENABLE_WRAP_AT_EOL_OUTPUT);
1902 	if (vt_enabled)
1903 	{
1904 		auto_wrap = 1;
1905 		defer_wrap = 1;
1906 	}
1907 }
1908 
1909 static void win32_deinit_vt_term(void)
1910 {
1911 	if (vt_enabled == 1 && con_out == con_out_save)
1912 		SetConsoleMode(con_out, init_console_output_mode);
1913 }
1914 
1915 /*
1916  * Termcap-like init with a private win32 console.
1917  */
1918 static void win32_init_term(void)
1919 {
1920 	CONSOLE_SCREEN_BUFFER_INFO scr;
1921 	COORD size;
1922 
1923 	if (con_out_save == INVALID_HANDLE_VALUE)
1924 		return;
1925 
1926 	GetConsoleScreenBufferInfo(con_out_save, &scr);
1927 
1928 	if (con_out_ours == INVALID_HANDLE_VALUE)
1929 	{
1930 		/*
1931 		 * Create our own screen buffer, so that we
1932 		 * may restore the original when done.
1933 		 */
1934 		con_out_ours = CreateConsoleScreenBuffer(
1935 			GENERIC_WRITE | GENERIC_READ,
1936 			FILE_SHARE_WRITE | FILE_SHARE_READ,
1937 			(LPSECURITY_ATTRIBUTES) NULL,
1938 			CONSOLE_TEXTMODE_BUFFER,
1939 			(LPVOID) NULL);
1940 
1941 		/*
1942 		 * We don't care about the initial state. We need processed
1943 		 * output without anything else (no wrap at EOL, no VT,
1944 		 * no disabled auto-return).
1945 		 */
1946 		if (SetConsoleMode(con_out_ours, ENABLE_PROCESSED_OUTPUT))
1947 			auto_wrap = 0;
1948 	}
1949 
1950 	size.X = scr.srWindow.Right - scr.srWindow.Left + 1;
1951 	size.Y = scr.srWindow.Bottom - scr.srWindow.Top + 1;
1952 	SetConsoleScreenBufferSize(con_out_ours, size);
1953 	SetConsoleActiveScreenBuffer(con_out_ours);
1954 	con_out = con_out_ours;
1955 }
1956 
1957 /*
1958  * Restore the startup console.
1959  */
1960 static void win32_deinit_term(void)
1961 {
1962 	if (con_out_save == INVALID_HANDLE_VALUE)
1963 		return;
1964 	if (quitting)
1965 		(void) CloseHandle(con_out_ours);
1966 	SetConsoleActiveScreenBuffer(con_out_save);
1967 	con_out = con_out_save;
1968 }
1969 
1970 #endif
1971 
1972 #if !MSDOS_COMPILER
1973 static void do_tputs(constant char *str, int affcnt, int (*f_putc)(int))
1974 {
1975 #if LESSTEST
1976 	if (is_lesstest() && f_putc == putchr)
1977 		putstr(str);
1978 	else
1979 #endif /*LESSTEST*/
1980 		tputs(str, affcnt, f_putc);
1981 }
1982 
1983 /*
1984  * Like tputs but we handle $<...> delay strings here because
1985  * some implementations of tputs don't perform delays correctly.
1986  */
1987 static void ltputs(constant char *str, int affcnt, int (*f_putc)(int))
1988 {
1989 	while (str != NULL && *str != '\0')
1990 	{
1991 		constant char *obrac = strstr(str, "$<");
1992 		if (obrac != NULL)
1993 		{
1994 			char str2[64];
1995 			size_t slen = ptr_diff(obrac, str);
1996 			if (slen < sizeof(str2))
1997 			{
1998 				int delay;
1999 				/* Output first part of string (before "$<"). */
2000 				memcpy(str2, str, slen);
2001 				str2[slen] = '\0';
2002 				do_tputs(str2, affcnt, f_putc);
2003 				str += slen + 2;
2004 				/* Perform the delay. */
2005 				delay = lstrtoic(str, &str, 10);
2006 				if (*str == '*')
2007 					if (ckd_mul(&delay, delay, affcnt))
2008 						delay = INT_MAX;
2009 				flush();
2010 				sleep_ms(delay);
2011 				/* Skip past closing ">" at end of delay string. */
2012 				str = strstr(str, ">");
2013 				if (str != NULL)
2014 					str++;
2015 				continue;
2016 			}
2017 		}
2018 		/* Pass the rest of the string to tputs and we're done. */
2019 		do_tputs(str, affcnt, f_putc);
2020 		break;
2021 	}
2022 }
2023 #endif /* MSDOS_COMPILER */
2024 
2025 /*
2026  * Configure the terminal so mouse clicks and wheel moves
2027  * produce input to less.
2028  */
2029 public void init_mouse(void)
2030 {
2031 #if !MSDOS_COMPILER
2032 	ltputs(sc_s_mousecap, sc_height, putchr);
2033 #else
2034 #if MSDOS_COMPILER==WIN32C
2035 	curr_console_input_mode = mouse_console_input_mode;
2036 	SetConsoleMode(tty, curr_console_input_mode);
2037 #endif
2038 #endif
2039 }
2040 
2041 /*
2042  * Configure the terminal so mouse clicks and wheel moves
2043  * are handled by the system (so text can be selected, etc).
2044  */
2045 public void deinit_mouse(void)
2046 {
2047 #if !MSDOS_COMPILER
2048 	ltputs(sc_e_mousecap, sc_height, putchr);
2049 #else
2050 #if MSDOS_COMPILER==WIN32C
2051 	curr_console_input_mode = base_console_input_mode;
2052 	SetConsoleMode(tty, curr_console_input_mode);
2053 #endif
2054 #endif
2055 }
2056 
2057 /*
2058  * Suspend screen updates.
2059  */
2060 public void suspend_screen(void)
2061 {
2062 #if !MSDOS_COMPILER
2063 	ltputs(sc_suspend, 1, putchr);
2064 #endif
2065 }
2066 
2067 /*
2068  * Resume screen updates.
2069  */
2070 public void resume_screen(void)
2071 {
2072 #if !MSDOS_COMPILER
2073 	ltputs(sc_resume, 1, putchr);
2074 #endif
2075 }
2076 
2077 /*
2078  * Initialize terminal
2079  */
2080 public void term_init(void)
2081 {
2082 	if (term_init_done)
2083 		return;
2084 	term_init_done = term_init_ever = TRUE;
2085 	clear_bot_if_needed();
2086 #if !MSDOS_COMPILER
2087 	if (!(quit_if_one_screen && one_screen))
2088 	{
2089 		if (!no_init)
2090 		{
2091 			ltputs(sc_init, sc_height, putchr);
2092 			/*
2093 			 * Some terminals leave the cursor unmoved when switching
2094 			 * to the alt screen. To avoid having the text appear at
2095 			 * a seemingly random line on the alt screen, move to
2096 			 * lower left if we are using an alt screen.
2097 			 */
2098 			if (*sc_init != '\0' && *sc_deinit != '\0' && !no_alt_screen)
2099 				lower_left();
2100 			term_addrs = TRUE;
2101 		}
2102 		if (!no_keypad)
2103 			ltputs(sc_s_keypad, sc_height, putchr);
2104 		if (emouse != 0)
2105 			init_mouse();
2106 		if (no_paste)
2107 			init_bracketed_paste();
2108 	}
2109 	if (top_scroll)
2110 	{
2111 		int i;
2112 
2113 		/*
2114 		 * This is nice to terminals with no alternate screen,
2115 		 * but with saved scrolled-off-the-top lines.  This way,
2116 		 * no previous line is lost, but we start with a whole
2117 		 * screen to ourself.
2118 		 */
2119 		for (i = 1; i < sc_height; i++)
2120 			putchr('\n');
2121 	} else
2122 		line_left();
2123 #else
2124 #if MSDOS_COMPILER==WIN32C
2125 	if (!(quit_if_one_screen && one_screen))
2126 	{
2127 		if (!no_init)
2128 		{
2129 			win32_init_term();
2130 			term_addrs = TRUE;
2131 		}
2132 		if (emouse != 0)
2133 			init_mouse();
2134 
2135 	}
2136 	win32_init_vt_term();
2137 #endif
2138 	initcolor();
2139 	flush();
2140 #endif
2141 }
2142 
2143 /*
2144  * Deinitialize terminal
2145  */
2146 public void term_deinit(void)
2147 {
2148 	if (!term_init_done)
2149 		return;
2150 #if !MSDOS_COMPILER
2151 	if (!(quit_if_one_screen && one_screen))
2152 	{
2153 		if (emouse != 0)
2154 			deinit_mouse();
2155 		if (no_paste)
2156 			deinit_bracketed_paste();
2157 		if (!no_keypad)
2158 			ltputs(sc_e_keypad, sc_height, putchr);
2159 		if (!no_init)
2160 			ltputs(sc_deinit, sc_height, putchr);
2161 	}
2162 #else
2163 	/* Restore system colors. */
2164 	SETCOLORS(sy_fg_color, sy_bg_color);
2165 #if MSDOS_COMPILER==WIN32C
2166 	win32_deinit_vt_term();
2167 	if (!(quit_if_one_screen && one_screen))
2168 	{
2169 		if (emouse != 0)
2170 			deinit_mouse();
2171 		if (!no_init)
2172 			win32_deinit_term();
2173 	}
2174 #else
2175 	/* Need clreol to make SETCOLORS take effect. */
2176 	clreol();
2177 #endif
2178 #endif
2179 	term_init_done = FALSE;
2180 }
2181 
2182 /*
2183  * Are we interactive (ie. writing to an initialized tty)?
2184  */
2185 public lbool interactive(void)
2186 {
2187 	return (is_tty && term_init_done);
2188 }
2189 
2190 static void assert_interactive(void)
2191 {
2192 }
2193 
2194 /*
2195  * Home cursor (move to upper left corner of screen).
2196  */
2197 public void home(void)
2198 {
2199 	assert_interactive();
2200 #if !MSDOS_COMPILER
2201 	ltputs(sc_home, 1, putchr);
2202 #else
2203 	flush();
2204 	_settextposition(1,1);
2205 #endif
2206 }
2207 
2208 #if LESSTEST
2209 public void dump_screen(void)
2210 {
2211 	char dump_cmd[32];
2212 	SNPRINTF1(dump_cmd, sizeof(dump_cmd), ESCS"0;0;%dR", sc_width * sc_height);
2213 	ltputs(dump_cmd, sc_height, putchr);
2214 	flush();
2215 }
2216 #endif /*LESSTEST*/
2217 
2218 /*
2219  * Add a blank line (called with cursor at home).
2220  * Should scroll the display down.
2221  */
2222 public void add_line(void)
2223 {
2224 	assert_interactive();
2225 #if !MSDOS_COMPILER
2226 	ltputs(sc_addline, sc_height, putchr);
2227 #else
2228 	flush();
2229 #if MSDOS_COMPILER==MSOFTC
2230 	_scrolltextwindow(_GSCROLLDOWN);
2231 	_settextposition(1,1);
2232 #else
2233 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
2234 	movetext(1,1, sc_width,sc_height-1, 1,2);
2235 	gotoxy(1,1);
2236 	clreol();
2237 #else
2238 #if MSDOS_COMPILER==WIN32C
2239     {
2240 	CHAR_INFO fillchar;
2241 	SMALL_RECT rcSrc, rcClip;
2242 	COORD new_org;
2243 	CONSOLE_SCREEN_BUFFER_INFO csbi;
2244 
2245 	GetConsoleScreenBufferInfo(con_out,&csbi);
2246 
2247 	/* The clip rectangle is the entire visible screen. */
2248 	rcClip.Left = csbi.srWindow.Left;
2249 	rcClip.Top = csbi.srWindow.Top;
2250 	rcClip.Right = csbi.srWindow.Right;
2251 	rcClip.Bottom = csbi.srWindow.Bottom;
2252 
2253 	/* The source rectangle is the visible screen minus the last line. */
2254 	rcSrc = rcClip;
2255 	rcSrc.Bottom--;
2256 
2257 	/* Move the top left corner of the source window down one row. */
2258 	new_org.X = rcSrc.Left;
2259 	new_org.Y = rcSrc.Top + 1;
2260 
2261 	/* Fill the right character and attributes. */
2262 	fillchar.Char.AsciiChar = ' ';
2263 	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
2264 	fillchar.Attributes = curr_attr;
2265 	ScrollConsoleScreenBuffer(con_out, &rcSrc, &rcClip, new_org, &fillchar);
2266 	_settextposition(1,1);
2267     }
2268 #endif
2269 #endif
2270 #endif
2271 #endif
2272 }
2273 
2274 #if 0
2275 /*
2276  * Remove the n topmost lines and scroll everything below it in the
2277  * window upward.  This is needed to stop leaking the topmost line
2278  * into the scrollback buffer when we go down-one-line (in WIN32).
2279  */
2280 public void remove_top(int n)
2281 {
2282 #if MSDOS_COMPILER==WIN32C
2283 	SMALL_RECT rcSrc, rcClip;
2284 	CHAR_INFO fillchar;
2285 	COORD new_org;
2286 	CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */
2287 
2288 	if (n >= sc_height - 1)
2289 	{
2290 		lclear();
2291 		home();
2292 		return;
2293 	}
2294 
2295 	flush();
2296 
2297 	GetConsoleScreenBufferInfo(con_out, &csbi);
2298 
2299 	/* Get the extent of all-visible-rows-but-the-last. */
2300 	rcSrc.Left    = csbi.srWindow.Left;
2301 	rcSrc.Top     = csbi.srWindow.Top + n;
2302 	rcSrc.Right   = csbi.srWindow.Right;
2303 	rcSrc.Bottom  = csbi.srWindow.Bottom;
2304 
2305 	/* Get the clip rectangle. */
2306 	rcClip.Left   = rcSrc.Left;
2307 	rcClip.Top    = csbi.srWindow.Top;
2308 	rcClip.Right  = rcSrc.Right;
2309 	rcClip.Bottom = rcSrc.Bottom ;
2310 
2311 	/* Move the source window up n rows. */
2312 	new_org.X = rcSrc.Left;
2313 	new_org.Y = rcSrc.Top - n;
2314 
2315 	/* Fill the right character and attributes. */
2316 	fillchar.Char.AsciiChar = ' ';
2317 	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
2318 	fillchar.Attributes = curr_attr;
2319 
2320 	ScrollConsoleScreenBuffer(con_out, &rcSrc, &rcClip, new_org, &fillchar);
2321 
2322 	/* Position cursor on first blank line. */
2323 	goto_line(sc_height - n - 1);
2324 #endif
2325 }
2326 #endif
2327 
2328 #if MSDOS_COMPILER==WIN32C
2329 /*
2330  * Clear the screen.
2331  */
2332 static void win32_clear(void)
2333 {
2334 	/*
2335 	 * This will clear only the currently visible rows of the NT
2336 	 * console buffer, which means none of the precious scrollback
2337 	 * rows are touched making for faster scrolling.  Note that, if
2338 	 * the window has fewer columns than the console buffer (i.e.
2339 	 * there is a horizontal scrollbar as well), the entire width
2340 	 * of the visible rows will be cleared.
2341 	 */
2342 	COORD topleft;
2343 	DWORD nchars;
2344 	DWORD winsz;
2345 	CONSOLE_SCREEN_BUFFER_INFO csbi;
2346 
2347 	/* get the number of cells in the current buffer */
2348 	GetConsoleScreenBufferInfo(con_out, &csbi);
2349 	winsz = csbi.dwSize.X * (csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
2350 	topleft.X = 0;
2351 	topleft.Y = csbi.srWindow.Top;
2352 
2353 	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
2354 	FillConsoleOutputCharacter(con_out, ' ', winsz, topleft, &nchars);
2355 	FillConsoleOutputAttribute(con_out, curr_attr, winsz, topleft, &nchars);
2356 }
2357 
2358 #if 0
2359 /*
2360  * Remove the n topmost lines and scroll everything below it in the
2361  * window upward.
2362  */
2363 public void win32_scroll_up(int n)
2364 {
2365 	SMALL_RECT rcSrc, rcClip;
2366 	CHAR_INFO fillchar;
2367 	COORD topleft;
2368 	COORD new_org;
2369 	DWORD nchars;
2370 	DWORD size;
2371 	CONSOLE_SCREEN_BUFFER_INFO csbi;
2372 
2373 	if (n <= 0)
2374 		return;
2375 
2376 	if (n >= sc_height - 1)
2377 	{
2378 		win32_clear();
2379 		_settextposition(1,1);
2380 		return;
2381 	}
2382 
2383 	/* Get the extent of what will remain visible after scrolling. */
2384 	GetConsoleScreenBufferInfo(con_out, &csbi);
2385 	rcSrc.Left    = csbi.srWindow.Left;
2386 	rcSrc.Top     = csbi.srWindow.Top + n;
2387 	rcSrc.Right   = csbi.srWindow.Right;
2388 	rcSrc.Bottom  = csbi.srWindow.Bottom;
2389 
2390 	/* Get the clip rectangle. */
2391 	rcClip.Left   = rcSrc.Left;
2392 	rcClip.Top    = csbi.srWindow.Top;
2393 	rcClip.Right  = rcSrc.Right;
2394 	rcClip.Bottom = rcSrc.Bottom ;
2395 
2396 	/* Move the source text to the top of the screen. */
2397 	new_org.X = rcSrc.Left;
2398 	new_org.Y = rcClip.Top;
2399 
2400 	/* Fill the right character and attributes. */
2401 	fillchar.Char.AsciiChar = ' ';
2402 	fillchar.Attributes = MAKEATTR(nm_fg_color, nm_bg_color);
2403 
2404 	/* Scroll the window. */
2405 	SetConsoleTextAttribute(con_out, fillchar.Attributes);
2406 	ScrollConsoleScreenBuffer(con_out, &rcSrc, &rcClip, new_org, &fillchar);
2407 
2408 	/* Clear remaining lines at bottom. */
2409 	topleft.X = csbi.dwCursorPosition.X;
2410 	topleft.Y = rcSrc.Bottom - n;
2411 	size = (n * csbi.dwSize.X) + (rcSrc.Right - topleft.X);
2412 	FillConsoleOutputCharacter(con_out, ' ', size, topleft,
2413 		&nchars);
2414 	FillConsoleOutputAttribute(con_out, fillchar.Attributes, size, topleft,
2415 		&nchars);
2416 	SetConsoleTextAttribute(con_out, curr_attr);
2417 
2418 	/* Move cursor n lines up from where it was. */
2419 	csbi.dwCursorPosition.Y -= n;
2420 	SetConsoleCursorPosition(con_out, csbi.dwCursorPosition);
2421 }
2422 #endif
2423 #endif
2424 
2425 /*
2426  * Move cursor to lower left corner of screen.
2427  */
2428 public void lower_left(void)
2429 {
2430 	assert_interactive();
2431 #if !MSDOS_COMPILER
2432 	ltputs(sc_lower_left, 1, putchr);
2433 #else
2434 	flush();
2435 	_settextposition(sc_height, 1);
2436 #endif
2437 }
2438 
2439 /*
2440  * Move cursor to left position of current line.
2441  */
2442 public void line_left(void)
2443 {
2444 	assert_interactive();
2445 #if !MSDOS_COMPILER
2446 	ltputs(sc_return, 1, putchr);
2447 #else
2448 	{
2449 		int row;
2450 		flush();
2451 #if MSDOS_COMPILER==WIN32C
2452 		{
2453 			CONSOLE_SCREEN_BUFFER_INFO scr;
2454 			GetConsoleScreenBufferInfo(con_out, &scr);
2455 			row = scr.dwCursorPosition.Y - scr.srWindow.Top + 1;
2456 		}
2457 #else
2458 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
2459 			row = wherey();
2460 #else
2461 		{
2462 			struct rccoord tpos = _gettextposition();
2463 			row = tpos.row;
2464 		}
2465 #endif
2466 #endif
2467 		_settextposition(row, 1);
2468 	}
2469 #endif
2470 }
2471 
2472 /*
2473  * Check if the console size has changed and reset internals
2474  * (for systems without SIGWINCH).
2475  */
2476 public void check_winch(void)
2477 {
2478 }
2479 
2480 /*
2481  * Goto a specific line on the screen.
2482  */
2483 public void goto_line(int sindex)
2484 {
2485 	assert_interactive();
2486 #if !MSDOS_COMPILER
2487 	ltputs(ltgoto(sc_move, 0, sindex), 1, putchr);
2488 #else
2489 	flush();
2490 	_settextposition(sindex+1, 1);
2491 #endif
2492 }
2493 
2494 #if MSDOS_COMPILER==MSOFTC || MSDOS_COMPILER==BORLANDC
2495 /*
2496  * Create an alternate screen which is all white.
2497  * This screen is used to create a "flash" effect, by displaying it
2498  * briefly and then switching back to the normal screen.
2499  * {{ Yuck!  There must be a better way to get a visual bell. }}
2500  */
2501 static void create_flash(void)
2502 {
2503 #if MSDOS_COMPILER==MSOFTC
2504 	struct videoconfig w;
2505 	char *blanks;
2506 	int row, col;
2507 
2508 	_getvideoconfig(&w);
2509 	videopages = w.numvideopages;
2510 	if (videopages < 2)
2511 	{
2512 		at_enter(AT_STANDOUT);
2513 		at_exit();
2514 	} else
2515 	{
2516 		_setactivepage(1);
2517 		at_enter(AT_STANDOUT);
2518 		blanks = (char *) ecalloc(w.numtextcols, sizeof(char));
2519 		for (col = 0;  col < w.numtextcols;  col++)
2520 			blanks[col] = ' ';
2521 		for (row = w.numtextrows;  row > 0;  row--)
2522 			_outmem(blanks, w.numtextcols);
2523 		_setactivepage(0);
2524 		_setvisualpage(0);
2525 		free(blanks);
2526 		at_exit();
2527 	}
2528 #else
2529 #if MSDOS_COMPILER==BORLANDC
2530 	int n;
2531 
2532 	whitescreen = (unsigned short *)
2533 		malloc(sc_width * sc_height * sizeof(short));
2534 	if (whitescreen == NULL)
2535 		return;
2536 	for (n = 0;  n < sc_width * sc_height;  n++)
2537 		whitescreen[n] = 0x7020;
2538 #endif
2539 #endif
2540 	flash_created = 1;
2541 }
2542 #endif /* MSDOS_COMPILER */
2543 
2544 /*
2545  * Output the "visual bell", if there is one.
2546  */
2547 public void vbell(void)
2548 {
2549 	if (no_vbell)
2550 		return;
2551 #if !MSDOS_COMPILER
2552 	if (*sc_visual_bell == '\0')
2553 		return;
2554 	ltputs(sc_visual_bell, sc_height, putchr);
2555 #else
2556 #if MSDOS_COMPILER==DJGPPC
2557 	ScreenVisualBell();
2558 #else
2559 #if MSDOS_COMPILER==MSOFTC
2560 	/*
2561 	 * Create a flash screen on the second video page.
2562 	 * Switch to that page, then switch back.
2563 	 */
2564 	if (!flash_created)
2565 		create_flash();
2566 	if (videopages < 2)
2567 		return;
2568 	_setvisualpage(1);
2569 	delay(100);
2570 	_setvisualpage(0);
2571 #else
2572 #if MSDOS_COMPILER==BORLANDC
2573 	unsigned short *currscreen;
2574 
2575 	/*
2576 	 * Get a copy of the current screen.
2577 	 * Display the flash screen.
2578 	 * Then restore the old screen.
2579 	 */
2580 	if (!flash_created)
2581 		create_flash();
2582 	if (whitescreen == NULL)
2583 		return;
2584 	currscreen = (unsigned short *)
2585 		malloc(sc_width * sc_height * sizeof(short));
2586 	if (currscreen == NULL) return;
2587 	gettext(1, 1, sc_width, sc_height, currscreen);
2588 	puttext(1, 1, sc_width, sc_height, whitescreen);
2589 	delay(100);
2590 	puttext(1, 1, sc_width, sc_height, currscreen);
2591 	free(currscreen);
2592 #else
2593 #if MSDOS_COMPILER==WIN32C
2594 	/* paint screen with an inverse color */
2595 	lclear();
2596 
2597 	/* leave it displayed for 100 msec. */
2598 	Sleep(100);
2599 
2600 	/* restore with a redraw */
2601 	repaint();
2602 #endif
2603 #endif
2604 #endif
2605 #endif
2606 #endif
2607 }
2608 
2609 /*
2610  * Make a noise.
2611  */
2612 static void lbeep(void)
2613 {
2614 #if !MSDOS_COMPILER
2615 	putchr(CONTROL('G'));
2616 #else
2617 #if MSDOS_COMPILER==WIN32C
2618 	MessageBeep(0);
2619 #else
2620 	write(1, "\7", 1);
2621 #endif
2622 #endif
2623 }
2624 
2625 /*
2626  * Ring the terminal bell.
2627  */
2628 public void lbell(void)
2629 {
2630 	if (quiet == VERY_QUIET)
2631 		vbell();
2632 	else
2633 		lbeep();
2634 }
2635 
2636 /*
2637  * Clear the screen.
2638  */
2639 public void lclear(void)
2640 {
2641 	assert_interactive();
2642 	suspend_screen();
2643 #if !MSDOS_COMPILER
2644 	ltputs(sc_clear, sc_height, putchr);
2645 #else
2646 	flush();
2647 #if MSDOS_COMPILER==WIN32C
2648 	win32_clear();
2649 #else
2650 	_clearscreen(_GCLEARSCREEN);
2651 #endif
2652 #endif
2653 }
2654 
2655 /*
2656  * Clear from the cursor to the end of the cursor's line.
2657  * {{ This must not move the cursor. }}
2658  */
2659 public void clear_eol(void)
2660 {
2661 	/* assert_interactive();*/
2662 #if !MSDOS_COMPILER
2663 	ltputs(sc_eol_clear, 1, putchr);
2664 #else
2665 #if MSDOS_COMPILER==MSOFTC
2666 	short top, left;
2667 	short bot, right;
2668 	struct rccoord tpos;
2669 
2670 	flush();
2671 	/*
2672 	 * Save current state.
2673 	 */
2674 	tpos = _gettextposition();
2675 	_gettextwindow(&top, &left, &bot, &right);
2676 	/*
2677 	 * Set a temporary window to the current line,
2678 	 * from the cursor's position to the right edge of the screen.
2679 	 * Then clear that window.
2680 	 */
2681 	_settextwindow(tpos.row, tpos.col, tpos.row, sc_width);
2682 	_clearscreen(_GWINDOW);
2683 	/*
2684 	 * Restore state.
2685 	 */
2686 	_settextwindow(top, left, bot, right);
2687 	_settextposition(tpos.row, tpos.col);
2688 #else
2689 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
2690 	flush();
2691 	clreol();
2692 #else
2693 #if MSDOS_COMPILER==WIN32C
2694 	DWORD           nchars;
2695 	COORD           cpos;
2696 	CONSOLE_SCREEN_BUFFER_INFO scr;
2697 
2698 	flush();
2699 	memset(&scr, 0, sizeof(scr));
2700 	GetConsoleScreenBufferInfo(con_out, &scr);
2701 	cpos.X = scr.dwCursorPosition.X;
2702 	cpos.Y = scr.dwCursorPosition.Y;
2703 	curr_attr = MAKEATTR(nm_fg_color, nm_bg_color);
2704 	FillConsoleOutputAttribute(con_out, curr_attr,
2705 		scr.dwSize.X - cpos.X, cpos, &nchars);
2706 	FillConsoleOutputCharacter(con_out, ' ',
2707 		scr.dwSize.X - cpos.X, cpos, &nchars);
2708 #endif
2709 #endif
2710 #endif
2711 #endif
2712 }
2713 
2714 /*
2715  * Clear the current line.
2716  * Clear the screen if there's off-screen memory below the display.
2717  */
2718 static void clear_eol_bot(void)
2719 {
2720 	assert_interactive();
2721 #if MSDOS_COMPILER
2722 	clear_eol();
2723 #else
2724 	if (below_mem)
2725 		ltputs(sc_eos_clear, 1, putchr);
2726 	else
2727 		ltputs(sc_eol_clear, 1, putchr);
2728 #endif
2729 }
2730 
2731 /*
2732  * Clear the bottom line of the display.
2733  * Leave the cursor at the beginning of the bottom line.
2734  */
2735 public void clear_bot(void)
2736 {
2737 	/*
2738 	 * If we're in a non-normal attribute mode, temporarily exit
2739 	 * the mode while we do the clear.  Some terminals fill the
2740 	 * cleared area with the current attribute.
2741 	 */
2742 	if (oldbot)
2743 		lower_left();
2744 	else
2745 		line_left();
2746 
2747 	if (attrmode == AT_NORMAL)
2748 		clear_eol_bot();
2749 	else
2750 	{
2751 		int saved_attrmode = attrmode;
2752 
2753 		at_exit();
2754 		clear_eol_bot();
2755 		at_enter(saved_attrmode);
2756 	}
2757 }
2758 
2759 /*
2760  * Enable or disable bracketed paste mode.
2761  * When enabled, the terminal sends an "open bracket" sequence
2762  * before pasted content and "close bracket" after it.
2763  */
2764 public void init_bracketed_paste(void)
2765 {
2766 #if !MSDOS_COMPILER
2767 	ltputs(sc_s_bracketed_paste, 1, putchr);
2768 #endif
2769 }
2770 
2771 public void deinit_bracketed_paste(void)
2772 {
2773 #if !MSDOS_COMPILER
2774 	ltputs(sc_e_bracketed_paste, 1, putchr);
2775 #endif
2776 }
2777 
2778 /*
2779  * Color string may be "x[y]" where x and y are 4-bit color chars,
2780  * or "N[.M]" where N and M are decimal integers>
2781  * Any of x,y,N,M may also be "-" to mean "unchanged".
2782  */
2783 
2784 static lbool is_attr_char(char ch)
2785 {
2786 	return (strchr("*~_&dsul", ch) != NULL);
2787 }
2788 
2789 /*
2790  * Parse a 4-bit color char.
2791  */
2792 static int parse_color4(char ch)
2793 {
2794 	switch (ch)
2795 	{
2796 	case 'k': return 0;
2797 	case 'r': return CV_RED;
2798 	case 'g': return CV_GREEN;
2799 	case 'y': return CV_RED|CV_GREEN;
2800 	case 'b': return CV_BLUE;
2801 	case 'm': return CV_RED|CV_BLUE;
2802 	case 'c': return CV_GREEN|CV_BLUE;
2803 	case 'w': return CV_RED|CV_GREEN|CV_BLUE;
2804 	case 'K': return 0|CV_BRIGHT;
2805 	case 'R': return CV_RED|CV_BRIGHT;
2806 	case 'G': return CV_GREEN|CV_BRIGHT;
2807 	case 'Y': return CV_RED|CV_GREEN|CV_BRIGHT;
2808 	case 'B': return CV_BLUE|CV_BRIGHT;
2809 	case 'M': return CV_RED|CV_BLUE|CV_BRIGHT;
2810 	case 'C': return CV_GREEN|CV_BLUE|CV_BRIGHT;
2811 	case 'W': return CV_RED|CV_GREEN|CV_BLUE|CV_BRIGHT;
2812 	case '-': return CV_NOCHANGE;
2813 	default:  return CV_ERROR;
2814 	}
2815 }
2816 
2817 /*
2818  * Parse a color as a decimal integer.
2819  */
2820 static int parse_color6(constant char **ps)
2821 {
2822 	if (**ps == '\0')
2823 		return CV_NOCHANGE;
2824 	if (**ps == '-')
2825 	{
2826 		(*ps)++;
2827 		return CV_NOCHANGE;
2828 	} else
2829 	{
2830 		constant char *os = *ps;
2831 		int color = lstrtoic(os, ps, 10);
2832 		if (color < 0 || *ps == os)
2833 			return CV_ERROR;
2834 		return color;
2835 	}
2836 }
2837 
2838 /*
2839  * Parse a color pair and return the foreground/background/attribute values.
2840  * Return type of color specifier:
2841  *  CV_4BIT: fg/bg values are OR of CV_{RGB} bits.
2842  *  CV_6BIT: fg/bg values are integers entered by user.
2843  */
2844 public COLOR_TYPE parse_color(constant char *str, mutable int *p_fg, mutable int *p_bg, mutable CHAR_ATTR *p_cattr)
2845 {
2846 	int fg;
2847 	int bg = CV_ERROR;
2848 	CHAR_ATTR cattr = CATTR_NULL;
2849 	COLOR_TYPE type = CT_NULL;
2850 
2851 	if (str == NULL || *str == '\0')
2852 		return CT_NULL;
2853 	if (*str == '+')
2854 		str++; /* ignore leading + */
2855 
2856 	if (is_attr_char(*str))
2857 	{
2858 		fg = bg = CV_NOCHANGE;
2859 	} else
2860 	{
2861 		fg = parse_color4(*str);
2862 		if (fg != CV_ERROR)
2863 		{
2864 			if (str[1] == '\0' || is_attr_char(str[1]))
2865 			{
2866 				bg = CV_NOCHANGE;
2867 				str++; /* skip the fg char */
2868 			} else
2869 			{
2870 				bg = parse_color4(str[1]);
2871 				if (bg != CV_ERROR)
2872 					str += 2; /* skip both fg and bg chars */
2873 			}
2874 		}
2875 	}
2876 	if (fg != CV_ERROR && bg != CV_ERROR)
2877 		type = CT_4BIT;
2878 	else
2879 	{
2880 		fg = (*str == '.') ? CV_NOCHANGE : parse_color6(&str);
2881 		if (fg != CV_ERROR)
2882 		{
2883 			if (*str != '.')
2884 				bg = CV_NOCHANGE;
2885 			else
2886 			{
2887 				str++; /* skip the dot */
2888 				bg = parse_color6(&str);
2889 			}
2890 		}
2891 		if (fg != CV_ERROR && bg != CV_ERROR)
2892 			type = CT_6BIT;
2893 	}
2894 	if (type != CT_NULL)
2895 	{
2896 		for (;; str++)
2897 		{
2898 			if (*str == '*' || *str == 'd')
2899 				cattr |= CATTR_BOLD;
2900 			else if (*str == '~' || *str == 's')
2901 				cattr |= CATTR_STANDOUT;
2902 			else if (*str == '_' || *str == 'u')
2903 				cattr |= CATTR_UNDERLINE;
2904 			else if (*str == '&' || *str == 'l') /* can't use 'k' because of conflict with "black" */
2905 				cattr |= CATTR_BLINK;
2906 			else
2907 				break;
2908 		}
2909 		if (p_fg != NULL) *p_fg = fg;
2910 		if (p_bg != NULL) *p_bg = bg;
2911 		if (p_cattr != NULL) *p_cattr = cattr;
2912 	}
2913 	return type;
2914 }
2915 
2916 #if !MSDOS_COMPILER
2917 
2918 static int sgr_color(int color)
2919 {
2920 	switch (color)
2921 	{
2922 	case 0:                                    return 30;
2923 	case CV_RED:                               return 31;
2924 	case CV_GREEN:                             return 32;
2925 	case CV_RED|CV_GREEN:                      return 33;
2926 	case CV_BLUE:                              return 34;
2927 	case CV_RED|CV_BLUE:                       return 35;
2928 	case CV_GREEN|CV_BLUE:                     return 36;
2929 	case CV_RED|CV_GREEN|CV_BLUE:              return 37;
2930 
2931 	case CV_BRIGHT:                            return 90;
2932 	case CV_RED|CV_BRIGHT:                     return 91;
2933 	case CV_GREEN|CV_BRIGHT:                   return 92;
2934 	case CV_RED|CV_GREEN|CV_BRIGHT:            return 93;
2935 	case CV_BLUE|CV_BRIGHT:                    return 94;
2936 	case CV_RED|CV_BLUE|CV_BRIGHT:             return 95;
2937 	case CV_GREEN|CV_BLUE|CV_BRIGHT:           return 96;
2938 	case CV_RED|CV_GREEN|CV_BLUE|CV_BRIGHT:    return 97;
2939 
2940 	default: return color;
2941 	}
2942 }
2943 
2944 static void tput_fmt(constant char *fmt, int color, int (*f_putc)(int))
2945 {
2946 	char buf[INT_STRLEN_BOUND(int)+16];
2947 	if (color == attrcolor)
2948 		return;
2949 	SNPRINTF1(buf, sizeof(buf), fmt, color);
2950 	ltputs(buf, 1, f_putc);
2951 	attrcolor = color;
2952 }
2953 
2954 static void tput_char_cattr(CHAR_ATTR cattr, int (*f_putc)(int))
2955 {
2956 	if (cattr & CATTR_UNDERLINE)
2957 	{
2958 		ltputs(sc_u_in, 1, f_putc);
2959 		attrbits |= AT_UNDERLINE;
2960 	}
2961 	if (cattr & CATTR_BOLD)
2962 	{
2963 		ltputs(sc_b_in, 1, f_putc);
2964 		attrbits |= AT_BOLD;
2965 	}
2966 	if (cattr & CATTR_BLINK)
2967 	{
2968 		ltputs(sc_bl_in, 1, f_putc);
2969 		attrbits |= AT_BLINK;
2970 	}
2971 	if (cattr & CATTR_STANDOUT)
2972 	{
2973 		ltputs(sc_s_in, 1, f_putc);
2974 		attrbits |= AT_STANDOUT;
2975 	}
2976 }
2977 
2978 static void tput_color(constant char *str, int (*f_putc)(int))
2979 {
2980 	int fg;
2981 	int bg;
2982 	CHAR_ATTR cattr;
2983 
2984 	if (str != NULL && strcmp(str, "*") == 0)
2985 	{
2986 		/* Special case: reset to normal */
2987 		tput_fmt(ESCS"[m", -1, f_putc);
2988 		return;
2989 	}
2990 	switch (parse_color(str, &fg, &bg, &cattr))
2991 	{
2992 	case CT_4BIT:
2993 		if (fg >= 0)
2994 			tput_fmt(ESCS"[%dm", sgr_color(fg), f_putc);
2995 		if (bg >= 0)
2996 			tput_fmt(ESCS"[%dm", sgr_color(bg)+10, f_putc);
2997 		tput_char_cattr(cattr, f_putc);
2998 		break;
2999 	case CT_6BIT:
3000 		if (fg >= 0)
3001 			tput_fmt(ESCS"[38;5;%dm", fg, f_putc);
3002 		if (bg >= 0)
3003 			tput_fmt(ESCS"[48;5;%dm", bg, f_putc);
3004 		tput_char_cattr(cattr, f_putc);
3005 		break;
3006 	default:
3007 		break;
3008 	}
3009 }
3010 
3011 static void tput_inmode(constant char *mode_str, int attr, int attr_bit, int (*f_putc)(int))
3012 {
3013 	constant char *color_str;
3014 	if ((attr & attr_bit) == 0)
3015 		return;
3016 	color_str = get_color_map(attr_bit);
3017 	if (color_str == NULL || *color_str == '\0' || *color_str == '+')
3018 	{
3019 		ltputs(mode_str, 1, f_putc);
3020 		attrbits |= attr_bit;
3021 		if (color_str == NULL || *color_str++ != '+')
3022 			return;
3023 	}
3024 	/* Color overrides mode string */
3025 	tput_color(color_str, f_putc);
3026 }
3027 
3028 static void tput_outmode(constant char *mode_str, int attr_bit, int (*f_putc)(int))
3029 {
3030 	if ((attrbits & attr_bit) == 0)
3031 		return;
3032 	ltputs(mode_str, 1, f_putc);
3033 	attrbits &= ~attr_bit;
3034 }
3035 
3036 #else /* MSDOS_COMPILER */
3037 
3038 #if MSDOS_COMPILER==WIN32C
3039 static lbool WIN32put_fmt(constant char *fmt, int color)
3040 {
3041 	char buf[INT_STRLEN_BOUND(int)+16];
3042 	int len = (size_t) SNPRINTF1(buf, sizeof(buf), fmt, color);
3043 	if (len > 0)
3044 		WIN32textout(buf, (size_t) len);
3045 	return TRUE;
3046 }
3047 
3048 static void win_set_cattr(CHAR_ATTR cattr)
3049 {
3050 	if (cattr & CATTR_UNDERLINE)
3051 		WIN32textout(ESCS"[4m", 4);
3052 	if (cattr & CATTR_BOLD)
3053 		WIN32textout(ESCS"[1m", 4);
3054 	if (cattr & CATTR_BLINK)
3055 		WIN32textout(ESCS"[5m", 4);
3056 	if (cattr & CATTR_STANDOUT)
3057 		WIN32textout(ESCS"[7m", 4);
3058 }
3059 #endif
3060 
3061 static lbool win_set_color(int attr)
3062 {
3063 	int fg;
3064 	int bg;
3065 	CHAR_ATTR cattr;
3066 	lbool out = FALSE;
3067 	constant char *str = get_color_map(attr);
3068 	if (str == NULL || str[0] == '\0')
3069 		return FALSE;
3070 	switch (parse_color(str, &fg, &bg, &cattr))
3071 	{
3072 	case CT_4BIT:
3073 		if (fg >= 0 && bg >= 0)
3074 		{
3075 			SETCOLORS(fg, bg);
3076 			out = TRUE;
3077 		} else if (fg >= 0)
3078 		{
3079 			SET_FG_COLOR(fg);
3080 			out = TRUE;
3081 		} else if (bg >= 0)
3082 		{
3083 			SET_BG_COLOR(bg);
3084 			out = TRUE;
3085 		}
3086 #if MSDOS_COMPILER==WIN32C
3087 		if (vt_enabled)
3088 			win_set_cattr(cattr);
3089 #endif
3090 		break;
3091 #if MSDOS_COMPILER==WIN32C
3092 	case CT_6BIT:
3093 		if (vt_enabled)
3094 		{
3095 			if (fg > 0)
3096 				out = WIN32put_fmt(ESCS"[38;5;%dm", fg);
3097 			if (bg > 0)
3098 				out = WIN32put_fmt(ESCS"[48;5;%dm", bg);
3099 			win_set_cattr(cattr);
3100 		}
3101 		break;
3102 #endif
3103 	default:
3104 		break;
3105 	}
3106 	return out;
3107 }
3108 
3109 #endif /* MSDOS_COMPILER */
3110 
3111 public void at_enter(int attr)
3112 {
3113 	attr = apply_at_specials(attr);
3114 #if !MSDOS_COMPILER
3115 	/* The one with the most priority is last.  */
3116 	tput_inmode(sc_u_in, attr, AT_UNDERLINE, putchr);
3117 	tput_inmode(sc_b_in, attr, AT_BOLD, putchr);
3118 	tput_inmode(sc_bl_in, attr, AT_BLINK, putchr);
3119 	/* Don't use standout and color at the same time. */
3120 	if (use_color && (attr & AT_COLOR))
3121 		tput_color(get_color_map(attr), putchr);
3122 	else
3123 		tput_inmode(sc_s_in, attr, AT_STANDOUT, putchr);
3124 #else
3125 	flush();
3126 	/* The one with the most priority is first.  */
3127 	if ((attr & AT_COLOR) && use_color)
3128 	{
3129 		win_set_color(attr);
3130 	} else if (attr & AT_STANDOUT)
3131 	{
3132 		SETCOLORS(so_fg_color, so_bg_color);
3133 	} else if (attr & AT_BLINK)
3134 	{
3135 		SETCOLORS(bl_fg_color, bl_bg_color);
3136 	} else if (attr & AT_BOLD)
3137 	{
3138 		SETCOLORS(bo_fg_color, bo_bg_color);
3139 	} else if (attr & AT_UNDERLINE)
3140 	{
3141 		SETCOLORS(ul_fg_color, ul_bg_color);
3142 	}
3143 #endif
3144 	attrmode = attr;
3145 }
3146 
3147 public void at_exit(void)
3148 {
3149 #if !MSDOS_COMPILER
3150 	/* Undo things in the reverse order we did them.  */
3151 	tput_color("*", putchr);
3152 	tput_outmode(sc_s_out, AT_STANDOUT, putchr);
3153 	tput_outmode(sc_bl_out, AT_BLINK, putchr);
3154 	tput_outmode(sc_b_out, AT_BOLD, putchr);
3155 	tput_outmode(sc_u_out, AT_UNDERLINE, putchr);
3156 #else
3157 	flush();
3158 	SETCOLORS(nm_fg_color, nm_bg_color);
3159 #endif
3160 	attrmode = AT_NORMAL;
3161 }
3162 
3163 public void at_switch(int attr)
3164 {
3165 	int new_attrmode = apply_at_specials(attr);
3166 	int ignore_modes = AT_ANSI;
3167 
3168 	if ((new_attrmode & ~ignore_modes) != (attrmode & ~ignore_modes))
3169 	{
3170 		at_exit();
3171 		at_enter(attr);
3172 	}
3173 }
3174 
3175 public lbool is_at_equiv(int attr1, int attr2)
3176 {
3177 	attr1 = apply_at_specials(attr1);
3178 	attr2 = apply_at_specials(attr2);
3179 
3180 	return (attr1 == attr2);
3181 }
3182 
3183 public int apply_at_specials(int attr)
3184 {
3185 	if (attr & AT_BINARY)
3186 		attr |= binattr;
3187 	if (attr & AT_HILITE)
3188 		attr |= AT_STANDOUT;
3189 	attr &= ~(AT_BINARY|AT_HILITE);
3190 
3191 	return attr;
3192 }
3193 
3194 /*
3195  * Output a plain backspace, without erasing the previous char.
3196  */
3197 public void putbs(void)
3198 {
3199 #if !MSDOS_COMPILER
3200 	if (termcap_debug)
3201 		putstr("<bs>");
3202 	else
3203 		ltputs(sc_backspace, 1, putchr);
3204 #else
3205 	int row, col;
3206 
3207 	flush();
3208 	{
3209 #if MSDOS_COMPILER==MSOFTC
3210 		struct rccoord tpos;
3211 		tpos = _gettextposition();
3212 		row = tpos.row;
3213 		col = tpos.col;
3214 #else
3215 #if MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
3216 		row = wherey();
3217 		col = wherex();
3218 #else
3219 #if MSDOS_COMPILER==WIN32C
3220 		CONSOLE_SCREEN_BUFFER_INFO scr;
3221 		GetConsoleScreenBufferInfo(con_out, &scr);
3222 		row = scr.dwCursorPosition.Y - scr.srWindow.Top + 1;
3223 		col = scr.dwCursorPosition.X - scr.srWindow.Left + 1;
3224 #endif
3225 #endif
3226 #endif
3227 	}
3228 	if (col <= 1)
3229 		return;
3230 	_settextposition(row, col-1);
3231 #endif /* MSDOS_COMPILER */
3232 }
3233 
3234 #if MSDOS_COMPILER==WIN32C
3235 
3236 #define WIN32_MAX_REPEAT 3
3237 #define LAST_DOWN_COUNT 8
3238 static LWCHAR last_downs[LAST_DOWN_COUNT] = { 0 };
3239 static int last_down_index = 0;
3240 static LWCHAR hi_surr = 0;
3241 
3242 typedef struct XINPUT_RECORD {
3243 	INPUT_RECORD ir;
3244 	LWCHAR ichar; /* because ir...UnicodeChar is only 16 bits */
3245 } XINPUT_RECORD;
3246 
3247 typedef struct WIN32_CHAR {
3248 	struct WIN32_CHAR *wc_next;
3249 	int wc_ch;
3250 } WIN32_CHAR;
3251 
3252 static WIN32_CHAR *win32_queue = NULL;
3253 
3254 /*
3255  * Is the win32_queue nonempty?
3256  */
3257 static lbool win32_queued_char(void)
3258 {
3259 	return (win32_queue != NULL);
3260 }
3261 
3262 /*
3263  * Push a char onto the back of the win32_queue.
3264  */
3265 public void win32_enqueue(int ch)
3266 {
3267 	WIN32_CHAR *wch = (WIN32_CHAR *) ecalloc(1, sizeof(WIN32_CHAR));
3268 	wch->wc_ch = ch;
3269 	wch->wc_next = NULL;
3270 	if (win32_queue == NULL)
3271 		win32_queue = wch;
3272 	else
3273 	{
3274 		WIN32_CHAR *pch;
3275 		for (pch = win32_queue; pch->wc_next != NULL; pch = pch->wc_next)
3276 			continue;
3277 		pch->wc_next = wch;
3278 	}
3279 }
3280 
3281 /*
3282  * Push a char onto the front of the win32_queue.
3283  * Makes the next call to WIN32getch return ch.
3284  */
3285 public void WIN32ungetch(int ch)
3286 {
3287 	WIN32_CHAR *wch = (WIN32_CHAR *) ecalloc(1, sizeof(WIN32_CHAR));
3288 	wch->wc_ch = ch;
3289 	wch->wc_next = win32_queue;
3290 	win32_queue = wch;
3291 }
3292 
3293 /*
3294  * Get a char from the front of the win32_queue.
3295  */
3296 static int win32_get_queue(void)
3297 {
3298 	WIN32_CHAR *wch = win32_queue;
3299 	int ch = wch->wc_ch;
3300 	win32_queue = wch->wc_next;
3301 	free(wch);
3302 	return ch;
3303 }
3304 
3305 public void win32_clear_queue(void)
3306 {
3307 	while (win32_queued_char())
3308 		(void) win32_get_queue();
3309 }
3310 
3311 /*
3312  * Handle a mouse input event.
3313  */
3314 static lbool win32_mouse_event(XINPUT_RECORD *xip)
3315 {
3316 	char b;
3317 
3318 	if (emouse == 0 || xip->ir.EventType != MOUSE_EVENT)
3319 		return (FALSE);
3320 
3321 	/* Generate an X11 mouse sequence from the mouse event. */
3322 	/* TODO: switch to the 1006 protocol to allow specific-button-up reports */
3323 	switch (xip->ir.Event.MouseEvent.dwEventFlags)
3324 	{
3325 	case 0: /* press or release */
3326 		if (xip->ir.Event.MouseEvent.dwButtonState == 0)
3327 			b = X11MOUSE_OFFSET + X11MOUSE_BUTTON_REL;
3328 		else if (xip->ir.Event.MouseEvent.dwButtonState == 1)  /* leftmost */
3329 			b = X11MOUSE_OFFSET + X11MOUSE_BUTTON1;
3330 		else if (xip->ir.Event.MouseEvent.dwButtonState == 2)  /* rightmost */
3331 			b = X11MOUSE_OFFSET + X11MOUSE_BUTTON3;
3332 		else if (xip->ir.Event.MouseEvent.dwButtonState == 4)  /* middle ("next-to-leftmost") */
3333 			b = X11MOUSE_OFFSET + X11MOUSE_BUTTON2;
3334 		else  /* don't bother to figure out what changed */
3335 			return (FALSE);
3336 		break;
3337 	case MOUSE_WHEELED:
3338 		b = X11MOUSE_OFFSET + (((int)xip->ir.Event.MouseEvent.dwButtonState < 0) ? X11MOUSE_WHEEL_DOWN : X11MOUSE_WHEEL_UP);
3339 		break;
3340 	case MOUSE_MOVED:
3341 		if (xip->ir.Event.MouseEvent.dwButtonState != 1)
3342 			return (FALSE);
3343 		/* Drag with left button down. */
3344 		b = X11MOUSE_OFFSET + X11MOUSE_DRAG;
3345 		break;
3346 	default:
3347 		return (FALSE);
3348 	}
3349 	/* {{ TODO: change to X11 1006 format. }} */
3350 	win32_enqueue(ESC);
3351 	win32_enqueue('[');
3352 	win32_enqueue('M');
3353 	win32_enqueue(b);
3354 	win32_enqueue(X11MOUSE_OFFSET + xip->ir.Event.MouseEvent.dwMousePosition.X + 1);
3355 	win32_enqueue(X11MOUSE_OFFSET + xip->ir.Event.MouseEvent.dwMousePosition.Y + 1);
3356 	return (TRUE);
3357 }
3358 
3359 static void set_last_down(LWCHAR ch)
3360 {
3361 	if (ch == 0) return;
3362 	last_downs[last_down_index] = ch;
3363 	if (++last_down_index >= LAST_DOWN_COUNT)
3364 		last_down_index = 0;
3365 }
3366 
3367 static LWCHAR *find_last_down(LWCHAR ch)
3368 {
3369 	int i;
3370 	for (i = 0; i < LAST_DOWN_COUNT; ++i)
3371 		if (last_downs[i] == ch)
3372 			return &last_downs[i];
3373 	return NULL;
3374 }
3375 
3376 /*
3377  * Get an input char from an INPUT_RECORD and store in xip->ichar.
3378  * Handles surrogate chars, and KeyUp without previous corresponding KeyDown.
3379  */
3380 static lbool win32_get_ichar(XINPUT_RECORD *xip)
3381 {
3382 	LWCHAR ch = xip->ir.Event.KeyEvent.uChar.UnicodeChar;
3383 	xip->ichar = ch;
3384 	if (!is_ascii_char(ch))
3385 	{
3386 		int is_down = xip->ir.Event.KeyEvent.bKeyDown;
3387 		LWCHAR *last_down = find_last_down(ch);
3388 		if (last_down == NULL) { /* key was up */
3389 			if (is_down) { /* key was up, now is down */
3390 				set_last_down(ch);
3391 			} else { /* key up without previous down: pretend this is a down. */
3392 				xip->ir.Event.KeyEvent.bKeyDown = 1;
3393 			}
3394 		} else if (!is_down) { /* key was down, now is up */
3395 			*last_down = 0; /* use this last_down only once */
3396 		}
3397 
3398 		if (ch >= 0xD800 && ch < 0xDC00) { /* high surrogate */
3399 			hi_surr = 0x10000 + ((ch - 0xD800) << 10);
3400 			return (FALSE); /* get next input, which should be the low surrogate */
3401 		}
3402 		if (ch >= 0xDC00 && ch < 0xE000) { /* low surrogate */
3403 			xip->ichar = hi_surr + (ch - 0xDC00);
3404 			hi_surr = 0;
3405 		}
3406 	}
3407 	return (TRUE);
3408 }
3409 
3410 /*
3411  * Handle a scan code (non-ASCII) key input.
3412  */
3413 static lbool win32_scan_code(XINPUT_RECORD *xip)
3414 {
3415 	int scan = -1;
3416 	if (xip->ir.Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
3417 	{
3418 		switch (xip->ir.Event.KeyEvent.wVirtualScanCode)
3419 		{
3420 		case PCK_RIGHT:  scan = PCK_CTL_RIGHT;  break;
3421 		case PCK_LEFT:   scan = PCK_CTL_LEFT;   break;
3422 		case PCK_UP:     scan = PCK_CTL_UP;     break;
3423 		case PCK_DOWN:   scan = PCK_CTL_DOWN;   break;
3424 		case PCK_DELETE: scan = PCK_CTL_DELETE; break;
3425 		case PCK_HOME:   scan = PCK_CTL_HOME;   break;
3426 		case PCK_END:    scan = PCK_CTL_END;    break;
3427 		case PCK_PAGEUP: scan = PCK_CTL_PAGEUP; break;
3428 		case PCK_PAGEDOWN: scan = PCK_CTL_PAGEDOWN; break;
3429 		}
3430 	} else if (xip->ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)
3431 	{
3432 		if (xip->ichar == '\t')
3433 			scan = PCK_SHIFT_TAB;
3434 		else
3435 		{
3436 			switch (xip->ir.Event.KeyEvent.wVirtualScanCode)
3437 			{
3438 				case PCK_RIGHT:  scan = PCK_SHIFT_RIGHT;  break;
3439 				case PCK_LEFT:   scan = PCK_SHIFT_LEFT;   break;
3440 				case PCK_UP:     scan = PCK_SHIFT_UP;     break;
3441 				case PCK_DOWN:   scan = PCK_SHIFT_DOWN;   break;
3442 				case PCK_DELETE: scan = PCK_SHIFT_DELETE; break;
3443 				case PCK_HOME:   scan = PCK_SHIFT_HOME;   break;
3444 				case PCK_END:    scan = PCK_SHIFT_END;    break;
3445 				case PCK_PAGEUP: scan = PCK_SHIFT_PAGEUP; break;
3446 				case PCK_PAGEDOWN: scan = PCK_SHIFT_PAGEDOWN; break;
3447 			}
3448 		}
3449 	}
3450 	if (scan < 0 && xip->ichar == 0)
3451 		scan = xip->ir.Event.KeyEvent.wVirtualScanCode;
3452 	if (scan < 0)
3453 		return (FALSE);
3454 	/*
3455 	 * An extended key returns a 2 byte sequence consisting of
3456 	 * a zero byte followed by the scan code.
3457 	 */
3458 	win32_enqueue('\0');
3459 	win32_enqueue(scan);
3460 	return (TRUE);
3461 }
3462 
3463 /*
3464  * Handle a key input event.
3465  */
3466 static lbool win32_key_event(XINPUT_RECORD *xip, char *pch)
3467 {
3468 	int repeat;
3469 	char utf8[UTF8_MAX_LENGTH];
3470 	char *up;
3471 
3472 	if (xip->ir.EventType != KEY_EVENT ||
3473 	    ((xip->ir.Event.KeyEvent.dwControlKeyState & (RIGHT_ALT_PRESSED|LEFT_CTRL_PRESSED)) == (RIGHT_ALT_PRESSED|LEFT_CTRL_PRESSED) && xip->ir.Event.KeyEvent.uChar.UnicodeChar == 0) ||
3474 	    (xip->ir.Event.KeyEvent.wVirtualScanCode == 0 && xip->ir.Event.KeyEvent.uChar.UnicodeChar == 0) ||
3475 	    xip->ir.Event.KeyEvent.wVirtualScanCode == PCK_CAPS_LOCK ||
3476 	    xip->ir.Event.KeyEvent.wVirtualScanCode == PCK_NUM_LOCK ||
3477 	    (xip->ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU && xip->ir.Event.KeyEvent.uChar.UnicodeChar == 0) ||
3478 	    xip->ir.Event.KeyEvent.wVirtualKeyCode == VK_KANJI ||
3479 	    xip->ir.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT ||
3480 	    xip->ir.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL)
3481 		return (FALSE);
3482 
3483 	if (!win32_get_ichar(xip))
3484 		return (FALSE);
3485 	if (!xip->ir.Event.KeyEvent.bKeyDown)
3486 		return (FALSE);
3487 
3488 	/*
3489 	 * If caller wants to see an immediate (unqueued) ASCII char from the
3490 	 * keyboard, return it now. Otherwise queue the char for WIN32getch to read.
3491 	 */
3492 	if (pch != NULL && xip->ichar != '\0' && is_ascii_char(xip->ichar))
3493 	{
3494 		*pch = (char) xip->ichar;
3495 		return (TRUE);
3496 	}
3497 
3498 	if (win32_scan_code(xip))
3499 		return (pch == NULL);
3500 
3501 	repeat = xip->ir.Event.KeyEvent.wRepeatCount;
3502 	if (repeat > WIN32_MAX_REPEAT)
3503 		repeat = WIN32_MAX_REPEAT;
3504 	up = utf8;
3505 	put_wchar(&up, xip->ichar);
3506 	for (; repeat > 0; --repeat)
3507 	{
3508 		constant char *p;
3509 		for (p = utf8; p < up; ++p)
3510 			 win32_enqueue((unsigned char) *p);
3511 	}
3512 	return (pch == NULL);
3513 }
3514 
3515 /*
3516  * Handle a window input event.
3517  */
3518 static lbool win32_window_event(XINPUT_RECORD *xip)
3519 {
3520 	if (xip->ir.EventType != WINDOW_BUFFER_SIZE_EVENT)
3521 		return (FALSE);
3522 	sigs |= S_WINCH;
3523 	win32_enqueue(READ_AGAIN);
3524 	return (TRUE);
3525 }
3526 
3527 /*
3528  * Determine whether an input character is waiting to be read.
3529  */
3530 public lbool win32_kbhit2(char *pch)
3531 {
3532 	XINPUT_RECORD xip;
3533 
3534 	if (pch == NULL && win32_queued_char())
3535 		return (TRUE);
3536 
3537 	for (;;)
3538 	{
3539 		DWORD nread;
3540 		DWORD console_input_mode;
3541 		/*
3542 		 * When an input pipe closes, cmd may reset the console mode,
3543 		 * so set the mode every time we read input.
3544 		 */
3545 		if (GetConsoleMode(tty, &console_input_mode) && console_input_mode != curr_console_input_mode)
3546 			SetConsoleMode(tty, curr_console_input_mode);
3547 		PeekConsoleInputW(tty, &xip.ir, 1, &nread);
3548 		if (nread == 0)
3549 			return (FALSE);
3550 		ReadConsoleInputW(tty, &xip.ir, 1, &nread);
3551 		if (nread == 0)
3552 			return (FALSE);
3553 		if (win32_key_event(&xip, pch))
3554 			return (TRUE);
3555 		if (win32_mouse_event(&xip) || win32_window_event(&xip))
3556 			return (pch == NULL);
3557 	}
3558 }
3559 
3560 public lbool win32_kbhit(void)
3561 {
3562 	return win32_kbhit2(NULL);
3563 }
3564 
3565 /*
3566  * Read a character from the keyboard.
3567  */
3568 public int WIN32getch(void)
3569 {
3570 	while (!win32_kbhit())
3571 	{
3572 		Sleep(20);
3573 		if (ABORT_SIGS())
3574 			return ('\003');
3575 	}
3576 	return (win32_get_queue());
3577 }
3578 
3579 public void win32_getch_clear(void)
3580 {
3581 	while (win32_kbhit())
3582 		(void) WIN32getch();
3583 }
3584 
3585 #endif /* MSDOS_COMPILER==WIN32C */
3586 
3587 #if MSDOS_COMPILER
3588 /*
3589  */
3590 public void WIN32setcolors(int fg, int bg)
3591 {
3592 	SETCOLORS(fg, bg);
3593 }
3594 
3595 /*
3596  */
3597 public void WIN32textout(constant char *text, size_t len)
3598 {
3599 #if MSDOS_COMPILER==WIN32C
3600 	DWORD written;
3601 	if (utf_mode == 2)
3602 	{
3603 		/*
3604 		 * We've got UTF-8 text in a non-UTF-8 console.  Convert it to
3605 		 * wide and use WriteConsoleW.
3606 		 * Biggest input len is OUTBUF_SIZE of obuf from win_flush,
3607 		 * which is also the biggest output count if it's ASCII.
3608 		 * "static" wtext is not a state - only avoid 16K on stack.
3609 		 */
3610 		static WCHAR wtext[OUTBUF_SIZE];
3611 		len = MultiByteToWideChar(CP_UTF8, 0, text, len, wtext, countof(wtext));
3612 		WriteConsoleW(con_out, wtext, len, &written, NULL);
3613 	} else
3614 		WriteConsole(con_out, text, (DWORD) len, &written, NULL);
3615 #else
3616 	char buf[2048];
3617 	if (len >= sizeof(buf))
3618 		len = sizeof(buf) - 1;
3619 	memcpy(buf, text, len);
3620 	buf[len] = 0;
3621 	cputs(buf);
3622 #endif
3623 }
3624 #endif
3625