Getting all you can out of a keyboard layout, Part #6

by Michael S. Kaplan, published on 2006/03/31 03:01 -05:00, original URI: http://blogs.msdn.com/b/michkap/archive/2006/03/31/565407.aspx


Previous posts in this series: Parts 0, 1, 2, 3, 4 and 5.

It is funny when I do posts like these and find that although they get a lot of comments, they do get a lot of hits. Of course that could mean that people don't find them helpful so they move on to the next site. :-)

Anyway, if you look at the first six posts, between them all of the "basics" are covered; everything from here on in gets a little molre complicated. Mainly because most of the additional information is more complicated....

We'll start with the small issue I mentioned earlier about using Scan Codes versus using Virtual Keys. The problem is that as a rule there is not a 100% round trip between them, and the strange behavior can happen when you move between VK and SC, not the other way around.

So if I take the following code:

for(KeysEx ke = KeysEx.VK_NUMPAD0; ke <= KeysEx.VK_NUMPAD9; ke++) {
    uint sc02 = MapVirtualKeyEx((uint)ke, 0, hkl);
    uint vk02 = MapVirtualKeyEx(sc02, 1, hkl);
    uint sc03 = MapVirtualKeyEx(vk02, 0, hkl);
    uint vk03 = MapVirtualKeyEx(sc03, 1, hkl);
    uint sc04 = MapVirtualKeyEx(vk03, 0, hkl);
    uint vk04 = MapVirtualKeyEx(sc04, 1, hkl);
    Console.WriteLine("{0} == {1:x2} -> {2:x2} -> {3:x2} -> {4:x2} -> {5:x2} -> {6:x2} -> {7:x2} == {8}",
      ke, ((uint)ke).ToString("x2"), sc02, vk02, sc03, vk03, sc04, vk04,((KeysEx)vk04));
}

then the output will be:

VK_NUMPAD0 == 60 -> 52 -> 2d -> 52 -> 2d -> 52 -> 2d == VK_INSERT
VK_NUMPAD1 == 61 -> 4f -> 23 -> 4f -> 23 -> 4f -> 23 == VK_END
VK_NUMPAD2 == 62 -> 50 -> 28 -> 50 -> 28 -> 50 -> 28 == VK_DOWN
VK_NUMPAD3 == 63 -> 51 -> 22 -> 51 -> 22 -> 51 -> 22 == VK_NEXT
VK_NUMPAD4 == 64 -> 4b -> 25 -> 4b -> 25 -> 4b -> 25 == VK_LEFT
VK_NUMPAD5 == 65 -> 4c -> 0c -> 4c -> 0c -> 4c -> 0c == VK_CLEAR
VK_NUMPAD6 == 66 -> 4d -> 27 -> 4d -> 27 -> 4d -> 27 == VK_RIGHT
VK_NUMPAD7 == 67 -> 47 -> 24 -> 47 -> 24 -> 47 -> 24 == VK_HOME
VK_NUMPAD8 == 68 -> 48 -> 26 -> 48 -> 26 -> 48 -> 26 == VK_UP
VK_NUMPAD9 == 69 -> 49 -> 21 -> 49 -> 21 -> 49 -> 21 == VK_PRIOR

You can see how the journey from VK to SC to VK to SC to VK clearly has some round tripping issues, basically at the point I marked in red, at that first transition.

Now, if you think about how these keys are mapped, you'll see a pattern in what is happening here:

7      8      9        Home  Up    PgUp
4      5      6        Left        Right
1      2      3        End   Down  PgDown
0                      Ins

Suddenly what is happening becomes clear, doesn't it? :-)

What is more, you do not need to have the NUMLOCK key toggled to get the VK_NUMPAD# Virtual Key values to give you numbers either -- they always work.

And to stay consistently inconsistent with the rest of the keyboard, other VK values with the VK_NUMLOCK toggled don'e return numbers.

So, the advantage to using Scan Codes for most of the keyboard is that you are appropriately limiting yourself to keys that are expected to exist. Though to get all of the keys on the keyboard there are as few that you will need to handle via VK values anyway....

So what we will insert into our keyboard code is a little bit of info to get the keys that we cannot get via Scan Code alone (new code in black):

// Scroll through the Scan Code (SC) values and get the valid Virtual Key (VK)
// values in it. Then, store the SC in each valid VK so it can act as both a
// flag that the VK is valid, and it can store the SC value.
for(uint sc = 0x01; sc <= 0x7f; sc++) {
    uint vk = MapVirtualKeyEx(sc, 1, hkl);
    if(vk != 0) {
        rgScOfVk[vk] = sc;
    }
}


// add the special keys that do not get added from the code above
for(KeysEx ke = KeysEx.VK_NUMPAD0; ke <= KeysEx.VK_NUMPAD9; ke++) {
    rgScOfVk[(uint)ke] = MapVirtualKeyEx((uint)ke, 0, hkl);
}
rgScOfVk[(uint)KeysEx.VK_DECIMAL] = MapVirtualKeyEx((uint)KeysEx.VK_DECIMAL, 0, hkl);
rgScOfVk[(uint)KeysEx.VK_DIVIDE] = MapVirtualKeyEx((uint)KeysEx.VK_DIVIDE, 0, hkl);
rgScOfVk[(uint)KeysEx.VK_CANCEL] = MapVirtualKeyEx((uint)KeysEx.VK_CANCEL, 0, hkl);

Now, just in case any of this is making sense, I'll close with some #defines in kbd.h:

#define SCANCODE_NUMPAD_FIRST 0x47
#define SCANCODE_NUMPAD_LAST  0x52

I think we have already established that most of the Scan Codes do not fit in this range (not to mention the fact that 0x47 to 0x52 would be eleven keys, not ten!). At this point I still have no idea what these are for, if not solely to confuse -- I did find it used a few places in the Windows source but clearly they are not meant for most people since the functions that come out of user32.dll do not ever provide them.

Coming up, even weirder stuff....

 

This post brought to you by "6" (U+0036, DIGIT SIX)
A Unicode character that is in the very small family of those whose VK value is the same as it's code point!


# Phylyp on 31 Mar 2006 5:47 AM:

> that could mean that people don't find them helpful so they move on to the next site
If others are like me... they get too engrossed meddling with the code you've provided. I do, until I'm reminded (by self or others) that I'm at work :)

# Mihai on 31 Mar 2006 12:43 PM:

"Of course that could mean that people don't find them helpful so they move on to the next site."

Or it means it is interesting, so ppl just shut up an listen (like you do for a good movie :-)

# Mihai on 31 Mar 2006 1:55 PM:

for(uint sc = 0x01; sc <= 0x7f; sc++) {

Enumerating only to 0x7F seems a bit arbitrary to me.

There are a bunch of scancodes above 0x7f that do not map to VK codes, is true, but are still valid.
They return good info with GetKeyNameText, for instance (ie 0x137 => "Prnt Scrn", 0x138 => "Right Alt").

# Maurits [MSFT] on 1 Apr 2006 2:58 AM:

Actually, 0x47 to 0x52 is twelve keys.

# Maurits [MSFT] on 1 Apr 2006 3:42 AM:

This is a bit of a stretch, but...

It may be that FIRST and LAST don't refer to a range (twelve keys is still too few) but rather to the layout of the number pad.

Check out the layout of the Apple Extended Keyboard (I know, I know, this is really a stretch.  But keep reading until the end of this comment...)

http://www.wasd.k12.pa.us/schools/fairview/Gayle/tn5250%20folder/Mocha%20TN5250%20Help

Notice that the first key in the FIRST row of the number pad ("clear") has scan code 0x47.
Notice that the first key in the LAST row of the number pad ("0") has scan code 0x52.

#define SCANCODE_NUMPAD_FIRST 0x47
#define SCANCODE_NUMPAD_LAST  0x52

# Michael S. Kaplan on 1 Apr 2006 6:24 AM:

I'd be more convinced if it were from the top left to the bottom right? :-)

Just kidding -- this may well be what is meant here....

# jennico on 18 Dec 2007 7:50 PM:

well, the use of the scan codes is quite clear. if you want to make a script, that uses hoteys, working no matter of the kb layout you will have to refer to scan codes. so you can emulate a certain char to any key no matter if it is a cirillic, arabic or western keyboard, even no matter if qwerty, qwertz or azerty. that is the point of globalized programming.  j.


referenced by

2007/07/04 Pimping the numeric keypad

2006/09/14 Why doesn't MSKLC have a numeric keypad?

2006/04/22 Getting all you can out of a keyboard layout, Part #10a

2006/04/13 Getting all you can out of a keyboard layout, Part #9b

2006/04/12 Getting all you can out of a keyboard layout, Part #9a

2006/04/10 Getting all you can out of a keyboard layout, Part #8

2006/04/06 Getting all you can out of a keyboard layout, Part #7

go to newer or older post, or back to index or month or day