1 /**
2  * @file
3  * @brief Functions for windows console mode support.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE_LOCAL)
9 
10 // WINDOWS INCLUDES GO HERE
11 /*
12  * Exclude parts of windows.h that are not needed
13  */
14 #define NOCOMM            /* Comm driver APIs and definitions */
15 #define NOLOGERROR        /* LogError() and related definitions */
16 #define NOPROFILER        /* Profiler APIs */
17 #define NOLFILEIO         /* _l* file I/O routines */
18 #define NOOPENFILE        /* OpenFile and related definitions */
19 #define NORESOURCE        /* Resource management */
20 #define NOATOM            /* Atom management */
21 #define NOLANGUAGE        /* Character test routines */
22 #define NOLSTRING         /* lstr* string management routines */
23 #define NODBCS            /* Double-byte character set routines */
24 #define NOKEYBOARDINFO    /* Keyboard driver routines */
25 #define NOCOLOR           /* COLOR_* colour values */
26 #define NODRAWTEXT        /* DrawText() and related definitions */
27 #define NOSCALABLEFONT    /* Truetype scalable font support */
28 #define NOMETAFILE        /* Metafile support */
29 #define NOSYSTEMPARAMSINFO /* SystemParametersInfo() and SPI_* definitions */
30 #define NODEFERWINDOWPOS  /* DeferWindowPos and related definitions */
31 #define NOKEYSTATES       /* MK_* message key state flags */
32 #define NOWH              /* SetWindowsHook and related WH_* definitions */
33 #define NOCLIPBOARD       /* Clipboard APIs and definitions */
34 #define NOICONS           /* IDI_* icon IDs */
35 #define NOMDI             /* MDI support */
36 #define NOCTLMGR          /* Control management and controls */
37 #define NOHELP            /* Help support */
38 /*
39  * Exclude parts of windows.h that are not needed (Win32)
40  */
41 #define WIN32_LEAN_AND_MEAN
42 #define NONLS             /* All NLS defines and routines */
43 #define NOSERVICE         /* All Service Controller routines, SERVICE_ equates, etc. */
44 #define NOKANJI           /* Kanji support stuff. */
45 #define NOMCX             /* Modem Configuration Extensions */
46 
47 #include <excpt.h>
48 #include <stdarg.h>
49 #include <windows.h>
50 #undef max
51 #undef S_NORMAL
52 
53 // END -- WINDOWS INCLUDES
54 
55 #ifdef TARGET_COMPILER_MINGW
56 #include <signal.h>
57 #endif
58 
59 #include <string.h>
60 #include <stdio.h>
61 
62 #include "cio.h"
63 #include "defines.h"
64 #include "libutil.h"
65 #include "options.h"
66 #include "state.h"
67 #include "unicode.h"
68 #include "version.h"
69 #include "viewgeom.h"
70 #include "view.h"
71 
72 wchar_t oldTitle[80];
73 
74 static HANDLE inbuf = nullptr;
75 static HANDLE outbuf = nullptr;
76 static HANDLE old_outbuf = nullptr;
77 static int current_colour = -1;
78 static bool cursor_is_enabled = false;
79 static CONSOLE_CURSOR_INFO initial_cci;
80 static bool have_initial_cci = false;
81 // dirty line (sx,ex,y)
82 static int chsx = 0, chex = 0, chy = -1;
83 // cursor position (start at 0,0 --> 1,1)
84 static int cx = 0, cy = 0;
85 
86 // and now, for the screen buffer
87 static CHAR_INFO *screen = nullptr;
88 static COORD screensize;
89 #define SCREENINDEX(x,y) ((x)+screensize.X*(y))
90 static unsigned InputCP, OutputCP;
91 static const unsigned PREFERRED_CODEPAGE = 437;
92 
93 // we can do straight translation of DOS colour to win32 console colour.
94 #define WIN32COLOR(col) (WORD)(col)
95 static void writeChar(char32_t c);
96 static void bFlush();
97 
98 // [ds] Unused for portability reasons
99 /*
100 static DWORD crawlColorData[16] =
101 // BGR data, easier to put in registry
102 {
103     0x00000000,  // BLACK
104     0x00ff00cd,  // BLUE
105     0x0046b964,  // GREEN
106     0x00b4b400,  // CYAN
107     0x000085ff,  // RED
108     0x00ee82ee,  // MAGENTA
109     0x005a6fcd,  // BROWN
110     0x00c0c0c0,  // LT GREY
111     0x00808080,  // DK GREY
112     0x00ff8600,  // LT BLUE
113     0x0000ff85,  // LT GREEN
114     0x00ffff00,  // LT CYAN
115     0x000000ff,  // LT RED
116     0x00bf7091,  // LT MAGENTA
117     0x0000ffff,  // YELLOW
118     0x00ffffff   // WHITE
119 };
120  */
121 
122 /** @brief The current foreground @em colour. */
123 static COLOURS FG_COL = LIGHTGREY;
124 
125 /** @brief The current background @em colour. */
126 static COLOURS BG_COL = BLACK;
127 
writeChar(char32_t c)128 void writeChar(char32_t c)
129 {
130     if (c == '\t')
131     {
132         for (int i = 0; i < 8; ++i)
133             writeChar(' ');
134         return;
135     }
136 
137     bool noop = true;
138     PCHAR_INFO pci;
139 
140     // check for CR: noop
141     if (c == 0x0D)
142         return;
143 
144     // check for newline
145     if (c == 0x0A)
146     {
147         // must flush current buffer
148         bFlush();
149 
150         // reposition
151         gotoxy_sys(1, cy+2);
152 
153         return;
154     }
155 
156     // check for upper Unicode which Windows can't handle
157     if (c > 0xFFFF)
158         c = U'\xbf'; //¿
159 
160     int tc = WIN32COLOR(current_colour);
161     pci = &screen[SCREENINDEX(cx,cy)];
162 
163     // is this a no-op?
164     if (pci->Char.UnicodeChar != c)
165         noop = false;
166     else if (pci->Attributes != tc)
167         noop = false;
168 
169     if (!noop)
170     {
171         // write the info and update the dirty area
172         pci->Char.UnicodeChar = c;
173         pci->Attributes = tc;
174 
175         if (chy < 0)
176             chsx = cx;
177         chy  = cy;
178         chex = cx;
179     }
180 
181     // update x position
182     cx += 1;
183     if (cx >= screensize.X)
184         cx = screensize.X - 1;
185 }
186 
bFlush()187 void bFlush()
188 {
189     COORD source;
190     SMALL_RECT target;
191 
192     // see if we have a dirty area
193     if (chy < 0)
194         return;
195 
196     // set up call
197     source.X = chsx;
198     source.Y = chy;
199 
200     target.Left = chsx;
201     target.Top = chy;
202     target.Right = chex;
203     target.Bottom = chy;
204 
205     WriteConsoleOutputW(outbuf, screen, screensize, source, &target);
206 
207     chy = -1;
208 
209     // if cursor is not NOCURSOR, update screen
210     if (cursor_is_enabled)
211     {
212         COORD xy;
213         xy.X = cx;
214         xy.Y = cy;
215         SetConsoleCursorPosition(outbuf, xy);
216     }
217 }
218 
set_mouse_enabled(bool enabled)219 void set_mouse_enabled(bool enabled)
220 {
221     DWORD inmode;
222     if (::GetConsoleMode(inbuf, &inmode))
223     {
224         if (enabled)
225             inmode |= ENABLE_MOUSE_INPUT;
226         else
227             inmode &= ~ENABLE_MOUSE_INPUT;
228 
229         ::SetConsoleMode(inbuf, inmode);
230     }
231 }
232 
_set_string_input(bool value)233 static void _set_string_input(bool value)
234 {
235     DWORD inmodes, outmodes;
236     if (value == TRUE)
237     {
238         inmodes = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT
239                      | ENABLE_PROCESSED_INPUT
240                      | ENABLE_MOUSE_INPUT
241                      | ENABLE_WINDOW_INPUT;
242         outmodes = ENABLE_PROCESSED_OUTPUT;
243     }
244     else
245     {
246         inmodes = ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT;
247         outmodes = 0;
248     }
249 
250     if (SetConsoleMode(inbuf, inmodes) == 0)
251     {
252         fputs("Error initialising console input mode.", stderr);
253         exit(0);
254     }
255 
256     if (SetConsoleMode(outbuf, outmodes) == 0)
257     {
258         fputs("Error initialising console output mode.", stderr);
259         exit(0);
260     }
261 
262     // now flush it
263     FlushConsoleInputBuffer(inbuf);
264 }
265 
266 // Fake the user pressing Esc to break out of wait-for-input loops.
267 // Just one should be enough as we check the seen_hups flag, we just
268 // need to interrupt the syscall.
w32_insert_escape()269 void w32_insert_escape()
270 {
271     INPUT_RECORD esc;
272     esc.EventType = KEY_EVENT;
273     esc.Event.KeyEvent.bKeyDown = TRUE;
274     esc.Event.KeyEvent.wRepeatCount = 1;
275     esc.Event.KeyEvent.wVirtualKeyCode = VK_ESCAPE;
276     // .wVirtualScanCode ?
277     esc.Event.KeyEvent.uChar.UnicodeChar = ESCAPE;
278     esc.Event.KeyEvent.dwControlKeyState = 0;
279     WriteConsoleInputW(inbuf, &esc, 1, nullptr);
280 }
281 
282 #ifdef TARGET_COMPILER_MINGW
install_sighandlers()283 static void install_sighandlers()
284 {
285     signal(SIGINT, SIG_IGN);
286 }
287 #endif
288 
set_w32_screen_size()289 static void set_w32_screen_size()
290 {
291     CONSOLE_SCREEN_BUFFER_INFO cinf;
292     if (::GetConsoleScreenBufferInfo(outbuf, &cinf))
293     {
294         screensize.X = cinf.srWindow.Right - cinf.srWindow.Left + 1;
295         screensize.Y = cinf.srWindow.Bottom - cinf.srWindow.Top + 1;
296     }
297     else
298     {
299         screensize.X = 80;
300         screensize.Y = 25;
301     }
302 
303     if (screen)
304     {
305         delete [] screen;
306         screen = nullptr;
307     }
308 
309     screen = new CHAR_INFO[screensize.X * screensize.Y];
310 
311     COORD topleft;
312     SMALL_RECT used;
313     topleft.X = topleft.Y = 0;
314     ::ReadConsoleOutputW(outbuf, screen, screensize, topleft, &used);
315 }
316 
w32_handle_resize_event()317 static void w32_handle_resize_event()
318 {
319     if (crawl_state.waiting_for_command)
320         handle_terminal_resize();
321     else
322         crawl_state.terminal_resized = true;
323 }
324 
w32_check_screen_resize()325 static void w32_check_screen_resize()
326 {
327     CONSOLE_SCREEN_BUFFER_INFO cinf;
328     if (::GetConsoleScreenBufferInfo(outbuf, &cinf))
329     {
330         if (screensize.X != cinf.srWindow.Right - cinf.srWindow.Left + 1
331             || screensize.Y != cinf.srWindow.Bottom - cinf.srWindow.Top + 1)
332         {
333             w32_handle_resize_event();
334         }
335     }
336 }
337 
w32_term_resizer()338 static void w32_term_resizer()
339 {
340     set_w32_screen_size();
341     crawl_view.init_geometry();
342 }
343 
console_startup()344 void console_startup()
345 {
346     inbuf = GetStdHandle(STD_INPUT_HANDLE);
347     old_outbuf = GetStdHandle(STD_OUTPUT_HANDLE);
348 
349     // Create a new "console screen buffer" so we don't tramp all over
350     // the user's scrollback.
351     outbuf = CreateConsoleScreenBuffer(
352         GENERIC_READ |GENERIC_WRITE,
353         FILE_SHARE_READ | FILE_SHARE_WRITE, // shared
354         nullptr,                    // default security attributes
355         CONSOLE_TEXTMODE_BUFFER, // must be TEXTMODE
356         nullptr);                   // reserved; must be nullptr
357 
358     SetConsoleActiveScreenBuffer(outbuf);
359 
360     if (inbuf == INVALID_HANDLE_VALUE || outbuf == INVALID_HANDLE_VALUE)
361     {
362         fputs("Could not initialise libw32c console support.", stderr);
363         exit(0);
364     }
365 
366     string title = CRAWL " " + string(Version::Long);
367 
368     if (!GetConsoleTitleW(oldTitle, 78))
369         *oldTitle = 0;
370     SetConsoleTitleW(OUTW(title));
371 
372     // Use the initial Windows setting for cursor size if it exists.
373     // TODO: Respect changing cursor size manually while Crawl is running.
374     have_initial_cci = GetConsoleCursorInfo(outbuf, &initial_cci);
375 
376 #ifdef TARGET_COMPILER_MINGW
377     install_sighandlers();
378 #endif
379 
380     // by default, set string input to false:  use char-input only
381     _set_string_input(false);
382 
383     // set up screen size
384     set_w32_screen_size();
385 
386     // initialise text colour
387     textcolour(DARKGREY);
388 
389     cursor_is_enabled = true; // ensure cursor is set regardless of actual state
390     set_cursor_enabled(false);
391 
392     crawl_state.terminal_resize_handler = w32_term_resizer;
393     crawl_state.terminal_resize_check   = w32_check_screen_resize;
394 
395     // JWM, 06/12/2004: Code page setting, as XP does not use ANSI 437 by
396     // default.
397     InputCP = GetConsoleCP();
398     OutputCP = GetConsoleOutputCP();
399 
400     // Don't kill Crawl if we can't set the codepage. Windows 95/98/ME
401     // don't have support for setting the input and output codepage.
402     // I'm also not convinced we need to set the input codepage at all.
403     if (InputCP != PREFERRED_CODEPAGE)
404         SetConsoleCP(PREFERRED_CODEPAGE);
405 
406     if (OutputCP != PREFERRED_CODEPAGE)
407         SetConsoleOutputCP(PREFERRED_CODEPAGE);
408 }
409 
console_shutdown()410 void console_shutdown()
411 {
412     // don't do anything if we were never initted
413     if (inbuf == nullptr && outbuf == nullptr && old_outbuf == nullptr)
414         return;
415 
416     // JWM, 06/12/2004: Code page stuff. If it was the preferred code page, it
417     // doesn't need restoring. Shouldn't be an error and too bad if there is.
418     if (InputCP && InputCP != PREFERRED_CODEPAGE)
419         SetConsoleCP(InputCP);
420 
421     if (OutputCP && OutputCP != PREFERRED_CODEPAGE)
422         SetConsoleOutputCP(OutputCP);
423 
424     // restore console attributes for normal function
425     _set_string_input(true);
426 
427     // set cursor and normal textcolour
428     set_cursor_enabled(true);
429     textcolour(DARKGREY);
430 
431     inbuf = nullptr;
432 
433     delete [] screen;
434     screen = nullptr;
435 
436     // finally, restore title
437     if (*oldTitle)
438         SetConsoleTitleW(oldTitle);
439 
440     // and switch back to the former console buffer
441     if (old_outbuf)
442     {
443         SetConsoleActiveScreenBuffer(old_outbuf);
444         CloseHandle(outbuf);
445         old_outbuf = 0;
446         outbuf = 0;
447     }
448 }
449 
is_cursor_enabled()450 bool is_cursor_enabled()
451 {
452     return cursor_is_enabled;
453 }
454 
set_cursor_enabled(bool curstype)455 void set_cursor_enabled(bool curstype)
456 {
457     CONSOLE_CURSOR_INFO cci;
458 
459     if (curstype == cursor_is_enabled)
460         return;
461 
462     cci.dwSize = have_initial_cci && initial_cci.dwSize ? initial_cci.dwSize
463                                                         : 5;
464 
465     cci.bVisible = curstype ? TRUE : FALSE;
466     cursor_is_enabled = curstype;
467     SetConsoleCursorInfo(outbuf, &cci);
468 
469     // now, if we just changed from NOCURSOR to CURSOR,
470     // actually move screen cursor
471     if (cursor_is_enabled)
472         gotoxy_sys(cx+1, cy+1);
473 }
474 
475 // This will force the cursor down to the next line.
clear_to_end_of_line()476 void clear_to_end_of_line()
477 {
478     const int pos = wherex();
479     const int cols = get_number_of_cols();
480     if (pos <= cols)
481         cprintf("%*s", cols - pos + 1, "");
482 }
483 
clrscr_sys()484 void clrscr_sys()
485 {
486     int x,y;
487     COORD source;
488     SMALL_RECT target;
489 
490     PCHAR_INFO pci = screen;
491 
492     for (x = 0; x < screensize.X; x++)
493         for (y = 0; y < screensize.Y; y++)
494         {
495             pci->Char.UnicodeChar = ' ';
496             pci->Attributes = 0;
497             pci++;
498         }
499 
500     source.X = 0;
501     source.Y = 0;
502     target.Left = 0;
503     target.Top = 0;
504     target.Right = screensize.X - 1;
505     target.Bottom = screensize.Y - 1;
506 
507     WriteConsoleOutputW(outbuf, screen, screensize, source, &target);
508 }
509 
gotoxy_sys(int x,int y)510 void gotoxy_sys(int x, int y)
511 {
512     // always flush on goto
513     bFlush();
514 
515     // bounds check
516     if (x < 1)
517         x = 1;
518     if (x > screensize.X)
519         x = screensize.X;
520     if (y < 1)
521         y = 1;
522     if (y > screensize.Y)
523         y = screensize.Y;
524 
525     // change current cursor
526     cx = x - 1;
527     cy = y - 1;
528 
529     // if cursor is not NOCURSOR, update screen
530     if (cursor_is_enabled)
531     {
532         COORD xy;
533         xy.X = cx;
534         xy.Y = cy;
535         if (SetConsoleCursorPosition(outbuf, xy) == 0)
536             fputs("SetConsoleCursorPosition() failed!", stderr);
537     }
538 }
539 
_dos_reverse_brand(unsigned short colour)540 static unsigned short _dos_reverse_brand(unsigned short colour)
541 {
542     if (Options.dos_use_background_intensity)
543     {
544         // If the console treats the intensity bit on background colours
545         // correctly, we can do a very simple colour invert.
546 
547         // Special casery for shadows.
548         if (colour == BLACK)
549             colour = (DARKGREY << 4);
550         else
551             colour = (colour & 0xF) << 4;
552     }
553     else
554     {
555         // If we're on a console that takes its DOSness very seriously the
556         // background high-intensity bit is actually a blink bit. Blinking is
557         // evil, so we strip the background high-intensity bit. This, sadly,
558         // limits us to 7 background colours.
559 
560         // Strip off high-intensity bit. Special case DARKGREY, since it's the
561         // high-intensity counterpart of black, and we don't want black on
562         // black.
563         //
564         // We *could* set the foreground colour to WHITE if the background
565         // intensity bit is set, but I think we've carried the
566         // angry-fruit-salad theme far enough already.
567 
568         if (colour == DARKGREY)
569             colour |= (LIGHTGREY << 4);
570         else if (colour == BLACK)
571             colour = LIGHTGREY << 4;
572         else
573         {
574             // Zap out any existing background colour, and the high
575             // intensity bit.
576             colour  &= 7;
577 
578             // And swap the foreground colour over to the background
579             // colour, leaving the foreground black.
580             colour <<= 4;
581         }
582     }
583 
584     return colour;
585 }
586 
_dos_hilite_brand(unsigned short colour,unsigned short hilite)587 static unsigned short _dos_hilite_brand(unsigned short colour,
588                                         unsigned short hilite)
589 {
590     if (!hilite)
591         return colour;
592 
593     if (colour == hilite)
594         colour = 0;
595 
596     colour |= (hilite << 4);
597     return colour;
598 }
599 
_dos_brand(unsigned short colour,unsigned brand)600 static unsigned short _dos_brand(unsigned short colour, unsigned brand)
601 {
602     if ((brand & CHATTR_ATTRMASK) == CHATTR_NORMAL)
603         return colour;
604 
605     colour &= 0xFF;
606 
607     if ((brand & CHATTR_ATTRMASK) == CHATTR_HILITE)
608         return _dos_hilite_brand(colour, (brand & CHATTR_COLMASK) >> 8);
609     else
610         return _dos_reverse_brand(colour);
611 }
612 
get_brand(int col)613 static inline unsigned get_brand(int col)
614 {
615     return (col & COLFLAG_FRIENDLY_MONSTER) ? Options.friend_brand :
616            (col & COLFLAG_NEUTRAL_MONSTER)  ? Options.neutral_brand :
617            (col & COLFLAG_ITEM_HEAP)        ? Options.heap_brand :
618            (col & COLFLAG_WILLSTAB)         ? Options.stab_brand :
619            (col & COLFLAG_MAYSTAB)          ? Options.may_stab_brand :
620            (col & COLFLAG_FEATURE_ITEM)     ? Options.feature_item_brand :
621            (col & COLFLAG_TRAP_ITEM)        ? Options.trap_item_brand :
622            (col & COLFLAG_REVERSE)          ? unsigned{CHATTR_REVERSE}
623                                             : unsigned{CHATTR_NORMAL};
624 }
625 
update_text_colours(int brand)626 static void update_text_colours(int brand)
627 {
628     unsigned short branded_bg_fg = _dos_brand(FG_COL, brand);
629     const bool brand_overrides_bg = branded_bg_fg & 0xF0;
630 
631     const short fg = branded_bg_fg & 0x0F;
632     const short bg = brand_overrides_bg ? (branded_bg_fg & 0xF0) >> 4 : BG_COL;
633 
634     const short macro_fg = Options.colour[fg];
635     const short macro_bg = Options.colour[bg];
636 
637     current_colour = (macro_bg << 4) | macro_fg;
638 }
639 
textcolour(int c)640 void textcolour(int c)
641 {
642     FG_COL = static_cast<COLOURS>(c & 0xF);
643     update_text_colours(get_brand(c));
644 }
645 
textbackground(int c)646 void textbackground(int c)
647 {
648     BG_COL = static_cast<COLOURS>(c & 0xF);
649     update_text_colours(get_brand(c));
650 }
651 
cprintf_aux(const char * s)652 static void cprintf_aux(const char *s)
653 {
654     // early out -- not initted yet
655     if (outbuf == nullptr)
656     {
657         printf("%ls", OUTW(s));
658         return;
659     }
660 
661     // loop through string
662     char32_t c;
663     while (int taken = utf8towc(&c, s))
664     {
665         s += taken;
666         writeChar(c);
667     }
668 
669     // flush string
670     bFlush();
671 }
672 
cprintf(const char * format,...)673 void cprintf(const char *format, ...)
674 {
675     va_list argp;
676     char buffer[4096]; // one could hope it's enough
677 
678     va_start(argp, format);
679 
680     vsnprintf(buffer, sizeof(buffer), format, argp);
681     cprintf_aux(buffer);
682 
683     va_end(argp);
684 }
685 
wherex()686 int wherex()
687 {
688     return cx+1;
689 }
690 
wherey()691 int wherey()
692 {
693     return cy+1;
694 }
695 
putwch(char32_t c)696 void putwch(char32_t c)
697 {
698     if (c == 0)
699         c = ' ';
700     writeChar(c);
701 }
702 
703 // translate virtual keys
704 
705 #define VKEY_MAPPINGS 11
706 static int vk_tr[4][VKEY_MAPPINGS] = // virtual key, unmodified, shifted, control
707 {
708     { VK_END, VK_DOWN, VK_NEXT, VK_LEFT, VK_CLEAR, VK_RIGHT,
709       VK_HOME, VK_UP, VK_PRIOR, VK_INSERT, VK_TAB },
710     { CK_END, CK_DOWN, CK_PGDN, CK_LEFT, CK_CLEAR, CK_RIGHT,
711       CK_HOME, CK_UP, CK_PGUP , CK_INSERT, CONTROL('I') },
712     { CK_SHIFT_END, CK_SHIFT_DOWN, CK_SHIFT_PGDN, CK_SHIFT_LEFT, CK_SHIFT_CLEAR, CK_SHIFT_RIGHT,
713       CK_SHIFT_HOME, CK_SHIFT_UP, CK_SHIFT_PGUP, CK_SHIFT_INSERT, CK_SHIFT_TAB },
714     { CK_CTRL_END, CK_CTRL_DOWN, CK_CTRL_PGDN, CK_CTRL_LEFT, CK_CTRL_CLEAR, CK_CTRL_RIGHT,
715       CK_CTRL_HOME, CK_CTRL_UP, CK_CTRL_PGUP, CK_CTRL_INSERT, CK_CTRL_TAB },
716 };
717 
vk_translate(WORD VirtCode,WCHAR c,DWORD cKeys)718 static int vk_translate(WORD VirtCode, WCHAR c, DWORD cKeys)
719 {
720     bool shftDown = false;
721     bool ctrlDown = false;
722     bool altDown  = !!(cKeys & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED));
723 
724     // step 1 - we don't care about shift or control
725     if (VirtCode == VK_SHIFT || VirtCode == VK_CONTROL
726         || VirtCode == VK_MENU || VirtCode == VK_CAPITAL
727         || VirtCode == VK_NUMLOCK)
728     {
729         return 0;
730     }
731 
732     // step 2 - translate the <Esc> key to 0x1b
733     if (VirtCode == VK_ESCAPE)
734         return 0x1b;            // same as it ever was..
735 
736     // step 3 - translate shifted or controlled numeric keypad keys
737     if (cKeys & SHIFT_PRESSED)
738         shftDown = true;
739     if (cKeys & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
740         ctrlDown = true;           // control takes precedence over shift
741 
742     // hack - translate ^P and ^Q since 16 and 17 are taken by CTRL and SHIFT
743     if ((VirtCode == 80 || VirtCode == 81) && ctrlDown)
744         return VirtCode & 0x003f;     // shift back down
745 
746     if (VirtCode == VK_DELETE && !ctrlDown)         // assume keypad '.'
747         return CK_DELETE;
748 
749     // see if we're a vkey
750     int mkey;
751     for (mkey = 0; mkey<VKEY_MAPPINGS; mkey++)
752         if (VirtCode == vk_tr[0][mkey])
753             break;
754 
755     // step 4 - just return the damn key.
756     if (mkey == VKEY_MAPPINGS)
757     {
758         if (c)
759             return c;
760 
761         // ds -- Icky hacks to allow keymaps with funky keys.
762         if (ctrlDown)
763             VirtCode |= 512;
764         if (shftDown)
765             VirtCode |= 1024;
766         if (altDown)
767             VirtCode |= 2048;
768 
769         // ds -- Cheat and returns 256 + VK if the char is zero. This allows us
770         // to use the VK for macros and is on par for evil with the rest of
771         // this function anyway.
772         return VirtCode | 256;
773     }
774 
775     // now translate the key. Dammit. This is !@#$(*& garbage.
776 
777     // control key?
778     if (ctrlDown)
779         return vk_tr[3][mkey];
780 
781     // shifted?
782     if (shftDown)
783         return vk_tr[2][mkey];
784     return vk_tr[1][mkey];
785 }
786 
w32_proc_mouse_event(const MOUSE_EVENT_RECORD & mer)787 static int w32_proc_mouse_event(const MOUSE_EVENT_RECORD &mer)
788 {
789     const coord_def pos(mer.dwMousePosition.X + 1, mer.dwMousePosition.Y + 1);
790     crawl_view.mousep = pos;
791 
792     if (!crawl_state.mouse_enabled)
793         return 0;
794 
795     c_mouse_event cme(pos);
796     if (mer.dwEventFlags & MOUSE_MOVED)
797         return CK_MOUSE_MOVE;
798 
799     if (mer.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED)
800         cme.bstate |= c_mouse_event::BUTTON1;
801     else if (mer.dwButtonState & RIGHTMOST_BUTTON_PRESSED)
802         cme.bstate |= c_mouse_event::BUTTON3;
803 
804     if ((mer.dwEventFlags & MOUSE_WHEELED) && mer.dwButtonState)
805     {
806         if (!(mer.dwButtonState & 0x10000000UL))
807             cme.bstate |= c_mouse_event::BUTTON_SCRL_UP;
808         else
809             cme.bstate |= c_mouse_event::BUTTON_SCRL_DN;
810     }
811 
812     if (cme)
813     {
814         new_mouse_event(cme);
815         return CK_MOUSE_CLICK;
816     }
817 
818     return 0;
819 }
820 
821 
set_getch_returns_resizes(bool rr)822 void set_getch_returns_resizes(bool rr)
823 {
824     UNUSED(rr);
825     // no-op on windows console: see mantis issue #11532
826 }
827 
getch_ck()828 int getch_ck()
829 {
830     INPUT_RECORD ir;
831     DWORD nread;
832     int key = 0;
833     static int repeat_count = 0;
834     static int repeat_key = 0;
835 
836     KEY_EVENT_RECORD *kr;
837 
838     // handle key repeats
839     if (repeat_count > 0)
840     {
841         repeat_count -= 1;
842         return repeat_key;
843     }
844 
845     bool waiting_for_event = true;
846     while (waiting_for_event)
847     {
848         if (crawl_state.seen_hups)
849             return ESCAPE;
850 
851         if (ReadConsoleInputW(inbuf, &ir, 1, &nread) == 0)
852             fputs("Error in ReadConsoleInputW()!", stderr);
853         if (nread > 0)
854         {
855             // ignore if it isn't a keyboard event.
856             switch (ir.EventType)
857             {
858             case KEY_EVENT:
859                 kr = &ir.Event.KeyEvent;
860                 // ignore if it is a 'key up' - we only want 'key down'
861                 if (kr->bKeyDown)
862                 {
863                     key = vk_translate(kr->wVirtualKeyCode,
864                                        kr->uChar.UnicodeChar,
865                                        kr->dwControlKeyState);
866                     if (key != 0)
867                     {
868                         repeat_count = kr->wRepeatCount - 1;
869                         repeat_key = key;
870                         waiting_for_event = false;
871                         break;
872                     }
873                 }
874                 break;
875 
876             case WINDOW_BUFFER_SIZE_EVENT:
877                 w32_handle_resize_event();
878                 break;
879 
880             case MOUSE_EVENT:
881                 if ((key = w32_proc_mouse_event(ir.Event.MouseEvent)))
882                     waiting_for_event = false;
883                 break;
884             }
885         }
886     }
887 
888     return key;
889 }
890 
kbhit()891 bool kbhit()
892 {
893     if (crawl_state.seen_hups)
894         return 1;
895 
896     INPUT_RECORD ir[10];
897     DWORD read_count = 0;
898     PeekConsoleInputW(inbuf, ir, ARRAYSZ(ir), &read_count);
899     if (read_count > 0)
900     {
901         for (unsigned i = 0; i < read_count; ++i)
902             if (ir[i].EventType == KEY_EVENT)
903             {
904                 KEY_EVENT_RECORD *kr;
905                 kr = &(ir[i].Event.KeyEvent);
906 
907                 if (kr->bKeyDown)
908                     return 1;
909             }
910     }
911     return 0;
912 }
913 
delay(unsigned int ms)914 void delay(unsigned int ms)
915 {
916     if (crawl_state.disables[DIS_DELAY])
917         return;
918 
919     Sleep((DWORD)ms);
920 }
921 
puttext(int x1,int y1,const crawl_view_buffer & vbuf)922 void puttext(int x1, int y1, const crawl_view_buffer &vbuf)
923 {
924     const screen_cell_t *cell = vbuf;
925     const coord_def size = vbuf.size();
926     for (int y = 0; y < size.y; ++y)
927     {
928         cgotoxy(x1, y1 + y);
929         for (int x = 0; x < size.x; ++x)
930         {
931             textcolour(cell->colour);
932             putwch(cell->glyph);
933             cell++;
934         }
935     }
936     textcolour(WHITE);
937 }
938 
update_screen()939 void update_screen()
940 {
941     bFlush();
942 }
943 
get_number_of_lines()944 int get_number_of_lines()
945 {
946     return screensize.Y;
947 }
948 
get_number_of_cols()949 int get_number_of_cols()
950 {
951     return screensize.X;
952 }
953 
num_to_lines(int num)954 int num_to_lines(int num)
955 {
956     return num;
957 }
958 
959 #endif /* #if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE_LOCAL) */
960