by Michael S. Kaplan, published on 2006/04/12 12:01 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2006/04/12/575080.aspx
(Previous posts in this series: Parts 0, 1, 2, 3, 4, 5, 6, 7, and 8)
Today, we're going to take a look at the more complex shift states -- like when a keyboard layout takes the Control, Alt, and/or Shift keys on the right side of the keyboard and give yourself up to three additional "shift" keys. This gives you a theoretical total of up to 62 potential shift states (64 - 2, since we do not use the LEFT ALT or the LEFT ALT+LEFT SHIFT).
Think of the X1, X2, and X3 below as being the three RIGHT shift state keys (without forcing the identity of any of them, specifically):
_T(" "), /* 0 */
_T("LS "), /* 1 */
_T(" LC "), /* 2 */
_T("LS+LC "), /* 3 */
_T(" LA "), /* 4:not used */
_T("LS+ LA "), /* 5:not used */
_T(" LC+LA "), /* 6 */
_T("LS+LC+LA "), /* 7 */
_T(" X1 "), /* 8 */
_T("LS+ X1 "), /* 9 */
_T(" LC+ X1 "), /* 10 */
_T("LS+LC+ X1 "), /* 11 */
_T(" LA+X1 "), /* 12 */
_T("LS+ LA+X1 "), /* 13 */
_T(" LC+LA+X1 "), /* 14 */
_T("LS+LC+LA+X1 "), /* 15 */
_T(" X2 "), /* 16 */
_T("LS+ X2 "), /* 17 */
_T(" LC+ X2 "), /* 18 */
_T("LS+LC+ X2 "), /* 19 */
_T(" LA+ X2 "), /* 20 */
_T("LS+ LA+ X2 "), /* 21 */
_T(" LC+LA+ X2 "), /* 22 */
_T("LS+LC+LA+ X2 "), /* 23 */
_T(" X1+X2 "), /* 24 */
_T("LS+ X1+X2 "), /* 25 */
_T(" LC+ X1+X2 "), /* 26 */
_T("LS+LC+ X1+X2 "), /* 27 */
_T(" LA+X1+X2 "), /* 28 */
_T("LS+ LA+X1+X2 "), /* 29 */
_T(" LC+LA+X1+X2 "), /* 30 */
_T("LS+LC+LA+X1+X2 "), /* 31 */
_T(" X3"), /* 32 */
_T("LS+ X3"), /* 33 */
_T(" LC+ X3"), /* 34 */
_T("LS+LC+ X3"), /* 35 */
_T(" LA+ X3"), /* 36 */
_T("LS+ LA+ X3"), /* 37 */
_T(" LC+LA+ X3"), /* 38 */
_T("LS+LC+LA+ X3"), /* 39 */
_T(" X1+ X3"), /* 40 */
_T("LS+ X1+ X3"), /* 41 */
_T(" LC+ X1+ X3"), /* 42 */
_T("LS+LC+ X1+ X3"), /* 43 */
_T(" LA+X1+ X3"), /* 44 */
_T("LS+ LA+X1+ X3"), /* 45 */
_T(" LC+LA+X1+ X3"), /* 46 */
_T("LS+LC+LA+X1+ X3"), /* 47 */
_T(" X2+X3"), /* 48 */
_T("LS+ X2+X3"), /* 49 */
_T(" LC+ X2+X3"), /* 50 */
_T("LS+LC+ X2+X3"), /* 51 */
_T(" LA+ X2+X3"), /* 52 */
_T("LS+ LA+ X2+X3"), /* 53 */
_T(" LC+LA+ X2+X3"), /* 54 */
_T("LS+LC+LA+ X2+X3"), /* 55 */
_T(" X1+X2+X3"), /* 56 */
_T("LS+ X1+X2+X3"), /* 57 */
_T(" LC+ X1+X2+X3"), /* 58 */
_T("LS+LC+ X1+X2+X3"), /* 59 */
_T(" LA+X1+X2+X3"), /* 60 */
_T("LS+ LA+X1+X2+X3"), /* 61 */
_T(" LC+LA+X1+X2+X3"), /* 62 */
_T("LS+LC+LA+X1+X2+X3"), /* 63 */
And thus we have 64 shift states. I left X1, X2, and X3 defined as there is no standard for the order, and the only keyboard that has them defined uses the RIGHT CONTROL character, which is not exactly reflecting the order on the left side....
Now I mentioned that this is a theoretical limit, and my belief is based on several factors:
Anticipating another question -- I am not covering how to create such keyboards in this post; I am still trying to figure out how to interrogate a layout to get the information from it....
So, I talked about VkKeyScan and VkKeyScanEx recently, so let's make some good use of it. If I load up the Canadian Multilingual Standard keyboard and call VkKeyScanEx to ask for some of the characters that are only in those shift states, here is what comes back:
So, the VkKeyScanEx docs got it wrong again -- it was right about that 0x06 in the high byte is CRTL+ALT or AltGR, but that 0x08 and 0x09 in the high byte are not Hankaku an SHFT+Hankaku! They are both defined by the keyboard layout.
But, trying to set the "pressed" bit on VK_RCONTROL in our FillKeyState function created in prior posts does not create these characters. So how can we get at them?
The trick is in the way the keyboard actually remaps to another VK value and makes it the one that will act as the shifting key. Then, using MapVirtualKeyEx and two of it's amazing uMapType values:
And running code like the following:
for(KeysEx vk = KeysEx.None; vk <= KeysEx.VK_OEM_CLEAR; vk++) {
uint sc = VirtualKey.MapVirtualKeyEx((uint)vk, 0, hkl);
uint vkL = VirtualKey.MapVirtualKeyEx(sc, 1, hkl);
uint vkR = VirtualKey.MapVirtualKeyEx(sc, 3, hkl);
if(vkL != vkR) {
if((uint)vk != vkL) {
Console.WriteLine("{0}\t{1:x2}\t{2}\t{3}",
vk.ToString(),
sc,
((KeysEx)vkL).ToString(),
((KeysEx)vkR).ToString());
}
}
}
The following output gets produced on the US and most other keyboards:
VK_LSHIFT 2a VK_SHIFT VK_LSHIFT
VK_RSHIFT 36 VK_SHIFT VK_RSHIFT
VK_LCONTROL 1d VK_CONTROL VK_LCONTROL
VK_RCONTROL 1d VK_CONTROL VK_LCONTROL
VK_LMENU 38 VK_MENU VK_LMENU
VK_RMENU 38 VK_MENU VK_LMENU
Notice how the first column, which lists the original VK, shows only six VKs that will not roundtrip the same way between a regular VK->SC->VK mapping and one that distinguishes between the left and right hand keys? And it is exactly the ones we expected!
If you look carefully at the VK_RMENU and VK_RCONTROL virtual keys, you will see that they are mapped to their left handed counterparts; this is due to the fact that they do not haved defined scan codes that are different other than by being EXTENDED scan codes, and MapVirtualKeyEx strips out the extended bit for reasons that have never been adequately explained to me....
But that is okay -- we just need to know the identity of the six keys; it is only of secondary importance to distinguish them from each other....
Anyway, running that same code again, the following output gets produced on the Canadian Multilingual Standard keyboard:
VK_LSHIFT 2a VK_SHIFT VK_LSHIFT
VK_RSHIFT 36 VK_SHIFT VK_RSHIFT
VK_LCONTROL 1d VK_CONTROL VK_LCONTROL
VK_LMENU 38 VK_MENU VK_LMENU
VK_RMENU 38 VK_MENU VK_LMENU
VK_OEM_8 1d VK_CONTROL VK_LCONTROL
Do you see what happened? The VK_RCONTROL is no longer returning different results, as the VK_OEM_8 key has been mapped to it. If we treat the VK_OEM_8 key as our shifting key in the FillKeyState function, then we suddenly have access to those two new shift states!
So what we would have to theoretically do here would be to look back at the list of shift states from earlier and assume that we now had 16 shift states (the 8 old ones and now 8 new ones), but I'm going to just pick up the additional two since I know that is all that is in the keyboard.
It is important to remember that first tester's axiom and not write a bunch of code that I can't really test effectively....
I'll post the updated version of our code later today (in Part #9b), along with the full version that gets spit out with the Canadian Multilingual Standard keyboard....
referenced by
2010/09/28 Like one of those standards that can't/won't be fully implemented
2008/02/02 [Confusing functionality] + [New features] = [Documentation that can be confusing]
2006/08/29 When you think it couldn't get any harder, it gets easier
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