Background Key Press Listener

2020-02-09 18:44发布

问题:

I've got a simple window form application that turns on capslock when I press space and turns it off if I press a letter.

Problem is that I have to focus on the window for it to work (top-most doesn't work either, top-most doesn't focus it just displaying the window above all other unfocused).

Anyone has any idea how can I make it work even if im writing in a notepad?

回答1:

Key logging can be used for naughty stuff, and manipulating Caps Lock like that seems rather strange, but since the info is already publicly available, and you know your user stories better than me, I've posted a solution.

Here's an example based on the code snippet posted from keylogger code in C# in the MSDN forum.

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

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

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

    [DllImport("user32.dll")]
    static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);

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

    [STAThread]
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        _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 static void ToggleCapsLock()
    {
        const int KEYEVENTF_EXTENDEDKEY = 0x1;
        const int KEYEVENTF_KEYUP = 0x2;

        UnhookWindowsHookEx(_hookID);
        keybd_event(0x14, 0x45, KEYEVENTF_EXTENDEDKEY, (UIntPtr)0);
        keybd_event(0x14, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, (UIntPtr)0);
        _hookID = SetHook(_proc);
    }

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            if (lastKeyWasLetter)
            {
                if (Control.IsKeyLocked(System.Windows.Forms.Keys.CapsLock))
                {
                    ToggleCapsLock();
                }
                lastKeyWasLetter = false;
            }
            Keys key = (Keys)Marshal.ReadInt32(lParam);            
            if (key == Keys.Space)
            {
                if (!Control.IsKeyLocked(System.Windows.Forms.Keys.CapsLock))
                {
                    ToggleCapsLock();
                }
            }
            else if (key >= Keys.A && key <= Keys.Z)
            {
                lastKeyWasLetter = true;
            }
        }
        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }
}

Paste that into a new Windows application's Program.cs in Visual Studio to try it out.

If you intercept a key down event to turn Caps Lock on and off, then the event is intercepted before the application handles it. This means that turning Caps Lock off when a letter key is pressed will result in the application you are typing in receiving a lower case letter, even directly after a space.

I've assumed you are trying to force the capitalization of the first letter in each word (and if so, you may need to handle other keys such as Return too), so my snippet will only turn Caps Lock off on the next key down event following a letter being pressed. Note that you can't just try and capture the key up, as when typing fast you may hold the initial key down until after you've pressed the following key.