by Michael S. Kaplan, published on 2006/04/13 03:01 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2006/04/13/575500.aspx
(Previous posts in this series: Parts 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9a)
In my last post, I promised to provide the updated version of the code sample that will handle any keyboard layout that ships with Windows, including the Canadian Multilingual Standard layout....
A brief note on why it may seem like I am picking on this particular layout.... it represents to me the prototypical example of a government to try to support a mutlilingual keyboard. But as I discussed in Keyboards: Monolingual or Multilingual? such attempts, while being perhaps quite useful for a limited segment of the population, can be quite difficult to use for many in the intended market....
The other problem is of course the extensive use of the custom shift states, when there was actually room in the other, more conventional shift states for all of the assigned letters. Though I guess if they never did that, I would not have been posting this particular part of the series. :-)
At the end of this post you'll be able to see the layout and you can decide for yourself if you would swaer by the layout, or at it, were you to use it....
Anyway, here is the updated code (which fixes a few bugs exposed by this particular layout):
using System;
using System.Text;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace KeyboardLayouts {
// 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
ShftMenuCtrl = Shft | Menu | Ctrl, // 7
Xxxx = 8, // 8
ShftXxxx = Shft | Xxxx, // 9
}
public class DeadKey {
private char m_deadchar;
private ArrayList m_rgbasechar = new ArrayList();
private ArrayList m_rgcombchar = new ArrayList();
public DeadKey(char deadCharacter) {
this.m_deadchar = deadCharacter;
}
public char DeadCharacter {
get {
return this.m_deadchar;
}
}
public void AddDeadKeyRow(char baseCharacter, char combinedCharacter) {
this.m_rgbasechar.Add(baseCharacter);
this.m_rgcombchar.Add(combinedCharacter);
}
public int Count {
get {
return this.m_rgbasechar.Count;
}
}
public char GetBaseCharacter(int index) {
return (char)this.m_rgbasechar[index];
}
public char GetCombinedCharacter(int index) {
return (char)this.m_rgcombchar[index];
}
public bool ContainsBaseCharacter(char baseCharacter) {
return this.m_rgbasechar.Contains(baseCharacter);
}
}
public class VirtualKey {
[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="MapVirtualKeyExW", ExactSpelling=true)]
internal static extern uint MapVirtualKeyEx(
uint uCode,
uint uMapType,
IntPtr dwhkl);
private IntPtr m_hkl;
private uint m_vk;
private uint m_sc;
private bool[,] m_rgfDeadKey = new bool[(int)ShiftState.ShftXxxx + 1,2];
private string[,] m_rgss = new string[(int)ShiftState.ShftXxxx + 1,2];
public VirtualKey(IntPtr hkl, KeysEx virtualKey) {
this.m_sc = MapVirtualKeyEx((uint)virtualKey, 0, hkl);
this.m_hkl = hkl;
this.m_vk = (uint)virtualKey;
}
public VirtualKey(IntPtr hkl, uint scanCode) {
this.m_vk = MapVirtualKeyEx(scanCode, 1, hkl);
this.m_hkl = hkl;
this.m_sc = scanCode;
}
public KeysEx VK {
get { return (KeysEx)this.m_vk; }
}
public uint SC {
get { return this.m_sc; }
}
public string GetShiftState(ShiftState shiftState, bool capsLock) {
if(this.m_rgss[(uint)shiftState, (capsLock ? 1 : 0)] == null) {
return("");
}
return(this.m_rgss[(uint)shiftState, (capsLock ? 1 : 0)]);
}
public void SetShiftState(ShiftState shiftState, string value, bool isDeadKey, bool capsLock) {
this.m_rgfDeadKey[(uint)shiftState, (capsLock ? 1 : 0)] = isDeadKey;
this.m_rgss[(uint)shiftState, (capsLock ? 1 : 0)] = value;
}
public bool IsSGCAPS {
get {
string stBase = this.GetShiftState(ShiftState.Base, false);
string stShift = this.GetShiftState(ShiftState.Shft, false);
string stCaps = this.GetShiftState(ShiftState.Base, true);
string stShiftCaps = this.GetShiftState(ShiftState.Shft, true);
return(
((stCaps.Length > 0) &&
(! stBase.Equals(stCaps)) &&
(! stShift.Equals(stCaps))) ||
((stShiftCaps.Length > 0) &&
(! stBase.Equals(stShiftCaps)) &&
(! stShift.Equals(stShiftCaps))));
}
}
public bool IsCapsEqualToShift {
get {
string stBase = this.GetShiftState(ShiftState.Base, false);
string stShift = this.GetShiftState(ShiftState.Shft, false);
string stCaps = this.GetShiftState(ShiftState.Base, true);
return(
(stBase.Length > 0) &&
(stShift.Length > 0) &&
(! stBase.Equals(stShift)) &&
(stShift.Equals(stCaps)));
}
}
public bool IsAltGrCapsEqualToAltGrShift {
get {
string stBase = this.GetShiftState(ShiftState.MenuCtrl, false);
string stShift = this.GetShiftState(ShiftState.ShftMenuCtrl, false);
string stCaps = this.GetShiftState(ShiftState.MenuCtrl, true);
return(
(stBase.Length > 0) &&
(stShift.Length > 0) &&
(! stBase.Equals(stShift)) &&
(stShift.Equals(stCaps)));
}
}
public bool IsXxxxGrCapsEqualToXxxxShift {
get {
string stBase = this.GetShiftState(ShiftState.Xxxx, false);
string stShift = this.GetShiftState(ShiftState.ShftXxxx, false);
string stCaps = this.GetShiftState(ShiftState.Xxxx, true);
return(
(stBase.Length > 0) &&
(stShift.Length > 0) &&
(! stBase.Equals(stShift)) &&
(stShift.Equals(stCaps)));
}
}
public bool IsEmpty {
get {
for(int i = 0; i < this.m_rgss.GetUpperBound(0); i++) {
for(int j = 0; j <= 1; j++) {
if(this.GetShiftState((ShiftState)i, (j == 1)).Length > 0) {
return(false);
}
}
}
return true;
}
}
public string LayoutRow {
get {
StringBuilder sbRow = new StringBuilder();
// First, get the SC/VK info stored
sbRow.Append(string.Format("{0:x2}\t{1:x2} - {2}", this.SC, (byte)this.VK, ((KeysEx)this.VK).ToString().PadRight(13)));
// Now the CAPSLOCK value
int capslock =
0 |
(this.IsCapsEqualToShift ? 1 : 0) |
(this.IsSGCAPS ? 2 : 0) |
(this.IsAltGrCapsEqualToAltGrShift ? 4 : 0) |
(this.IsXxxxGrCapsEqualToXxxxShift ? 8 : 0);
sbRow.Append(string.Format("\t{0}", capslock));
for(ShiftState ss = 0; ss <= Loader.MaxShiftState; ss++) {
if(ss == ShiftState.Menu || ss == ShiftState.ShftMenu) {
// Alt and Shift+Alt don't work, so skip them
continue;
}
for(int caps = 0; caps <= 1; caps++) {
string st = this.GetShiftState(ss, (caps == 1));
if(st.Length == 0) {
// No character assigned here, put in -1.
sbRow.Append("\t -1");
}
else if((caps == 1) && st == (this.GetShiftState(ss, (caps == 0)))) {
// Its a CAPS LOCK state and the assigned character(s) are
// identical to the non-CAPS LOCK state. Put in a MIDDLE DOT.
sbRow.Append("\t \u00b7");
}
else if(this.m_rgfDeadKey[(int)ss, caps]) {
// It's a dead key, append an @ sign.
sbRow.Append(string.Format("\t{0:x4}@", ((ushort)st[0])));
}
else {
// It's some characters; put 'em in there.
StringBuilder sbChar = new StringBuilder((5 * st.Length) + 1);
for(int ich = 0; ich < st.Length; ich++) {
sbChar.Append(((ushort)st[ich]).ToString("x4"));
sbChar.Append(' ');
}
sbRow.Append(string.Format("\t{0}", sbChar.ToString(0, sbChar.Length - 1)));
}
}
}
return sbRow.ToString();
}
}
}
public class Loader {
private const uint KLF_NOTELLSHELL = 0x00000080;
internal static KeysEx[] lpKeyStateNull = new KeysEx[256];
[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="LoadKeyboardLayoutW", ExactSpelling=true)]
private static extern IntPtr LoadKeyboardLayout(string pwszKLID, uint Flags);
[DllImport("user32.dll", ExactSpelling=true)]
private static extern bool UnloadKeyboardLayout(IntPtr hkl);
[DllImport("user32.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
private static extern int ToUnicodeEx(
uint wVirtKey,
uint wScanCode,
KeysEx[] lpKeyState,
StringBuilder pwszBuff,
int cchBuff,
uint wFlags,
IntPtr dwhkl);
[DllImport("user32.dll", CharSet=CharSet.Unicode, EntryPoint="VkKeyScanExW", ExactSpelling=true)]
private static extern ushort VkKeyScanEx(char ch, IntPtr dwhkl);
[DllImport("user32.dll", ExactSpelling=true)]
private static extern int GetKeyboardLayoutList(int nBuff, [Out, MarshalAs(UnmanagedType.LPArray)] IntPtr[] lpList);
private static KeysEx m_XxxxVk = KeysEx.None;
public static KeysEx XxxxVk {
get {
return m_XxxxVk;
}
set {
m_XxxxVk = value;
}
}
public static ShiftState MaxShiftState {
get {
return (Loader.XxxxVk == KeysEx.None ? ShiftState.ShftMenuCtrl : ShiftState.ShftXxxx);
}
}
private static void FillKeyState(KeysEx[] lpKeyState, ShiftState ss, bool fCapsLock) {
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);
if(Loader.XxxxVk != KeysEx.None) {
// The Xxxx key has been assigned, so let's include it
lpKeyState[(int)Loader.XxxxVk] = (((ss & ShiftState.Xxxx) != 0) ? (KeysEx)0x80 : (KeysEx)0x00);
}
lpKeyState[(int)KeysEx.VK_CAPITAL] = (fCapsLock ? (KeysEx)0x01 : (KeysEx)0x00);
}
private static DeadKey ProcessDeadKey(
uint iKeyDead, // The index into the VirtualKey of the dead key
ShiftState shiftStateDead, // The shiftstate that contains the dead key
KeysEx[] lpKeyStateDead, // The key state for the dead key
VirtualKey[] rgKey, // Our array of dead keys
bool fCapsLock, // Was the caps lock key pressed?
IntPtr hkl) { // The keyboard layout
KeysEx[] lpKeyState = new KeysEx[256];
DeadKey deadKey = new DeadKey(rgKey[iKeyDead].GetShiftState(shiftStateDead, fCapsLock)[0]);
for(uint iKey = 0; iKey < rgKey.Length; iKey++) {
if(rgKey[iKey] != null) {
StringBuilder sbBuffer = new StringBuilder(10); // Scratchpad we use many places
for(ShiftState ss = ShiftState.Base; ss <= Loader.MaxShiftState; ss++) {
int rc = 0;
if(ss == ShiftState.Menu || ss == ShiftState.ShftMenu) {
// Alt and Shift+Alt don't work, so skip them
continue;
}
for(int caps = 0; caps <=1; caps++) {
// First the dead key
while(rc >= 0) {
// We know that this is a dead key coming up, otherwise
// this function would never have been called. If we do
// *not* get a dead key then that means the state is
// messed up so we run again and again to clear it up.
// Risk is technically an infinite loop but per Hiroyama
// that should be impossible here.
rc = ToUnicodeEx((uint)rgKey[iKeyDead].VK, rgKey[iKeyDead].SC, lpKeyStateDead, sbBuffer, sbBuffer.Capacity, 0, hkl);
}
// Now fill the key state for the potential base character
FillKeyState(lpKeyState, ss, (caps != 0));
sbBuffer = new StringBuilder(10);
rc = ToUnicodeEx((uint)rgKey[iKey].VK, rgKey[iKey].SC, lpKeyState, sbBuffer, sbBuffer.Capacity, 0, hkl);
if(rc == 1) {
// That was indeed a base character for our dead key.
// And we now have a composite character. Let's run
// through one more time to get the actual base
// character that made it all possible?
char combchar = sbBuffer[0];
sbBuffer = new StringBuilder(10);
rc = ToUnicodeEx((uint)rgKey[iKey].VK, rgKey[iKey].SC, lpKeyState, sbBuffer, sbBuffer.Capacity, 0, hkl);
char basechar = sbBuffer[0];
if(deadKey.DeadCharacter == combchar) {
// Since the combined character is the same as the dead key,
// we must clear out the keyboard buffer.
ClearKeyboardBuffer((uint)KeysEx.VK_DECIMAL, rgKey[(uint)KeysEx.VK_DECIMAL].SC, hkl);
}
if((((ss == ShiftState.Ctrl) || (ss == ShiftState.ShftCtrl)) &&
(char.IsControl(basechar))) ||
(basechar.Equals(combchar))) {
// ToUnicodeEx has an internal knowledge about those
// VK_A ~ VK_Z keys to produce the control characters,
// when the conversion rule is not provided in keyboard
// layout files
// Additionally, dead key state is lost for some of these
// character combinations, for unknown reasons.
// Therefore, if the base character and combining are equal,
// and its a CTRL or CTRL+SHIFT state, and a control character
// is returned, then we do not add this "dead key" (which
// is not really a dead key).
continue;
}
if(! deadKey.ContainsBaseCharacter(basechar)) {
deadKey.AddDeadKeyRow(basechar, combchar);
}
}
else if(rc > 1) {
// Not a valid dead key combination, sorry! We just ignore it.
}
else if(rc < 0) {
// It's another dead key, so we ignore it (other than to flush it from the state)
ClearKeyboardBuffer((uint)KeysEx.VK_DECIMAL, rgKey[(uint)KeysEx.VK_DECIMAL].SC, hkl);
}
}
}
}
}
return deadKey;
}
private static void ClearKeyboardBuffer(uint vk, uint sc, IntPtr hkl) {
StringBuilder sb = new StringBuilder(10);
int rc = 0;
while(rc != 1) {
rc = ToUnicodeEx(vk, sc, lpKeyStateNull, sb, sb.Capacity, 0, hkl);
}
}
[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];
VirtualKey[] rgKey = new VirtualKey[256];
ArrayList alDead = new ArrayList();
// 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++) {
VirtualKey key = new VirtualKey(hkl, sc);
if(key.VK != 0) {
rgKey[(uint)key.VK] = key;
}
}
// add the special keys that do not get added from the code above
for(KeysEx ke = KeysEx.VK_NUMPAD0; ke <= KeysEx.VK_NUMPAD9; ke++) {
rgKey[(uint)ke] = new VirtualKey(hkl, ke);
}
rgKey[(uint)KeysEx.VK_DIVIDE] = new VirtualKey(hkl, KeysEx.VK_DIVIDE);
rgKey[(uint)KeysEx.VK_CANCEL] = new VirtualKey(hkl, KeysEx.VK_CANCEL);
rgKey[(uint)KeysEx.VK_DECIMAL] = new VirtualKey(hkl, KeysEx.VK_DECIMAL);
// See if there is a special shift state added
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) &&
((uint)vk != vkL)) {
switch(vk) {
case KeysEx.VK_LCONTROL:
case KeysEx.VK_RCONTROL:
case KeysEx.VK_LSHIFT:
case KeysEx.VK_RSHIFT:
case KeysEx.VK_LMENU:
case KeysEx.VK_RMENU:
break;
default:
Loader.XxxxVk = vk;
break;
}
}
}
for(uint iKey = 0; iKey < rgKey.Length; iKey++) {
if(rgKey[iKey] != null) {
StringBuilder sbBuffer; // Scratchpad we use many places
for(ShiftState ss = ShiftState.Base; ss <= Loader.MaxShiftState; ss++) {
if(ss == ShiftState.Menu || ss == ShiftState.ShftMenu) {
// Alt and Shift+Alt don't work, so skip them
continue;
}
for(int caps = 0; caps <= 1; caps++) {
ClearKeyboardBuffer((uint)KeysEx.VK_DECIMAL, rgKey[(uint)KeysEx.VK_DECIMAL].SC, hkl);
FillKeyState(lpKeyState, ss, (caps != 0));
sbBuffer = new StringBuilder(10)
int rc = ToUnicodeEx((uint)rgKey[iKey].VK, rgKey[iKey].SC, lpKeyState, sbBuffer, sbBuffer.Capacity, 0, hkl);
if(rc > 0) {
if(sbBuffer.Length == 0) {
// Someone defined NULL on the keyboard; let's coddle them
rgKey[iKey].SetShiftState(ss, "\u0000", false, (caps != 0));
}
else {
if((rc == 1) &&
(ss == ShiftState.Ctrl || ss == ShiftState.ShftCtrl) &&
((int)rgKey[iKey].VK == ((uint)sbBuffer[0] + 0x40))) {
// ToUnicodeEx has an internal knowledge about those
// VK_A ~ VK_Z keys to produce the control characters,
// when the conversion rule is not provided in keyboard
// layout files
continue;
}
rgKey[iKey].SetShiftState(ss, sbBuffer.ToString().Substring(0, rc), false, (caps != 0));
}
}
else if(rc < 0) {
rgKey[iKey].SetShiftState(ss, sbBuffer.ToString().Substring(0, 1), true, (caps != 0));
// It's a dead key; let's flush out whats stored in the keyboard state.
ClearKeyboardBuffer((uint)KeysEx.VK_DECIMAL, rgKey[(uint)KeysEx.VK_DECIMAL].SC, hkl);
DeadKey dk = null;
for(int iDead = 0; iDead < alDead.Count; iDead++) {
dk = (DeadKey)alDead[iDead];
if(dk.DeadCharacter == rgKey[iKey].GetShiftState(ss, caps != 0)[0]) {
break;
}
dk = null;
}
if(dk == null) {
alDead.Add(ProcessDeadKey(iKey, ss, lpKeyState, rgKey, caps == 1, hkl));
}
}
}
}
}
}
foreach(IntPtr i in rghkl) {
if(hkl == i) {
hkl = IntPtr.Zero;
break;
}
}
if(hkl != IntPtr.Zero) {
UnloadKeyboardLayout(hkl);
}
// Okay, now we can dump the layout
Console.Write("\nSC\tVK \t\t\tCAPS\t_\t_C\ts\tsC\tc\tcC\tsc\tscC\t\tca\tcaC\tsca\tscaC");
if(Loader.XxxxVk != KeysEx.None) {
Console.Write("\tx\txC\tsx\tsxC");
}
Console.WriteLine();
Console.Write("==\t==========\t\t====\t====\t====\t====\t====\t====\t====\t====\t====\t====\t====\t====\t====");
if(Loader.XxxxVk != KeysEx.None) {
Console.Write("\t====\t====\t====\t====");
}
Console.WriteLine();
for(uint iKey = 0; iKey < rgKey.Length; iKey++) {
if((rgKey[iKey] != null) &&
( !rgKey[iKey].IsEmpty)) {
Console.WriteLine(rgKey[iKey].LayoutRow);
}
}
foreach(DeadKey dk in alDead) {
Console.WriteLine();
Console.WriteLine("0x{0:x4}\t{1}", ((ushort)dk.DeadCharacter).ToString("x4"), dk.Count);
for(int id = 0; id < dk.Count; id++) {
Console.WriteLine("\t0x{0:x4}\t0x{1:x4}",
((ushort)dk.GetBaseCharacter(id)).ToString("x4"),
((ushort)dk.GetCombinedCharacter(id)).ToString("x4"));
}
}
Console.WriteLine();
}
}
}
}
The code above fixes a bug not found with previous versions that would cause dead key tables to show up more than once if the dead key is defined multiple times....
And now, here is that keyboard with the 00011009 KLID value (you'll want a console window with 170 columns in it to print it out yourself!):
C:\KeyboardLayouts\bin\Debug>KeyboardLayouts.exe 00011009
SC VK CAPS _ _C s sC c cC sc scC ca caC sca scaC x xC sx sxC
== ========== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
46 03 - VK_CANCEL 0 0003 · 0003 · 0003 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
0e 08 - VK_BACK 0 0008 · 0008 · 007f · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
7c 09 - VK_TAB 0 0009 · 0009 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
1c 0d - VK_RETURN 0 000d · 000d · 000a · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
01 1b - VK_ESCAPE 0 001b · 001b · 001b · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
39 20 - VK_SPACE 0 0020 · 0020 · 0020 · -1 -1 00a0 · -1 -1 0020 · 0020 ·
0b 30 - VK_0 0 0030 · 0029 · -1 -1 -1 -1 005d · -1 -1 -1 -1 -1 -1
02 31 - VK_1 0 0031 · 0021 · -1 -1 -1 -1 -1 -1 -1 -1 00b9 · 00a1 ·
03 32 - VK_2 0 0032 · 0040 · -1 -1 -1 -1 -1 -1 -1 -1 00b2 · -1 -1
04 33 - VK_3 0 0033 · 0023 · -1 -1 -1 -1 -1 -1 -1 -1 00b3 · 00a3 ·
05 34 - VK_4 0 0034 · 0024 · -1 -1 -1 -1 00a4 · -1 -1 00bc · 20ac ·
06 35 - VK_5 0 0035 · 0025 · -1 -1 -1 -1 -1 -1 -1 -1 00bd · 215c ·
07 36 - VK_6 0 0036 · 003f · -1 -1 -1 -1 -1 -1 -1 -1 00be · 215d ·
08 37 - VK_7 0 0037 · 0026 · -1 -1 -1 -1 007b · -1 -1 -1 -1 215e ·
09 38 - VK_8 0 0038 · 002a · -1 -1 -1 -1 007d · -1 -1 -1 -1 2122 ·
0a 39 - VK_9 0 0039 · 0028 · -1 -1 -1 -1 005b · -1 -1 -1 -1 00b1 ·
1e 41 - VK_A 1 0061 0041 0041 0061 -1 -1 -1 -1 -1 -1 -1 -1 00e6 · 00c6 ·
30 42 - VK_B 1 0062 0042 0042 0062 -1 -1 -1 -1 -1 -1 -1 -1 201d · 2019 ·
2e 43 - VK_C 1 0063 0043 0043 0063 -1 -1 -1 -1 -1 -1 -1 -1 00a2 · 00a9 ·
20 44 - VK_D 1 0064 0044 0044 0064 -1 -1 -1 -1 -1 -1 -1 -1 00f0 · 00d0 ·
12 45 - VK_E 1 0065 0045 0045 0065 -1 -1 -1 -1 20ac · -1 -1 0153 · 0152 ·
21 46 - VK_F 1 0066 0046 0046 0066 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 00aa ·
22 47 - VK_G 1 0067 0047 0047 0067 -1 -1 -1 -1 -1 -1 -1 -1 014b · 014a ·
23 48 - VK_H 1 0068 0048 0048 0068 -1 -1 -1 -1 -1 -1 -1 -1 0127 · 0126 ·
17 49 - VK_I 1 0069 0049 0049 0069 -1 -1 -1 -1 -1 -1 -1 -1 2192 · 0131 ·
24 4a - VK_J 1 006a 004a 004a 006a -1 -1 -1 -1 -1 -1 -1 -1 0133 · 0132 ·
25 4b - VK_K 1 006b 004b 004b 006b -1 -1 -1 -1 -1 -1 -1 -1 0138 · -1 -1
26 4c - VK_L 1 006c 004c 004c 006c -1 -1 -1 -1 -1 -1 -1 -1 0140 · 013f ·
32 4d - VK_M 1 006d 004d 004d 006d -1 -1 -1 -1 -1 -1 -1 -1 00b5 · 00ba ·
31 4e - VK_N 1 006e 004e 004e 006e -1 -1 -1 -1 -1 -1 -1 -1 0149 · 266a ·
18 4f - VK_O 1 006f 004f 004f 006f -1 -1 -1 -1 -1 -1 -1 -1 00f8 · 00d8 ·
19 50 - VK_P 1 0070 0050 0050 0070 -1 -1 -1 -1 -1 -1 -1 -1 00fe · 00de ·
10 51 - VK_Q 1 0071 0051 0051 0071 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 2126 ·
13 52 - VK_R 1 0072 0052 0052 0072 -1 -1 -1 -1 -1 -1 -1 -1 00b6 · 00ae ·
1f 53 - VK_S 1 0073 0053 0053 0073 -1 -1 -1 -1 -1 -1 -1 -1 00df · 00a7 ·
14 54 - VK_T 1 0074 0054 0054 0074 -1 -1 -1 -1 -1 -1 -1 -1 0167 · 0166 ·
16 55 - VK_U 1 0075 0055 0055 0075 -1 -1 -1 -1 -1 -1 -1 -1 2193 · 2191 ·
2f 56 - VK_V 1 0076 0056 0056 0076 -1 -1 -1 -1 -1 -1 -1 -1 201c · 2018 ·
11 57 - VK_W 1 0077 0057 0057 0077 -1 -1 -1 -1 -1 -1 -1 -1 0142 · 0141 ·
2d 58 - VK_X 1 0078 0058 0058 0078 -1 -1 -1 -1 00bb · -1 -1 -1 -1 -1 -1
15 59 - VK_Y 1 0079 0059 0059 0079 -1 -1 -1 -1 -1 -1 -1 -1 2190 · 00a5 ·
2c 5a - VK_Z 1 007a 005a 005a 007a -1 -1 -1 -1 00ab · -1 -1 -1 -1 -1 -1
52 60 - VK_NUMPAD0 0 0030 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
4f 61 - VK_NUMPAD1 0 0031 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
50 62 - VK_NUMPAD2 0 0032 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
51 63 - VK_NUMPAD3 0 0033 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
4b 64 - VK_NUMPAD4 0 0034 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
4c 65 - VK_NUMPAD5 0 0035 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
4d 66 - VK_NUMPAD6 0 0036 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
47 67 - VK_NUMPAD7 0 0037 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
48 68 - VK_NUMPAD8 0 0038 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
49 69 - VK_NUMPAD9 0 0039 · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
37 6a - VK_MULTIPLY 0 002a · 002a · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
4e 6b - VK_ADD 0 002b · 002b · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
4a 6d - VK_SUBTRACT 0 002d · 002d · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
53 6e - VK_DECIMAL 0 002e · 002e · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
35 6f - VK_DIVIDE 0 002f · 002f · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
27 ba - VK_OEM_1 0 003b · 003a · -1 -1 -1 -1 00b0 · -1 -1 00b4@ · 02dd@ ·
0d bb - VK_OEM_PLUS 0 003d · 002b · -1 -1 -1 -1 00ac · -1 -1 00b8@ · 02db@ ·
33 bc - VK_OEM_COMMA 0 002c · 0027 · -1 -1 -1 -1 003c · -1 -1 2015 · 00d7 ·
0c bd - VK_OEM_MINUS 0 002d · 005f · -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 00bf ·
34 be - VK_OEM_PERIOD 0 002e · 0022 · -1 -1 -1 -1 003e · -1 -1 02d9@ · 00f7 ·
35 bf - VK_OEM_2 1 00e9 00c9 00c9 00e9 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 02d9@ ·
28 c0 - VK_OEM_3 1 00e8 00c8 00c8 00e8 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 02c7@ ·
1a db - VK_OEM_4 0 005e@ · 00a8@ · -1 -1 -1 -1 0060@ · -1 -1 -1 -1 02da@ ·
2b dc - VK_OEM_5 1 00e0 00c0 00c0 00e0 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 02d8@ ·
1b dd - VK_OEM_6 1 00e7 00c7 00c7 00e7 -1 -1 -1 -1 007e@ · -1 -1 007e · 00af@ ·
29 de - VK_OEM_7 0 002f · 005c · -1 -1 -1 -1 007c · -1 -1 -1 -1 00ad ·
56 e2 - VK_OEM_102 1 00f9 00d9 00d9 00f9 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 00a6 ·
0x00b4 25
0x0020 0x00b4
0x0061 0x00e1
0x0041 0x00c1
0x0063 0x0107
0x0043 0x0106
0x0065 0x00e9
0x0045 0x00c9
0x0069 0x00ed
0x0049 0x00cd
0x006c 0x013a
0x004c 0x0139
0x006e 0x0144
0x004e 0x0143
0x006f 0x00f3
0x004f 0x00d3
0x0072 0x0155
0x0052 0x0154
0x0073 0x015b
0x0053 0x015a
0x0075 0x00fa
0x0055 0x00da
0x0079 0x00fd
0x0059 0x00dd
0x007a 0x017a
0x005a 0x0179
0x02dd 5
0x0020 0x02dd
0x006f 0x0151
0x004f 0x0150
0x0075 0x0171
0x0055 0x0170
0x00b8 17
0x0020 0x00b8
0x0063 0x00e7
0x0043 0x00c7
0x0067 0x0123
0x0047 0x0122
0x006b 0x0137
0x004b 0x0136
0x006c 0x013c
0x004c 0x013b
0x006e 0x0146
0x004e 0x0145
0x0072 0x0157
0x0052 0x0156
0x0073 0x015f
0x0053 0x015e
0x0074 0x0163
0x0054 0x0162
0x02db 9
0x0020 0x02db
0x0061 0x0105
0x0041 0x0104
0x0065 0x0119
0x0045 0x0118
0x0069 0x012f
0x0049 0x012e
0x0075 0x0173
0x0055 0x0172
0x02d9 10
0x0020 0x02d9
0x0063 0x010b
0x0043 0x010a
0x0065 0x0117
0x0045 0x0116
0x0067 0x0121
0x0047 0x0120
0x0049 0x0130
0x007a 0x0017
0x005a 0x017b
0x02c7 19
0x0020 0x02c7
0x0063 0x010d
0x0043 0x010c
0x0064 0x010f
0x0044 0x010e
0x0065 0x011b
0x0045 0x011a
0x006c 0x013e
0x004c 0x013d
0x006e 0x0148
0x004e 0x0147
0x0072 0x0159
0x0052 0x0158
0x0073 0x0161
0x0053 0x0160
0x0074 0x0165
0x0054 0x0164
0x007a 0x017e
0x005a 0x017d
0x005e 25
0x0020 0x005e
0x0061 0x00e2
0x0041 0x00c2
0x0063 0x0109
0x0043 0x0108
0x0065 0x00ea
0x0045 0x00ca
0x0067 0x011d
0x0047 0x011c
0x0068 0x0125
0x0048 0x0124
0x0069 0x00ee
0x0049 0x00ce
0x006a 0x0135
0x004a 0x0134
0x006f 0x00f4
0x004f 0x00d4
0x0073 0x015d
0x0053 0x015c
0x0075 0x00fb
0x0055 0x00db
0x0077 0x0175
0x0057 0x0174
0x0079 0x0177
0x0059 0x0176
0x00a8 13
0x0020 0x00a8
0x0061 0x00e4
0x0041 0x00c4
0x0065 0x00eb
0x0045 0x00cb
0x0069 0x00ef
0x0049 0x00cf
0x006f 0x00f6
0x004f 0x00d6
0x0075 0x00fc
0x0055 0x00dc
0x0079 0x00ff
0x0059 0x0178
0x0060 11
0x0020 0x0060
0x0061 0x00e0
0x0041 0x00c0
0x0065 0x00e8
0x0045 0x00c8
0x0069 0x00ec
0x0049 0x00cc
0x006f 0x00f2
0x004f 0x00d2
0x0075 0x00f9
0x0055 0x00d9
0x02da 5
0x0020 0x02da
0x0061 0x00e5
0x0041 0x00c5
0x0075 0x016f
0x0055 0x016e
0x02d8 7
0x0020 0x02d8
0x0061 0x0103
0x0041 0x0102
0x0067 0x011f
0x0047 0x011e
0x0075 0x016d
0x0055 0x016c
0x007e 11
0x0020 0x007e
0x0061 0x00e3
0x0041 0x00c3
0x0069 0x0129
0x0049 0x0128
0x006e 0x00f1
0x004e 0x00d1
0x006f 0x00f5
0x004f 0x00d5
0x0075 0x0169
0x0055 0x0168
0x00af 11
0x0020 0x00af
0x0061 0x0101
0x0041 0x0100
0x0065 0x0113
0x0045 0x0112
0x0069 0x012b
0x0049 0x012a
0x006f 0x014d
0x004f 0x014c
0x0075 0x016b
0x0055 0x016a
Now the code could have been expanded to handle more "special" shift states, though as I mentioned back in Part 9a, this does not seem as immediately important given the fact that there are no other keyboards defined at the moment that make use of this functionality.
Obviously if the code needed to stretch in that direction, it would make sense to take a few additional steps:
For now, I just can't believe how hard it is to get some of this infornation!
This post brought to you by "9" (U+0039, DIGIT NINE)
A Unicode character that is in the very small family of those whose VK value is the same as it's code point!
# Joku on 17 Apr 2006 5:40 PM:
# Michael S. Kaplan on 17 Apr 2006 5:55 PM:
# Michael S. Kaplan on 17 Apr 2006 11:51 PM:
# Liisachan on 16 Aug 2008 5:54 AM:
The dead key U+02D9 [ ˙ ] is strange when combined with [ z ].
<quote>
0x02d9 10
0x0020 0x02d9
0x0063 0x010b
0x0043 0x010a
0x0065 0x0117
0x0045 0x0116
0x0067 0x0121
0x0047 0x0120
0x0049 0x0130
0x007a 0x0017 <-- Here
0x005a 0x017b
</quote>
This dead key adds a dot above, changing [ c ] to U+010B [ ċ ], [ C ] to U+010A [ Ċ ], [ e ] to U+0117 [ ė ], [ E ] to U+0116 [ Ė ], and so on.
So one would expect that [ Z ] becomes U+017B [ Ż ], and [ z ] becomes U+017C [ ż ]; which is indeed true about U+017B but not true about U+017C. You get U+0017 instead of U+017C.
I suspect this is a mistake in the KL design, where someone accidentally typed "017" when he or she had to type "017C", confusing 0 and C or dropping the trailing C or something.
Is U+0017 actually what is supposed to be there? I doubt it, because if you really need to input U+0017 for whatever reason, this KL already supports it as [Ctrl]+VK_W.
# Michael S. Kaplan on 16 Aug 2008 12:48 PM:
That looks like a typo from when I was fixing up the column... :-)
# Liisachan on 16 Aug 2008 10:43 PM:
No, it's officially wrong. Not your typo, but the KL itself, intrinsically.
You can test it by yourself on probably any keyboard, for example on the standard US keyboard, by just typing
[RCtrl]+[.] then [Shift]+[Z]
and
[RCtrl]+[.] then [Z]
after loading and activating the KL in question.
OR you can just check the source code of
http://www.microsoft.com/globaldev/keyboards/kbdcan.htm
which says
U+02D9 Dot Above [...] x0017 [...] x017B
so x0017 is "by design".
Maybe the bug itself is not very surprising. After all, humans make mistakes. Romanian KL coming with Windows XP (0x00000418) is officially wrong too, where they confused U+0163 [ ţ ] and U+021B [ ț ] for example. But I find it strange that nobody complained about this before anywhere on the Internet. Well maybe they did, but I couldn't find any page about it.
dk273 on 20 Apr 2009 3:43 PM:
After changing the 4 lines as indicated in your Post “There is a bug in Part 9b!” (i.e. changing 4 occurrences of “(caps != 0)” to “(caps == 0)”), I still get error found by Joku when processing the keyboard layout: KLID = "00000405" [Czech].
An unhandled exception of type 'System.IndexOutOfRangeException' occurred in KeyboardLayouts.exe
Additional information: Index was outside the bounds of the array.
As in Joku's case, this occurs at the second line of “DeadKey ProcessDeadKey”:
DeadKey deadKey = new DeadKey(rgKey[iKeyDead].GetShiftState(shiftStateDead, fCapsLock)[0]);
Where: iKeyDead = 48 (0x30), shiftStateDead = MenuCtrl, fCapsLock = false
All help gratefully received.
"GetShiftState" returns a empty value and hence index of [0] is out of bounds.
I am running under Windows XP SP3 using VS2008.
klaus on 24 Jan 2010 6:23 AM:
I can get the code to compile and run but the program never finds any dead keys, no matter what Keyboard LayoutID i input.
I tried 00011009 as mentioned above and my output looks correct exspect no dead keys get listed.
Naturally i stepped through the code tried a couple of different layout id. Changed my keyboard on my OS (and restarted VS everytime.) Still never finds dead keys.
My next guess would be my keyboard itself. Its a Kinesis Advantage.
Anyone have any idea?
JaredPar MSFT on 27 Jun 2012 5:25 PM:
I was wondering if you had any information on how to deal with the shift states that are reserved for the keyboard layout driver. In particular when bits 0x10 and 0x20 are returned in the high byte of VkKeyScanEx. There appears to be no documentation on how to figure out what virtual keys represent those shift states.
I devised a fairly horrible way of detecting it via experimentation. But my method is flimsy at best. In short, look for any virtual keys which produce no text themselves via ToUnicodeEx, consider them as possible shift state modifiers. Then take the original virtual key code returned from VkKeyScanEx and apply each possibility in order and see if ToUnicodeEx returns the original char. This works somewhat OK for single shift states but wouldn't work if VkKeyScanEx return 0x30 in the high bit (wouldn't be able to detect which virtual key was which shift state).
So far I've only encountered one keyboard layout which returns these modifiers. It's called neo-layout and is available here.
Thanks a lot for this particular blog series. It's been absolutely invaluable in every project where I've had to do key input processing.
referenced by
2010/09/28 Like one of those standards that can't/won't be fully implemented
2008/01/02 All things being equal, the road on the map is much safer to use
2006/04/22 Getting all you can out of a keyboard layout, Part #10a
2006/04/18 There is a bug in Part #9b!
2006/04/17 Get off my freaking key!