Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[PATCH] Win32 keyboard fixed the right way

13 views
Skip to first unread message

Ray Chason

unread,
May 11, 2003, 12:55:25 PM5/11/03
to
The Devteam has offered its fix for bug W341-2, in which non-US keyboards
don't always work. This fix involves settings in the defaults.nh file to
map the keys into Nethack commands.

But I think we'll all agree that this is a Band-Aid, that the user should
not have to worry about mapping his keyboard. Nethack should be able to
use the keyboard as it is.

I looked for a better fix for the forthcoming Spanish Nethack release.
This is the fix that I found for the text-mode Win32 Nethack.

Apply this patch to the original distribution, without the Finnish
keyboard patch. It might still be useful to work in a form of the
Finnish patch, say, for users with German keyboards who could use it to
swap the Y and Z keys and restore the HJKLYUBN keys to their proper
positions.

Some other improvements are:

* The keypad works as expected when answering a prompt, ignoring the
settings of number_pad and the num lock key. This is at least a
partial fix for bug W341-5.

* Non-ASCII characters are rejected when waiting for a command. This
prevents them from being mistaken for Alt-sequences.

A small price must be paid, though. On 95-based versions of Windows,
Alt sequences can no longer be formed with the right Alt key; they must
be formed with the left Alt key. (This is because, if a non-US layout is
selected on a keyboard without an AltGr key, the right Alt key is used
instead; and the program cannot tell the difference between a right Alt
key that means Alt and one that means AltGr.)

Furthermore, if there exists a keyboard layout in which a dead key is
needed to generate an ASCII character, that keyboard layout won't work
with this patch. I know of no such layout.

Share and enjoy.

*** nethack-3.4.1/sys/winnt/nttty.c.old Sun Feb 23 09:43:43 2003
--- nethack-3.4.1/sys/winnt/nttty.c Sat May 10 20:09:53 2003
***************
*** 24,29 ****
--- 24,30 ----
* The following WIN32 Console API routines are used in this file.
*
* CreateFile
+ * FlushConsoleInputBuffer
* GetConsoleScreenBufferInfo
* GetStdHandle
* SetConsoleCursorPosition
***************
*** 31,36 ****
--- 32,38 ----
* SetConsoleCtrlHandler
* PeekConsoleInput
* ReadConsoleInput
+ * ReadConsole
* WriteConsole
*/

***************
*** 82,87 ****
--- 84,91 ----
#define MIDBUTTON FROM_LEFT_2ND_BUTTON_PRESSED
#define MOUSEMASK (LEFTBUTTON | RIGHTBUTTON | MIDBUTTON)

+ static INPUT_RECORD bogus_key;
+
/*
* Called after returning from ! or ^Z
*/
***************
*** 162,167 ****
--- 166,173 ----
csbi.dwSize.X * csbi.dwSize.Y,
newcoord, &ccnt);
}
+ /* Keep the bogus_key off the command line */
+ FlushConsoleInputBuffer(hConIn);
}

extern boolean getreturn_disable; /* from sys/share/pcsys.c */
***************
*** 247,252 ****
--- 253,268 ----
cmode = 0; /* just to have a statement to break on for debugger */
}
get_scr_size();
+
+ /* A bogus key that will be filtered when received, to keep ReadConsole
+ * from blocking */
+ bogus_key.EventType = KEY_EVENT;
+ bogus_key.Event.KeyEvent.bKeyDown = 1;
+ bogus_key.Event.KeyEvent.wRepeatCount = 1;
+ bogus_key.Event.KeyEvent.wVirtualKeyCode = 0;
+ bogus_key.Event.KeyEvent.wVirtualScanCode = 0;
+ bogus_key.Event.KeyEvent.uChar.AsciiChar = 0x80;
+ bogus_key.Event.KeyEvent.dwControlKeyState = 0;
}

void
***************
*** 281,286 ****
--- 297,303 ----

#define PADKEYS (KEYPADHI - KEYPADLO + 1)
#define iskeypad(x) (KEYPADLO <= (x) && (x) <= KEYPADHI)
+ #define isnumkeypad(x) (KEYPADLO <= (x) && (x) <= 0x51 && (x) != 0x4A && (x) != 0x4E)

/*
* Keypad keys are translated to the normal values below.
***************
*** 322,330 ****

#define inmap(x,vk) (((x) > 'A' && (x) < 'Z') || (vk) == 0xBF || (x) == '2')

! static BYTE KeyState[256];
!
int FDECL(process_keystroke, (INPUT_RECORD *ir, boolean *valid, int portdebug));

int process_keystroke(ir, valid, portdebug)
INPUT_RECORD *ir;
--- 339,371 ----

#define inmap(x,vk) (((x) > 'A' && (x) < 'Z') || (vk) == 0xBF || (x) == '2')

! /* Use process_keystroke for key commands, process_keystroke2 for prompts */
int FDECL(process_keystroke, (INPUT_RECORD *ir, boolean *valid, int portdebug));
+ int FDECL(process_keystroke2, (INPUT_RECORD *ir, boolean *valid));
+ static int FDECL(is_altseq, (unsigned long shiftstate));
+
+ static int
+ is_altseq(shiftstate)
+ unsigned long shiftstate;
+ {
+ /* We need to distinguish the Alt keys from the AltGr key.
+ * On NT-based Windows, AltGr signals as right Alt and left Ctrl together;
+ * on 95-based Windows, AltGr signals as right Alt only.
+ * So on NT, we signal Alt if either Alt is pressed and left Ctrl is not,
+ * and on 95, we signal Alt for left Alt only. */
+ switch (shiftstate & (RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED)) {
+ case LEFT_ALT_PRESSED:
+ case LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED:
+ return 1;
+
+ case RIGHT_ALT_PRESSED:
+ case RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED:
+ return (GetVersion() & 0x80000000) == 0;
+
+ default:
+ return 0;
+ }
+ }

int process_keystroke(ir, valid, portdebug)
INPUT_RECORD *ir;
***************
*** 338,343 ****
--- 379,385 ----
unsigned long shiftstate;
int altseq = 0;
const struct pad *kpad;
+ DWORD count;

shiftstate = 0L;
ch = pre_ch = ir->Event.KeyEvent.uChar.AsciiChar;
***************
*** 345,356 ****
vk = ir->Event.KeyEvent.wVirtualKeyCode;
keycode = MapVirtualKey(vk, 2);
shiftstate = ir->Event.KeyEvent.dwControlKeyState;
- KeyState[VK_SHIFT] = (shiftstate & SHIFT_PRESSED) ? 0x81 : 0;
- KeyState[VK_CONTROL] = (shiftstate & (LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED)) ?
- 0x81 : 0;
- KeyState[VK_CAPITAL] = (shiftstate & CAPSLOCK_ON) ? 0x81 : 0;

! if (shiftstate & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) {
if (ch || inmap(keycode,vk)) altseq = 1;
else altseq = -1; /* invalid altseq */
}
--- 387,401 ----
vk = ir->Event.KeyEvent.wVirtualKeyCode;
keycode = MapVirtualKey(vk, 2);
shiftstate = ir->Event.KeyEvent.dwControlKeyState;

! if (scan == 0 && vk == 0) {
! /* It's the bogus_key */
! ReadConsoleInput(hConIn,ir,1,&count);
! *valid = FALSE;
! return 0;
! }
!
! if (is_altseq(shiftstate)) {
if (ch || inmap(keycode,vk)) altseq = 1;
else altseq = -1; /* invalid altseq */
}
***************
*** 372,377 ****
--- 417,423 ----
* left control key was pressed with the keystroke.
*/
if (iskeypad(scan)) {
+ ReadConsoleInput(hConIn,ir,1,&count);
kpad = iflags.num_pad ? numpad : keypad;
if (shiftstate & SHIFT_PRESSED) {
ch = kpad[scan - KEYPADLO].shift;
***************
*** 384,410 ****
}
}
else if (altseq > 0) { /* ALT sequence */
if (vk == 0xBF) ch = M('?');
else ch = M(tolower(keycode));
}
/* Attempt to work better with international keyboards. */
else {
! WORD chr[2];
! k = ToAscii(vk, scan, KeyState, chr, 0);
! if (k <= 2)
! switch(k) {
! case 2: /* two characters */
! ch = (unsigned char)chr[1];
! *valid = TRUE;
! break;
! case 1: /* one character */
! ch = (unsigned char)chr[0];
! *valid = TRUE;
! break;
! case 0: /* no translation */
! default: /* negative */
! *valid = FALSE;
! }
}
if (ch == '\r') ch = '\n';
#ifdef PORT_DEBUG
--- 430,459 ----
}
}
else if (altseq > 0) { /* ALT sequence */
+ ReadConsoleInput(hConIn,ir,1,&count);
if (vk == 0xBF) ch = M('?');
else ch = M(tolower(keycode));
}
+ else if (ch < 32 && !isnumkeypad(scan)) {
+ /* Control code; ReadConsole seems to filter some of these,
+ * including ESC */
+ ReadConsoleInput(hConIn,ir,1,&count);
+ }
/* Attempt to work better with international keyboards. */
else {
! CHAR ch2;
! DWORD written;
! /* The bogus_key guarantees that ReadConsole will return,
! * and does not itself do anything */
! WriteConsoleInput(hConIn, &bogus_key, 1, &written);
! ReadConsole(hConIn,&ch2,1,&count,NULL);
! /* Prevent high characters from being interpreted as alt
! * sequences; also filter the bogus_key */
! if (ch2 & 0x80)
! *valid = FALSE;
! else
! ch = ch2;
! if (ch == 0) *valid = FALSE;
}
if (ch == '\r') ch = '\n';
#ifdef PORT_DEBUG
***************
*** 419,424 ****
--- 468,537 ----
return ch;
}

+ int process_keystroke2(ir, valid)
+ INPUT_RECORD *ir;
+ boolean *valid;
+ {
+ /* Use these values for the numeric keypad */
+ static const char keypad_nums[] = "789-456+1230.";
+
+ unsigned char ch;
+ int vk;
+ unsigned short int scan;
+ unsigned long shiftstate;
+ int altseq;
+ DWORD count;
+
+ ch = ir->Event.KeyEvent.uChar.AsciiChar;
+ vk = ir->Event.KeyEvent.wVirtualKeyCode;
+ scan = ir->Event.KeyEvent.wVirtualScanCode;
+ shiftstate = ir->Event.KeyEvent.dwControlKeyState;
+
+ if (scan == 0 && vk == 0) {
+ /* It's the bogus_key */
+ ReadConsoleInput(hConIn,ir,1,&count);
+ *valid = FALSE;
+ return 0;
+ }
+
+ altseq = is_altseq(shiftstate);
+ if (ch || (iskeypad(scan)) || altseq)
+ *valid = TRUE;
+ /* if (!valid) return 0; */
+ /*
+ * shiftstate can be checked to see if various special
+ * keys were pressed at the same time as the key.
+ * Currently we are using the ALT & SHIFT & CONTROLS.
+ *
+ * RIGHT_ALT_PRESSED, LEFT_ALT_PRESSED,
+ * RIGHT_CTRL_PRESSED, LEFT_CTRL_PRESSED,
+ * SHIFT_PRESSED,NUMLOCK_ON, SCROLLLOCK_ON,
+ * CAPSLOCK_ON, ENHANCED_KEY
+ *
+ * are all valid bit masks to use on shiftstate.
+ * eg. (shiftstate & LEFT_CTRL_PRESSED) is true if the
+ * left control key was pressed with the keystroke.
+ */
+ if (iskeypad(scan) && !altseq) {
+ ReadConsoleInput(hConIn,ir,1,&count);
+ ch = keypad_nums[scan - KEYPADLO];
+ }
+ else if (ch < 32 && !isnumkeypad(scan)) {
+ /* Control code; ReadConsole seems to filter some of these,
+ * including ESC */
+ ReadConsoleInput(hConIn,ir,1,&count);
+ }
+ /* Attempt to work better with international keyboards. */
+ else {
+ CHAR ch2;
+ ReadConsole(hConIn,&ch2,1,&count,NULL);
+ ch = ch2 & 0xFF;
+ if (ch == 0) *valid = FALSE;
+ }
+ if (ch == '\r') ch = '\n';
+ return ch;
+ }
+
int
tgetch()
{
***************
*** 427,435 ****
int ch;
valid = 0;
while (!valid) {
! ReadConsoleInput(hConIn,&ir,1,&count);
if ((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown)
! ch = process_keystroke(&ir, &valid, 0);
}
return ch;
}
--- 540,551 ----
int ch;
valid = 0;
while (!valid) {
! WaitForSingleObject(hConIn, INFINITE);
! PeekConsoleInput(hConIn,&ir,1,&count);
if ((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown)
! ch = process_keystroke2(&ir, &valid);
! else
! ReadConsoleInput(hConIn,&ir,1,&count);
}
return ch;
}
***************
*** 442,456 ****
int keystroke = 0;
int done = 0;
boolean valid = 0;
while (!done)
{
count = 0;
! ReadConsoleInput(hConIn,&ir,1,&count);
if (count > 0) {
if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown) {
keystroke = process_keystroke(&ir, &valid, 0);
if (valid) return keystroke;
! } else if (ir.EventType == MOUSE_EVENT) {
if ((ir.Event.MouseEvent.dwEventFlags == 0) &&
(ir.Event.MouseEvent.dwButtonState & MOUSEMASK)) {
*x = ir.Event.MouseEvent.dwMousePosition.X + 1;
--- 558,577 ----
int keystroke = 0;
int done = 0;
boolean valid = 0;
+ int ch;
+
while (!done)
{
count = 0;
! WaitForSingleObject(hConIn, INFINITE);
! PeekConsoleInput(hConIn,&ir,1,&count);
if (count > 0) {
if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown) {
keystroke = process_keystroke(&ir, &valid, 0);
if (valid) return keystroke;
! } else {
! ReadConsoleInput(hConIn,&ir,1,&count);
! if (ir.EventType == MOUSE_EVENT) {
if ((ir.Event.MouseEvent.dwEventFlags == 0) &&
(ir.Event.MouseEvent.dwButtonState & MOUSEMASK)) {
*x = ir.Event.MouseEvent.dwMousePosition.X + 1;
***************
*** 466,481 ****
#endif
return 0;
}
! }
#if 0
! /* We ignore these types of console events */
! else if (ir.EventType == FOCUS_EVENT) {
! }
! else if (ir.EventType == MENU_EVENT) {
! }
#endif
! } else
! done = 1;
}
/* NOTREACHED */
*mod = 0;
--- 587,603 ----
#endif
return 0;
}
! }
#if 0
! /* We ignore these types of console events */
! else if (ir.EventType == FOCUS_EVENT) {
! }
! else if (ir.EventType == MENU_EVENT) {
! }
#endif
! }
! } else
! done = 1;
}
/* NOTREACHED */
*mod = 0;
***************
*** 505,511 ****
shiftstate = ir.Event.KeyEvent.dwControlKeyState;
vk = ir.Event.KeyEvent.wVirtualKeyCode;
keycode = MapVirtualKey(vk, 2);
! if (shiftstate & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) {
if (ch || inmap(keycode,vk)) altseq = 1;
else altseq = -1; /* invalid altseq */
}
--- 627,633 ----
shiftstate = ir.Event.KeyEvent.dwControlKeyState;
vk = ir.Event.KeyEvent.wVirtualKeyCode;
keycode = MapVirtualKey(vk, 2);
! if (is_altseq(shiftstate)) {
if (ch || inmap(keycode,vk)) altseq = 1;
else altseq = -1; /* invalid altseq */
}
***************
*** 929,937 ****
int ch;
xputs("\n");
while (!valid || ch != 27) {
! ReadConsoleInput(hConIn,&ir,1,&count);
if ((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown)
ch = process_keystroke(&ir, &valid, 1);
}
(void)doredraw();
}
--- 1051,1062 ----
int ch;
xputs("\n");
while (!valid || ch != 27) {
! WaitForSingleObject(hConIn, INFINITE);
! PeekConsoleInput(hConIn,&ir,1,&count);
if ((ir.EventType == KEY_EVENT) && ir.Event.KeyEvent.bKeyDown)
ch = process_keystroke(&ir, &valid, 1);
+ else
+ ReadConsoleInput(hConIn,&ir,1,&count);
}
(void)doredraw();
}


--
--------------===============<[ Ray Chason ]>===============--------------
PGP public key at http://www.smart.net/~rchason/pubkey.asc
Delendae sunt RIAA, MPAA et Windoze

Boudewijn Waijers

unread,
May 12, 2003, 10:26:01 PM5/12/03
to
Jukka Lahtinen, cunningly disguised as juk...@despammed.com wrote:

> But fortunately tilde and grave accent aren't needed in the game, and
> the ; command can be used instead of ^.

IIRC, the ; command only shows the top item of a space. If there's
something on top of the trap, ^ will still work.

--
Boudewijn Waijers (bwaijers at tiscali.nl).

There are 10 types of people in the world:
those who understand binary, and those who don't.

Ray Chason

unread,
May 15, 2003, 1:59:02 AM5/15/03
to
Jukka Lahtinen <juk...@despammed.com> wrote:

>Ray Chason <johnn...@southland.smart.net.SPAMMEN.VERBOTEN> writes:
>
>> Furthermore, if there exists a keyboard layout in which a dead key is
>> needed to generate an ASCII character, that keyboard layout won't work
>> with this patch. I know of no such layout.
>

>For example with the default Finnish keyboard layout, dead key sequences
>are needed to produce tilde(~), caret(^) and grave accent(`).


>But fortunately tilde and grave accent aren't needed in the game, and the
>; command can be used instead of ^.

I realized, after reading this post, that the US International layout
under WinXP also has this property. So I tried this layout, and it
worked, although the full dead-key sequence (caret, space) had to be
typed. Perhaps another reason to integrate the DevTeam's patch.

I tried it with a French keyboard under Win98, setting it up with
"keyb fr" from the DOS prompt because the GUI keyboard setting seems to
have no effect on command line programs. And much to my surprise, it
worked as well! Caret and space got me a trap command.

So my own patch works better than I myself expected. I am so long
accustomed to the opposite outcome, as perhaps most programmers are.

Some technical notes (non-programmers may stop reading here): XP
signals a dead key by setting uAsciiChar in the event structure to zero.
98 sets this to the ASCII code (or perhaps ISO-8859-1 in some cases) of
the dead key that was struck, and that complicates the process of
converting keyboard events to proper characters because the app cannot
reliably distinguish dead keys from the other kind.

0 new messages