public static ConsoleKeyInfo ReadKey(bool intercept)
{
Interop.InputRecord ir;
int numEventsRead = -1;
bool r;
lock (s_readKeySyncObject)
{
if (_cachedInputRecord.eventType == Interop.KEY_EVENT)
{
// We had a previous keystroke with repeated characters.
ir = _cachedInputRecord;
if (_cachedInputRecord.keyEvent.repeatCount == 0)
_cachedInputRecord.eventType = -1;
else
{
_cachedInputRecord.keyEvent.repeatCount--;
}
// We will return one key from this method, so we decrement the
// repeatCount here, leaving the cachedInputRecord in the "queue".
}
else
{ // We did NOT have a previous keystroke with repeated characters:
while (true)
{
r = Interop.Kernel32.ReadConsoleInput(InputHandle, out ir, 1, out numEventsRead);
if (!r || numEventsRead == 0)
{
// This will fail when stdin is redirected from a file or pipe.
// We could theoretically call Console.Read here, but I
// think we might do some things incorrectly then.
throw new InvalidOperationException(SR.InvalidOperation_ConsoleReadKeyOnFile);
}
short keyCode = ir.keyEvent.virtualKeyCode;
// First check for non-keyboard events & discard them. Generally we tap into only KeyDown events and ignore the KeyUp events
// but it is possible that we are dealing with a Alt+NumPad unicode key sequence, the final unicode char is revealed only when
// the Alt key is released (i.e when the sequence is complete). To avoid noise, when the Alt key is down, we should eat up
// any intermediate key strokes (from NumPad) that collectively forms the Unicode character.
if (!IsKeyDownEvent(ir))
{
// REVIEW: Unicode IME input comes through as KeyUp event with no accompanying KeyDown.
if (keyCode != AltVKCode)
continue;
}
char ch = (char)ir.keyEvent.uChar;
// In a Alt+NumPad unicode sequence, when the alt key is released uChar will represent the final unicode character, we need to
// surface this. VirtualKeyCode for this event will be Alt from the Alt-Up key event. This is probably not the right code,
// especially when we don't expose ConsoleKey.Alt, so this will end up being the hex value (0x12). VK_PACKET comes very
// close to being useful and something that we could look into using for this purpose...
if (ch == 0)
{
// Skip mod keys.
if (IsModKey(ir))
continue;
}
// When Alt is down, it is possible that we are in the middle of a Alt+NumPad unicode sequence.
// Escape any intermediate NumPad keys whether NumLock is on or not (notepad behavior)
ConsoleKey key = (ConsoleKey)keyCode;
if (IsAltKeyDown(ir) && ((key >= ConsoleKey.NumPad0 && key <= ConsoleKey.NumPad9)
|| (key == ConsoleKey.Clear) || (key == ConsoleKey.Insert)
|| (key >= ConsoleKey.PageUp && key <= ConsoleKey.DownArrow)))
{
continue;
}
if (ir.keyEvent.repeatCount > 1)
{
ir.keyEvent.repeatCount--;
_cachedInputRecord = ir;
}
break;
}
} // we did NOT have a previous keystroke with repeated characters.
}
ControlKeyState state = (ControlKeyState)ir.keyEvent.controlKeyState;
bool shift = (state & ControlKeyState.ShiftPressed) != 0;
bool alt = (state & (ControlKeyState.LeftAltPressed | ControlKeyState.RightAltPressed)) != 0;
bool control = (state & (ControlKeyState.LeftCtrlPressed | ControlKeyState.RightCtrlPressed)) != 0;
ConsoleKeyInfo info = new ConsoleKeyInfo((char)ir.keyEvent.uChar, (ConsoleKey)ir.keyEvent.virtualKeyCode, shift, alt, control);
if (!intercept)
Console.Write(ir.keyEvent.uChar);
return info;
}