1 /* SCCS Id: @(#)nhraykey.c 3.4 $Date: 2003/11/01 23:57:00 $ */
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
173 ProcessKeystroke, (HANDLE hConIn, INPUT_RECORD *ir,
174 boolean *valid, BOOLEAN_P numberpad, int portdebug));
176 static INPUT_RECORD bogus_key;
178 int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
180 char dlltmpname[512];
181 char *tmp = dlltmpname, *tmp2;
182 *(tmp + GetModuleFileName(hInstance, tmp, 511)) = '\0';
183 (void)strcpy(dllname, tmp);
184 tmp2 = strrchr(dllname, '\\');
189 /* A bogus key that will be filtered when received, to keep ReadConsole
191 bogus_key.EventType = KEY_EVENT;
192 bogus_key.Event.KeyEvent.bKeyDown = 1;
193 bogus_key.Event.KeyEvent.wRepeatCount = 1;
194 bogus_key.Event.KeyEvent.wVirtualKeyCode = 0;
195 bogus_key.Event.KeyEvent.wVirtualScanCode = 0;
196 bogus_key.Event.KeyEvent.uChar.AsciiChar = (uchar)0x80;
197 bogus_key.Event.KeyEvent.dwControlKeyState = 0;
202 * Keyboard translation tables.
203 * (Adopted from the MSDOS port)
206 #define KEYPADLO 0x47
207 #define KEYPADHI 0x53
209 #define PADKEYS (KEYPADHI - KEYPADLO + 1)
210 #define iskeypad(x) (KEYPADLO <= (x) && (x) <= KEYPADHI)
211 #define isnumkeypad(x) (KEYPADLO <= (x) && (x) <= 0x51 && (x) != 0x4A && (x) != 0x4E)
214 * Keypad keys are translated to the normal values below.
215 * Shifted keypad keys are translated to the
216 * shift values below.
219 static const struct pad {
220 uchar normal, shift, cntrl;
221 } keypad[PADKEYS] = {
222 {'y', 'Y', C('y')}, /* 7 */
223 {'k', 'K', C('k')}, /* 8 */
224 {'u', 'U', C('u')}, /* 9 */
225 {'m', C('p'), C('p')}, /* - */
226 {'h', 'H', C('h')}, /* 4 */
227 {'g', 'G', 'g'}, /* 5 */
228 {'l', 'L', C('l')}, /* 6 */
229 {'+', 'P', C('p')}, /* + */
230 {'b', 'B', C('b')}, /* 1 */
231 {'j', 'J', C('j')}, /* 2 */
232 {'n', 'N', C('n')}, /* 3 */
233 {'i', 'I', C('i')}, /* Ins */
234 {'.', ':', ':'} /* Del */
235 }, numpad[PADKEYS] = {
236 {'7', M('7'), '7'}, /* 7 */
237 {'8', M('8'), '8'}, /* 8 */
238 {'9', M('9'), '9'}, /* 9 */
239 {'m', C('p'), C('p')}, /* - */
240 {'4', M('4'), '4'}, /* 4 */
241 {'g', 'G', 'g'}, /* 5 */
242 {'6', M('6'), '6'}, /* 6 */
243 {'+', 'P', C('p')}, /* + */
244 {'1', M('1'), '1'}, /* 1 */
245 {'2', M('2'), '2'}, /* 2 */
246 {'3', M('3'), '3'}, /* 3 */
247 {'i', 'I', C('i')}, /* Ins */
248 {'.', ':', ':'} /* Del */
251 #define inmap(x,vk) (((x) > 'A' && (x) < 'Z') || (vk) == 0xBF || (x) == '2')
253 /* Use process_keystroke for key commands, process_keystroke2 for prompts */
254 /* int FDECL(process_keystroke, (INPUT_RECORD *ir, boolean *valid, int portdebug)); */
255 int FDECL(process_keystroke2, (HANDLE,INPUT_RECORD *ir, boolean *valid));
256 static int FDECL(is_altseq, (unsigned long shiftstate));
259 is_altseq(shiftstate)
260 unsigned long shiftstate;
262 /* We need to distinguish the Alt keys from the AltGr key.
263 * On NT-based Windows, AltGr signals as right Alt and left Ctrl together;
264 * on 95-based Windows, AltGr signals as right Alt only.
265 * So on NT, we signal Alt if either Alt is pressed and left Ctrl is not,
266 * and on 95, we signal Alt for left Alt only. */
267 switch (shiftstate & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED)) {
268 case LEFT_ALT_PRESSED:
269 case LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED:
272 case RIGHT_ALT_PRESSED:
273 case RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED:
274 return (GetVersion() & 0x80000000) == 0;
281 int __declspec(dllexport) __stdcall
282 ProcessKeystroke(hConIn, ir, valid, numberpad, portdebug)
289 int metaflags = 0, k = 0;
291 unsigned char ch, pre_ch, mk = 0;
292 unsigned short int scan;
293 unsigned long shiftstate;
295 const struct pad *kpad;
299 ch = pre_ch = ir->Event.KeyEvent.uChar.AsciiChar;
300 scan = ir->Event.KeyEvent.wVirtualScanCode;
301 vk = ir->Event.KeyEvent.wVirtualKeyCode;
302 keycode = MapVirtualKey(vk, 2);
303 shiftstate = ir->Event.KeyEvent.dwControlKeyState;
304 if (scan == 0 && vk == 0) {
305 /* It's the bogus_key */
306 ReadConsoleInput(hConIn,ir,1,&count);
311 if (is_altseq(shiftstate)) {
312 if (ch || inmap(keycode,vk)) altseq = 1;
313 else altseq = -1; /* invalid altseq */
315 if (ch || (iskeypad(scan)) || (altseq > 0))
317 /* if (!valid) return 0; */
319 * shiftstate can be checked to see if various special
320 * keys were pressed at the same time as the key.
321 * Currently we are using the ALT & SHIFT & CONTROLS.
323 * RIGHT_ALT_PRESSED, LEFT_ALT_PRESSED,
324 * RIGHT_CTRL_PRESSED, LEFT_CTRL_PRESSED,
325 * SHIFT_PRESSED,NUMLOCK_ON, SCROLLLOCK_ON,
326 * CAPSLOCK_ON, ENHANCED_KEY
328 * are all valid bit masks to use on shiftstate.
329 * eg. (shiftstate & LEFT_CTRL_PRESSED) is true if the
330 * left control key was pressed with the keystroke.
332 if (iskeypad(scan)) {
333 ReadConsoleInput(hConIn,ir,1,&count);
334 kpad = numberpad ? numpad : keypad;
335 if (shiftstate & SHIFT_PRESSED) {
336 ch = kpad[scan - KEYPADLO].shift;
338 else if (shiftstate & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) {
339 ch = kpad[scan - KEYPADLO].cntrl;
342 ch = kpad[scan - KEYPADLO].normal;
345 else if (altseq > 0) { /* ALT sequence */
346 ReadConsoleInput(hConIn,ir,1,&count);
347 if (vk == 0xBF) ch = M('?');
348 else ch = M(tolower(keycode));
350 else if (ch < 32 && !isnumkeypad(scan)) {
351 /* Control code; ReadConsole seems to filter some of these,
353 ReadConsoleInput(hConIn,ir,1,&count);
355 /* Attempt to work better with international keyboards. */
359 /* The bogus_key guarantees that ReadConsole will return,
360 * and does not itself do anything */
361 WriteConsoleInput(hConIn, &bogus_key, 1, &written);
362 ReadConsole(hConIn,&ch2,1,&count,NULL);
363 /* Prevent high characters from being interpreted as alt
364 * sequences; also filter the bogus_key */
369 if (ch == 0) *valid = FALSE;
371 if (ch == '\r') ch = '\n';
376 "PORTDEBUG: ch=%u, scan=%u, vk=%d, pre=%d, shiftstate=0x%X (ESC to end)\n",
377 ch, scan, vk, pre_ch, shiftstate);
378 fprintf(stdout, "\n%s", buf);
384 int process_keystroke2(hConIn, ir, valid)
389 /* Use these values for the numeric keypad */
390 static const char keypad_nums[] = "789-456+1230.";
394 unsigned short int scan;
395 unsigned long shiftstate;
399 ch = ir->Event.KeyEvent.uChar.AsciiChar;
400 vk = ir->Event.KeyEvent.wVirtualKeyCode;
401 scan = ir->Event.KeyEvent.wVirtualScanCode;
402 shiftstate = ir->Event.KeyEvent.dwControlKeyState;
404 if (scan == 0 && vk == 0) {
405 /* It's the bogus_key */
406 ReadConsoleInput(hConIn,ir,1,&count);
411 altseq = is_altseq(shiftstate);
412 if (ch || (iskeypad(scan)) || altseq)
414 /* if (!valid) return 0; */
416 * shiftstate can be checked to see if various special
417 * keys were pressed at the same time as the key.
418 * Currently we are using the ALT & SHIFT & CONTROLS.
420 * RIGHT_ALT_PRESSED, LEFT_ALT_PRESSED,
421 * RIGHT_CTRL_PRESSED, LEFT_CTRL_PRESSED,
422 * SHIFT_PRESSED,NUMLOCK_ON, SCROLLLOCK_ON,
423 * CAPSLOCK_ON, ENHANCED_KEY
425 * are all valid bit masks to use on shiftstate.
426 * eg. (shiftstate & LEFT_CTRL_PRESSED) is true if the
427 * left control key was pressed with the keystroke.
429 if (iskeypad(scan) && !altseq) {
430 ReadConsoleInput(hConIn,ir,1,&count);
431 ch = keypad_nums[scan - KEYPADLO];
433 else if (ch < 32 && !isnumkeypad(scan)) {
434 /* Control code; ReadConsole seems to filter some of these,
436 ReadConsoleInput(hConIn,ir,1,&count);
438 /* Attempt to work better with international keyboards. */
441 ReadConsole(hConIn,&ch2,1,&count,NULL);
443 if (ch == 0) *valid = FALSE;
445 if (ch == '\r') ch = '\n';
449 int __declspec(dllexport) __stdcall
450 CheckInput(hConIn, ir, count, numpad, mode, mod, cc)
459 boolean valid = 0, done = 0;
462 WaitForSingleObject(hConIn, INFINITE);
463 PeekConsoleInput(hConIn,ir,1,count);
465 if ((ir->EventType == KEY_EVENT) && ir->Event.KeyEvent.bKeyDown) {
466 ch = process_keystroke2(hConIn, ir, &valid);
469 ReadConsoleInput(hConIn,ir,1,count);
473 if (ir->EventType == KEY_EVENT && ir->Event.KeyEvent.bKeyDown) {
474 ch = ProcessKeystroke(hConIn, ir, &valid, numpad,
480 if (valid) return ch;
482 ReadConsoleInput(hConIn,ir,1,count);
483 if (ir->EventType == MOUSE_EVENT) {
484 if ((ir->Event.MouseEvent.dwEventFlags == 0) &&
485 (ir->Event.MouseEvent.dwButtonState & MOUSEMASK)) {
486 cc->x = ir->Event.MouseEvent.dwMousePosition.X + 1;
487 cc->y = ir->Event.MouseEvent.dwMousePosition.Y - 1;
489 if (ir->Event.MouseEvent.dwButtonState & LEFTBUTTON)
491 else if (ir->Event.MouseEvent.dwButtonState & RIGHTBUTTON)
493 #if 0 /* middle button */
494 else if (ir->Event.MouseEvent.dwButtonState & MIDBUTTON)
501 /* We ignore these types of console events */
502 else if (ir->EventType == FOCUS_EVENT) {
504 else if (ir->EventType == MENU_EVENT) {
516 int __declspec(dllexport) __stdcall
521 int done = 0; /* true = "stop searching" */
522 int retval; /* true = "we had a match" */
524 unsigned short int scan;
526 unsigned long shiftstate;
527 int altseq = 0, keycode, vk;
533 PeekConsoleInput(hConIn,ir,1,&count);
535 if (ir->EventType == KEY_EVENT && ir->Event.KeyEvent.bKeyDown) {
536 ch = ir->Event.KeyEvent.uChar.AsciiChar;
537 scan = ir->Event.KeyEvent.wVirtualScanCode;
538 shiftstate = ir->Event.KeyEvent.dwControlKeyState;
539 vk = ir->Event.KeyEvent.wVirtualKeyCode;
540 keycode = MapVirtualKey(vk, 2);
541 if (is_altseq(shiftstate)) {
542 if (ch || inmap(keycode,vk)) altseq = 1;
543 else altseq = -1; /* invalid altseq */
545 if (ch || iskeypad(scan) || altseq) {
546 done = 1; /* Stop looking */
547 retval = 1; /* Found what we sought */
549 /* Strange Key event; let's purge it to avoid trouble */
550 ReadConsoleInput(hConIn,ir,1,&count);
554 else if ((ir->EventType == MOUSE_EVENT &&
555 (ir->Event.MouseEvent.dwButtonState & MOUSEMASK))) {
560 else /* Discard it, it's an insignificant event */
561 ReadConsoleInput(hConIn,ir,1,&count);
562 } else /* There are no events in console event queue */ {
563 done = 1; /* Stop looking */
571 int __declspec(dllexport) __stdcall
576 *buf = where_to_get_source;
580 int __declspec(dllexport) __stdcall
589 int __declspec(dllexport) __stdcall
590 KeyHandlerName(buf, full)
595 if (full) *buf = dllname;
596 else *buf = shortdllname;