1 /* NetHack 3.6 nhraykey.c $NHDT-Date: 1432512794 2015/05/25 00:13:14 $ $NHDT-Branch: master $:$NHDT-Revision: 1.15 $ */
2 /* Copyright (c) NetHack PC Development Team 2003 */
3 /* NetHack may be freely redistributed. See license for details. */
6 * Keystroke handling contributed by Ray Chason.
7 * The following text was written by Ray Chason.
12 * The console-mode Nethack wants both keyboard and mouse input. The
13 * problem is that the Windows API provides no easy way to get mouse input
14 * and also keyboard input properly translated according to the user's
15 * chosen keyboard layout.
17 * The ReadConsoleInput function returns a stream of keyboard and mouse
18 * events. Nethack is interested in those events that represent a key
19 * pressed, or a click on a mouse button. The keyboard events from
20 * ReadConsoleInput are not translated according to the keyboard layout,
21 * and do not take into account the shift, control, or alt keys.
23 * The PeekConsoleInput function works similarly to ReadConsoleInput,
24 * except that it does not remove an event from the queue and it returns
25 * instead of blocking when the queue is empty.
27 * A program can also use ReadConsole to get a properly translated stream
28 * of characters. Unfortunately, ReadConsole does not return mouse events,
29 * does not distinguish the keypad from the main keyboard, does not return
30 * keys shifted with Alt, and does not even return the ESC key when
33 * We want both the functionality of ReadConsole and the functionality of
34 * ReadConsoleInput. But Microsoft didn't seem to think of that.
37 * The solution, in the original code
38 * ==================================
40 * The original 3.4.1 distribution tries to get proper keyboard translation
41 * by passing keyboard events to the ToAscii function. This works, to some
42 * extent -- it takes the shift key into account, and it processes dead
43 * keys properly. But it doesn't take non-US keyboards into account. It
44 * appears that ToAscii is meant for windowed applications, and does not
45 * have enough information to do its job properly in a console application.
48 * The Finnish keyboard patch
49 * ==========================
51 * This patch adds the "subkeyvalue" option to the defaults.nh file. The
52 * user can then add OPTIONS=sukeyvalue:171/92, for instance, to replace
53 * the 171 character with 92, which is \. This works, once properly
54 * configured, but places too much burden on the user. It also bars the
55 * use of the substituted characters in naming objects or monsters.
58 * The solution presented here
59 * ===========================
61 * The best way I could find to combine the functionality of ReadConsole
62 * with that of ReadConsoleInput is simple in concept. First, call
63 * PeekConsoleInput to get the first event. If it represents a key press,
64 * call ReadConsole to retrieve the key. Otherwise, pop it off the queue
65 * with ReadConsoleInput and, if it's a mouse click, return it as such.
67 * But the Devil, as they say, is in the details. The problem is in
68 * recognizing an event that ReadConsole will return as a key. We don't
69 * want to call ReadConsole unless we know that it will immediately return:
70 * if it blocks, the mouse and the Alt sequences will cease to function
73 * Separating process_keystroke into two functions, one for commands and a
74 * new one, process_keystroke2, for answering prompts, makes the job a lot
75 * easier. process_keystroke2 doesn't have to worry about mouse events or
76 * Alt sequences, and so the consequences are minor if ReadConsole blocks.
77 * process_keystroke, OTOH, never needs to return a non-ASCII character
78 * that was read from ReadConsole; it returns bytes with the high bit set
79 * only in response to an Alt sequence.
81 * So in process_keystroke, before calling ReadConsole, a bogus key event
82 * is pushed on the queue. This event causes ReadConsole to return, even
83 * if there was no other character available. Because the bogus key has
84 * the eighth bit set, it is filtered out. This is not done in
85 * process_keystroke2, because that would render dead keys unusable.
87 * A separate process_keystroke2 can also process the numeric keypad in a
88 * way that makes sense for prompts: just return the corresponding symbol,
89 * and pay no mind to number_pad or the num lock key.
91 * The recognition of Alt sequences is modified, to support the use of
92 * characters generated with the AltGr key. A keystroke is an Alt sequence
93 * if an Alt key is seen that can't be an AltGr (since an AltGr sequence
94 * could be a character, and in some layouts it could even be an ASCII
95 * character). This recognition is different on NT-based and 95-based
98 * * On NT-based Windows, AltGr signals as right Alt and left Ctrl
99 * together. So an Alt sequence is recognized if either Alt key is
100 * pressed and if right Alt and left Ctrl are not both present. This
101 * is true even if the keyboard in use does not have an AltGr key, and
102 * uses right Alt for AltGr.
104 * * On 95-based Windows, with a keyboard that lacks the AltGr key, the
105 * right Alt key is used instead. But it still signals as right Alt,
106 * without left Ctrl. There is no way for the application to know
107 * whether right Alt is Alt or AltGr, and so it is always assumed
108 * to be AltGr. This means that Alt sequences must be formed with
111 * So the patch processes keystrokes as follows:
113 * * If the scan and virtual key codes are both 0, it's the bogus key,
116 * * Keys on the numeric keypad are processed for commands as in the
117 * unpatched Nethack, and for prompts by returning the ASCII
118 * character, even if the num lock is off.
120 * * Alt sequences are processed for commands as in the unpatched
121 * Nethack, and ignored for prompts.
123 * * Control codes are returned as received, because ReadConsole will
124 * not return the ESC key.
126 * * Other key-down events are passed to ReadConsole. The use of
127 * ReadConsole is different for commands than for prompts:
129 * o For commands, the bogus key is pushed onto the queue before
130 * ReadConsole is called. On return, non-ASCII characters are
131 * filtered, so they are not mistaken for Alt sequences; this also
132 * filters the bogus key.
134 * o For prompts, the bogus key is not used, because that would
135 * interfere with dead keys. Eight bit characters may be returned,
136 * and are coded in the configured code page.
139 * Possible improvements
140 * =====================
142 * Some possible improvements remain:
144 * * Integrate the existing Finnish keyboard patch, for use with non-
145 * QWERTY layouts such as the German QWERTZ keyboard or Dvorak.
147 * * Fix the keyboard glitches in the graphical version. Namely, dead
148 * keys don't work, and input comes in as ISO-8859-1 but is displayed
149 * as code page 437 if IBMgraphics is set on startup.
151 * * Transform incoming text to ISO-8859-1, for full compatibility with
152 * the graphical version.
154 * * After pushing the bogus key and calling ReadConsole, check to see
155 * if we got the bogus key; if so, and an Alt is pressed, process the
156 * event as an Alt sequence.
160 static char where_to_get_source[] = "http://www.nethack.org/";
161 static char author[] = "Ray Chason";
165 #include "win32api.h"
167 extern HANDLE hConIn;
168 extern INPUT_RECORD ir;
172 int FDECL(__declspec(dllexport) __stdcall ProcessKeystroke,
173 (HANDLE hConIn, INPUT_RECORD *ir, boolean *valid,
174 BOOLEAN_P numberpad, int portdebug));
176 static INPUT_RECORD bogus_key;
179 DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
181 char dlltmpname[512];
182 char *tmp = dlltmpname, *tmp2;
183 *(tmp + GetModuleFileName(hInstance, tmp, 511)) = '\0';
184 (void) strcpy(dllname, tmp);
185 tmp2 = strrchr(dllname, '\\');
190 /* A bogus key that will be filtered when received, to keep ReadConsole
192 bogus_key.EventType = KEY_EVENT;
193 bogus_key.Event.KeyEvent.bKeyDown = 1;
194 bogus_key.Event.KeyEvent.wRepeatCount = 1;
195 bogus_key.Event.KeyEvent.wVirtualKeyCode = 0;
196 bogus_key.Event.KeyEvent.wVirtualScanCode = 0;
197 bogus_key.Event.KeyEvent.uChar.AsciiChar = (uchar) 0x80;
198 bogus_key.Event.KeyEvent.dwControlKeyState = 0;
203 * Keyboard translation tables.
204 * (Adopted from the MSDOS port)
207 #define KEYPADLO 0x47
208 #define KEYPADHI 0x53
210 #define PADKEYS (KEYPADHI - KEYPADLO + 1)
211 #define iskeypad(x) (KEYPADLO <= (x) && (x) <= KEYPADHI)
212 #define isnumkeypad(x) \
213 (KEYPADLO <= (x) && (x) <= 0x51 && (x) != 0x4A && (x) != 0x4E)
216 * Keypad keys are translated to the normal values below.
217 * Shifted keypad keys are translated to the
218 * shift values below.
221 static const struct pad {
222 uchar normal, shift, cntrl;
225 { 'y', 'Y', C('y') }, /* 7 */
226 { 'k', 'K', C('k') }, /* 8 */
227 { 'u', 'U', C('u') }, /* 9 */
228 { 'm', C('p'), C('p') }, /* - */
229 { 'h', 'H', C('h') }, /* 4 */
230 { 'g', 'G', 'g' }, /* 5 */
231 { 'l', 'L', C('l') }, /* 6 */
232 { '+', 'P', C('p') }, /* + */
233 { 'b', 'B', C('b') }, /* 1 */
234 { 'j', 'J', C('j') }, /* 2 */
235 { 'n', 'N', C('n') }, /* 3 */
236 { 'i', 'I', C('i') }, /* Ins */
237 { '.', ':', ':' } /* Del */
240 { '7', M('7'), '7' }, /* 7 */
241 { '8', M('8'), '8' }, /* 8 */
242 { '9', M('9'), '9' }, /* 9 */
243 { 'm', C('p'), C('p') }, /* - */
244 { '4', M('4'), '4' }, /* 4 */
245 { 'g', 'G', 'g' }, /* 5 */
246 { '6', M('6'), '6' }, /* 6 */
247 { '+', 'P', C('p') }, /* + */
248 { '1', M('1'), '1' }, /* 1 */
249 { '2', M('2'), '2' }, /* 2 */
250 { '3', M('3'), '3' }, /* 3 */
251 { 'i', 'I', C('i') }, /* Ins */
252 { '.', ':', ':' } /* Del */
255 #define inmap(x, vk) (((x) > 'A' && (x) < 'Z') || (vk) == 0xBF || (x) == '2')
257 /* Use process_keystroke for key commands, process_keystroke2 for prompts */
258 /* int FDECL(process_keystroke, (INPUT_RECORD *ir, boolean *valid, int
260 int FDECL(process_keystroke2, (HANDLE, INPUT_RECORD *ir, boolean *valid));
261 static int FDECL(is_altseq, (unsigned long shiftstate));
264 is_altseq(shiftstate)
265 unsigned long shiftstate;
267 /* We need to distinguish the Alt keys from the AltGr key.
268 * On NT-based Windows, AltGr signals as right Alt and left Ctrl together;
269 * on 95-based Windows, AltGr signals as right Alt only.
270 * So on NT, we signal Alt if either Alt is pressed and left Ctrl is not,
271 * and on 95, we signal Alt for left Alt only. */
273 & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED)) {
274 case LEFT_ALT_PRESSED:
275 case LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED:
278 case RIGHT_ALT_PRESSED:
279 case RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED:
280 return (GetVersion() & 0x80000000) == 0;
287 int __declspec(dllexport) __stdcall ProcessKeystroke(hConIn, ir, valid,
288 numberpad, portdebug)
295 int metaflags = 0, k = 0;
297 unsigned char ch, pre_ch, mk = 0;
298 unsigned short int scan;
299 unsigned long shiftstate;
301 const struct pad *kpad;
305 ch = pre_ch = ir->Event.KeyEvent.uChar.AsciiChar;
306 scan = ir->Event.KeyEvent.wVirtualScanCode;
307 vk = ir->Event.KeyEvent.wVirtualKeyCode;
308 keycode = MapVirtualKey(vk, 2);
309 shiftstate = ir->Event.KeyEvent.dwControlKeyState;
310 if (scan == 0 && vk == 0) {
311 /* It's the bogus_key */
312 ReadConsoleInput(hConIn, ir, 1, &count);
317 if (is_altseq(shiftstate)) {
318 if (ch || inmap(keycode, vk))
321 altseq = -1; /* invalid altseq */
323 if (ch || (iskeypad(scan)) || (altseq > 0))
325 /* if (!valid) return 0; */
327 * shiftstate can be checked to see if various special
328 * keys were pressed at the same time as the key.
329 * Currently we are using the ALT & SHIFT & CONTROLS.
331 * RIGHT_ALT_PRESSED, LEFT_ALT_PRESSED,
332 * RIGHT_CTRL_PRESSED, LEFT_CTRL_PRESSED,
333 * SHIFT_PRESSED,NUMLOCK_ON, SCROLLLOCK_ON,
334 * CAPSLOCK_ON, ENHANCED_KEY
336 * are all valid bit masks to use on shiftstate.
337 * eg. (shiftstate & LEFT_CTRL_PRESSED) is true if the
338 * left control key was pressed with the keystroke.
340 if (iskeypad(scan)) {
341 ReadConsoleInput(hConIn, ir, 1, &count);
342 kpad = numberpad ? numpad : keypad;
343 if (shiftstate & SHIFT_PRESSED) {
344 ch = kpad[scan - KEYPADLO].shift;
345 } else if (shiftstate & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {
346 ch = kpad[scan - KEYPADLO].cntrl;
348 ch = kpad[scan - KEYPADLO].normal;
350 } else if (altseq > 0) { /* ALT sequence */
351 ReadConsoleInput(hConIn, ir, 1, &count);
355 ch = M(tolower(keycode));
356 } else if (ch < 32 && !isnumkeypad(scan)) {
357 /* Control code; ReadConsole seems to filter some of these,
359 ReadConsoleInput(hConIn, ir, 1, &count);
361 /* Attempt to work better with international keyboards. */
365 /* The bogus_key guarantees that ReadConsole will return,
366 * and does not itself do anything */
367 WriteConsoleInput(hConIn, &bogus_key, 1, &written);
368 ReadConsole(hConIn, &ch2, 1, &count, NULL);
369 /* Prevent high characters from being interpreted as alt
370 * sequences; also filter the bogus_key */
383 Sprintf(buf, "PORTDEBUG: ch=%u, scan=%u, vk=%d, pre=%d, "
384 "shiftstate=0x%X (ESC to end)\n",
385 ch, scan, vk, pre_ch, shiftstate);
386 fprintf(stdout, "\n%s", buf);
393 process_keystroke2(hConIn, ir, valid)
398 /* Use these values for the numeric keypad */
399 static const char keypad_nums[] = "789-456+1230.";
403 unsigned short int scan;
404 unsigned long shiftstate;
408 ch = ir->Event.KeyEvent.uChar.AsciiChar;
409 vk = ir->Event.KeyEvent.wVirtualKeyCode;
410 scan = ir->Event.KeyEvent.wVirtualScanCode;
411 shiftstate = ir->Event.KeyEvent.dwControlKeyState;
413 if (scan == 0 && vk == 0) {
414 /* It's the bogus_key */
415 ReadConsoleInput(hConIn, ir, 1, &count);
420 altseq = is_altseq(shiftstate);
421 if (ch || (iskeypad(scan)) || altseq)
423 /* if (!valid) return 0; */
425 * shiftstate can be checked to see if various special
426 * keys were pressed at the same time as the key.
427 * Currently we are using the ALT & SHIFT & CONTROLS.
429 * RIGHT_ALT_PRESSED, LEFT_ALT_PRESSED,
430 * RIGHT_CTRL_PRESSED, LEFT_CTRL_PRESSED,
431 * SHIFT_PRESSED,NUMLOCK_ON, SCROLLLOCK_ON,
432 * CAPSLOCK_ON, ENHANCED_KEY
434 * are all valid bit masks to use on shiftstate.
435 * eg. (shiftstate & LEFT_CTRL_PRESSED) is true if the
436 * left control key was pressed with the keystroke.
438 if (iskeypad(scan) && !altseq) {
439 ReadConsoleInput(hConIn, ir, 1, &count);
440 ch = keypad_nums[scan - KEYPADLO];
441 } else if (ch < 32 && !isnumkeypad(scan)) {
442 /* Control code; ReadConsole seems to filter some of these,
444 ReadConsoleInput(hConIn, ir, 1, &count);
446 /* Attempt to work better with international keyboards. */
449 ReadConsole(hConIn, &ch2, 1, &count, NULL);
459 int __declspec(dllexport) __stdcall CheckInput(hConIn, ir, count, numpad,
468 #if defined(SAFERHANGUP)
472 boolean valid = 0, done = 0;
475 dwWait = WaitForSingleObject(hConIn, INFINITE);
476 #if defined(SAFERHANGUP)
477 if (dwWait == WAIT_FAILED)
480 PeekConsoleInput(hConIn, ir, 1, count);
482 if ((ir->EventType == KEY_EVENT) && ir->Event.KeyEvent.bKeyDown) {
483 ch = process_keystroke2(hConIn, ir, &valid);
486 ReadConsoleInput(hConIn, ir, 1, count);
490 if (ir->EventType == KEY_EVENT
491 && ir->Event.KeyEvent.bKeyDown) {
492 ch = ProcessKeystroke(hConIn, ir, &valid, numpad,
501 ReadConsoleInput(hConIn, ir, 1, count);
502 if (ir->EventType == MOUSE_EVENT) {
503 if ((ir->Event.MouseEvent.dwEventFlags == 0)
504 && (ir->Event.MouseEvent.dwButtonState
507 ir->Event.MouseEvent.dwMousePosition.X + 1;
509 ir->Event.MouseEvent.dwMousePosition.Y - 1;
511 if (ir->Event.MouseEvent.dwButtonState
514 else if (ir->Event.MouseEvent.dwButtonState
517 #if 0 /* middle button */
518 else if (ir->Event.MouseEvent.dwButtonState & MIDBUTTON)
525 /* We ignore these types of console events */
526 else if (ir->EventType == FOCUS_EVENT) {
528 else if (ir->EventType == MENU_EVENT) {
540 int __declspec(dllexport) __stdcall NHkbhit(hConIn, ir)
544 int done = 0; /* true = "stop searching" */
545 int retval; /* true = "we had a match" */
547 unsigned short int scan;
549 unsigned long shiftstate;
550 int altseq = 0, keycode, vk;
555 PeekConsoleInput(hConIn, ir, 1, &count);
557 if (ir->EventType == KEY_EVENT && ir->Event.KeyEvent.bKeyDown) {
558 ch = ir->Event.KeyEvent.uChar.AsciiChar;
559 scan = ir->Event.KeyEvent.wVirtualScanCode;
560 shiftstate = ir->Event.KeyEvent.dwControlKeyState;
561 vk = ir->Event.KeyEvent.wVirtualKeyCode;
562 keycode = MapVirtualKey(vk, 2);
563 if (is_altseq(shiftstate)) {
564 if (ch || inmap(keycode, vk))
567 altseq = -1; /* invalid altseq */
569 if (ch || iskeypad(scan) || altseq) {
570 done = 1; /* Stop looking */
571 retval = 1; /* Found what we sought */
573 /* Strange Key event; let's purge it to avoid trouble */
574 ReadConsoleInput(hConIn, ir, 1, &count);
577 } else if ((ir->EventType == MOUSE_EVENT
578 && (ir->Event.MouseEvent.dwButtonState
584 else /* Discard it, it's an insignificant event */
585 ReadConsoleInput(hConIn, ir, 1, &count);
586 } else /* There are no events in console event queue */ {
587 done = 1; /* Stop looking */
594 int __declspec(dllexport) __stdcall SourceWhere(buf)
599 *buf = where_to_get_source;
603 int __declspec(dllexport) __stdcall SourceAuthor(buf)
612 int __declspec(dllexport) __stdcall KeyHandlerName(buf, full)