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