views:

531

answers:

2

I found this keyboard hook code, which I'm trying to slightly modify for my purposes: http://blogs.msdn.com/toub/archive/2006/05/03/589423.aspx

As an overview, I want to have the user press a key, say 'E', and have the keyboard return a different character, 'Z', to whatever app is in focus.

The relevant method I changed now looks like:

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            //The truely typed character:
            int vkCode = Marshal.ReadInt32(lParam);
            Console.WriteLine((Keys)vkCode);

            KBDLLHOOKSTRUCT replacementKey = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
            replacementKey.vkCode = 90; // char 'Z'
            Marshal.StructureToPtr(replacementKey, lParam, false);

            //Now changed to my set character
            vkCode = Marshal.ReadInt32(lParam);
            Console.WriteLine((Keys)vkCode);
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

The console correctly outputs this as:

E
Z
T
Z
G
Z
etc.

HOWEVER, the in focus app still types 'E' instead of 'Z'. Why? I changed the hooked keyboard input to contain 'Z' instead of 'E', and the console lines show that it was changed correctly!

As I understand it, calling the return CallNextHookEx(_hookID, nCode, wParam, lParam); is what sends the "print this now" command to the open app. Is that not how it works? Is there something that's preventing me from typing the character I want? I know apps like AutoHotkey take an input key, check it, and return a different character. How do I do the same here?

Thanks!

+1  A: 

You have, most likely, installed the hook "thread wide" and not "system wide", which means that the key translation will occur only for the thread installing the hook.

In order to install it "system wide" you will need two pieces: one dll having the "hook provider" and an exe managing it. Here is a good tutorial http://www.codeproject.com/KB/system/hooksys.aspx and here an example: http://www.codeguru.com/cpp/com-tech/shell/article.php/c4509/

But: 1. Installing system wide hooks can seriously screw up you system (make sure that you forward the keys that you don't translate). 2. Please... don't create another keylogger

Madalina Dragomir
He uses the low-level keyboard hook, not the ordinary keyboard hook. The low-level hook doesn't need a separate dll that gets loaded into every process. From http://msdn.microsoft.com/en-us/library/ms644985.aspx - However, the WH_KEYBOARD_LL hook is not injected into another process. Instead, the context switches back to the process that installed the hook and it is called in its original context. Then the context switches back to the application that generated the event.
Pent Ploompuu
So... does that mean I'm using this correctly? :)
cksubs
Lots of information on implementing/using WH_KEYBOARD_LL (and lots of discussion on issues on detecting control-modifiers, passing key information on to the next app or blocking, etc. in the comments on) CodeProject : http://www.codeproject.com/KB/system/CSLLKeyboard.aspx "Low-level Windows API hooks from C# to stop unwanted keystrokes" By Emma Burrows. 2007 article.
BillW
+1  A: 

I've done this before but a little different.
Instead of trying to change the parameters sent to CallNextHookEx, I 'swallowed' the key press (you can do this by returning a nonzero value from the hook procedure to prevent subsequent procedures from being called).

Then I used SendInput to send the new key that I wanted to 'inject'.

So basically it works like this:

  • Hook procedure identifies a target key is pressed
  • Call to SendInput, with the new key
  • Return 1 from the hook procedure to ignore the original key

Be careful of cyclic redirects, i.e. 'a' redirected to 'b' redirected to 'a', it can easily blow up ;)

Idan K
Cool, I'v successfully ignored the original key with return `(IntPtr)1;` Now, how do I `SendInput`? Is there a specific `using XXX` I need to include in the header, or other code I need to add in order to use the command?
cksubs
pinvoke.net has all the code needed to call `SendInput` - http://www.pinvoke.net/default.aspx/user32/SendInput.html
Pent Ploompuu
Ok, got it using `[DllImport("coredll.dll", SetLastError = true)] static extern uint SendInput(uint cInputs, /* [MarshalAs(UnmanagedType.LPArray)] */ KEYBOARDINPUT[] inputs, int cbSize);` and a KEYBOARDINPUT structure. Now I'm working on passing SendInput() the right parameters.
cksubs
coredll.dll is for Windows CE, use the user32.dll variant and you might want to have a look at this also: http://inputsimulator.codeplex.com/
Pent Ploompuu
Oh that looks awesome. Really wish they included a 'how to get this working in your code' page on that site though. I'm working through it though... I have to import that DLL like I did for the one above, right? Do I have to do anything with the other files in the .zip?
cksubs
not exactly. the first DLL you 'imported' was a native one, this is a managed one so you only need to add a reference to it (right click project->Add Reference->Browse...) and start using it. if you don't want to use a separate DLL, using SendInput is trivial and should only be a few lines
Idan K