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