Author Topic: Odd behavior of ReadConsoleInput (Windows 7)  (Read 11717 times)

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Odd behavior of ReadConsoleInput (Windows 7)
« on: August 18, 2014, 10:56:08 PM »
Ran into an strange problem, managed to work around it, but research of various MSDN references and a few hours of googling resulted in no joy. 

The behavior is with key events where a control key is held while pressing an alphanumeric key, the record retrieved via kernel32's ReadConsoleInput shows AsciiChar (or UnicodeChar) set to 1, the virtual key member set to the VK_XXXX value of the ascii key, and the control state member set to the bit for the control key pressed. 

This is decidedly odd to me, and results in a rather hackish handling of what seems to be an unnecessary case.  Has anyone else run across this?  Any idea on why it is done that way?  Did I miss some easy fix that would deliver the more expected result of seeing the actual character representation in AsciiChar (or UnicodeChar) member?


TheCreator

  • Rogueliker
  • ***
  • Posts: 370
  • Karma: +0/-0
    • View Profile
    • Fame
    • Email
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #1 on: August 19, 2014, 08:35:18 AM »
Would you show some code or you expect that we use our supernatural powers?
Fame (Untitled) - my game. Everything is a roguelike.

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #2 on: August 19, 2014, 06:55:44 PM »
MSDN ReadConsoleInput function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx

MSDN INPUT_RECORD struct: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx

MSDN KEY_EVENT_RECORD struct: http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx

D language source snippet using Windows kernel32 api:

Code: [Select]
module main;

import std.stdio;
import core.sys.windows.windows;

void main() {
    auto hCon = GetStdHandle(STD_INPUT_HANDLE);
    FlushConsoleInputBuffer(hCon);
    for(;;) { // in default console mode, ctrl-C will terminate
        INPUT_RECORD inrec;
        DWORD numread;
        while(inrec.EventType != KEY_EVENT) {
            WaitForSingleObject(hCon, INFINITE);
            ReadConsoleInputW(hCon, &inrec, 1, &numread);
        }
        auto keyEvent = inrec.KeyEvent;
        writefln("VK: %x \tChar: %x \tState: %x",
                 keyEvent.wVirtualKeyCode,
                 keyEvent.UnicodeChar,
                 keyEvent.dwControlKeyState);
    }
}

Observe behavior when pressing an alphanumeric key while holding down a control key.  Especially observe the difference between holding down an Alt key vs a Control key and pressing an alphanumeric key.  (At least on Windows 7, unsure of behavior on other windows versions or under linux with Wine).

I vaguely recall seeing something about this years ago in some windows dev article but can't find a link.  The behavior exhibited is afaik undocumented.  You can, of course, work around the behavior but it requires some odd corner case handling.

UPDATE: found a partial explanation of what is presumably going on under the hood inside Microsoft's implementation of ReadConsoleInput - http://www.ngedit.com/a_wm_keydown_wm_char.html.  I'd really like to see the results of testing the above snippet in linux under wine, any volunteers?
« Last Edit: August 19, 2014, 08:12:09 PM by Omnivore »

TheCreator

  • Rogueliker
  • ***
  • Posts: 370
  • Karma: +0/-0
    • View Profile
    • Fame
    • Email
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #3 on: August 20, 2014, 06:33:06 AM »
I don't own a D compiler, but I could test a C equivalent on Win 7 and Win XP. One question though: does D initialize structures automatically? I'm asking because in the snippet you seem to be reading an uninitialized value (inrec.EventType) in the loop.
Fame (Untitled) - my game. Everything is a roguelike.

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #4 on: August 20, 2014, 06:50:04 AM »
Yes, D initializes basic types inside structures.  The EventType member is initialized to 0, which is not equal to KEY_EVENT.  In C you'll probably want to initialize that member of the struct.  Also the UnicodeChar member is actually part of an anonymous union in the D version.  The C equivalent of that same structure has a named union and that will need to be dereferenced.  Other than that, no immediate differences jump out, D shares quite a bit with C.

I appreciate your interest and look forward to hearing the results, thanks :)

Cfyz

  • Rogueliker
  • ***
  • Posts: 194
  • Karma: +0/-0
    • View Profile
    • Email
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #5 on: August 20, 2014, 10:57:44 AM »
I've tried this snippet under wine 1.6 in ubuntu 14.10. Using MinGW and VC++2012 (no differences between these two). First of all, the program is not working because of WaitForSingleObject call which hangs (waits indefinitely). So there was no output at all and I had to add some before that call to see if program even running. After getting rid of WaitForSingleObject, results are (in VK-Char-State format, hexadecimal):
Code: [Select]
Combination  | Wine      | Windows
-------------+-----------+---------
A            |  41-61-00 | 41-61-00
             |  41-61-00 | 41-61-00
-------------+-----------+---------
Ctrl+A       |  11-00-08 | 11-00-08
             |  00-01-08 | 41-01-08
             |  00-01-08 | 41-01-08
             |  11-00-00 | 11-00-00
-------------+-----------+---------
Shift+A      |  10-00-10 | 10-00-10
             | 141-41-10 | 41-41-10
             | 141-41-10 | 41-41-10
             |  10-00-00 | 10-00-00
-------------+-----------+---------
Ctrl+Shift+A |  11-00-08 | 11-00-08
             |  00-01-08 | 10-00-18
             |  00-01-08 | 41-01-18
             |  11-00-00 | 41-01-18
             |           | 10-00-08
             |           | 11-00-00
-------------+-----------+---------
Alt+A        |  1B-1B-00 | 12-00-02
             |  1B-1B-00 | 41-61-02
             |  41-61-00 | 41-00-02
             |  41-61-00 | 12-00-00
Dunno what all these numbers are good for but once you started collecting data it's hard to stop.

Wine vs Windows aside, I'm curious about two other things.

1. What did you expect from ReadConsoleInput? Console means kind of terminal emulation and obviously some of input will be handled by that terminal program. You do not expect to be able to receive Ctrl+C, do you? Same with Ctrl+D, Ctrl+Z and other special combinations. I believe it is not rational to rely on any Ctrl+<?> key combinations because most users will expect them to be handled predictably by a terminal emulator, not the application running through it. Same with Alt, but this key is usually reserved for a desktop environment (system menus, window modes, etc.). Things are different for windowed applications because they are generally expected to handle input in a more complex way.

2. Why bother with these console input methods? Windows' console support is abysmal even on native platform and I do not believe people motivated to fully implement that in Wine. If you are already ready to go unportable way, why not discard that cmd.exe and use regular windowed API? Despite what written in that WM_KEYDOWN/WM_CHAR article, it is not that bad (or, more like it is equally bad everywhere) and after ruling out some extreme cases it does function consistently. Or event use dedicated output library like libtcod or BearLibTerminal (I couldn't miss this opportunity, could I).

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #6 on: August 20, 2014, 01:06:01 PM »
Wine vs Windows aside, I'm curious about two other things.

1. What did you expect from ReadConsoleInput? Console means kind of terminal emulation and obviously some of input will be handled by that terminal program. You do not expect to be able to receive Ctrl+C, do you? Same with Ctrl+D, Ctrl+Z and other special combinations. I believe it is not rational to rely on any Ctrl+<?> key combinations because most users will expect them to be handled predictably by a terminal emulator, not the application running through it. Same with Alt, but this key is usually reserved for a desktop environment (system menus, window modes, etc.). Things are different for windowed applications because they are generally expected to handle input in a more complex way.

2. Why bother with these console input methods? Windows' console support is abysmal even on native platform and I do not believe people motivated to fully implement that in Wine. If you are already ready to go unportable way, why not discard that cmd.exe and use regular windowed API? Despite what written in that WM_KEYDOWN/WM_CHAR article, it is not that bad (or, more like it is equally bad everywhere) and after ruling out some extreme cases it does function consistently. Or event use dedicated output library like libtcod or BearLibTerminal (I couldn't miss this opportunity, could I).
First, thanks for testing.  I had suspicions about Wine given some problems noted elsewhere.  Did you use "wine start testprog" or "wineconsole testprog" or something else?  Just wondering because of behavior noted here: http://gnuplot.10905.n7.nabble.com/gnuplot-exe-in-wine-td13162.html along with the infinite hang of WaitForSingleObject.

1) ReadConsoleInput is actually a nice API, despite the weird handling of control keys.  It is aimed at the Windows console, not terminal emulators.  In Windows you can set the console mode to disable default CTRL-C handling.  Many pre-Windows DOS programs handled control and alt keys for various purposes in text user interfaces.  The biggest drawback is the lack of square mono-spaced fonts.

2) I used to write code for CP/M and DOS back in the day so the Windows console is overall a familiar interface for me.  Using D, I have a full fledged console i/o interface similar to termbox in about 300 loc.  Simpler and easier than making a wrapper for PDCurses or getting a ncurses lib compiled properly on Windows for use with the published D wrapper. 

There aren't any mature GUI frameworks for the current version of D yet, very few under current development, and I really don't feel like writing GUI code direct to the Windows API - been there, done that and have the scars!  However, I am keeping the interface to the UI relatively clean, simple, and well-defined so that I can replace it later with something better.

The alternative libraries don't seem to have current D wrappers.  The Derelict package dropped support for libtcod and the old version no longer works with the current version of D.  Wrapping C libraries in D is quite possible but it is highly dependent on the nature of the C header files.  Macros and conditionals are a problem there.  I haven't run BearLibTerminal.h through D's htod conversion tool so not sure how much additional work would be required.  There is a current SDL2 wrapping which might actually be less work.

Every gamer I know has some access to Windows, whether through Wine, a VM, dual booting, or simply native.  So for hobby/prototyping purposes, it isn't the worst choice of platforms.

mushroom patch

  • Rogueliker
  • ***
  • Posts: 554
  • Karma: +0/-0
    • View Profile
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #7 on: August 20, 2014, 01:49:00 PM »
Step 1: Get curses.

Step 2: Make a socket.

Step 3: Send/receive some hand shake stuff.

Step 4: Call newterm() using the appropriate file descriptors and terminal type.

Step 5: Write code for a real terminal application.

Step 6: Write a batch file that starts your game and connects to it with a telnet client.

Cfyz

  • Rogueliker
  • ***
  • Posts: 194
  • Karma: +0/-0
    • View Profile
    • Email
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #8 on: August 20, 2014, 03:32:04 PM »
Quote from: Omnivore
Did you use "wine start testprog" or "wineconsole testprog" or something else?
Whoa. As I rarely use Wine, I did not even know about wineconsole. Indeed, after running the snippet application this way, it behaves more like under native Windows and WaitForSingleObject does not hang now. Though output still differs from that under Windows. Wine 1.4 under Ubuntu 12.04:
Keyrelease (I think it is keyrelease) of the modifier key is doubled so Ctrl+A looks like:
Code: [Select]
11-00-08
41-01-08
41-01-08
11-00-00
11-00-00

Depending on the release order (key or modifier released first), this doubled line moves accordingly but somehow mingles with normal ones, e. g. Shift+A:
Code: [Select]
Shift down, A down, A up, Shift up | but Shift down, A down, Shift up, A up
10-00-10                           | 10-00-10
41-41-10                           | 41-41-10
41-41-10                           | 10-41-00
10-00-00                           | 10-00-00
10-00-00                           | 41-00-00
Where I honestly see only chaos.

Alt+A differs only slightly:
Code: [Select]
12-00-02
41-61-02
41-61-02
12-00-00
12-00-00

Quote from: Omnivore
The alternative libraries don't seem to have current D wrappers.
I specifically designed BearLibTerminal's API to simplify wrapping for another languages. If you are interested in this pseudoterminal output approach, I'll make a wrapper for D. Also, BearLibTerminal works fine under Wine (well, at least where I was able to test it).

Quote from: mushroom patch
Get curses <...>
I do not really understand this pure terminal way. It's harder to set up properly. In its extreme it requires networking for no real game purpose. In the end it just shifts the drawing responsibility from external library to external application. And even while using external tools for output it still severely restricts output functionality. Telnet games are cool in their own way but they are harder both ways, requiring more effort from both programmer and player.

Omnivore

  • Rogueliker
  • ***
  • Posts: 154
  • Karma: +0/-0
    • View Profile
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #9 on: August 20, 2014, 06:40:47 PM »
Whoa. As I rarely use Wine, I did not even know about wineconsole. Indeed, after running the snippet application this way, it behaves more like under native Windows and WaitForSingleObject does not hang now. Though output still differs from that under Windows.
Ah, much better.  I think you're right about the keyrelease, Wine is probably generating an extra WM_Something message being picked up by its implementation of ReadConsoleInput along with WM_CHAR et al.  Given that the Wine devs had to do all this from poorly documented APIs and various observations, bug reports, over many years, I'm impressed by how close it is.

I specifically designed BearLibTerminal's API to simplify wrapping for another languages. If you are interested in this pseudoterminal output approach, I'll make a wrapper for D. Also, BearLibTerminal works fine under Wine (well, at least where I was able to test it).
At the very least I'll give it a try and help test it.  D can always use good libraries, that is its current weak point.  The htod tool is available as a separate download, its not included in the normal D distribution, http://dlang.org/htod.html for download link and documentation.  Once the wrapper is working, you may want to make it compatible with Dub and get it listed at: http://code.dlang.org/.

Quote from: mushroom patch
Get curses <...>
I do not really understand this pure terminal way. It's harder to set up properly. In its extreme it requires networking for no real game purpose. In the end it just shifts the drawing responsibility from external library to external application. And even while using external tools for output it still severely restricts output functionality. Telnet games are cool in their own way but they are harder both ways, requiring more effort from both programmer and player.
The only gain I see is perhaps the loose coupling but I'd rather do that through a solid interface or perhaps using redirected stdio via popen3, treating the game 'engine' as a local, client controlled, server.  The gain would be inherent multicore compatibility and code isolation.  Still too much work for marginal gains as far as I can see.  It'd be easier on the user just to make it a browser based client with the game server being local or remote.

Anyhow, thanks once again for the excellent test data :)

mushroom patch

  • Rogueliker
  • ***
  • Posts: 554
  • Karma: +0/-0
    • View Profile
Re: Odd behavior of ReadConsoleInput (Windows 7)
« Reply #10 on: August 21, 2014, 02:03:06 AM »
Okay, in fact, this is not as straightforward on windows as I thought, because you don't have real BSD sockets. Winsock doesn't give you an actual file descriptor, so it would be somewhat more fiddly than just passing file descriptors to newterm. You'd probably have to link with cygwin to do this in a reasonable way.