Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get the char for a keypress knowing only the ConsoleKey enum and Modifiers?

I want to create a ConsoleKeyInfo instance of the matching closing brace of any open brace typed in a PowerShell session (I'm using PSReadline to do the key handling). For your convenience, here are the properties of all the keys involved

PS> while($true){ [System.Console]::ReadKey($true) }

    KeyChar     Key    Modifiers
    -------     ---    ---------
          [    Oem4            0
          ]    Oem6            0  
          {    Oem4        Shift
          }    Oem6        Shift

In the key handler, I am given the ConsoleKeyInfo for the "chord" that was pressed (and PSReadline does filtering, so I already know I'm receiving only an Oem4 or Shift+Oem4). I want to generate the matching ConsoleKeyInfo so I can send the pair printing to the console.

The ConsoleKeyInfo constructor takes

  • a char
  • a System.ConsoleKey
  • a bool each for Shift, Alt, and Control

I can get the correct ConsoleKey by casting it to an int and moving up two...

PS> [System.ConsoleKey]([int]$key.Key + 2)
Oem6

And I can map from the pressed key's Modifiers by testing it bitwise...

PS> ($key.Modifiers -band [System.ConsoleModifiers]::Shift) -ne 0
False

But, I have no idea how to get the literal char for this console key. How does the console get the char from the keyboard key? Can this only be done with a live console/keyboard?

I'd rather not maintain a map of the key pairs nor split the handlers, one for each "chord", and hardcode the matching key char. :(

like image 540
Anthony Mastrean Avatar asked Dec 20 '22 17:12

Anthony Mastrean


1 Answers

You probably don't need to create a ConsoleKeyInfo just for PSReadline.

Occasionally you may need to pass a ConsoleKeyInfo to a method in PSConsoleReadLine, but most methods in PSConsoleReadLine that accept a ConsoleKeyInfo don't even look at the argument. That's why the parameter is Nullable.

This doesn't actually answer your question though. JaredPar is absolutely correct that in general, it's not possible to convert a ConsoleKey/ConsoleModifiers pair to a char. If we don't care about full generality (and currently PSReadline doesn't), you can use code similar to what PSReadline uses:

internal static char GetCharFromConsoleKey(ConsoleKey key, ConsoleModifiers modifiers)
{
    // default for unprintables and unhandled
    char keyChar = '\u0000';

    // emulate GetKeyboardState bitmap - set high order bit for relevant modifier virtual keys
    var state = new byte[256];
    state[NativeMethods.VK_SHIFT] = (byte)(((modifiers & ConsoleModifiers.Shift) != 0) ? 0x80 : 0);
    state[NativeMethods.VK_CONTROL] = (byte)(((modifiers & ConsoleModifiers.Control) != 0) ? 0x80 : 0);
    state[NativeMethods.VK_ALT] = (byte)(((modifiers & ConsoleModifiers.Alt) != 0) ? 0x80 : 0);

    // a ConsoleKey enum's value is a virtual key code
    uint virtualKey = (uint)key;

    // get corresponding scan code
    uint scanCode = NativeMethods.MapVirtualKey(virtualKey, NativeMethods.MAPVK_VK_TO_VSC);

    // get corresponding character  - maybe be 0, 1 or 2 in length (diacriticals)
    var chars = new char[2];
    int charCount = NativeMethods.ToUnicode(
        virtualKey, scanCode, state, chars, chars.Length, NativeMethods.MENU_IS_INACTIVE);

    // TODO: support diacriticals (charCount == 2)
    if (charCount == 1)
    {
        keyChar = chars[0];
    }

    return keyChar;
}
like image 123
Jason Shirk Avatar answered Jan 05 '23 00:01

Jason Shirk