K_Trenholm
K_Trenholm

Reputation: 504

C# .NET - Detecting and handling "extended" USB HID keyboard codes

I'm looking to detect and handle keycodes from a USB HID keyboard that fall outside the "normal" set of codes, i.e. codes above 100 (0x64) in a .NET Windows Forms application (.NET Framework 4.5).

Specifically, in my case I need to detect codes between 0x68 and 0x78, but I'd like to be able to detect anything up to 0xA4, which seems to be the upper limit of HID keyboard codes (aside from things like Ctrl, Alt, Win, etc.)

This question here seemed to be exactly what I was looking for, but I have had no success getting the advice on that answer to work. I have KeyPreview set to true for the form, and event handlers registered for KeyDown, KeyPress, and PreviewKeyDown, but none of them fire on reception of an 0x68 (F13) code. For now I'd just like to print the pressed key to a richtextbox control:

    public mainFrm()
    {
        InitializeComponent();
        this.KeyPreview = true;
        this.KeyDown += new KeyEventHandler(KeyDownHandler);
        this.KeyPress += new KeyPressEventHandler(KeyPressHandler);
        this.PreviewKeyDown += new PreviewKeyDownEventHandler(PreviewKeyHandler);
    }

    private void KeyPressHandler(object sender, KeyPressEventArgs e)
    {
        rtb_hidLog.AppendText("Press: " + e.KeyChar.ToString() + "\r\n");
    }

    private void KeyDownHandler(object sender, KeyEventArgs e)
    {
        rtb_hidLog.AppendText("KeyDown: "+ e.KeyCode.ToString() + "\r\n");
    }

    private void PreviewKeyHandler(object sender, PreviewKeyDownEventArgs e)
    {
        rtb_hidLog.AppendText("Preview: " + e.KeyCode.ToString() + "\r\n");
    }

I even tried overriding ProcessCmdKey (as per this question) and that also does not fire on 0x68:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        rtb_hidLog.AppendText("CmdKey: " + keyData.ToString() + "\r\n");
        return base.ProcessCmdKey(ref msg, keyData);
    }

I have a USB HID Keyboard device connected (a PSoC microcontroller as a HID keyboard) that sends the 0x68 (F13) keycode when I press a button, but it doesn't fire the PreviewKeyHander. A standard 'A' code (0x04) from the PSoC device fires the KeyDownHandler and KeyPressHandler events with no problem. I have confirmed via USB Analyzer that the 0x68 code is being sent correctly, I just can't seem to force .NET to recognize it and fire an event. Is there something I'm missing or a trick I need to do to force my application to fire an event on these codes?

I've now also tried using Interop to use the win32 API (User32.dll) to hook into the keyboard input, and that also does not work. I get the same results; the hooked event will fire for all the keys on my keyboard, but anything not in that range does not fire a key pressed event.

My USB HID descriptor for the keyboard device, in case there is some issue there:

Enter image description here

Upvotes: 1

Views: 2297

Answers (1)

Ibrahim Magdy
Ibrahim Magdy

Reputation: 127

You can use a keyboard interceptor "in a separate DLL project that is referenced in your application" that is used by Form like this:

public delegate IntPtr KeyBoardHook( int nCode, IntPtr wParam, IntPtr lParam);

public class InterceptKeys : IDisposable
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private KeyBoardHook _proc;
    public  event KeyBoardHook  OnKeyBoardKeyClicked;
    private static IntPtr _hookID = IntPtr.Zero;

    public InterceptKeys()
    {
        _proc = HookCallback;
        _hookID = SetHook(_proc);
        if(_hookID == IntPtr.Zero)
        {
            throw new Exception($"Error Happened [{Marshal.GetLastWin32Error()}]");
        }

    }

    public void Dispose()
    {
        UnhookWindowsHookEx(_hookID);
    }

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

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


    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook,
        KeyBoardHook 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);

}



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

Upvotes: 0

Related Questions