ToUnicodeEx and dead keys inside system wide hooks
Hello there. I'm new here and I want to ask help to you on solving this problem. I'm trying to detect which character is produced whenever the user presses a key. For this, I'm using the ToUnicodeEx function, which translates the specified virtual-key code and keyboard state to the corresponding Unicode character, inside a keyboard system wide hook. However, every time the user presses dead keys, such as the ones producing '¨', '~' or '´', strange things happen. Here is sample code of the first version:
Code:
private static int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
//indicates if any of underlaing events set e.Handled flag
bool handled = false;
if (nCode >= 0)
{
//read structure KeyboardHookStruct at lParam
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
//raise KeyDown
if (s_KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
Keys keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode;
KeyEventArgs e = new KeyEventArgs(keyData);
s_KeyDown.Invoke(null, e);
handled = e.Handled;
}
// raise KeyPress
if (s_KeyPress != null && wParam == WM_KEYDOWN)
{
bool isDownShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
bool isDownCapslock = (GetKeyState(VK_CAPITAL) != 0 ? true : false);
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
System.Text.StringBuilder sbString = new System.Text.StringBuilder();
IntPtr HKL = GetKeyboardLayout(0);
switch (ToUnicodeEx((uint)MyKeyboardHookStruct.VirtualKeyCode,
(uint)MyKeyboardHookStruct.ScanCode,
keyState,
sbString,
5,
(uint)MyKeyboardHookStruct.Flags,
HKL))
{
case 1:
char key = sbString.ToString()[0];
if ((isDownCapslock ^ isDownShift) && Char.IsLetter(key)) key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
s_KeyPress.Invoke(null, e);
handled = handled || e.Handled;
break;
}
}
// raise KeyUp
if (s_KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
{
Keys keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode;
KeyEventArgs e = new KeyEventArgs(keyData);
s_KeyUp.Invoke(null, e);
handled = handled || e.Handled;
}
}
//if event handled in application do not handoff to other listeners
if (handled)
return -1;
//forward to other application
return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam);
}
(I know the sample code I'm posting is C#, but it is really easy to read and the question here is about the ToUnicodeEx WinAPI call.)
With this code I faced the following problem: Whenever I tried to type "á" in notepad, which requires '´' to be pressed before 'a', the result would be "´´a". Although the captured character by ToUnicodeEx was 'á', I can't admit that my application prevents the user from using characters such as 'á'.
So, I worked around this problem by avoiding ToUnicodeEx to be called whenever a dead key is pressed. This way, I can avoid the ToUnicodeEx function from messing with the keyboard state. Here is the current version code:
Code:
private static int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
//indicates if any of underlaing events set e.Handled flag
bool handled = false;
if (nCode >= 0)
{
//read structure KeyboardHookStruct at lParam
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
//raise KeyDown
if (s_KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
Keys keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode;
KeyEventArgs e = new KeyEventArgs(keyData);
s_KeyDown.Invoke(null, e);
handled = e.Handled;
}
// raise KeyPress
if (s_KeyPress != null && wParam == WM_KEYDOWN)
{
bool isDownShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
bool isDownCapslock = (GetKeyState(VK_CAPITAL) != 0 ? true : false);
byte[] keyState = new byte[256];
GetKeyboardState(keyState);
System.Text.StringBuilder sbString = new System.Text.StringBuilder();
IntPtr HKL = GetKeyboardLayout(0);
if (!IsDeadKey((uint)MyKeyboardHookStruct.VirtualKeyCode))
{
switch (ToUnicodeEx((uint)MyKeyboardHookStruct.VirtualKeyCode,
(uint)MyKeyboardHookStruct.ScanCode,
keyState,
sbString,
5,
(uint)MyKeyboardHookStruct.Flags,
HKL))
{
case 1:
char key = sbString.ToString()[0];
if ((isDownCapslock ^ isDownShift) && Char.IsLetter(key)) key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
s_KeyPress.Invoke(null, e);
handled = handled || e.Handled;
break;
}
}
}
// raise KeyUp
if (s_KeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
{
Keys keyData = (Keys)MyKeyboardHookStruct.VirtualKeyCode;
KeyEventArgs e = new KeyEventArgs(keyData);
s_KeyUp.Invoke(null, e);
handled = handled || e.Handled;
}
}
//if event handled in application do not handoff to other listeners
if (handled)
return -1;
//forward to other application
return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam);
}
static bool IsDeadKey(uint key)
{
if ((MapVirtualKey(key, 2) & 2147483648) == 2147483648)
{
return true;
}
else
{
return false;
}
}
I though this solution would solve my problem, but I was wrong. The result has improved, but it's not quite what I wanted. Now, typing 'á' in notepad results in 'a'. Notice that the accent '´' is missing.
Does anyone know how to work around this issue? My current method already captures the correct character, but prevents the user from producing characters with accents or diacritics. I think the KeyboardHookProc method just need a few changes, but I can't figure them out.
Thanks in advance.
PS: I tried the solution described in the following link: http://social.msdn.microsoft.com/For...1-33c7ccea735c. However, the result is quite the same as in my current version. That code only erases the keyboard buffer, which deletes the accents, therefore not allowing the user to use them.