by Michael S. Kaplan, published on 2006/03/28 03:11 -05:00, original URI: http://blogs.msdn.com/b/michkap/archive/2006/03/28/561541.aspx
Previous posts in this series: Parts 0, 1, 2, 3, and 4.
I have been slowly building up a sample that is (so far) actually of limited use. I mean, who the hell cares about running through one shift state of a keyboard, even if it is definitive?
But it has only been a few days, and I am going to take care of the "easy shift states" now. So you can take the code from this sample and consider it done enough to handle a lot of the conventional scenarios and keyboards that exist.
Now it won't get you everything, but there are future posts for that.... :-)
We'll start by defining a nice enumeration to cover all those easy shift states:
public enum ShiftState : int {
Base = 0, // 0
Shft = 1, // 1
Ctrl = 2, // 2
ShftCtrl = Shft | Ctrl, // 3
Menu = 4, // 4 -- NOT USED
ShftMenu = Shft | Menu, // 5 -- NOT USED
MenuCtrl = Menu | Ctrl, // 6
ShiftMenuCtrl = Shft | Menu | Ctrl, // 7
}
Now, there are a few important things to note here:
And, how will we use this enumeration, exactly?
Well, we will build a Key State array (basically an array of Virtual Key values), and send it in our various calls to ToUnicodeEx. According to the documentation such as that in handy functions like GetKeyboardState:
...each member of the array pointed to by the lpKeyState parameter contains status data for a virtual key. If the high-order bit is 1, the key is down; otherwise, it is up.
There is more there, but we'll get to it later. :-)
The high order bit of a byte is the top bit -- thus setting the byte value to 0x80 will indicate it is pressed. We'll also add a handy function to do the bit work for us:
private static void FillKeyState(KeysEx[] lpKeyState, ShiftState ss) {
lpKeyState[(int)KeysEx.VK_SHIFT] = (((ss & ShiftState.Shft) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
lpKeyState[(int)KeysEx.VK_CONTROL] = (((ss & ShiftState.Ctrl) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
lpKeyState[(int)KeysEx.VK_MENU] = (((ss & ShiftState.Menu) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
}
Since as I mentioned before, we are only dealing with the up/down state of these three keys, that's all we need to get the Key State array set up correctly.
Then, as I mentioned in Part #4, we do need to clean up our output a bit to keep it sensible. So, let's look at our function:
using System;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;namespace KeyboardLayouts {
class Class1 {
// You'll want to insert that enumeration from part #0 here!
public enum ShiftState : int {
Base = 0, // 0
Shft = 1, // 1
Ctrl = 2, // 2
ShftCtrl = Shft | Ctrl, // 3
Menu = 4, // 4 -- NOT USED
ShftMenu = Shft | Menu, // 5 -- NOT USED
MenuCtrl = Menu | Ctrl, // 6
ShiftMenuCtrl = Shft | Menu | Ctrl, // 7
}internal const uint KLF_NOTELLSHELL = 0x00000080;
[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="MapVirtualKeyExW", ExactSpelling=true)]
internal static extern uint MapVirtualKeyEx(
uint uCode,
uint uMapType,
IntPtr dwhkl);[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="LoadKeyboardLayoutW", ExactSpelling=true)]
internal static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags);[DllImport("user32.dll", ExactSpelling=true)]
internal static extern bool UnloadKeyboardLayout(IntPtr hkl);[DllImport("user32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
internal static extern int ToUnicodeEx(
uint wVirtKey,
uint wScanCode,
KeysEx[] lpKeyState,
StringBuilder pwszBuff,
int cchBuff,
uint wFlags,
IntPtr dwhkl);[DllImport("user32.dll", ExactSpelling=true)]
public static extern int GetKeyboardLayoutList(int nBuff, [Out, MarshalAs(UnmanagedType.LPArray)] IntPtr[] lpList);[STAThread]
static void Main(string[] args) {
int cKeyboards = GetKeyboardLayoutList(0, null);
IntPtr[] rghkl = new IntPtr[cKeyboards];
GetKeyboardLayoutList(cKeyboards, rghkl);
IntPtr hkl = LoadKeyboardLayout(args[0], KLF_NOTELLSHELL);
if(hkl == IntPtr.Zero) {
Console.WriteLine("Sorry, that keyboard does not seem to be valid.");
}
else {
KeysEx[] lpKeyState = new KeysEx[256];
uint[] rgScOfVk = new uint[256];// 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;
}
}
Console.WriteLine("SC\tVK \t_\ts\tc\tsc\tca\tsca");
Console.WriteLine("==\t==========\t\t====\t====\t====\t====\t====\t====");
for(uint vk = 0x01; vk < rgScOfVk.Length; vk++) {
if(rgScOfVk[vk] != 0) {
bool fSomeActualLetters = false; // Were actual letters found in the row?
StringBuilder sbBuffer; // Scratchpad we use many places
StringBuilder sbLayout = new StringBuilder(); // Will hold an entire layout 'row'
StringBuilder sbStates = new StringBuilder(); // Will hold all of the shift state info// First, get the SC/VK info stored
sbLayout.Append(string.Format("{0:x2}\t{1:x2} - {2}", rgScOfVk[vk], vk, ((KeysEx)vk).ToString().PadRight(13)));
for(ShiftState ss = ShiftState.Base; ss <= ShiftState.ShiftMenuCtrl; ss++) {
if(ss == ShiftState.Menu || ss == ShiftState.ShftMenu) {
// Alt and Shift+Alt don't work, so skip them
continue;
}FillKeyState(lpKeyState, ss);
sbBuffer = new StringBuilder(10);
int rc = ToUnicodeEx(vk, rgScOfVk[vk], lpKeyState, sbBuffer, sbBuffer.Capacity, 0, hkl);
if(rc > 0) {
StringBuilder sbChar = new StringBuilder(5 * rc);
if(sbBuffer.Length == 0) {
// Someone defined NULL on the keyboard; let's coddle them
sbChar.Append("0000 ");
}
else {
for(int ich = 0; ich < rc; ich++) {
sbChar.Append(((ushort)sbBuffer.ToString()[ich]).ToString("x4"));
sbChar.Append(' ');
}
}
fSomeActualLetters = true;
sbStates.Append(string.Format("\t{0}", sbChar.ToString(0, sbChar.Length - 1)));
}
else if(rc < 0) {
fSomeActualLetters = true;
sbStates.Append(string.Format("\t{0:x4}@", ((ushort)sbBuffer.ToString()[0])));// It's a dead key; let's flush out whats stored in the keyboard state.
ToUnicodeEx((uint)KeysEx.VK_DECIMAL, rgScOfVk[(uint)KeysEx.VK_DECIMAL], lpKeyState, sbBuffer, sbBuffer.Capacity, 0, hkl);
}
else {
sbStates.Append("\t -1");
}
}
// Skip the layout rows that have nothing in them
if(fSomeActualLetters) {
sbLayout.Append(sbStates.ToString());
Console.WriteLine(sbLayout.ToString());
}
}
}foreach(IntPtr i in rghkl) {
if(hkl == i) {
hkl = IntPtr.Zero;
break;
}
}if(hkl != IntPtr.Zero) {
UnloadKeyboardLayout(hkl);
}
}
}private static void FillKeyState(KeysEx[] lpKeyState, ShiftState ss) {
lpKeyState[(int)KeysEx.VK_SHIFT] = (((ss & ShiftState.Shft) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
lpKeyState[(int)KeysEx.VK_CONTROL] = (((ss & ShiftState.Ctrl) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
lpKeyState[(int)KeysEx.VK_MENU] = (((ss & ShiftState.Menu) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
lpKeyState[(int)KeysEx.VK_CAPITAL] = (fCapsLock ? (KeysEx)0x01 : (KeysEx)0x00);
}
}
}
And there we go.
Note some of the important changes:
Let's take a look at a random keyboard when we run this code, say the French keyboard layout (0000040c), chosen since we get to see both dead keys and keys that have multiple code points in them (even though the latter messes up our columns for a few rows!):
SC VK _ s c sc ca sca
== ========== ==== ==== ==== ==== ==== ====
0e 08 - VK_BACK 0008 0008 007f -1 -1 -1
7c 09 - VK_TAB 0009 0009 -1 -1 -1 -1
1c 0d - VK_RETURN 000d 000d 000a -1 -1 -1
01 1b - VK_ESCAPE 001b 001b 001b -1 -1 -1
39 20 - VK_SPACE 0020 0020 0020 -1 -1 -1
0b 30 - VK_0 00e0 0030 0000 -1 0040 -1
02 31 - VK_1 0026 0031 -1 -1 -1 -1
03 32 - VK_2 00e9 0032 -1 -1 007e@ -1
04 33 - VK_3 007e 0022 0033 -1 -1 0023 -1
05 34 - VK_4 0027 0034 -1 -1 007b -1
06 35 - VK_5 0028 0035 -1 001b 005b -1
07 36 - VK_6 002d 0036 -1 001f 007c -1
08 37 - VK_7 00e8 0037 -1 -1 0060@ -1
09 38 - VK_8 0060 005f 0038 -1 001c 005c -1
0a 39 - VK_9 00e7 0039 -1 001e 005e -1
10 41 - VK_A 0061 0041 0001 0001 -1 -1
30 42 - VK_B 0062 0042 0002 0002 -1 -1
2e 43 - VK_C 0063 0043 0003 0003 -1 -1
20 44 - VK_D 0064 0044 0004 0004 -1 -1
12 45 - VK_E 0065 0045 0005 0005 20ac -1
21 46 - VK_F 0066 0046 0006 0006 -1 -1
22 47 - VK_G 0067 0047 0007 0007 -1 -1
23 48 - VK_H 0068 0048 0008 0008 -1 -1
17 49 - VK_I 0069 0049 0009 0009 -1 -1
24 4a - VK_J 006a 004a 000a 000a -1 -1
25 4b - VK_K 006b 004b 000b 000b -1 -1
26 4c - VK_L 006c 004c 000c 000c -1 -1
27 4d - VK_M 006d 004d 000d 000d -1 -1
31 4e - VK_N 006e 004e 000e 000e -1 -1
18 4f - VK_O 006f 004f 000f 000f -1 -1
19 50 - VK_P 0070 0050 0010 0010 -1 -1
1e 51 - VK_Q 0071 0051 0011 0011 -1 -1
13 52 - VK_R 0072 0052 0012 0012 -1 -1
1f 53 - VK_S 0073 0053 0013 0013 -1 -1
14 54 - VK_T 0074 0054 0014 0014 -1 -1
16 55 - VK_U 0075 0055 0015 0015 -1 -1
2f 56 - VK_V 0076 0056 0016 0016 -1 -1
2c 57 - VK_W 0077 0057 0017 0017 -1 -1
2d 58 - VK_X 0078 0058 0018 0018 -1 -1
15 59 - VK_Y 0079 0059 0019 0019 -1 -1
11 5a - VK_Z 007a 005a 001a 001a -1 -1
37 6a - VK_MULTIPLY 002a 002a -1 -1 -1 -1
4e 6b - VK_ADD 002b 002b -1 -1 -1 -1
4a 6d - VK_SUBTRACT 002d 002d -1 -1 -1 -1
1b ba - VK_OEM_1 0024 00a3 001d -1 00a4 -1
0d bb - VK_OEM_PLUS 003d 002b -1 -1 007d -1
32 bc - VK_OEM_COMMA 002c 003f -1 -1 -1 -1
33 be - VK_OEM_PERIOD 003b 002e -1 -1 -1 -1
34 bf - VK_OEM_2 003a 002f -1 -1 -1 -1
28 c0 - VK_OEM_3 00f9 0025 -1 -1 -1 -1
0c db - VK_OEM_4 0029 00b0 -1 -1 005d -1
2b dc - VK_OEM_5 002a 00b5 001c -1 -1 -1
1a dd - VK_OEM_6 005e@ 00a8@ 001b -1 -1 -1
29 de - VK_OEM_7 00b2 -1 -1 -1 -1 -1
35 df - VK_OEM_8 0021 00a7 -1 -1 -1 -1
56 e2 - VK_OEM_102 003c 003e 001c -1 -1 -1
And there we have it -- all of the easy shift states in a nice grid.
Items for future posts (not necessarily in this order!):
And perhaps a few more goodies if anyone is still reading by then....
This post brought to you by "5" (U+0035, DIGIT FIVE)
A Unicode character that is in the very small family of those whose VK value is the same as it's code point!
# Stuart Dunkeld on 13 Nov 2007 10:47 AM:
This is a great series, it's been extremely useful to me, thank you.
This part doesn't compile though as FillKeyState references fCapsLock which isn't introduced until part 8 or so..
referenced by
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
2006/03/31 Getting all you can out of a keyboard layout, Part #6
2006/03/29 Is the CAPS LOCK on?