views:

481

answers:

2

I'm writing a program that sits in the systray. It will have a keyboard hook into all typed input, in any open application. I want it to intercept any typed input, run code on that input, and then send the new 'correct' character to be typed. This new character will be what shows up in the app that has focus.

I found this code via another question on StackOverflow, and it looks good. http://blogs.msdn.com/toub/archive/2006/05/03/589423.aspx

I added it as a class in my solution, created a new instance of it in the Form1 constructor, and it compiles. Any typed input into any app shows up in the Visual Studio output pane, one character at a time.

The problem is that this code is way over my head. That's the drawback of using this great concise code for my keyboard hooks: I haven't had the trial and error to teach me how to use it.

I envisioned this program working something like this:

key is pressed, triggers an event

get key information from the event

do computation on the key information, pick the character to be typed

send that key to the relevant program

my character is typed rather than the original keypress character

How does this code fit in this chain of events? I need to read the input characters before they are inputed. Then, where would the code to analyse the input and decide the correct input live? Finally, what's the correct 'sendInput()' type command to invoke to send a character to whatever app has focus?

Here's the full code for reference:

using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class InterceptKeys
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static LowLevelKeyboardProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;

public static void Main()
{
    _hookID = SetHook(_proc);
    Application.Run();
    UnhookWindowsHookEx(_hookID);
}

private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
    using (Process curProcess = Process.GetCurrentProcess())
    using (ProcessModule curModule = curProcess.MainModule)
    {
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
            GetModuleHandle(curModule.ModuleName), 0);
    }
}

private delegate IntPtr LowLevelKeyboardProc(
    int nCode, IntPtr wParam, IntPtr lParam);

private static IntPtr HookCallback(
    int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
    {
        int vkCode = Marshal.ReadInt32(lParam);
        Console.WriteLine((Keys)vkCode);
    }
    return CallNextHookEx(_hookID, nCode, wParam, lParam);
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
    LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
    IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}

Thanks for any advice you have! Or, is there a better way to go about this?

Thanks!


UPDATE


My HookCallback method now looks like this:

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            //Console.WriteLine((Keys)vkCode);

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

        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

Shoot... adding Marshal.StructureToPtr is close, but results in a A first chance exception of type 'System.ArgumentException' occurred in foobar.exe error.


VERSION WITH UNSAFE CODE, NO MARSHALLING, etc. (still doesn't output 'Z'!)


    unsafe private static IntPtr HookCallback(int nCode, IntPtr wParam, void* lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            KBDLLHOOKSTRUCT* replacementKey = (KBDLLHOOKSTRUCT*)lParam;
            replacementKey->vkCode = 90;
        }
        return CallNextHookEx(_hookID, nCode, wParam, (IntPtr) lParam);
    }
+1  A: 

How does this code fit in this chain of events? I need to read the input characters before they are inputed. Then, where would the code to analyse the input and decide the correct input live?

The HookCallback method is the method which you're installing to intercept keystrokes.

Finally, what's the correct 'sendInput()' type command to invoke to send a character to whatever app has focus?

To modify the character which is passed to other processes, modify the parameters (nCode, wParam, and/or lParam) before you pass them to the CallNextHookEx method.

ChrisW
+1  A: 

At the moment, this code just writes to the console. This happens in line 4 of the HookCallback method. So you need to replace that line with your own code.

However, the info you're given isn't in a very C# friendly format. You need to look at the SDK docs for LowLevelKeyboardProc to figure out how to unpack it. It turns out all the good stuff is in lParam, which is a pointer to a KBDLLHOOKSTRUCT structure.

So your code needs to marshal that to a C# equivalent. You need to declare a KBDLLHOOKSTRUCT structure, or see if there's one on pinvoke.net, and use Marshal.PtrToStructure to unpack it. You can then party on this structure as much as Windows will allow -- I think modifying the event data is a better bet than trying to kill the keyboard event and then simulate a new one (for a start, I think the simulated "send keys" would run straight into the hook itself!) -- however I haven't tried this to see what it possible. The SDK struct does include a virtual key code and scan code, so you may be able to replace them to achieve the effect you want, but this will probably take some trial and error, and will be very specific to your app.

itowlson
Thanks for the answer, I understand his code a lot more now. Where should this KBDLLHOOKSTRUCT be declared? Is that something that has to be put in its' own file? Why does it need to be 'unpacked', instead of like keyboardHook.vkcode = 'e' or whatever? Once I have my declared KBDLLHOOKSTRUCT how I want it, how do I copy it into the original generated one? Will the act of going "compHook = myHook" actually cause the computer to type a different character, or do I have to do something else? Or am I thinking of that part entirely wrong? Sorry about all the questions, I really appreciate it.
cksubs
You *will* be able to write "keyboardHook.vkCode = 'e' or whatever," *once you have a keyboardHook object of type KBDLLHOOKSTRUCT*. But all this code gives you is an IntPtr (lParam). So you need to turn that into a KBDLLHOOKSTRUCT with a vkCode member. Then you use Marshal.PtrToStructure to convert the IntPtr into a KBDLLHOOKSTRUCT -- called, say, keyboardHook. Where you declare BDLLHOOKSTRUCT is a matter of taste, but I'd declare it as a private nested struct of the InterceptKeys class (because it's an implementation detail of that class).
itowlson
Ok, I have my KBDLLHOOKSTRUCT object from the lParam. I then changed the replacementKey.vkCode to another character. Is that all that needs to be changed to allow for another character? Finally, how do I get the needed `return CallNextHookEx(_hookID, nCode, wParam, lParam);` values from my KBB object?
cksubs
Oh and I updated my original question too. Does that look ok so far?
cksubs
Your update looks okay, but I think you will need to do a bit more to push the changed vkCode into the original lParam memory block. (Marshal.PtrToStructure makes a copy, so you're modifying the copy, not the original.) I *think* you would be able to do this by calling Marshal.StructureToPtr to copy your modified KBB back over the top of the original one. **But I have not tried this and you may need to do some experimentation.** You can then call CallNextHookEx just as you do at the moment.
itowlson
Ok, that makes sense and answers my question of how the original keystroke is being modified. Unfortunately it results in the error in my update above. I'm trying to figure that out now...
cksubs
I'm getting out of my depth here, but one last suggestion: put all this in an `unsafe` block (you may need to enable unsafe code in the project properties) and declare lParam as a `void*` instead of an IntPtr. When you ned a KBB, cast the `void*` to a `KBDLLHOOKSTRUCT*` (this is **instead of** calling Marshal.PtrToStructure). Then write `replacementKey->vkCode = 90;` (note use of `->` instead of `.`). The idea here is that by using the unsafe pointer you are manipulating the KBB *in place* rather than mucking around with the marshalling stuff. Hope this makes sense - post comment if not!
itowlson
This link may help with the above suggestion: http://msdn.microsoft.com/en-us/library/50sbeks5.aspx
itowlson
I feel like I'm so close but it's still outputting the damn original key. I updated the question with your newest idea, that's correct right? Any idea?I also posted a new question here, with a better form of marshalling. Trying to pinpoint why my designated letter isn't coming out. You've helped enough though, thanks so much!http://stackoverflow.com/questions/2062978
cksubs