Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to intercept all the keyboard events and prevent losing focus in a WinForms application?

A friend of mine is blind and I've got an idea of developing a program that would let him use a PC with the help of a blind typing method and audial feedback. The experience would be much richer (making use of more keys for particular functions) and flawless (like preventing occasional focus loss) if my application could forcibly take total control over the whole keyboard input as soon as it is started (I'd put it in start-up for him). I am a WinForms C# .NET developer so I'd like to implement this in an application using this particular framework and language (don't mind wrapped WinAPI calls though).

PS: I don't mind the system keeping control over the Ctrl+Alt+Del combination, but I'd like to take control over all the other keys and combinations, including the Windows logo and standard application launcher buttons.

like image 407
Ivan Avatar asked Jul 10 '13 19:07

Ivan


2 Answers

You can use the low-level keyboard hook implementation posted here. It shouldn't steal focus away from any programs but your program can be notified when keys are pressed. This is the code from the post in case the link stops working.

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);
}

You can handle the key presses in the HookCallback event (or further abstract the main parts into a separate class and raise an event).

like image 96
keyboardP Avatar answered Sep 27 '22 19:09

keyboardP


I know this topic is old, but I found a possible solution. You can use the last answer and edit it a little bit:

Instead of calling the next hook:

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);
}

just return -1 for stopping preceding any handle to other controls:

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 -1;
}

This isn't nice but it's working. Be careful with this!

like image 39
Lord_Fry Avatar answered Sep 27 '22 19:09

Lord_Fry