We help IT Professionals succeed at work.

LowLevel Keystroke Hook removes Accents on French Keyboard

dddogget
dddogget asked
on
When I filter all keystrokes on a French keyboard, the Accent keys no longer work.

Im using the SetWindowsHookEx(WH_KEYBOARD_LL, MyKeyboardProc, hInst, 0); to install the low-level keyboard hook.

Apparently the call to ToAsciiEx() inside the low-level keystroke hook stops the proper functioning on Accent keys. If I set the keyboard to Canadian French the call to ToAscii() or ToAsciiEx() stops the creation of Accent keys (on an English keyboard, press the [ key followed by the a and you should get a â key.)
There is an ToAsciiEx() which allows me to specify the Input Local. Ive tried that with a similar result. In that call, I get the keyboard local for the foreground window process.

When this hook is active and the keyboard is set to Canadian French, I can no longer get an "accent" key when typing in Notepad, Word, etc... If I deactivate the hook, then I can create an accented letter.
LRESULT CALLBACK MyKeyboardProc(int ccode, WPARAM wParam, LPARAM lParam)
{
if (ccode == HC_ACTION)
{
   KBDLLHOOKSTRUCT 
     *pkbdllhook = (KBDLLHOOKSTRUCT *)lParam;
    HKL
      dwhkl = 0;
    BYTE
      dbKbdState[256];
    TCHAR
      szCharBuf[32];
 
    GetKeyboardState(dbKbdState);
    dwhkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), NULL));
    ToAsciiEx(pkbdllhook->vkCode, pkbdllhook->scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl);
}
 
return (CallNextHookEx(hHook, ccode, wParam, lParam));
}

Open in new window

Comment
Watch Question

Commented:
I'm not 100% sure what your goal here is.

Is the problem that after installing your hook that other applications break? Or is it the character that you are getting inside of your hook is not what you expect?

I'm going to tackle the second one:

Your hook function gets the keystrokes at a lower level before any dead key processing is done. In order to get an accented key in this case you will need to handle the dead key "[" yourself.
You should buffer the dead key press until the NEXT keystroke and then process this combination.

Commented:
And if you're really asking question #1, I found some information that will help you right out!

http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx

Author

Commented:
My problem deals witn question #1. Once my hook is active, foreground apps break. The blog post seems to describe what's happening, but I don't understand the solution they are proposing. Is there a way to detect that the keystroke is a "dead-key"? If so, I suppose I could not process any dead-keys.

Commented:
Here's my interpretation:

When you call ToAsciiEx (or ToUnicodeEx) with a dead key, you will be placing a dead key into the keyboard layout's static buffer (which is apparently global). You will know this is the case, because the function will return -1. This then puts the function into the wrong state for the NEXT app that calls it.

So your goal here is to put that buffer back into it's prior state.

So what you could do is, whenever you have added a dead key to the buffer, store it in your own buffer, then call the ToAsciiEx function again with the same parameters until it does not return -1. That means that the buffer is cleared.

Next time your hook is called, the dead key will ALREADY be in the ToAsciiEx buffer. Check to see if the key you pressed is again a dead key by checking for -1. After you get your character translated, put the buffer you built back into the ToAsciiEx buffer for the next function.

There's some implementation details hidden in there, but that's the idea. I hope that makes sense.

Author

Commented:
That's similar to what I found in http://blogs.msdn.com/michkap/archive/2005/01/19/355870.aspx. However, I'm still foggy on the implementation. I can certainly call ToAscii() until it returns a non-negative. I don't understand how to "put the buffer you built back into the ToAsciiEx buffer..." A code snippet would be GREAT!
Commented:
Here's some pseudoy-code for what is described. Hope that this helps!
LRESULT CALLBACK MyKeyboardProc(int ccode, WPARAM wParam, LPARAM lParam)
{
if (ccode == HC_ACTION)
{
   KBDLLHOOKSTRUCT *pkbdllhook = (KBDLLHOOKSTRUCT *)lParam;
   HKL dwhkl = 0;
   BYTE dbKbdState[256];
   TCHAR szCharBuf[32];
   static KBDLLHOOKSTRUCT lastState = {0};
 
   GetKeyboardState(dbKbdState);
   dwhkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), NULL));
 
   if(ToAsciiEx(pkbdllhook->vkCode, pkbdllhook->scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl) == -1)
   {
      //Save the current keyboard state.
      lastState = *pkbdllhook;
 
      //You might also need to hang onto the dbKbdState array... I'm thinking not.
      
      //Clear out the buffer to return to the previous state - wait for ToAsciiEx to return a value other than -1 by passing the same key again. It should happen after 1 call.
      while(ToAsciiEx(pkbdllhook->vkCode, pkbdllhook->scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl) <0);
   }
   else
   {
      //Do something with szCharBuf here since this will overwrite it...
 
      //If we have a saved vkCode from last call, it was a dead key we need to place back in the buffer.
      if(lastState.vkCode != 0)
      {
         //Safest to just clear this.
         memset(dbKbdState, 0, 256);
 
         //Put the old vkCode back into the locale's buffer. 
         ToAsciiEx(lastState.vkCode, lastState.scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl);
 
         //Set vkCode to 0, we can use this as a flag as a vkCode of 0 is invalid.
         lastState.vkCode = 0;
      }
   }
}
 
return (CallNextHookEx(hHook, ccode, wParam, lParam));
}

Open in new window

Author

Commented:
Perfect! Thanks so much. I would have spent a week trying to do this one by myself. Your code snippet did the trick!!!