Link to home
Start Free TrialLog in
Avatar of 208Fireball
208Fireball

asked on

Keyboard filtering...

I have scanned through previous questions here, but nothing seems to achieve what i want it to.

I am after (once again) a global hook so i can implement a filter that will expand certain characters typed into a series of characters (e.g., tab into 4 spaces.)

I have constructed something similar to the following code, based on previous examples, but fails miserably....

Any suggestions to make the function work (and be relatively robust?)

Cheers,

Dave

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if(nCode < 0)
    {      
     CallNextHookEx(hMsghook, nCode, wParam, lParam);
     return 0;
    }

    LPMSG lpMsg = (LPMSG) lParam;

    if(lpMsg->message == WM_CHAR && wParam == PM_REMOVE && lpMsg->wParam == chOldChar)
    {
        // Insert as many characters as needed...
        PostMessage(lpMsg->hwnd,WM_CHAR,chNewChar,lpMsg->lParam);
        PostMessage(lpMsg->hwnd,WM_CHAR,chNewChar,lpMsg->lParam);
        PostMessage(lpMsg->hwnd,WM_CHAR,chNewChar,lpMsg->lParam);
        PostMessage(lpMsg->hwnd,WM_CHAR,chNewChar,lpMsg->lParam);
    }
   
    return CallNextHookEx(hMsghook, nCode, wParam, lParam);
}
Avatar of KurtVon
KurtVon

How does it fail miserably?  If nothing happens, you may need to override WM_KEYDOWN and WM_KEYUP as well as WM_CHAR.

Also, it may be that the call to the next hook should be dropped if you process the keystroke.  After all, you don't want the original character to go through, do you?  Just return -1 in that case, this kills the message.
Avatar of 208Fireball

ASKER

Even when returning -1, it seems not to remove the original character. Also, if one of the keys i want to replace the original is the same as the one i am replacing, the thing will go into an endless loop...

Cheers,

Dave
Did you also override WM_KEYDOWN and WM_KEYUP?  I don't know what program you are trying to change, but these messages are processed by many controls.  You need to block the original character in all three cases.  Also, Microsoft suggests returning TRUE to swallow messages.  Sorry about the -1 (which should be the same but . . .)

And of course it is an endless loop.  You either need some flag to indicate the keypress should not be processed, like a static bool that can be set when it is processed, and then cleared if it is set and the message arrives a second time.

Unfortunately, SendMessage is illegal in a hook (may cause a deadlock) or you could just use a SendMessage and call the next hook when you get to the place the character should be.

Oh, and if you override the WM_KEYUP and WM_KEYDOWN what you should do is send them around the WM_CHAR messages you send so that the calls are

::PostMessage(hWnd, WM_KEYDOWN, chNewChar1, lParam);
::PostMessage(hWnd, WM_CHAR, chNewChar1, lParam);
::PostMessage(hWnd, WM_KEYUP, chNewChar1, lParam);
::PostMessage(hWnd, WM_KEYDOWN, chNewChar2, lParam);
...

This prevents Windows from thinking the user is pushing every key simultaneously.
Ok here it is, as the function currently stands. There seem to be a couple of problems: Even though i return TRUE the message is not eaten, and when i am using a static BOOL to stop messages from my insertion characters, it still goes into an endless loop if the character i use is one of the ones i am inserting...

If the character is not one of the ones i am inserting, it does not loop endlessly, but i get a few extra characters after the ones i have pasted!!!

Here's the function with some test chars. (change a to e to see the endless loop!)

Cheers,

Dave

LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{

     if(nCode < 0)
     {      
        CallNextHookEx(hMsghook, nCode, wParam, lParam);
        return 0;
     }    

     if (bProcess == TRUE){

          LPMSG lpMsg = (LPMSG) lParam;

          if(lpMsg->message == WM_CHAR && wParam == PM_REMOVE && lpMsg->wParam == 'a')
          {
               // Lock messages...
               bProcess = FALSE;
               PostMessage(lpMsg->hwnd,WM_KEYDOWN,'c',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_CHAR,'c',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_KEYUP,'c',lpMsg->lParam);

               PostMessage(lpMsg->hwnd,WM_KEYDOWN,'h',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_CHAR,'h',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_KEYUP,'h',lpMsg->lParam);

               PostMessage(lpMsg->hwnd,WM_KEYDOWN,'e',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_KEYUP,'e',lpMsg->lParam);

               PostMessage(lpMsg->hwnd,WM_KEYDOWN,'e',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_KEYUP,'e',lpMsg->lParam);

               PostMessage(lpMsg->hwnd,WM_KEYDOWN,'s',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_CHAR,'s',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_KEYUP,'s',lpMsg->lParam);

               PostMessage(lpMsg->hwnd,WM_KEYDOWN,'e',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
               PostMessage(lpMsg->hwnd,WM_KEYUP,'e',lpMsg->lParam);

               bProcess = TRUE;
          }
          //return CallNextHookEx(hMsghook, nCode, wParam, lParam);
          return TRUE; // Swallow message
     }
     else
     {
          return CallNextHookEx(hMsghook, nCode, wParam, lParam);
     }

}
Well, first of all you need to move that bProcess.  Since the message calls are PostMessage, the hook returns before any of them are called.

Instead you need to set the bProcess and, when the character loops back, *then* ignore it.  If the character appears twice, use an int and ignore it twice.  There's probably a nice way to do this with semaphores, but that's probably a bit more complicated than you need here.

Also, as a alast resort, you can always change the message.  WM_NULL is the NOP message, so just use that.  Not as elegant, but at least we can be sure it will work.

So the inner part of the hook should probably look like


     LPMSG lpMsg = (LPMSG) lParam;

     if(lpMsg->message == WM_CHAR && wParam == PM_REMOVE && lpMsg->wParam == 'a')
     {
         if (bProcess)
         {
              // Lock messages...
              bProcess = FALSE;
              PostMessage(lpMsg->hwnd,WM_KEYDOWN,'c',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_CHAR,'c',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_KEYUP,'c',lpMsg->lParam);

              PostMessage(lpMsg->hwnd,WM_KEYDOWN,'h',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_CHAR,'h',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_KEYUP,'h',lpMsg->lParam);

              PostMessage(lpMsg->hwnd,WM_KEYDOWN,'e',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_KEYUP,'e',lpMsg->lParam);

              PostMessage(lpMsg->hwnd,WM_KEYDOWN,'e',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_KEYUP,'e',lpMsg->lParam);

              PostMessage(lpMsg->hwnd,WM_KEYDOWN,'s',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_CHAR,'s',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_KEYUP,'s',lpMsg->lParam);

              PostMessage(lpMsg->hwnd,WM_KEYDOWN,'e',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
              PostMessage(lpMsg->hwnd,WM_KEYUP,'e',lpMsg->lParam);

              lpMsg->message = WM_NULL;
        }
        else
            bProcess = TRUE;
     }
     return CallNextHookEx(hMsghook, nCode, wParam, lParam);
;
 
Cheers for the mods. Once these were implemented, some improvement was seen over the original version. However now, it will not show the original character the first time, but it ONLY shows the original character the second time it is pressed. The third time is like the first.

Still the program is adding bizarre characters to the end as well!

Here is the output after typing 4 'a's:

"cheese3388555555acheese3388555555a"

It's getting closer!

Cheers,

Dave
ASKER CERTIFIED SOLUTION
Avatar of KurtVon
KurtVon

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Cheers! We are very close to the final answer!

The only problem i can see now is if there is a character in the replacement series of characters that was the same as the original character. I guess i'll be able to do another boolean case, but not quite sure where to put it so that it still processes ok...

Here's the app that now works if the char does not equal one of the replacement chars:

Cheers,

Dave

if(nCode < 0)
     {      
        CallNextHookEx(hMsghook, nCode, wParam, lParam);
        return 0;
     }    

     LPMSG lpMsg = (LPMSG) lParam;

     if(wParam == PM_REMOVE && lpMsg->message == WM_CHAR)
     {
          if (bProcess)
          {
               // Lock messages...
               if (lpMsg->wParam == 'a'){
                    bProcess = FALSE;
                   
                    PostMessage(lpMsg->hwnd,WM_CHAR,'c',lpMsg->lParam);

                    PostMessage(lpMsg->hwnd,WM_CHAR,'h',lpMsg->lParam);
                   
                    PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
                   
                    PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
                   
                    PostMessage(lpMsg->hwnd,WM_CHAR,'s',lpMsg->lParam);
                   
                    PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
                   
                    lpMsg->message = WM_NULL;
               }
          }
          else
          {
               bProcess = TRUE;
          }
     }
     return CallNextHookEx(hMsghook, nCode, wParam, lParam);
Try swapping it the position of the boolean process test:

if(nCode < 0)
    {      
       CallNextHookEx(hMsghook, nCode, wParam, lParam);
       return 0;
    }    

    LPMSG lpMsg = (LPMSG) lParam;

    if(wParam == PM_REMOVE && lpMsg->message == WM_CHAR)
    {
              // Lock messages...
         if (lpMsg->wParam == 'a'){
              if (bProcess)
              {
                   bProcess = FALSE;
                   
                   PostMessage(lpMsg->hwnd,WM_CHAR,'c',lpMsg->lParam);

                   PostMessage(lpMsg->hwnd,WM_CHAR,'h',lpMsg->lParam);
                   
                   PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
                   
                   PostMessage(lpMsg->hwnd,WM_CHAR,'a',lpMsg->lParam);
                   
                   PostMessage(lpMsg->hwnd,WM_CHAR,'s',lpMsg->lParam);
                   
                   PostMessage(lpMsg->hwnd,WM_CHAR,'e',lpMsg->lParam);
                   
                   lpMsg->message = WM_NULL;
              }
              else
              {
                   bProcess = TRUE;
              }
         }
    }
    return CallNextHookEx(hMsghook, nCode, wParam, lParam);


Note that this version has an 'a' in it.  The boolean test should not be used at all if it doesn't.  If there is more than one 'a' then use a numeric counter, set it to teh number of 'a's where the boolean is set to false, decrement it where the boolean is set to true, and only process the character when the numeric value is zero.

I think if you look at it for a while you will see how that works.

Ok i should be right now...hope it has been a help for others out there as well!

Cheers,

Dave
Thanks for the assistance and sticking with the problem!