1 /*
2  * Uses the Win32 console API.
3  *
4  * $Id: ntconio.c,v 1.106 2020/01/17 23:11:00 tom Exp $
5  */
6 
7 #include "estruct.h"
8 #include "edef.h"
9 #include "chgdfunc.h"
10 
11 #include <process.h>
12 
13 /*
14  * Define this if you want to kernel fault win95 when ctrl brk is pressed.
15  */
16 #undef DONT_USE_ON_WIN95
17 
18 #define MAX_CURBLK_HEIGHT 100	/* max cursor block height (%)  */
19 
20 #define NROW	128		/* Max Screen size.             */
21 #define NCOL    256		/* Edit if you want to.         */
22 #define NOKYMAP (-1)
23 
24 #define ABS(x) (((x) < 0) ? -(x) : (x))
25 
26 /* CHAR_INFO and KEY_EVENT_RECORD use the same struct member */
27 #ifdef UNICODE
28 #define WHICH_CHAR UnicodeChar
29 #else
30 #define WHICH_CHAR AsciiChar
31 #endif
32 
33 #define DFT_BCOLOR  C_BLACK
34 #define DFT_FCOLOR  ((ncolors >= 8) ? 7 : (ncolors - 1))
35 
36 #define ForeColor(f)	(ctrans[((f) < 0 ? DFT_FCOLOR : (f))] & (NCOLORS-1))
37 #define BackColor(b)	(ctrans[((b) < 0 ? DFT_BCOLOR : (b))] & (NCOLORS-1))
38 #define AttrColor(b,f)	((WORD)((BackColor(b) << 4) | ForeColor(f)))
39 
40 static HANDLE hConsoleOutput;	/* handle to the console display */
41 static HANDLE hOldConsoleOutput;	/* handle to the old console display */
42 static HANDLE hConsoleInput;
43 static CONSOLE_SCREEN_BUFFER_INFO csbi;
44 static CONSOLE_CURSOR_INFO origcci;
45 static BOOL origcci_ok;
46 static WORD originalAttribute;
47 static WORD currentAttribute;
48 static int icursor;		/* T -> enable insertion cursor       */
49 static int icursor_cmdmode;	/* cmd mode cursor height             */
50 static int icursor_insmode;	/* insertion mode  cursor height      */
51 static int chgd_cursor;		/* must restore cursor height on exit */
52 static ENC_CHOICES my_encoding = enc_DEFAULT;
53 
54 static int conemu = 0;		/* whether running under conemu */
55 static int cattr = 0;		/* current attributes */
56 static int cfcolor = -1;	/* current foreground color */
57 static int cbcolor = -1;	/* current background color */
58 static int nfcolor = -1;	/* normal foreground color */
59 static int nbcolor = -1;	/* normal background color */
60 static int rvcolor = 0;		/* nonzero if we reverse colors */
61 static int crow = -1;		/* current row */
62 static int ccol = -1;		/* current col */
63 static int keyboard_open = FALSE;	/* keyboard is open */
64 static int keyboard_was_closed = TRUE;
65 
66 #ifdef VAL_AUTOCOLOR
67 static int ac_active = FALSE;	/* autocolor active */
68 #endif
69 
70 /* ansi to ibm color translation table */
71 static const char *initpalettestr = "0 4 2 6 1 5 3 7 8 12 10 14 9 13 11 15";
72 /* black, red, green, yellow, blue, magenta, cyan, white   */
73 
74 static W32_CHAR linebuf[NCOL];
75 static int bufpos = 0;
76 
77 /* Add state variables for console vile's autoscroll feature.              */
78 static int buttondown = FALSE;	/* is the left mouse button currently down? */
79 static RECT client_rect;	/* writable portion of editor's window      */
80 static HANDLE hAsMutex;		/* autoscroll mutex handle (prevents worker
81 				 * thread and main thread from updating the
82 				 * display at the same time).
83 				 */
84 static WINDOW *mouse_wp;	/* vile window ptr during mouse operations. */
85 static int row_height;		/* pixels per row within client_rect        */
86 #define AS_TMOUT 2000		/* hAsMutex timeout period (2 sec) */
87 
88 #ifdef GVAL_VIDEO
89 static WORD
AttrVideo(int b,int f)90 AttrVideo(int b, int f)
91 {
92     WORD result;
93     if (conemu) {
94 	if (cattr & VABOLD) {
95 	    result = ((WORD) ((12 << 4) | ForeColor(f)));
96 	    TRACE2(("bold AttrVideo(%d,%d) = %04x\n", f, b, result));
97 	    return result;
98 	}
99 	if (cattr & VAITAL) {
100 	    result = ((WORD) ((13 << 4) | ForeColor(f)));
101 	    TRACE2(("ital AttrVideo(%d,%d) = %04x\n", f, b, result));
102 	    return result;
103 	}
104     }
105     if (rvcolor) {
106 	result = ((WORD) ((ForeColor(f) << 4) | BackColor(b)));
107 	TRACE2(("rev AttrVideo(%d,%d) = %04x\n", f, b, result));
108     } else {
109 	result = ((WORD) ((BackColor(b) << 4) | ForeColor(f)));
110 	TRACE2(("AttrVideo(%d,%d) = %04x\n", f, b, result));
111     }
112     return result;
113 }
114 #undef AttrColor
115 #define AttrColor(b,f) AttrVideo(b,f)
116 #endif
117 
118 static void
set_current_attr(void)119 set_current_attr(void)
120 {
121     currentAttribute = AttrColor(cbcolor, cfcolor);
122     TRACE2(("set_current_attr %04x\n", currentAttribute));
123 }
124 
125 static void
show_cursor(BOOL visible,int percent)126 show_cursor(BOOL visible, int percent)
127 {
128     CONSOLE_CURSOR_INFO cci;
129 
130     /*
131      * if a 100% block height is fed back into the win32 console routines
132      * on a win9x/winme host, the cursor is turned off.  win32 bug?
133      */
134     if (percent < 0)
135 	percent = 0;
136     if (percent >= MAX_CURBLK_HEIGHT)
137 	percent = (MAX_CURBLK_HEIGHT - 1);
138 
139     cci.bVisible = visible;
140     cci.dwSize = percent;
141     SetConsoleCursorInfo(hConsoleOutput, &cci);
142 }
143 
144 #if OPT_ICURSOR
145 static void
nticursor(int cmode)146 nticursor(int cmode)
147 {
148     if (icursor) {
149 	show_cursor(TRUE, (cmode == 0) ? icursor_cmdmode : icursor_insmode);
150     }
151 }
152 #endif
153 
154 #if OPT_TITLE
155 static void
ntconio_title(const char * title)156 ntconio_title(const char *title)
157 {				/* set the current window title */
158     if (title != 0)
159 	w32_set_console_title(title);
160 }
161 #endif
162 
163 static void
scflush(void)164 scflush(void)
165 {
166     if (bufpos) {
167 	COORD coordCursor;
168 	DWORD written;
169 
170 	coordCursor.X = (SHORT) ccol;
171 	coordCursor.Y = (SHORT) crow;
172 	TRACE2(("scflush %04x [%d,%d]%.*s\n",
173 		currentAttribute, crow, ccol, bufpos, linebuf));
174 	WriteConsoleOutputCharacter(
175 				       hConsoleOutput, linebuf, bufpos,
176 				       coordCursor, &written
177 	    );
178 	FillConsoleOutputAttribute(
179 				      hConsoleOutput, currentAttribute,
180 				      bufpos, coordCursor, &written
181 	    );
182 	ccol += bufpos;
183 	bufpos = 0;
184     }
185 }
186 
187 #if OPT_COLOR
188 static void
ntconio_fcol(int color)189 ntconio_fcol(int color)
190 {				/* set the current output color */
191     TRACE2(("ntconio_fcol(%d)\n", color));
192     scflush();
193     nfcolor = cfcolor = color;
194     set_current_attr();
195 }
196 
197 static void
ntconio_bcol(int color)198 ntconio_bcol(int color)
199 {				/* set the current background color */
200     TRACE2(("ntconio_bcol(%d)\n", color));
201     scflush();
202     nbcolor = cbcolor = color;
203     set_current_attr();
204 }
205 #endif
206 
207 static void
ntconio_flush(void)208 ntconio_flush(void)
209 {
210     COORD coordCursor;
211 
212     scflush();
213     coordCursor.X = (SHORT) ccol;
214     coordCursor.Y = (SHORT) crow;
215     SetConsoleCursorPosition(hConsoleOutput, coordCursor);
216 }
217 
218 static void
ntconio_move(int row,int col)219 ntconio_move(int row, int col)
220 {
221     scflush();
222     crow = row;
223     ccol = col;
224 }
225 
226 static void
erase_at(COORD coordCursor,int length)227 erase_at(COORD coordCursor, int length)
228 {
229     W32_CHAR blank = ' ';
230     DWORD written;
231 
232     FillConsoleOutputCharacter(
233 				  hConsoleOutput, blank, length,
234 				  coordCursor, &written
235 	);
236     FillConsoleOutputAttribute(
237 				  hConsoleOutput, currentAttribute, length,
238 				  coordCursor, &written
239 	);
240 }
241 
242 /* erase to the end of the line */
243 static void
ntconio_eeol(void)244 ntconio_eeol(void)
245 {
246     COORD coordCursor;
247     int length;
248 
249     scflush();
250     length = csbi.dwMaximumWindowSize.X - ccol;
251     coordCursor.X = (SHORT) ccol;
252     coordCursor.Y = (SHORT) crow;
253     TRACE2(("ntconio_eeol [%d,%d] erase %d with %04x\n", crow, ccol, length, currentAttribute));
254     erase_at(coordCursor, length);
255 }
256 
257 /*
258  * Move 'n' lines starting at 'from' to 'to'
259  *
260  * OPT_PRETTIER_SCROLL is prettier but slower -- it scrolls a line at a time
261  *	instead of all at once.
262  */
263 
264 /* move howmany lines starting at from to to */
265 static void
ntconio_scroll(int from,int to,int n)266 ntconio_scroll(int from, int to, int n)
267 {
268     SMALL_RECT sRect;
269     COORD dest;
270     CHAR_INFO fill;
271     int scroll_pause;
272 
273     scflush();
274     if (to == from)
275 	return;
276 #if OPT_PRETTIER_SCROLL
277     if (ABS(from - to) > 1) {
278 	ntconio_scroll(from, (from < to) ? to - 1 : to + 1, n);
279 	if (from < to)
280 	    from = to - 1;
281 	else
282 	    from = to + 1;
283     }
284 #endif
285     fill.Char.WHICH_CHAR = ' ';
286     fill.Attributes = currentAttribute;
287 
288     sRect.Left = 0;
289     sRect.Top = (SHORT) from;
290     sRect.Right = (SHORT) (csbi.dwMaximumWindowSize.X - 1);
291     sRect.Bottom = (SHORT) (from + n - 1);
292 
293     dest.X = 0;
294     dest.Y = (SHORT) to;
295 
296     ScrollConsoleScreenBuffer(hConsoleOutput, &sRect, NULL, dest, &fill);
297     if ((scroll_pause = global_g_val(GVAL_SCROLLPAUSE)) > 0) {
298 	/*
299 	 * If the user has cheap video HW (1 MB or less) and
300 	 * there's a busy background app (say, dev studio), then
301 	 * the console version of vile can exhibit serious repaint
302 	 * problems when the display is rapidly scrolled.  By
303 	 * inserting a user-defined sleep after the scroll, the
304 	 * video HW has a chance to properly paint before the
305 	 * next scroll operation.
306 	 */
307 
308 	Sleep(scroll_pause);
309     }
310 #if !OPT_PRETTIER_SCROLL
311     if (ABS(from - to) > n) {
312 	DWORD cnt;
313 	COORD coordCursor;
314 
315 	coordCursor.X = 0;
316 	if (to > from) {
317 	    coordCursor.Y = (SHORT) (from + n);
318 	    cnt = to - from - n;
319 	} else {
320 	    coordCursor.Y = (SHORT) (to + n);
321 	    cnt = from - to - n;
322 	}
323 	cnt *= csbi.dwMaximumWindowSize.X;
324 	erase_at(coordCursor, cnt);
325     }
326 #endif
327 }
328 
329 #if OPT_FLASH
330 static void
flash_display(void)331 flash_display(void)
332 {
333     DWORD length = term.cols * term.rows;
334     DWORD got;
335     WORD *buf1;
336     WORD *buf2;
337 
338     static COORD origin;
339 
340     if ((buf1 = typeallocn(WORD, length)) != 0
341 	&& (buf2 = typeallocn(WORD, length)) != 0) {
342 	ReadConsoleOutputAttribute(hConsoleOutput, buf1, length, origin, &got);
343 	ReadConsoleOutputAttribute(hConsoleOutput, buf2, length, origin, &got);
344 	for (got = 0; got < length; got++) {
345 	    buf2[got] ^= (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
346 	}
347 	WriteConsoleOutputAttribute(hConsoleOutput, buf2, length, origin, &got);
348 	Sleep(200);
349 	WriteConsoleOutputAttribute(hConsoleOutput, buf1, length, origin, &got);
350 	free(buf1);
351 	free(buf2);
352     }
353 }
354 #endif
355 
356 static void
ntconio_beep(void)357 ntconio_beep(void)
358 {
359 #if	OPT_FLASH
360     if (global_g_val(GMDFLASH)) {
361 	flash_display();
362 	return;
363     }
364 #endif
365     MessageBeep(0xffffffff);
366 }
367 
368 /*
369  * vile very rarely generates any of the ASCII printing control characters
370  * except for a few hand coded routines but we have to support them anyway.
371  */
372 
373 /* put a character at the current position in the current colors */
374 static void
ntconio_putc(int ch)375 ntconio_putc(int ch)
376 {
377 #define maybe_flush() \
378     if (bufpos >= ((int) sizeof(linebuf) - 2)) \
379 	scflush()
380 
381     if (ch >= ' ') {
382 
383 	/* This is an optimization for the most common case. */
384 	maybe_flush();
385 	linebuf[bufpos++] = (W32_CHAR) ch;
386 
387     } else {
388 
389 	switch (ch) {
390 
391 	case '\b':
392 	    scflush();
393 	    if (ccol)
394 		ccol--;
395 	    break;
396 
397 	case '\a':
398 	    ntconio_beep();
399 	    break;
400 
401 	case '\t':
402 	    do {
403 		maybe_flush();
404 		linebuf[bufpos++] = ' ';
405 	    } while ((ccol + bufpos) % 8 != 0);
406 	    break;
407 
408 	case '\r':
409 	    scflush();
410 	    ccol = 0;
411 	    break;
412 
413 	case '\n':
414 	    scflush();
415 	    if (crow < csbi.dwMaximumWindowSize.Y - 1) {
416 		crow++;
417 	    } else {
418 		ntconio_scroll(1, 0, csbi.dwMaximumWindowSize.Y - 1);
419 	    }
420 	    break;
421 
422 	default:
423 	    linebuf[bufpos++] = (W32_CHAR) ch;
424 	    break;
425 	}
426     }
427     maybe_flush();
428 }
429 
430 static void
ntconio_eeop(void)431 ntconio_eeop(void)
432 {
433     DWORD cnt;
434     COORD coordCursor;
435 
436     scflush();
437     coordCursor.X = (SHORT) ccol;
438     coordCursor.Y = (SHORT) crow;
439     cnt = csbi.dwMaximumWindowSize.X - ccol
440 	+ (csbi.dwMaximumWindowSize.Y - crow - 1)
441 	* csbi.dwMaximumWindowSize.X;
442     TRACE2(("ntconio_eeop [%d,%d] erase %d with %04x\n", crow, ccol, cnt, currentAttribute));
443     erase_at(coordCursor, cnt);
444 }
445 
446 static void
ntconio_rev(UINT attr)447 ntconio_rev(UINT attr)
448 {				/* change video state */
449     scflush();
450     cattr = attr;
451     cbcolor = nbcolor;
452     cfcolor = nfcolor;
453     rvcolor = (global_g_val(GVAL_VIDEO) & VAREV) ? 1 : 0;
454     attr &= (VASPCOL | VACOLOR | VABOLD | VAITAL | VASEL | VAREV);
455 
456     TRACE2(("ntconio_rev(%04x) f=%d, b=%d\n", attr, cfcolor, cbcolor));
457 
458     if (attr) {
459 	if (attr & VASPCOL)
460 	    cfcolor = (VCOLORNUM(attr) & (NCOLORS - 1));
461 	else if (attr & VACOLOR)
462 	    cfcolor = ((VCOLORNUM(attr)) & (NCOLORS - 1));
463 
464 	if (cfcolor == ENUM_UNKNOWN)
465 	    cfcolor = DFT_FCOLOR;
466 	if (cbcolor == ENUM_UNKNOWN)
467 	    cbcolor = DFT_BCOLOR;
468 
469 	if (attr == VABOLD) {
470 	    cfcolor |= FOREGROUND_INTENSITY;
471 	}
472 	if (attr == VAITAL) {
473 	    cbcolor |= BACKGROUND_INTENSITY;
474 	}
475 
476 	if (attr & (VASEL | VAREV)) {	/* reverse video? */
477 	    rvcolor ^= 1;
478 	}
479 
480 	TRACE2(("...ntconio_rev(%04x) f=%d, b=%d\n", attr, cfcolor, cbcolor));
481     }
482     set_current_attr();
483 }
484 
485 static int
ntconio_cres(const char * res)486 ntconio_cres(const char *res)
487 {				/* change screen resolution */
488     (void) res;
489     scflush();
490     return 0;
491 }
492 
493 static BOOL WINAPI
nthandler(DWORD ctrl_type)494 nthandler(DWORD ctrl_type)
495 {
496     switch (ctrl_type) {
497     case CTRL_CLOSE_EVENT:
498     case CTRL_LOGOFF_EVENT:
499     case CTRL_SHUTDOWN_EVENT:
500 	imdying(1);
501 	break;
502     }
503     return TRUE;
504 }
505 
506 static void
ntconio_set_encoding(ENC_CHOICES code)507 ntconio_set_encoding(ENC_CHOICES code)
508 {
509     my_encoding = code;
510 }
511 
512 static ENC_CHOICES
ntconio_get_encoding(void)513 ntconio_get_encoding(void)
514 {
515     return my_encoding;
516 }
517 
518 static void
ntconio_open(void)519 ntconio_open(void)
520 {
521     CONSOLE_CURSOR_INFO newcci;
522     BOOL newcci_ok;
523 
524     TRACE((T_CALLED "ntconio_open\n"));
525 
526     conemu = getenv("ConEmuPID") != NULL;
527     set_colors(NCOLORS);
528     set_palette(initpalettestr);
529 
530     hOldConsoleOutput = 0;
531 
532     hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
533     TRACE(("hConsoleOutput %p\n", hConsoleOutput));
534 
535     origcci_ok = GetConsoleCursorInfo(hConsoleOutput, &origcci);
536 
537     GetConsoleScreenBufferInfo(hConsoleOutput, &csbi);
538     TRACE(("GetConsoleScreenBufferInfo X:%d Y:%d Top:%d Bottom:%d Left:%d Right:%d\n",
539 	   csbi.dwMaximumWindowSize.X,
540 	   csbi.dwMaximumWindowSize.Y,
541 	   csbi.srWindow.Top,
542 	   csbi.srWindow.Bottom,
543 	   csbi.srWindow.Left,
544 	   csbi.srWindow.Right));
545 
546     TRACE(("...compare height %d vs %d\n",
547 	   csbi.dwMaximumWindowSize.Y,
548 	   csbi.srWindow.Bottom - csbi.srWindow.Top + 1));
549     TRACE(("...compare width  %d vs %d\n",
550 	   csbi.dwMaximumWindowSize.X,
551 	   csbi.srWindow.Right - csbi.srWindow.Left + 1));
552 
553     if (csbi.dwMaximumWindowSize.Y !=
554 	csbi.srWindow.Bottom - csbi.srWindow.Top + 1
555 	|| csbi.dwMaximumWindowSize.X !=
556 	csbi.srWindow.Right - csbi.srWindow.Left + 1) {
557 
558 	TRACE(("..creating alternate screen buffer\n"));
559 	hOldConsoleOutput = hConsoleOutput;
560 	hConsoleOutput = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
561 						   0, NULL,
562 						   CONSOLE_TEXTMODE_BUFFER, NULL);
563 	SetConsoleActiveScreenBuffer(hConsoleOutput);
564 
565 	GetConsoleScreenBufferInfo(hConsoleOutput, &csbi);
566 	TRACE(("GetConsoleScreenBufferInfo X:%d Y:%d Top:%d Bottom:%d Left:%d Right:%d\n",
567 	       csbi.dwMaximumWindowSize.X,
568 	       csbi.dwMaximumWindowSize.Y,
569 	       csbi.srWindow.Top,
570 	       csbi.srWindow.Bottom,
571 	       csbi.srWindow.Left,
572 	       csbi.srWindow.Right));
573 
574 	newcci_ok = GetConsoleCursorInfo(hConsoleOutput, &newcci);
575 	if (newcci_ok && origcci_ok && newcci.dwSize != origcci.dwSize) {
576 	    /*
577 	     * Ensure that user's cursor size prefs are carried forward
578 	     * in the newly created console.
579 	     */
580 	    show_cursor(TRUE, origcci.dwSize);
581 	}
582     }
583 
584     originalAttribute = csbi.wAttributes;
585 
586     crow = csbi.dwCursorPosition.Y;
587     ccol = csbi.dwCursorPosition.X;
588 
589     nfcolor = cfcolor = gfcolor;
590     nbcolor = cbcolor = gbcolor;
591     set_current_attr();
592 
593     newscreensize(csbi.dwMaximumWindowSize.Y, csbi.dwMaximumWindowSize.X);
594 
595     hConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
596     TRACE(("hConsoleInput %p\n", hConsoleInput));
597 
598     SetConsoleCtrlHandler(nthandler, TRUE);
599     ntconio_set_encoding(enc_DEFAULT);
600     returnVoid();
601 }
602 
603 static void
ntconio_close(void)604 ntconio_close(void)
605 {
606     TRACE((T_CALLED "ntconio_close\n"));
607     if (chgd_cursor) {
608 	/* restore cursor */
609 	show_cursor(TRUE, origcci.dwSize);
610     }
611     scflush();
612     ntconio_move(term.rows - 1, 0);
613     currentAttribute = originalAttribute;
614     ntconio_eeol();
615     ntconio_flush();
616     set_current_attr();
617 
618     SetConsoleTextAttribute(hConsoleOutput, originalAttribute);
619     if (hOldConsoleOutput) {
620 	TRACE(("...restoring screen buffer\n"));
621 	SetConsoleActiveScreenBuffer(hOldConsoleOutput);
622 	CloseHandle(hConsoleOutput);
623     }
624     SetConsoleCtrlHandler(nthandler, FALSE);
625     SetConsoleMode(hConsoleInput, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT);
626     keyboard_open = FALSE;
627     returnVoid();
628 }
629 
630 static void
ntconio_kopen(void)631 ntconio_kopen(void)
632 {				/* open the keyboard */
633     TRACE((T_CALLED "ntconio_kopen (open:%d, was-closed:%d)\n",
634 	   keyboard_open, keyboard_was_closed));
635     if (!keyboard_open) {
636 	if (hConsoleOutput) {
637 	    SetConsoleActiveScreenBuffer(hConsoleOutput);
638 	}
639 	keyboard_open = TRUE;
640 #ifdef DONT_USE_ON_WIN95
641 	SetConsoleCtrlHandler(NULL, TRUE);
642 #endif
643 	SetConsoleMode(hConsoleInput, ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
644     }
645     returnVoid();
646 }
647 
648 static void
ntconio_kclose(void)649 ntconio_kclose(void)
650 {				/* close the keyboard */
651     TRACE((T_CALLED "ntconio_kclose\n"));
652     if (keyboard_open) {
653 	keyboard_open = FALSE;
654 	keyboard_was_closed = TRUE;
655 	if (hOldConsoleOutput) {
656 	    SetConsoleActiveScreenBuffer(hOldConsoleOutput);
657 	}
658 #ifdef DONT_USE_ON_WIN95
659 	SetConsoleCtrlHandler(NULL, FALSE);
660 #endif
661     }
662     returnVoid();
663 }
664 
665 #define isModified(state) (state & \
666 		(LEFT_CTRL_PRESSED \
667 		 | RIGHT_CTRL_PRESSED \
668 		 | LEFT_ALT_PRESSED \
669 		 | RIGHT_ALT_PRESSED \
670 		 | SHIFT_PRESSED))
671 
672 static int
modified_key(int key,DWORD state)673 modified_key(int key, DWORD state)
674 {
675     key |= mod_KEY;
676     if (state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
677 	key |= mod_CTRL;
678     if (state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
679 	key |= mod_ALT;
680     if (state & SHIFT_PRESSED)
681 	key |= mod_SHIFT;
682 
683     return key;
684 }
685 
686 static struct {
687     int windows;
688     int vile;
689 } keyxlate[] = {
690     /* *INDENT-OFF* */
691     { VK_NEXT,		KEY_Next },
692     { VK_PRIOR,		KEY_Prior },
693     { VK_END,		KEY_End },
694     { VK_HOME,		KEY_Home },
695     { VK_LEFT,		KEY_Left },
696     { VK_RIGHT,		KEY_Right },
697     { VK_UP,		KEY_Up },
698     { VK_DOWN,		KEY_Down },
699     { VK_INSERT,	KEY_Insert },
700     { VK_DELETE,	KEY_Delete },
701     { VK_HELP,		KEY_Help },
702     { VK_SELECT,	KEY_Select },
703 #if 0
704     /* Merely pressing the Alt key generates a VK_MENU key event. */
705     { VK_MENU,		KEY_Menu },
706 #endif
707     { VK_F1,		KEY_F1 },
708     { VK_F2,		KEY_F2 },
709     { VK_F3,		KEY_F3 },
710     { VK_F4,		KEY_F4 },
711     { VK_F5,		KEY_F5 },
712     { VK_F6,		KEY_F6 },
713     { VK_F7,		KEY_F7 },
714     { VK_F8,		KEY_F8 },
715     { VK_F9,		KEY_F9 },
716     { VK_F10,		KEY_F10 },
717     { VK_F11,		KEY_F11 },
718     { VK_F12,		KEY_F12 },
719     { VK_F13,		KEY_F13 },
720     { VK_F14,		KEY_F14 },
721     { VK_F15,		KEY_F15 },
722     { VK_F16,		KEY_F16 },
723     { VK_F17,		KEY_F17 },
724     { VK_F18,		KEY_F18 },
725     { VK_F19,		KEY_F19 },
726     { VK_F20,		KEY_F20 },
727     { VK_F21,		KEY_F21 },
728     { VK_F22,		KEY_F22 },
729     { VK_F23,		KEY_F23 },
730     { VK_F24,		KEY_F24 },
731     /* winuser.h stops with VK_F24 */
732     /* Allow ^-6 to invoke the alternate-buffer command, a la Unix.  */
733     { '6',		'6' },
734     /* Support recognition of ^@ */
735     { '2',		'2' },
736     /* *INDENT-ON* */
737 
738 };
739 
740 static int savedChar;
741 static int saveCount = 0;
742 
743 static int
decode_key_event(INPUT_RECORD * irp)744 decode_key_event(INPUT_RECORD * irp)
745 {
746     DWORD state = irp->Event.KeyEvent.dwControlKeyState;
747     int key;
748     int i;
749 
750     if (!irp->Event.KeyEvent.bKeyDown)
751 	return (NOKYMAP);
752 
753     TRACE(("decode_key_event(%c=%02x, Virtual=%#x,%#x, State=%#lx)\n",
754 	   irp->Event.KeyEvent.uChar.WHICH_CHAR,
755 	   irp->Event.KeyEvent.uChar.WHICH_CHAR,
756 	   irp->Event.KeyEvent.wVirtualKeyCode,
757 	   irp->Event.KeyEvent.wVirtualScanCode,
758 	   irp->Event.KeyEvent.dwControlKeyState));
759 
760     if ((key = irp->Event.KeyEvent.uChar.WHICH_CHAR) != 0) {
761 	if (isCntrl(key)) {
762 	    DWORD cstate = state & ~(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);
763 	    if (isModified(cstate))
764 		key = modified_key(key, cstate);
765 	}
766 	return key;
767     }
768 
769     key = irp->Event.KeyEvent.wVirtualKeyCode;
770     if ((state & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED)) != 0
771 	&& (state & (RIGHT_CTRL_PRESSED
772 		     | LEFT_CTRL_PRESSED
773 		     | SHIFT_PRESSED)) == state) {
774 	/*
775 	 * Control-shift-6 is control/^, control/~ or control/`.
776 	 */
777 	if (key == '6'
778 	    || key == '^'
779 	    || key == '\036') {
780 	    TRACE(("...decode_key_event ^^\n"));
781 	    return '\036';
782 	}
783     }
784 
785     key = NOKYMAP;
786     for (i = 0; i < (int) TABLESIZE(keyxlate); i++) {
787 	if (keyxlate[i].windows == irp->Event.KeyEvent.wVirtualKeyCode) {
788 
789 	    /*
790 	     * Add the modifiers that we recognize.  Specifically, we don't
791 	     * care about ENHANCED_KEY, since we already have portable
792 	     * pageup/pagedown and arrow key bindings that would be lost if we
793 	     * used the Win32-only definition.
794 	     */
795 	    if (isModified(state)) {
796 		key = modified_key(keyxlate[i].vile, state);
797 		if (keyxlate[i].vile == '2') {
798 		    if ((key & mod_CTRL) && ((key & mod_ALT) == 0)) {
799 			/* either ^2 or ^@, => nul char */
800 
801 			key = 0;
802 		    } else if ((key & mod_SHIFT) &&
803 			       ((key & (mod_ALT | mod_CTRL)) == 0)) {
804 			/*
805 			 * this will be mapped to '@' if we let Windows do
806 			 * the translation
807 			 */
808 
809 			key = NOKYMAP;
810 		    }
811 		}
812 	    } else
813 		key = keyxlate[i].vile;
814 	    TRACE(("... %#x -> %#x\n", irp->Event.KeyEvent.wVirtualKeyCode, key));
815 	    break;
816 	}
817     }
818 
819     return key;
820 }
821 
822 static int
MouseClickSetPos(COORD * result,int * onmode)823 MouseClickSetPos(COORD * result, int *onmode)
824 {
825     WINDOW *wp;
826 
827     TRACE(("GETC:setcursor(%d, %d)\n", result->Y, result->X));
828 
829     /*
830      * If we're getting a button-down in a window, allow it to begin a
831      * selection.  A button-down on its modeline will allow resizing the
832      * window.
833      */
834     *onmode = FALSE;
835     if ((wp = row2window(result->Y)) != 0) {
836 	if (result->Y == mode_row(wp)) {
837 	    *onmode = TRUE;
838 	    return TRUE;
839 	}
840 	return setcursor(result->Y, result->X);
841     }
842     return FALSE;
843 }
844 
845 /*
846  * Shrink a window by dragging the modeline
847  */
848 static int
adjust_window(WINDOW * wp,COORD * current,COORD * latest)849 adjust_window(WINDOW *wp, COORD * current, COORD * latest)
850 {
851     if (latest->Y == mode_row(wp)) {
852 	if (current->Y != latest->Y) {
853 	    WINDOW *save_wp = curwp;
854 	    set_curwp(wp);
855 	    shrinkwind(FALSE, latest->Y - current->Y);
856 	    set_curwp(save_wp);
857 	    update(TRUE);
858 	}
859 	*latest = *current;
860 	return TRUE;
861     }
862     return FALSE;
863 }
864 
865 /*
866  * Return the current mouse position scaled in character cells, with the
867  * understanding that the cursor might not be in the client rect (due to a
868  * captured mouse).  The right way to do this is to call GetCursorPos() and
869  * convert that info to a character cell location by performing some simple
870  * computations based on the current font geometry, as is done in ntwinio.c .
871  * However, I've not found a way to obtain the font used in a command
872  * prompt's (aka DOS box's) client area (yes, I did try GetTextMetrics()).
873  * Without font info, other mechanisms are required.
874  *
875  * Note: GetMousePos() only returns accurate Y coordinate info, because
876  * that's all the info AutoScroll needs to make its decision.
877  */
878 static void
GetMousePos(POINT * result)879 GetMousePos(POINT * result)
880 {
881     HWND hwnd;
882 
883     hwnd = GetVileWindow();
884     GetCursorPos(result);
885     ScreenToClient(hwnd, result);
886     if (result->y < client_rect.top) {
887 	/*
888 	 * mouse is above the editor's client area, return a suitable
889 	 * character cell location that properly triggers autoscroll
890 	 */
891 
892 	result->y = -1;
893     } else if (result->y > client_rect.bottom) {
894 	/*
895 	 * mouse is below the editor's client area, return a suitable
896 	 * character cell location that properly triggers autoscroll
897 	 */
898 
899 	result->y = term.rows;
900     } else if (result->x < client_rect.left || result->x > client_rect.right) {
901 	/*
902 	 * dragged mouse out of client rectangle on either the left or
903 	 * right side.  don't know where the cursor really is, but it's
904 	 * easy to forestall autoscroll by returning a legit mouse
905 	 * coordinate within the current editor window.
906 	 */
907 
908 	result->y = mouse_wp->w_toprow;
909     } else {
910 	/* cursor within client area. */
911 
912 	result->y /= row_height;
913     }
914 }
915 
916 // Get current mouse position.  If the mouse is above/below the current
917 // window then scroll the window down/up proportionally to the time the LMB
918 // is held down.  This function is called from autoscroll_thread() when the
919 // mouse is captured and a wipe selection is active.
920 static void
AutoScroll(WINDOW * wp)921 AutoScroll(WINDOW *wp)
922 {
923 #define DVSR    10
924 #define INCR    6
925 #define TRIGGER (DVSR + INCR)
926 
927     POINT current;
928     int Scroll = 0;
929     static int ScrollCount = 0, Throttle = INCR;
930 
931     GetMousePos(&current);
932 
933     if (wp == 0)
934 	return;
935 
936     // Determine if we are above or below the window,
937     // and if so, how far...
938     if (current.y < wp->w_toprow) {
939 	// Above the window
940 	// Scroll = wp->w_toprow - current.y;
941 	Scroll = 1;
942     }
943     if (current.y > mode_row(wp)) {
944 	// Below
945 	// Scroll = current.y - mode_row(wp);
946 	// Scroll *= -1;
947 	Scroll = -1;
948     }
949     if (Scroll) {
950 	int row;
951 	if (Scroll > 0) {
952 	    row = wp->w_toprow;
953 	} else {
954 	    row = mode_row(wp) - 1;
955 	}
956 
957 	// Scroll the pre-determined amount, ensuring at least one line of
958 	// window movement per timer tick.  Note also that ScrollScale is
959 	// signed, so it will be negative if we want to scroll down.
960 	mvupwind(TRUE, Scroll * max(ScrollCount, TRIGGER) / (Throttle + DVSR));
961 
962 	// Set the cursor. Column doesn't really matter, it will
963 	// get updated as soon as we get back into the window...
964 	if (setcursor(row, 0)) {
965 	    sel_extend(TRUE, TRUE);
966 	}
967 	(void) update(TRUE);
968 	ScrollCount++;
969 	if (ScrollCount > TRIGGER && Throttle > 0 && ScrollCount % INCR == 0)
970 	    Throttle--;
971     } else {
972 	// Reset counters
973 	Throttle = INCR;
974 	ScrollCount = 0;
975     }
976 #undef DVSR
977 #undef INCR
978 #undef TRIGGER
979 }
980 
981 static void
halt_autoscroll_thread(void)982 halt_autoscroll_thread(void)
983 {
984     buttondown = FALSE;
985     (void) ReleaseCapture();	/* Release captured mouse */
986 
987     /* Wait for autoscroll thread to exit screen processing */
988     (void) WaitForSingleObject(hAsMutex, AS_TMOUT);
989     (void) CloseHandle(hAsMutex);
990 }
991 
992 static void
autoscroll_thread(void * unused)993 autoscroll_thread(void *unused)
994 {
995     DWORD status;
996 
997     (void) unused;
998     for_ever {
999 	status = WaitForSingleObject(hAsMutex, AS_TMOUT);
1000 	if (!buttondown) {
1001 	    (void) ReleaseMutex(hAsMutex);
1002 	    break;		/* button no longer held down, die */
1003 	}
1004 	if (status == WAIT_ABANDONED) {
1005 	    /* main thread closed thread handle or ??? */
1006 
1007 	    (void) ReleaseMutex(hAsMutex);
1008 	    break;
1009 	}
1010 	if (status == WAIT_OBJECT_0) {
1011 	    /* thread got mutex and "owns" the display. */
1012 
1013 	    AutoScroll(mouse_wp);
1014 	}
1015 	(void) ReleaseMutex(hAsMutex);
1016 	Sleep(25);		/* Don't hog the processor */
1017     }
1018 }
1019 
1020 /*
1021  * FUNCTION
1022  *   mousemove(int    *sel_pending,
1023  *             POINT  *first,
1024  *             POINT  *current,
1025  *             MARK   *lmbdn_mark,
1026  *             int    rect_rgn)
1027  *
1028  *   sel_pending - Boolean, T -> client has recorded a left mouse button (LMB)
1029  *                 click, and so, a selection is pending.
1030  *
1031  *   first       - editor row/col coordinates where LMB was initially recorded.
1032  *
1033  *   latest      - during the mouse move, assuming the LMB is still down,
1034  *                 "latest" tracks the cursor position wrt window resizing
1035  *                 operations (via a modeline drag).
1036  *
1037  *   current     - current cursor row/col coordinates.
1038  *
1039  *   lmbdn_mark  - editor MARK when "LMB down" was initially recorded.
1040  *
1041  *   rect_rgn    - Boolean, T -> user wants rectangular region selection.
1042  *
1043  * DESCRIPTION
1044  *   Using several state variables, this function handles all the semantics
1045  *   of a left mouse button "MOVE" event.  The semantics are as follows:
1046  *
1047  *   1) This function will not be called unless the LMB is down and the
1048  *      cursor is not being used to drage the mode line (enforced by caller).
1049  *   2) a LMB move within the current editor window selects a region of text.
1050  *      Later, when the user releases the LMB, that text is yanked to the
1051  *      unnamed register (the yank code is not handled in this function).
1052  *
1053  * RETURNS
1054  *   None
1055  */
1056 static void
mousemove(int * sel_pending,COORD * first,COORD * current,MARK * lmbdn_mark,int rect_rgn)1057 mousemove(int *sel_pending,
1058 	  COORD * first,
1059 	  COORD * current,
1060 	  MARK *lmbdn_mark,
1061 	  int rect_rgn)
1062 {
1063     int dummy;
1064 
1065     if (WaitForSingleObject(hAsMutex, AS_TMOUT) == WAIT_OBJECT_0) {
1066 	if (*sel_pending) {
1067 	    /*
1068 	     * Selection pending.  If the mouse has moved at least one char,
1069 	     * start a selection.
1070 	     */
1071 
1072 	    if (MouseClickSetPos(current, &dummy)) {
1073 		/* ignore mouse jitter */
1074 
1075 		if (current->X != first->X || current->Y != first->Y) {
1076 		    *sel_pending = FALSE;
1077 		    DOT = *lmbdn_mark;
1078 		    (void) sel_begin();
1079 		    (void) update(TRUE);
1080 		} else {
1081 		    (void) ReleaseMutex(hAsMutex);
1082 		    return;
1083 		}
1084 	    }
1085 	}
1086 	if (mouse_wp != row2window(current->Y)) {
1087 	    /*
1088 	     * mouse moved into a different editor window or row2window()
1089 	     * returned a NULL ptr.
1090 	     */
1091 
1092 	    (void) ReleaseMutex(hAsMutex);
1093 	    return;
1094 	}
1095 	if (!setcursor(current->Y, current->X)) {
1096 	    (void) ReleaseMutex(hAsMutex);
1097 	    return;
1098 	}
1099 	if (rect_rgn)
1100 	    (void) sel_setshape(rgn_RECTANGLE);
1101 	if (sel_extend(TRUE, TRUE))
1102 	    (void) update(TRUE);
1103 	(void) ReleaseMutex(hAsMutex);
1104     }
1105     /*
1106      * Else either the worker thread abandoned the mutex (not possible as
1107      * currently coded) or timed out.  If the latter, something is
1108      * hung--don't do anything.
1109      */
1110 }
1111 
1112 static void
handle_mouse_event(MOUSE_EVENT_RECORD mer)1113 handle_mouse_event(MOUSE_EVENT_RECORD mer)
1114 {
1115     static DWORD lastclick = 0;
1116     static int clicks = 0;
1117 
1118     int onmode = FALSE;
1119     COORD current, first, latest;
1120     MARK lmbdn_mark;		/* left mouse button down here */
1121     int sel_pending = 0, state;
1122     DWORD thisclick;
1123     UINT clicktime = GetDoubleClickTime();
1124 
1125     memset(&first, 0, sizeof(first));
1126     memset(&latest, 0, sizeof(latest));
1127     memset(&current, 0, sizeof(current));
1128     memset(&lmbdn_mark, 0, sizeof(lmbdn_mark));
1129     buttondown = FALSE;
1130     for_ever {
1131 	current = mer.dwMousePosition;
1132 	switch (mer.dwEventFlags) {
1133 	case 0:
1134 	    state = mer.dwButtonState;
1135 	    if (state == 0) {	/* button released */
1136 		thisclick = GetTickCount();
1137 		TRACE(("CLICK %ld/%ld\n", lastclick, thisclick));
1138 		if (thisclick - lastclick < clicktime) {
1139 		    clicks++;
1140 		    TRACE(("MOUSE CLICKS %d\n", clicks));
1141 		} else {
1142 		    clicks = 0;
1143 		}
1144 		lastclick = thisclick;
1145 
1146 		switch (clicks) {
1147 		case 1:
1148 		    on_double_click();
1149 		    break;
1150 		case 2:
1151 		    on_triple_click();
1152 		    break;
1153 		}
1154 
1155 		if (buttondown) {
1156 		    int dummy;
1157 
1158 		    halt_autoscroll_thread();
1159 
1160 		    /* Finalize cursor position. */
1161 		    (void) MouseClickSetPos(&current, &dummy);
1162 		    if (!(onmode || sel_pending))
1163 			sel_yank(0);
1164 		}
1165 		return;
1166 	    }
1167 	    if (state & FROM_LEFT_1ST_BUTTON_PRESSED) {
1168 		if (MouseClickSetPos(&current, &onmode)) {
1169 		    first = latest = current;
1170 		    lmbdn_mark = DOT;
1171 		    sel_pending = FALSE;
1172 		    mouse_wp = row2window(latest.Y);
1173 		    if (onmode) {
1174 			buttondown = TRUE;
1175 			sel_release();
1176 			update(TRUE);
1177 		    } else {
1178 			HWND hwnd;
1179 
1180 			(void) update(TRUE);	/* possible wdw change */
1181 			buttondown = FALSE;	/* until all inits are successful */
1182 
1183 			/* Capture mouse to console vile's window handle. */
1184 			hwnd = GetVileWindow();
1185 			(void) SetCapture(hwnd);
1186 
1187 			/* Compute pixel height of each row on screen. */
1188 			(void) GetClientRect(hwnd, &client_rect);
1189 			row_height = client_rect.bottom / term.rows;
1190 
1191 			/*
1192 			 * Create mutex to ensure that main thread and worker
1193 			 * thread don't update display at the same time.
1194 			 */
1195 			if ((hAsMutex = CreateMutex(0, FALSE, 0)) == NULL)
1196 			    mlforce("[Can't create autoscroll mutex]");
1197 			else {
1198 			    /*
1199 			     * Setup a worker thread to act as a pseudo
1200 			     * timer that kicks off autoscroll when
1201 			     * necessary.
1202 			     */
1203 
1204 			    if (_beginthread(autoscroll_thread,
1205 					     0,
1206 					     NULL) == (unsigned long) -1) {
1207 				(void) CloseHandle(hAsMutex);
1208 				mlforce("[Can't create autoscroll thread]");
1209 			    } else
1210 				sel_pending = buttondown = TRUE;
1211 			}
1212 			if (!buttondown)
1213 			    (void) ReleaseCapture();
1214 		    }
1215 		}
1216 	    } else if (state & FROM_LEFT_2ND_BUTTON_PRESSED) {
1217 		if (MouseClickSetPos(&current, &onmode)
1218 		    && !onmode) {
1219 		    sel_yank(0);
1220 		    sel_release();
1221 		    paste_selection();
1222 		    (void) update(TRUE);
1223 		}
1224 		return;
1225 	    } else {
1226 		if (MouseClickSetPos(&current, &onmode)
1227 		    && onmode) {
1228 		    sel_release();
1229 		    update(TRUE);
1230 		} else {
1231 		    kbd_alarm();
1232 		}
1233 	    }
1234 	    break;
1235 
1236 	case MOUSE_MOVED:
1237 	    if (!buttondown)
1238 		return;
1239 	    if (onmode) {
1240 		/* on mode line, resize window (if possible). */
1241 
1242 		if (!adjust_window(mouse_wp, &current, &latest)) {
1243 		    /*
1244 		     * left mouse button still down, but cursor moved off mode
1245 		     * line.  Update latest to keep track of cursor in case
1246 		     * it wanders back on the mode line.
1247 		     */
1248 
1249 		    latest = current;
1250 		}
1251 	    } else {
1252 		mousemove(&sel_pending,
1253 			  &first,
1254 			  &current,
1255 			  &lmbdn_mark,
1256 			  (mer.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
1257 		    );
1258 	    }
1259 	    break;
1260 
1261 #ifdef MOUSE_WHEELED
1262 	case MOUSE_WHEELED:
1263 	    /*
1264 	     * Trial and error experimentation shows that dwButtonState
1265 	     * has its high bit set when the wheel moves back and not
1266 	     * set otherwise.
1267 	     */
1268 	    mvupwind(TRUE, ((long) mer.dwButtonState < 0) ? -3 : 3);
1269 	    update(TRUE);
1270 	    return;
1271 #endif /* MOUSE_WHEELED */
1272 	}
1273 
1274 	for_ever {
1275 	    INPUT_RECORD ir;
1276 	    DWORD nr;
1277 	    int key;
1278 
1279 	    if (!ReadConsoleInput(hConsoleInput, &ir, 1, &nr))
1280 		imdying(0);
1281 	    switch (ir.EventType) {
1282 	    case KEY_EVENT:
1283 		key = decode_key_event(&ir);
1284 		if (key == ESC) {
1285 		    if (buttondown)
1286 			halt_autoscroll_thread();
1287 		    sel_release();
1288 		    (void) update(TRUE);
1289 		    return;
1290 		}
1291 		continue;
1292 
1293 	    case MOUSE_EVENT:
1294 		mer = ir.Event.MouseEvent;
1295 		break;
1296 	    }
1297 	    break;
1298 	}
1299     }
1300 }
1301 
1302 static int
ntconio_getch(void)1303 ntconio_getch(void)
1304 {
1305     INPUT_RECORD ir;
1306     DWORD nr;
1307     int key;
1308 #ifdef VAL_AUTOCOLOR
1309     int milli_ac, orig_milli_ac;
1310 #endif
1311 
1312     TRACE((T_CALLED "ntconio_getch saveCount:%d\n", saveCount));
1313 
1314     if (saveCount > 0) {
1315 	saveCount--;
1316 	returnCode(savedChar);
1317     }
1318 #ifdef VAL_AUTOCOLOR
1319     orig_milli_ac = global_b_val(VAL_AUTOCOLOR);
1320 #endif
1321     for_ever {
1322 #ifdef VAL_AUTOCOLOR
1323 	milli_ac = orig_milli_ac;
1324 	while (milli_ac > 0) {
1325 	    if (PeekConsoleInput(hConsoleInput, &ir, 1, &nr) == 0) {
1326 		TRACE(("PeekConsoleInput failed\n"));
1327 		break;		/* ?? system call failed ?? */
1328 	    }
1329 	    TRACE(("PeekConsoleInput nr %ld\n", nr));
1330 	    if (nr > 0)
1331 		break;		/* something in the queue */
1332 	    Sleep(20);		/* sleep a bit, but be responsive to keybd input */
1333 	    milli_ac -= 20;
1334 	}
1335 	if (orig_milli_ac && milli_ac <= 0) {
1336 	    ac_active = TRUE;
1337 	    autocolor();
1338 	    ac_active = FALSE;
1339 	}
1340 #endif
1341 	if (!ReadConsoleInput(hConsoleInput, &ir, 1, &nr))
1342 	    imdying(0);
1343 	switch (ir.EventType) {
1344 
1345 	case KEY_EVENT:
1346 	    key = decode_key_event(&ir);
1347 	    if (key == NOKYMAP)
1348 		continue;
1349 	    if (ir.Event.KeyEvent.wRepeatCount > 1) {
1350 		saveCount = ir.Event.KeyEvent.wRepeatCount - 1;
1351 		savedChar = key;
1352 	    }
1353 	    returnCode(key);
1354 
1355 	case WINDOW_BUFFER_SIZE_EVENT:
1356 	    GetConsoleScreenBufferInfo(hConsoleOutput, &csbi);
1357 	    newscreensize(
1358 			     ir.Event.WindowBufferSizeEvent.dwSize.Y,
1359 			     ir.Event.WindowBufferSizeEvent.dwSize.X
1360 		);
1361 	    continue;
1362 
1363 	case MOUSE_EVENT:
1364 	    handle_mouse_event(ir.Event.MouseEvent);
1365 	    continue;
1366 
1367 	}
1368     }
1369 }
1370 
1371 /*
1372  * The function `kbhit' returns true if there are *any* input records
1373  * available.  We need to define our own type ahead routine because
1374  * otherwise events which we will discard (like pressing or releasing
1375  * the Shift key) can block screen updates because `ntconio_getch' won't
1376  * return until a ordinary key event occurs.
1377  */
1378 
1379 static int
ntconio_typahead(void)1380 ntconio_typahead(void)
1381 {
1382     INPUT_RECORD ir;
1383     DWORD nr;
1384     int key;
1385 
1386     if (!keyboard_open)
1387 	return 0;
1388 #ifdef VAL_AUTOCOLOR
1389     if (ac_active) {
1390 	/*
1391 	 * Came here during an autocolor operation.  Do nothing, in an
1392 	 * attempt to avoid a keyboard lockup (editor loop) that occurs on
1393 	 * rare occasions (not reproducible).
1394 	 */
1395 
1396 	return (0);
1397     }
1398 #endif
1399     if (saveCount > 0)
1400 	return 1;
1401 
1402     for_ever {
1403 	if (!PeekConsoleInput(hConsoleInput, &ir, 1, &nr))
1404 	    return 0;
1405 
1406 	if (nr == 0)
1407 	    break;
1408 
1409 	switch (ir.EventType) {
1410 
1411 	case KEY_EVENT:
1412 	    key = decode_key_event(&ir);
1413 	    if (key < 0) {
1414 		ReadConsoleInput(hConsoleInput, &ir, 1, &nr);
1415 		continue;
1416 	    }
1417 	    return 1;
1418 
1419 	default:
1420 	    /* Ignore type-ahead for non-keyboard events. */
1421 	    return 0;
1422 	}
1423     }
1424 
1425     return 0;
1426 }
1427 
1428 void
ntcons_reopen(void)1429 ntcons_reopen(void)
1430 {
1431     /* If we are coming back from a shell command, pick up the current window
1432      * size, since that may have changed due to a 'mode con' command.  Run
1433      * this after the 'pressreturn()' call, since otherwise that gets lost
1434      * by side-effects of this code.
1435      */
1436     if (keyboard_was_closed) {
1437 	CONSOLE_SCREEN_BUFFER_INFO temp;
1438 	keyboard_was_closed = FALSE;
1439 	GetConsoleScreenBufferInfo(hConsoleOutput, &temp);
1440 	newscreensize(temp.dwMaximumWindowSize.Y, temp.dwMaximumWindowSize.X);
1441     }
1442 }
1443 
1444 #if OPT_ICURSOR
1445 
1446 /* supported syntax is described in chgd_icursor() */
1447 static int
parse_icursor_string(char * str,int * revert_cursor)1448 parse_icursor_string(char *str, int *revert_cursor)
1449 {
1450     int failed, rc = TRUE, tmp = 0;
1451     char *pinsmode, *pcmdmode;
1452 
1453     *revert_cursor = FALSE;
1454     pinsmode = str;
1455     if ((pcmdmode = strchr(pinsmode, ',')) != NULL) {
1456 	/* vl_atoul won't like the delimiter... */
1457 
1458 	tmp = *pcmdmode;
1459 	*pcmdmode = '\0';
1460     }
1461     icursor_insmode = (int) vl_atoul(pinsmode, 10, &failed);
1462     if (pcmdmode)
1463 	*pcmdmode = (char) tmp;	/* delimiter restored */
1464     if (failed)
1465 	return (FALSE);
1466     if (pcmdmode) {
1467 	icursor_cmdmode = (int) vl_atoul(pcmdmode + 1, 10, &failed);
1468 	if (failed)
1469 	    return (FALSE);
1470     } else {
1471 	/* block mode syntax */
1472 
1473 	icursor_cmdmode = icursor_insmode;
1474     }
1475 
1476     /* semantic chks */
1477     if (icursor_insmode == 0) {
1478 	if (!pcmdmode)
1479 	    *revert_cursor = TRUE;
1480 	else
1481 	    rc = FALSE;		/* 0% insmode cursor block heights not valid */
1482 	return (rc);
1483     }
1484     if (icursor_cmdmode == 0)
1485 	rc = FALSE;		/* 0% cmdmode cursor block heights not valid */
1486     else {
1487 	if (icursor_cmdmode > MAX_CURBLK_HEIGHT ||
1488 	    icursor_insmode > MAX_CURBLK_HEIGHT) {
1489 	    rc = FALSE;
1490 	}
1491     }
1492     return (rc);
1493 }
1494 
1495 /*
1496  * user changed icursor mode
1497  *
1498  * Insertion cursor mode is a string that may be used to either set a fixed
1499  * block cursor height or set the block cursor heights in insertion and
1500  * command mode.  Supported syntax:
1501  *
1502  *     "<fixed_block_height>"
1503  *
1504  *              or
1505  *
1506  *     "<insmode_height>,<cmdmode_height>"
1507  *
1508  * The valid range of <fixed_block_cursor_height> is 0-100.  Specifying 0
1509  * forces the editor to revert to the cursor height in effect when the
1510  * editor was invoked.
1511  *
1512  * The valid range of <insmode_height> and <cmdmode_height> is 1-100.
1513  */
1514 int
chgd_icursor(BUFFER * bp,VALARGS * args,int glob_vals,int testing)1515 chgd_icursor(BUFFER *bp, VALARGS * args, int glob_vals, int testing)
1516 {
1517     (void) bp;
1518     (void) glob_vals;
1519 
1520     if (!testing) {
1521 	int revert_cursor;
1522 	char *val = args->global->vp->p;
1523 
1524 	if (!parse_icursor_string(val, &revert_cursor)) {
1525 	    mlforce("[invalid icursor syntax]");
1526 	    return (FALSE);
1527 	}
1528 	if (!revert_cursor) {
1529 	    chgd_cursor = TRUE;
1530 	    icursor = (icursor_insmode != icursor_cmdmode);
1531 	    if (icursor)
1532 		term.icursor(insertmode);
1533 	    else {
1534 		/* just set a block cursor */
1535 		show_cursor(TRUE, icursor_cmdmode);
1536 	    }
1537 	} else {
1538 	    /*
1539 	     * user wants to disable previous changes made to cursor,
1540 	     * thereby reverting to cursor height in effect when the editor
1541 	     * was invoked.
1542 	     */
1543 
1544 	    if (chgd_cursor) {
1545 		/*
1546 		 * NB -- don't reset "chgd_cursor" here.  special cleanup
1547 		 * is required in ntconio_close().
1548 		 */
1549 
1550 		if (origcci_ok)
1551 		    show_cursor(TRUE, origcci.dwSize);
1552 	    }
1553 	}
1554     }
1555     return (TRUE);
1556 }
1557 #endif
1558 
1559 /*
1560  * Standard terminal interface dispatch table. None of the fields point into
1561  * "termio" code.
1562  */
1563 
1564 TERM term =
1565 {
1566     NROW,
1567     NROW,
1568     NCOL,
1569     NCOL,
1570     ntconio_set_encoding,
1571     ntconio_get_encoding,
1572     ntconio_open,
1573     ntconio_close,
1574     ntconio_kopen,
1575     ntconio_kclose,
1576     nullterm_clean,
1577     nullterm_unclean,
1578     nullterm_openup,
1579     ntconio_getch,
1580     ntconio_putc,
1581     ntconio_typahead,
1582     ntconio_flush,
1583     ntconio_move,
1584     ntconio_eeol,
1585     ntconio_eeop,
1586     ntconio_beep,
1587     ntconio_rev,
1588     ntconio_cres,
1589 #if OPT_COLOR
1590     ntconio_fcol,
1591     ntconio_bcol,
1592     set_ctrans,
1593 #else
1594     nullterm_setfore,
1595     nullterm_setback,
1596     nullterm_setpal,
1597 #endif
1598     nullterm_setccol,
1599     ntconio_scroll,
1600     nullterm_pflush,
1601 #if OPT_ICURSOR
1602     nticursor,
1603 #else
1604     nullterm_icursor,
1605 #endif
1606 #if OPT_TITLE
1607     ntconio_title,
1608 #else
1609     nullterm_settitle,
1610 #endif
1611     nullterm_watchfd,
1612     nullterm_unwatchfd,
1613     nullterm_cursorvis,
1614     nullterm_mopen,
1615     nullterm_mclose,
1616     nullterm_mevent,
1617 };
1618