ncurses 6.2 - patch 20201114
[ncurses.git] / ncurses / tinfo / lib_win32con.c
1 /****************************************************************************
2  * Copyright 2020 Thomas E. Dickey                                          *
3  * Copyright 1998-2009,2010 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29
30 /****************************************************************************
31  *  Author: Juergen Pfeifer                                                 *
32  *     and: Thomas E. Dickey                                                *
33  ****************************************************************************/
34
35 /*
36  * TODO - GetMousePos(POINT * result) from ntconio.c
37  */
38
39 #include <curses.priv.h>
40
41 MODULE_ID("$Id: lib_win32con.c,v 1.4 2020/11/14 23:37:16 tom Exp $")
42
43 #ifdef _NC_WINDOWS
44
45 #ifdef _NC_MINGW
46 #include <wchar.h>
47 #else
48 #include <tchar.h>
49 #endif
50
51 #include <io.h>
52
53 #if USE_WIDEC_SUPPORT
54 #define write_screen WriteConsoleOutputW
55 #define read_screen  ReadConsoleOutputW
56 #else
57 #define write_screen WriteConsoleOutput
58 #define read_screen  ReadConsoleOutput
59 #endif
60
61 static BOOL IsConsoleHandle(HANDLE hdl);
62 static bool save_original_screen(void);
63 static bool restore_original_screen(void) GCC_UNUSED;
64 static bool read_screen_data(void);
65 static int  Adjust(int milliseconds, int diff);
66 static int  decode_mouse(SCREEN *sp, int mask);
67 static bool handle_mouse(SCREEN *sp, MOUSE_EVENT_RECORD mer);
68 static int  rkeycompare(const void *el1, const void *el2);
69 static int  keycompare(const void *el1, const void *el2);
70 static int  MapKey(WORD vKey);
71 static int  AnsiKey(WORD vKey);
72
73 static ULONGLONG tdiff(FILETIME fstart, FILETIME fend);
74
75 #define GenMap(vKey,key) MAKELONG(key, vKey)
76 static const LONG keylist[] =
77 {
78     GenMap(VK_PRIOR, KEY_PPAGE),
79     GenMap(VK_NEXT, KEY_NPAGE),
80     GenMap(VK_END, KEY_END),
81     GenMap(VK_HOME, KEY_HOME),
82     GenMap(VK_LEFT, KEY_LEFT),
83     GenMap(VK_UP, KEY_UP),
84     GenMap(VK_RIGHT, KEY_RIGHT),
85     GenMap(VK_DOWN, KEY_DOWN),
86     GenMap(VK_DELETE, KEY_DC),
87     GenMap(VK_INSERT, KEY_IC)
88 };
89 static const LONG ansi_keys[] =
90 {
91     GenMap(VK_PRIOR, 'I'),
92     GenMap(VK_NEXT, 'Q'),
93     GenMap(VK_END, 'O'),
94     GenMap(VK_HOME, 'H'),
95     GenMap(VK_LEFT, 'K'),
96     GenMap(VK_UP, 'H'),
97     GenMap(VK_RIGHT, 'M'),
98     GenMap(VK_DOWN, 'P'),
99     GenMap(VK_DELETE, 'S'),
100     GenMap(VK_INSERT, 'R')
101 };
102 #define array_length(a) (sizeof(a)/sizeof(a[0]))
103 #define N_INI ((int)array_length(keylist))
104 #define FKEYS 24
105 #define MAPSIZE (FKEYS + N_INI)
106
107 /*   A process can only have a single console, so it's safe
108      to maintain all the information about it in a single
109      static structure.
110  */
111 NCURSES_EXPORT_VAR(ConsoleInfo) _nc_CONSOLE;
112 static bool console_initialized = FALSE;
113
114 #define EnsureInit() (void)(console_initialized ? TRUE : _nc_console_checkinit(TRUE,TRUE))
115
116 #define REQUIRED_MAX_V (DWORD)10
117 #define REQUIRED_MIN_V (DWORD)0
118 #define REQUIRED_BUILD (DWORD)17763
119 /*
120   This function returns 0 if the Windows version has no support for
121   the modern Console interface, otherwise it returns 1
122  */
123 NCURSES_EXPORT(int)
124 _nc_console_vt_supported(void) {
125     OSVERSIONINFO osvi;
126     int res = 0;
127
128     T((T_CALLED("lib_win32con::_nc_console_vt_supported")));
129     ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
130     osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
131
132     GetVersionEx(&osvi);
133     T(("GetVersionEx returnedMajor=%ld, Minor=%ld, Build=%ld", osvi.dwMajorVersion,osvi.dwMinorVersion,osvi.dwBuildNumber));    if (osvi.dwMajorVersion >= REQUIRED_MAX_V) {
134       if (osvi.dwMajorVersion == REQUIRED_MAX_V) {
135           if (((osvi.dwMinorVersion == REQUIRED_MIN_V) &&
136                (osvi.dwBuildNumber >= REQUIRED_BUILD)) ||
137               ((osvi.dwMinorVersion > REQUIRED_MIN_V)))
138               res = 1;
139       } else
140           res = 1;
141     }
142     returnCode(res);
143 }
144
145 NCURSES_EXPORT(void)
146 _nc_console_size(int* Lines, int* Cols) {
147   EnsureInit();
148   if (Lines != NULL && Cols != NULL) {
149       if (WINCONSOLE.buffered) {
150           *Lines = (int) (WINCONSOLE.SBI.dwSize.Y);
151           *Cols = (int) (WINCONSOLE.SBI.dwSize.X);
152       } else {
153           *Lines = (int) (WINCONSOLE.SBI.srWindow.Bottom + 1 -
154                           WINCONSOLE.SBI.srWindow.Top);
155           *Cols = (int) (WINCONSOLE.SBI.srWindow.Right + 1 -
156                          WINCONSOLE.SBI.srWindow.Left);
157       }
158   }
159 }
160
161 /* Convert a file descriptor into a HANDLE
162    That's not necessarily a console HANDLE
163 */
164 NCURSES_EXPORT(HANDLE)
165 _nc_console_handle(int fd)
166 {
167     intptr_t value = _get_osfhandle(fd);
168     return (HANDLE) value;
169 }
170
171 /* Validate that a HANDLE is actually a
172    console HANDLE
173 */
174 static BOOL
175 IsConsoleHandle(HANDLE hdl)
176 {
177     DWORD dwFlag = 0;
178     BOOL result = FALSE;
179
180     T((T_CALLED("lib_win32con::IsConsoleHandle(HANDLE=%p"),hdl));
181
182     EnsureInit();
183
184     if (!GetConsoleMode(hdl, &dwFlag)) {
185         T(("GetConsoleMode failed"));
186     } else {
187         result = TRUE;
188     }
189
190     returnBool(result);
191 }
192
193 /*   This is used when running in terminfo mode to discover,
194      whether or not the "terminal" is actually a Windows
195      Console. It's the responsibility of the console to deal
196      with the terminal escape sequences that are sent by
197      terminfo.
198  */
199 NCURSES_EXPORT(int)
200 _nc_console_test(int fd)
201 {
202     int code = 0;
203     HANDLE hdl = INVALID_HANDLE_VALUE;
204     T((T_CALLED("lib_win32con::_nc_console_test(%d)"), fd));
205     hdl = _nc_console_handle(fd);
206     code = (int) IsConsoleHandle(hdl);
207     returnCode(code);
208 }
209
210 #define OutHandle() ((WINCONSOLE.isTermInfoConsole || WINCONSOLE.progMode) ? WINCONSOLE.hdl : WINCONSOLE.out)
211
212 NCURSES_EXPORT(void)
213 _nc_console_selectActiveHandle(void)
214 {
215     if (WINCONSOLE.lastOut != WINCONSOLE.hdl) {
216         WINCONSOLE.lastOut = WINCONSOLE.hdl;
217         SetConsoleActiveScreenBuffer(WINCONSOLE.lastOut);
218     }
219 }
220
221 NCURSES_EXPORT(HANDLE)
222 _nc_console_fd2handle(int fd) {
223     HANDLE hdl = _nc_console_handle(fd);
224     if (hdl==WINCONSOLE.inp) {
225         T(("lib_win32con:validateHandle %d -> WINCONSOLE.inp", fd));
226     } else if (hdl==WINCONSOLE.hdl) {
227         T(("lib_win32con:validateHandle %d -> WINCONSOLE.hdl", fd));
228     } else if (hdl==WINCONSOLE.out) {
229         T(("lib_win32con:validateHandle %d -> WINCONSOLE.out", fd));
230     } else {
231         T(("lib_win32con:validateHandle %d maps to unknown HANDLE", fd));
232         hdl=INVALID_HANDLE_VALUE;
233     }
234 #if 1
235     assert(hdl!=INVALID_HANDLE_VALUE);
236 #endif
237     if (hdl != INVALID_HANDLE_VALUE) {
238         if (hdl!=WINCONSOLE.inp && (!WINCONSOLE.isTermInfoConsole && WINCONSOLE.progMode)) {
239             if (hdl==WINCONSOLE.out && hdl!=WINCONSOLE.hdl) {
240                 T(("lib_win32con:validateHandle forcing WINCONSOLE.out -> WINCONSOLE.hdl"));
241                 hdl=WINCONSOLE.hdl;
242             }
243         }
244     }
245     return hdl;
246 }
247
248 NCURSES_EXPORT(int)
249 _nc_console_setmode(HANDLE hdl, const TTY *arg)
250 {
251     DWORD dwFlag = 0;
252     int code = ERR;
253     HANDLE alt;
254
255     if (arg) {
256 #ifdef TRACE
257         TTY TRCTTY;
258 #define TRCTTYOUT(flag) TRCTTY.dwFlagOut=flag
259 #define TRCTTYIN(flag)  TRCTTY.dwFlagIn=flag
260 #else
261 #define TRCTTYOUT(flag)
262 #define TRCTTYIN(flag)
263 #endif
264         T(("lib_win32con:_nc_console_setmode %s", _nc_trace_ttymode(arg)));
265         if (hdl==WINCONSOLE.inp) {
266             dwFlag = arg->dwFlagIn | ENABLE_MOUSE_INPUT | VT_FLAG_IN;
267             if (WINCONSOLE.isTermInfoConsole)
268                 dwFlag |= (VT_FLAG_IN);
269             else
270                 dwFlag &= ~(VT_FLAG_IN);
271             TRCTTYIN(dwFlag);
272             SetConsoleMode(hdl, dwFlag);
273
274             alt = OutHandle();
275             dwFlag = arg->dwFlagOut;
276             if (WINCONSOLE.isTermInfoConsole)
277                 dwFlag |= (VT_FLAG_OUT);
278             else
279                 dwFlag |= (VT_FLAG_OUT);
280             TRCTTYOUT(dwFlag);
281             SetConsoleMode(alt, dwFlag);
282         } else {
283             dwFlag = arg->dwFlagOut;
284             if (WINCONSOLE.isTermInfoConsole)
285                 dwFlag |= (VT_FLAG_OUT);
286             else
287                 dwFlag |= (VT_FLAG_OUT);
288             TRCTTYOUT(dwFlag);
289             SetConsoleMode(hdl, dwFlag);
290
291             alt=WINCONSOLE.inp;
292             dwFlag = arg->dwFlagIn | ENABLE_MOUSE_INPUT;
293             if (WINCONSOLE.isTermInfoConsole)
294                 dwFlag |= (VT_FLAG_IN);
295             else
296                 dwFlag &= ~(VT_FLAG_IN);
297             TRCTTYIN(dwFlag);
298             SetConsoleMode(alt, dwFlag);
299             T(("effective mode set %s",_nc_trace_ttymode(&TRCTTY)));
300         }
301         code = OK;
302     }
303     return(code);
304 }
305
306 NCURSES_EXPORT(int)
307 _nc_console_getmode(HANDLE hdl, TTY *arg)
308 {
309     int code = ERR;
310
311     if (arg) {
312         DWORD dwFlag = 0;
313         HANDLE alt;
314
315         if (hdl==WINCONSOLE.inp) {
316             if(GetConsoleMode(hdl,&dwFlag)) {
317                 arg->dwFlagIn = dwFlag;
318                 alt = OutHandle();
319                 if (GetConsoleMode(alt,&dwFlag)) {
320                     arg->dwFlagOut = dwFlag;
321                     code = OK;
322                 }
323             }
324         } else {
325             if (GetConsoleMode(hdl,&dwFlag)) {
326                 arg->dwFlagOut = dwFlag;
327                 alt=WINCONSOLE.inp;
328                 if (GetConsoleMode(alt,&dwFlag)) {
329                     arg->dwFlagIn = dwFlag;
330                     code = OK;
331                 }
332             }
333         }
334     }
335     T(("lib_win32con:_nc_console_getmode %s", _nc_trace_ttymode(arg)));
336     return(code);
337 }
338
339 NCURSES_EXPORT(int)
340 _nc_console_flush(HANDLE hdl)
341 {
342     int code=OK;
343
344     T((T_CALLED("lib_win32con::_nc_console_flush(hdl=%p"),hdl));
345
346     if (hdl != INVALID_HANDLE_VALUE) {
347         if (hdl == WINCONSOLE.hdl ||
348             hdl == WINCONSOLE.inp ||
349             hdl == WINCONSOLE.out) {
350             if (!FlushConsoleInputBuffer(WINCONSOLE.inp))
351                 code=ERR;
352         } else {
353             code=ERR;
354             T(("_nc_console_flush not requesting a handle owned by console."));
355         }
356     }
357     returnCode(code);
358 }
359
360 NCURSES_EXPORT(WORD)
361 _nc_console_MapColor(bool fore, int color)
362 {
363     static const int _cmap[] =
364         {0, 4, 2, 6, 1, 5, 3, 7};
365     int a;
366     if (color < 0 || color > 7)
367         a = fore ? 7 : 0;
368     else
369         a = _cmap[color];
370     if (!fore)
371         a = a << 4;
372     return (WORD) a;
373 }
374
375
376 /*
377  * Attempt to save the screen contents.  PDCurses does this if
378  * PDC_RESTORE_SCREEN is set, giving the same visual appearance on
379  * restoration as if the library had allocated a console buffer.  MSDN
380  * says that the data which can be read is limited to 64Kb (and may be
381  * less).
382  */
383 static bool
384 save_original_screen(void)
385 {
386     bool result = FALSE;
387
388     WINCONSOLE.save_region.Top = 0;
389     WINCONSOLE.save_region.Left = 0;
390     WINCONSOLE.save_region.Bottom = (SHORT) (WINCONSOLE.SBI.dwSize.Y - 1);
391     WINCONSOLE.save_region.Right = (SHORT) (WINCONSOLE.SBI.dwSize.X - 1);
392
393     if (read_screen_data()) {
394         result = TRUE;
395     } else {
396
397         WINCONSOLE.save_region.Top = WINCONSOLE.SBI.srWindow.Top;
398         WINCONSOLE.save_region.Left = WINCONSOLE.SBI.srWindow.Left;
399         WINCONSOLE.save_region.Bottom = WINCONSOLE.SBI.srWindow.Bottom;
400         WINCONSOLE.save_region.Right = WINCONSOLE.SBI.srWindow.Right;
401
402         WINCONSOLE.window_only = TRUE;
403
404         if (read_screen_data()) {
405             result = TRUE;
406         }
407     }
408
409     T(("... save original screen contents %s", result ? "ok" : "err"));
410     return result;
411 }
412
413 static bool
414 restore_original_screen(void)
415 {
416     COORD bufferCoord;
417     bool result = FALSE;
418     SMALL_RECT save_region = WINCONSOLE.save_region;
419
420     T(("... restoring %s",
421        WINCONSOLE.window_only ? "window" : "entire buffer"));
422
423     bufferCoord.X = (SHORT) (WINCONSOLE.window_only ?
424                              WINCONSOLE.SBI.srWindow.Left : 0);
425     bufferCoord.Y = (SHORT) (WINCONSOLE.window_only ?
426                              WINCONSOLE.SBI.srWindow.Top : 0);
427
428     if (write_screen(WINCONSOLE.hdl,
429                      WINCONSOLE.save_screen,
430                      WINCONSOLE.save_size,
431                      bufferCoord,
432                      &save_region)) {
433         result = TRUE;
434         mvcur(-1, -1, LINES - 2, 0);
435         T(("... restore original screen contents ok %dx%d (%d,%d - %d,%d)",
436            WINCONSOLE.save_size.Y,
437            WINCONSOLE.save_size.X,
438            save_region.Top,
439            save_region.Left,
440            save_region.Bottom,
441            save_region.Right));
442     } else {
443         T(("... restore original screen contents err"));
444     }
445     return result;
446 }
447
448 static bool
449 read_screen_data(void)
450 {
451     bool result = FALSE;
452     COORD bufferCoord;
453     size_t want;
454
455     WINCONSOLE.save_size.X = (SHORT) (WINCONSOLE.save_region.Right
456                                       - WINCONSOLE.save_region.Left + 1);
457     WINCONSOLE.save_size.Y = (SHORT) (WINCONSOLE.save_region.Bottom
458                                       - WINCONSOLE.save_region.Top + 1);
459
460     want = (size_t) (WINCONSOLE.save_size.X * WINCONSOLE.save_size.Y);
461
462     if ((WINCONSOLE.save_screen = malloc(want * sizeof(CHAR_INFO))) != 0) {
463         bufferCoord.X = (SHORT) (WINCONSOLE.window_only ?
464                                  WINCONSOLE.SBI.srWindow.Left : 0);
465         bufferCoord.Y = (SHORT) (WINCONSOLE.window_only ?
466                                  WINCONSOLE.SBI.srWindow.Top : 0);
467
468         T(("... reading console %s %dx%d into %d,%d - %d,%d at %d,%d",
469            WINCONSOLE.window_only ? "window" : "buffer",
470            WINCONSOLE.save_size.Y, WINCONSOLE.save_size.X,
471            WINCONSOLE.save_region.Top,
472            WINCONSOLE.save_region.Left,
473            WINCONSOLE.save_region.Bottom,
474            WINCONSOLE.save_region.Right,
475            bufferCoord.Y,
476            bufferCoord.X));
477
478         if (read_screen(WINCONSOLE.hdl,
479                         WINCONSOLE.save_screen,
480                         WINCONSOLE.save_size,
481                         bufferCoord,
482                         &WINCONSOLE.save_region)) {
483             result = TRUE;
484         } else {
485             T((" error %#lx", (unsigned long) GetLastError()));
486             FreeAndNull(WINCONSOLE.save_screen);
487         }
488     }
489
490     return result;
491 }
492
493 NCURSES_EXPORT(bool)
494 _nc_console_get_SBI(void)
495 {
496     bool rc = FALSE;
497     if (GetConsoleScreenBufferInfo(WINCONSOLE.hdl, &(WINCONSOLE.SBI))) {
498         T(("GetConsoleScreenBufferInfo"));
499         T(("... buffer(X:%d Y:%d)",
500            WINCONSOLE.SBI.dwSize.X,
501            WINCONSOLE.SBI.dwSize.Y));
502         T(("... window(X:%d Y:%d)",
503            WINCONSOLE.SBI.dwMaximumWindowSize.X,
504            WINCONSOLE.SBI.dwMaximumWindowSize.Y));
505         T(("... cursor(X:%d Y:%d)",
506            WINCONSOLE.SBI.dwCursorPosition.X,
507            WINCONSOLE.SBI.dwCursorPosition.Y));
508         T(("... display(Top:%d Bottom:%d Left:%d Right:%d)",
509            WINCONSOLE.SBI.srWindow.Top,
510            WINCONSOLE.SBI.srWindow.Bottom,
511            WINCONSOLE.SBI.srWindow.Left,
512            WINCONSOLE.SBI.srWindow.Right));
513         if (WINCONSOLE.buffered) {
514             WINCONSOLE.origin.X = 0;
515             WINCONSOLE.origin.Y = 0;
516         } else {
517             WINCONSOLE.origin.X = WINCONSOLE.SBI.srWindow.Left;
518             WINCONSOLE.origin.Y = WINCONSOLE.SBI.srWindow.Top;
519         }
520         rc = TRUE;
521     } else {
522         T(("GetConsoleScreenBufferInfo ERR"));
523     }
524     return rc;
525 }
526
527 #define MIN_WIDE 80
528 #define MIN_HIGH 24
529
530 /*
531  * In "normal" mode, reset the buffer- and window-sizes back to their original values.
532  */
533 NCURSES_EXPORT(void)
534 _nc_console_set_scrollback(bool normal, CONSOLE_SCREEN_BUFFER_INFO * info)
535 {
536     SMALL_RECT rect;
537     COORD coord;
538     bool changed = FALSE;
539
540     T((T_CALLED("lib_win32con::_nc_console_set_scrollback(%s)"),
541        (normal
542         ? "normal"
543         : "application")));
544
545     T(("... SBI.srWindow %d,%d .. %d,%d",
546        info->srWindow.Top,
547        info->srWindow.Left,
548        info->srWindow.Bottom,
549        info->srWindow.Right));
550     T(("... SBI.dwSize %dx%d",
551        info->dwSize.Y,
552        info->dwSize.X));
553
554     if (normal) {
555         rect = info->srWindow;
556         coord = info->dwSize;
557         if (memcmp(info, &WINCONSOLE.SBI, sizeof(*info)) != 0) {
558             changed = TRUE;
559             WINCONSOLE.SBI = *info;
560         }
561     } else {
562         int high = info->srWindow.Bottom - info->srWindow.Top + 1;
563         int wide = info->srWindow.Right - info->srWindow.Left + 1;
564
565         if (high < MIN_HIGH) {
566             T(("... height %d < %d", high, MIN_HIGH));
567             high = MIN_HIGH;
568             changed = TRUE;
569         }
570         if (wide < MIN_WIDE) {
571             T(("... width %d < %d", wide, MIN_WIDE));
572             wide = MIN_WIDE;
573             changed = TRUE;
574         }
575
576         rect.Left =
577             rect.Top = 0;
578         rect.Right = (SHORT) (wide - 1);
579         rect.Bottom = (SHORT) (high - 1);
580
581         coord.X = (SHORT) wide;
582         coord.Y = (SHORT) high;
583
584         if (info->dwSize.Y != high ||
585             info->dwSize.X != wide ||
586             info->srWindow.Top != 0 ||
587             info->srWindow.Left != 0) {
588             changed = TRUE;
589         }
590
591     }
592
593     if (changed) {
594         T(("... coord %d,%d", coord.Y, coord.X));
595         T(("... rect %d,%d - %d,%d",
596            rect.Top, rect.Left,
597            rect.Bottom, rect.Right));
598         SetConsoleScreenBufferSize(WINCONSOLE.hdl, coord);      /* dwSize */
599         SetConsoleWindowInfo(WINCONSOLE.hdl, TRUE, &rect);      /* srWindow */
600         _nc_console_get_SBI();
601     }
602     returnVoid;
603 }
604
605 static ULONGLONG
606 tdiff(FILETIME fstart, FILETIME fend)
607 {
608     ULARGE_INTEGER ustart;
609     ULARGE_INTEGER uend;
610     ULONGLONG diff;
611
612     ustart.LowPart = fstart.dwLowDateTime;
613     ustart.HighPart = fstart.dwHighDateTime;
614     uend.LowPart = fend.dwLowDateTime;
615     uend.HighPart = fend.dwHighDateTime;
616
617     diff = (uend.QuadPart - ustart.QuadPart) / 10000;
618     return diff;
619 }
620
621 static int
622 Adjust(int milliseconds, int diff)
623 {
624     if (milliseconds != INFINITY) {
625         milliseconds -= diff;
626         if (milliseconds < 0)
627             milliseconds = 0;
628     }
629     return milliseconds;
630 }
631
632 #define BUTTON_MASK (FROM_LEFT_1ST_BUTTON_PRESSED | \
633                      FROM_LEFT_2ND_BUTTON_PRESSED | \
634                      FROM_LEFT_3RD_BUTTON_PRESSED | \
635                      FROM_LEFT_4TH_BUTTON_PRESSED | \
636                      RIGHTMOST_BUTTON_PRESSED)
637
638 static int
639 decode_mouse(SCREEN *sp, int mask)
640 {
641     int result = 0;
642
643     (void) sp;
644     assert(sp && console_initialized);
645
646     if (mask & FROM_LEFT_1ST_BUTTON_PRESSED)
647         result |= BUTTON1_PRESSED;
648     if (mask & FROM_LEFT_2ND_BUTTON_PRESSED)
649         result |= BUTTON2_PRESSED;
650     if (mask & FROM_LEFT_3RD_BUTTON_PRESSED)
651         result |= BUTTON3_PRESSED;
652     if (mask & FROM_LEFT_4TH_BUTTON_PRESSED)
653         result |= BUTTON4_PRESSED;
654
655     if (mask & RIGHTMOST_BUTTON_PRESSED) {
656         switch (WINCONSOLE.numButtons) {
657         case 1:
658             result |= BUTTON1_PRESSED;
659             break;
660         case 2:
661             result |= BUTTON2_PRESSED;
662             break;
663         case 3:
664             result |= BUTTON3_PRESSED;
665             break;
666         case 4:
667             result |= BUTTON4_PRESSED;
668             break;
669         }
670     }
671
672     return result;
673 }
674
675 #define AdjustY() (WINCONSOLE.buffered ? 0 : (int) WINCONSOLE.SBI.srWindow.Top)
676
677 static bool
678 handle_mouse(SCREEN *sp, MOUSE_EVENT_RECORD mer)
679 {
680     MEVENT work;
681     bool result = FALSE;
682
683     assert(sp);
684
685     sp->_drv_mouse_old_buttons = sp->_drv_mouse_new_buttons;
686     sp->_drv_mouse_new_buttons = mer.dwButtonState & BUTTON_MASK;
687
688     /*
689      * We're only interested if the button is pressed or released.
690      * FIXME: implement continuous event-tracking.
691      */
692     if (sp->_drv_mouse_new_buttons != sp->_drv_mouse_old_buttons) {
693         memset(&work, 0, sizeof(work));
694
695         if (sp->_drv_mouse_new_buttons) {
696             work.bstate |=
697                 (mmask_t) decode_mouse(sp,
698                                        sp->_drv_mouse_new_buttons);
699         } else {
700             /* cf: BUTTON_PRESSED, BUTTON_RELEASED */
701             work.bstate |=
702                 (mmask_t) (decode_mouse(sp,
703                                         sp->_drv_mouse_old_buttons)
704                            >> 1);
705             result = TRUE;
706         }
707
708         work.x = mer.dwMousePosition.X;
709         work.y = mer.dwMousePosition.Y - AdjustY();
710
711         sp->_drv_mouse_fifo[sp->_drv_mouse_tail] = work;
712         sp->_drv_mouse_tail += 1;
713     }
714     return result;
715 }
716
717 static int
718 rkeycompare(const void *el1, const void *el2)
719 {
720     WORD key1 = (LOWORD((*((const LONG *) el1)))) & 0x7fff;
721     WORD key2 = (LOWORD((*((const LONG *) el2)))) & 0x7fff;
722
723     return ((key1 < key2) ? -1 : ((key1 == key2) ? 0 : 1));
724 }
725
726
727 static int
728 keycompare(const void *el1, const void *el2)
729 {
730     WORD key1 = HIWORD((*((const LONG *) el1)));
731     WORD key2 = HIWORD((*((const LONG *) el2)));
732
733     return ((key1 < key2) ? -1 : ((key1 == key2) ? 0 : 1));
734 }
735
736 static int
737 MapKey(WORD vKey)
738 {
739     int code = -1;
740
741     if (!WINCONSOLE.isTermInfoConsole) {
742         WORD nKey = 0;
743         void *res;
744         LONG key = GenMap(vKey, 0);
745
746         res = bsearch(&key,
747                       WINCONSOLE.map,
748                       (size_t) (N_INI + FKEYS),
749                       sizeof(keylist[0]),
750                       keycompare);
751         if (res) {
752             key = *((LONG *) res);
753             nKey = LOWORD(key);
754             code = (int) (nKey & 0x7fff);
755             if (nKey & 0x8000)
756                 code = -code;
757         }
758     }
759     return code;
760 }
761
762 static int
763 AnsiKey(WORD vKey)
764 {
765     int code = -1;
766
767     if (!WINCONSOLE.isTermInfoConsole) {
768         WORD nKey = 0;
769         void *res;
770         LONG key = GenMap(vKey, 0);
771
772         res = bsearch(&key,
773                       WINCONSOLE.ansi_map,
774                       (size_t) (N_INI + FKEYS),
775                       sizeof(keylist[0]),
776                       keycompare);
777         if (res) {
778             key = *((LONG *) res);
779             nKey = LOWORD(key);
780             code = (int) (nKey & 0x7fff);
781             if (nKey & 0x8000)
782                 code = -code;
783         }
784     }
785     return code;
786 }
787
788 NCURSES_EXPORT(int)
789 _nc_console_keyok(int keycode,int flag)
790 {
791     int code = ERR;
792     WORD nKey;
793     WORD vKey;
794     void *res;
795     LONG key = GenMap(0, (WORD) keycode);
796
797     T((T_CALLED("lib_win32con::_nc_console_keyok(%d, %d)"), keycode, flag));
798
799     res = bsearch(&key,
800                   WINCONSOLE.rmap,
801                   (size_t) (N_INI + FKEYS),
802                   sizeof(keylist[0]),
803                   rkeycompare);
804     if (res) {
805         key = *((LONG *) res);
806         vKey = HIWORD(key);
807         nKey = (LOWORD(key)) & 0x7fff;
808         if (!flag)
809             nKey |= 0x8000;
810         *(LONG *) res = GenMap(vKey, nKey);
811     }
812     returnCode(code);
813 }
814
815 NCURSES_EXPORT(bool)
816 _nc_console_keyExist(int keycode)
817 {
818     WORD nKey;
819     void *res;
820     bool found = FALSE;
821     LONG key = GenMap(0, (WORD) keycode);
822
823     T((T_CALLED("lib_win32con::_nc_console_keyExist(%d)"), keycode));
824     res = bsearch(&key,
825                   WINCONSOLE.rmap,
826                   (size_t) (N_INI + FKEYS),
827                   sizeof(keylist[0]),
828                   rkeycompare);
829     if (res) {
830         key = *((LONG *) res);
831         nKey = LOWORD(key);
832         if (!(nKey & 0x8000))
833             found = TRUE;
834     }
835     returnCode(found);
836 }
837
838 NCURSES_EXPORT(int)
839 _nc_console_twait(
840     SCREEN *sp,
841     HANDLE hdl,
842     int mode,
843     int milliseconds,
844     int *timeleft
845     EVENTLIST_2nd(_nc_eventlist * evl))
846 {
847     INPUT_RECORD inp_rec;
848     BOOL b;
849     DWORD nRead = 0, rc = (DWORD) (-1);
850     int code = 0;
851     FILETIME fstart;
852     FILETIME fend;
853     int diff;
854     bool isNoDelay = (milliseconds == 0);
855
856 #ifdef NCURSES_WGETCH_EVENTS
857     (void) evl;                 /* TODO: implement wgetch-events */
858 #endif
859
860 #define IGNORE_CTRL_KEYS (SHIFT_PRESSED|LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED| \
861                           LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)
862 #define CONSUME() ReadConsoleInput(hdl,&inp_rec,1,&nRead)
863
864     assert(sp);
865
866     TR(TRACE_IEVENT, ("start twait: hdl=%p, %d milliseconds, mode: %d",
867                       hdl, milliseconds, mode));
868
869     if (milliseconds < 0)
870         milliseconds = INFINITY;
871
872     memset(&inp_rec, 0, sizeof(inp_rec));
873
874     while (true) {
875         if (!isNoDelay) {
876             GetSystemTimeAsFileTime(&fstart);
877             rc = WaitForSingleObject(hdl, (DWORD) milliseconds);
878             GetSystemTimeAsFileTime(&fend);
879             diff = (int) tdiff(fstart, fend);
880             milliseconds = Adjust(milliseconds, diff);
881             if (milliseconds< 0)
882                 break;
883         }
884
885         if (isNoDelay || (rc == WAIT_OBJECT_0)) {
886             if (mode) {
887                 nRead = 0;
888                 b = GetNumberOfConsoleInputEvents(hdl, &nRead);
889                 if (!b) {
890                     T(("twait:err GetNumberOfConsoleInputEvents"));
891                 }
892                 if (isNoDelay && b) {
893                     T(("twait: Events Available: %ld", nRead));
894                     if (nRead==0) {
895                         code=0;
896                         goto end;
897                     } else {
898                         DWORD n = 0;
899                         INPUT_RECORD* pInpRec =
900                             TypeAlloca(INPUT_RECORD,nRead);
901                         if (pInpRec != NULL) {
902                             DWORD i;
903                             BOOL f;
904                             memset(pInpRec,0,sizeof(INPUT_RECORD)*nRead);
905                             f = PeekConsoleInput(hdl, pInpRec, nRead, &n);
906                             if (f) {
907                                 for(i=0;i < n;i++) {
908                                     if (pInpRec[i].EventType==KEY_EVENT) {
909                                         if(pInpRec[i].Event.KeyEvent.bKeyDown) {
910                                           DWORD ctrlMask =
911                                               (pInpRec[i].Event.KeyEvent.dwControlKeyState & \
912                                                IGNORE_CTRL_KEYS);
913                                           if (!ctrlMask) {
914                                               code = TW_INPUT;
915                                               goto end;
916                                           }
917                                         }
918                                     }
919                                 }
920                             } else {
921                                 T(("twait:err PeekConsoleInput"));
922                             }
923                             code = 0;
924                             goto end;
925                         } else {
926                             T(("twait:err could not alloca input records"));
927                         }
928                     }
929                 }
930                 if (b && nRead > 0) {
931                     b = PeekConsoleInput(hdl, &inp_rec, 1, &nRead);
932                     if (!b) {
933                         T(("twait:err PeekConsoleInput"));
934                     }
935                     if (b && nRead > 0) {
936                         switch (inp_rec.EventType) {
937                         case KEY_EVENT:
938                             if (mode & TW_INPUT) {
939                                 WORD vk =
940                                     inp_rec.Event.KeyEvent.wVirtualKeyCode;
941                                 char ch =
942                                     inp_rec.Event.KeyEvent.uChar.AsciiChar;
943                                 T(("twait:event KEY_EVENT"));
944                                 T(("twait vk=%d, ch=%d, keydown=%d",
945                                    vk,ch,inp_rec.Event.KeyEvent.bKeyDown));
946                                 if (inp_rec.Event.KeyEvent.bKeyDown) {
947                                     T(("twait:event KeyDown"));
948                                     if (!WINCONSOLE.isTermInfoConsole &&
949                                         (0 == ch)) {
950                                         int nKey = MapKey(vk);
951                                         if (nKey < 0) {
952                                             CONSUME();
953                                             continue;
954                                         }
955                                     }
956                                     code = TW_INPUT;
957                                     goto end;
958                                 } else {
959                                     CONSUME();
960                                 }
961                             }
962                             continue;
963                         case MOUSE_EVENT:
964                             T(("twait:event MOUSE_EVENT"));
965                             if (decode_mouse(sp,
966                                              (inp_rec.Event.MouseEvent.dwButtonState
967                                               & BUTTON_MASK)) == 0) {
968                                 CONSUME();
969                             } else if (mode & TW_MOUSE) {
970                                 code = TW_MOUSE;
971                                 goto end;
972                             }
973                             continue;
974                             /* e.g., FOCUS_EVENT */
975                         default:
976                             T(("twait:event Tyoe %d",inp_rec.EventType));
977                             CONSUME();
978                             _nc_console_selectActiveHandle();
979                             continue;
980                         }
981                     }
982                 }
983             }
984             continue;
985         } else {
986             if (rc != WAIT_TIMEOUT) {
987                 code = -1;
988                 break;
989             } else {
990                 code = 0;
991                 break;
992             }
993         }
994     }
995 end:
996
997     TR(TRACE_IEVENT, ("end twait: returned %d (%d), remaining time %d msec",
998                       code, GetLastError(), milliseconds));
999
1000     if (timeleft)
1001         *timeleft = milliseconds;
1002
1003     return code;
1004 }
1005
1006 NCURSES_EXPORT(int)
1007 _nc_console_testmouse(
1008                       SCREEN *sp,
1009                       HANDLE hdl,
1010                       int delay
1011                       EVENTLIST_2nd(_nc_eventlist * evl))
1012 {
1013     int rc = 0;
1014
1015     assert(sp);
1016
1017     if (sp->_drv_mouse_head < sp->_drv_mouse_tail) {
1018         rc = TW_MOUSE;
1019     } else {
1020         rc = _nc_console_twait(sp,
1021                                hdl,
1022                                TWAIT_MASK,
1023                                delay,
1024                                (int *) 0
1025                                EVENTLIST_2nd(evl));
1026     }
1027     return rc;
1028 }
1029
1030 NCURSES_EXPORT(int)
1031 _nc_console_read(
1032                  SCREEN *sp,
1033                  HANDLE hdl,
1034                  int *buf)
1035 {
1036     int rc = -1;
1037     INPUT_RECORD inp_rec;
1038     BOOL b;
1039     DWORD nRead;
1040     WORD vk;
1041
1042     assert(sp);
1043     assert(buf);
1044
1045     memset(&inp_rec, 0, sizeof(inp_rec));
1046
1047     T((T_CALLED("lib_win32con::_nc_console_read(%p)"), sp));
1048
1049     while ((b = ReadConsoleInput(hdl, &inp_rec, 1, &nRead))) {
1050         if (b && nRead > 0) {
1051             if (rc < 0)
1052                 rc = 0;
1053             rc = rc + (int) nRead;
1054             if (inp_rec.EventType == KEY_EVENT) {
1055                 if (!inp_rec.Event.KeyEvent.bKeyDown)
1056                     continue;
1057                 *buf = (int) inp_rec.Event.KeyEvent.uChar.AsciiChar;
1058                 vk = inp_rec.Event.KeyEvent.wVirtualKeyCode;
1059                 /*
1060                  * There are 24 virtual function-keys, and typically
1061                  * 12 function-keys on a keyboard.  Use the shift-modifier
1062                  * to provide the remaining 12 keys.
1063                  */
1064                 if (vk >= VK_F1 && vk <= VK_F12) {
1065                     if (inp_rec.Event.KeyEvent.dwControlKeyState &
1066                         SHIFT_PRESSED) {
1067                         vk = (WORD) (vk + 12);
1068                     }
1069                 }
1070                 if (*buf == 0) {
1071                     int key = MapKey(vk);
1072                     if (key < 0)
1073                         continue;
1074                     if (sp->_keypad_on) {
1075                         *buf = key;
1076                     } else {
1077                         ungetch('\0');
1078                         *buf = AnsiKey(vk);
1079                     }
1080                 }
1081                 break;
1082             } else if (inp_rec.EventType == MOUSE_EVENT) {
1083                 if (handle_mouse(sp,
1084                                  inp_rec.Event.MouseEvent)) {
1085                     *buf = KEY_MOUSE;
1086                     break;
1087                 }
1088             }
1089             continue;
1090         }
1091     }
1092     returnCode(rc);
1093 }
1094
1095 /*   Our replacement for the systems _isatty to include also
1096      a test for mintty. This is called from the NC_ISATTY macro
1097      defined in curses.priv.h
1098
1099      Return codes:
1100      - 0 : Not a TTY
1101      - 1 : A Windows character device detected by _isatty
1102      - 2 : A future implementation may return 2 for mintty
1103  */
1104 NCURSES_EXPORT(int)
1105 _nc_console_isatty(int fd)
1106 {
1107     int result = 0;
1108     T((T_CALLED("lib_win32con::_nc_console_isatty(%d"),fd));
1109
1110     if (_isatty(fd))
1111         result = 1;
1112 #ifdef _NC_CHECK_MINTTY
1113     else {
1114         if (_nc_console_checkmintty(fd, NULL)) {
1115             result=2;
1116             fprintf(stderr,"ncurses on Windows must run in a Windows console.\n");
1117             fprintf(stderr,"On newer versions of Windows, the calling program should create a PTY-like.\n");
1118             fprintf(stderr,"device using the CreatePseudoConsole Windows API call.\n");
1119             exit(EXIT_FAILURE);
1120         }
1121     }
1122 #endif
1123     returnCode(result);
1124 }
1125
1126 NCURSES_EXPORT(bool)
1127 _nc_console_checkinit(bool initFlag, bool assumeTermInfo)
1128 {
1129     bool res = FALSE;
1130
1131     T((T_CALLED("lib_win32con::_nc_console_checkinit(initFlag=%d, assumeTermInfo=%d)"),
1132        initFlag,assumeTermInfo));
1133
1134     if (!initFlag) {
1135         res = console_initialized;
1136     } else {
1137         /* initialize once, or not at all */
1138         if (!console_initialized) {
1139             int i;
1140             DWORD num_buttons;
1141             WORD a;
1142             BOOL buffered = FALSE;
1143             BOOL b;
1144
1145             START_TRACE();
1146             WINCONSOLE.isTermInfoConsole = assumeTermInfo;
1147
1148             WINCONSOLE.map = (LPDWORD)malloc(sizeof(DWORD)*MAPSIZE);
1149             WINCONSOLE.rmap = (LPDWORD)malloc(sizeof(DWORD)*MAPSIZE);
1150             WINCONSOLE.ansi_map = (LPDWORD)malloc(sizeof(DWORD)*MAPSIZE);
1151
1152             for (i = 0; i < (N_INI + FKEYS); i++) {
1153                 if (i < N_INI) {
1154                     WINCONSOLE.rmap[i] = WINCONSOLE.map[i] =
1155                         (DWORD) keylist[i];
1156                     WINCONSOLE.ansi_map[i] = (DWORD) ansi_keys[i];
1157                 } else {
1158                     WINCONSOLE.rmap[i] = WINCONSOLE.map[i] =
1159                         (DWORD) GenMap((VK_F1 + (i - N_INI)),
1160                                        (KEY_F(1) + (i - N_INI)));
1161                     WINCONSOLE.ansi_map[i] =
1162                         (DWORD) GenMap((VK_F1 + (i - N_INI)),
1163                                        (';' + (i - N_INI)));
1164                 }
1165             }
1166             qsort(WINCONSOLE.ansi_map,
1167                   (size_t) (MAPSIZE),
1168                   sizeof(keylist[0]),
1169                   keycompare);
1170             qsort(WINCONSOLE.map,
1171                   (size_t) (MAPSIZE),
1172                   sizeof(keylist[0]),
1173                   keycompare);
1174             qsort(WINCONSOLE.rmap,
1175                   (size_t) (MAPSIZE),
1176                   sizeof(keylist[0]),
1177                   rkeycompare);
1178
1179             if (GetNumberOfConsoleMouseButtons(&num_buttons)) {
1180                 WINCONSOLE.numButtons = (int) num_buttons;
1181             } else {
1182                 WINCONSOLE.numButtons = 1;
1183             }
1184
1185             a = _nc_console_MapColor(true, COLOR_WHITE) |
1186                 _nc_console_MapColor(false, COLOR_BLACK);
1187             for (i = 0; i < CON_NUMPAIRS; i++)
1188                 WINCONSOLE.pairs[i] = a;
1189
1190             WINCONSOLE.inp = GetStdHandle(STD_INPUT_HANDLE);
1191             WINCONSOLE.out = GetStdHandle(STD_OUTPUT_HANDLE);
1192             WINCONSOLE.hdl = WINCONSOLE.out;
1193
1194             GetConsoleMode(WINCONSOLE.inp,&WINCONSOLE.originalMode.dwFlagIn);
1195             GetConsoleMode(WINCONSOLE.out,&WINCONSOLE.originalMode.dwFlagOut);
1196
1197             if (!WINCONSOLE.isTermInfoConsole) {
1198                 b = AllocConsole();
1199
1200                 if (!b)
1201                     b = AttachConsole(ATTACH_PARENT_PROCESS);
1202
1203                 if (getenv("NCGDB") || getenv("NCURSES_CONSOLE2")) {
1204                     T(("... will not buffer console"));
1205                 } else {
1206                     T(("... creating console buffer"));
1207                     WINCONSOLE.hdl =
1208                         CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
1209                                                   0,
1210                                                   NULL,
1211                                                   CONSOLE_TEXTMODE_BUFFER,
1212                                                   NULL);
1213                     buffered = TRUE;
1214                 }
1215             }
1216
1217             /* We set binary I/O even when using the console
1218                driver to cover the situation, that the
1219                TERM variable is set to #win32con, but actually
1220                Windows supports virtual terminal processing.
1221                So if terminfo functions are used in this setup,
1222                they actually may work.
1223             */
1224             _setmode(fileno(stdin) ,_O_BINARY);
1225             _setmode(fileno(stdout),_O_BINARY);
1226
1227             if (WINCONSOLE.hdl != INVALID_HANDLE_VALUE) {
1228                 WINCONSOLE.buffered = buffered;
1229                 _nc_console_get_SBI();
1230                 WINCONSOLE.save_SBI = WINCONSOLE.SBI;
1231                 if (!buffered) {
1232                     save_original_screen();
1233                     _nc_console_set_scrollback(FALSE, &WINCONSOLE.SBI);
1234                 }
1235                 GetConsoleCursorInfo(WINCONSOLE.hdl, &WINCONSOLE.save_CI);
1236                 T(("... initial cursor is %svisible, %d%%",
1237                    (WINCONSOLE.save_CI.bVisible ? "" : "not-"),
1238                    (int) WINCONSOLE.save_CI.dwSize));
1239             }
1240
1241             WINCONSOLE.initialized = TRUE;
1242             console_initialized = TRUE;
1243         }
1244         res = (WINCONSOLE.hdl != INVALID_HANDLE_VALUE);
1245     }
1246     returnBool(res);
1247 }
1248
1249 #endif // _NC_WINDOWS