SetWindowsHookEx not working when Unity player is focused

I’m trying to block the Windows key input in my Unity game using SetWindowsHookEx. While this approach works perfectly in a standard C# application, it’s not functioning as expected in Unity.

Here’s my current implementation:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public static class NativeKeyBlocker
{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private const int WM_KEYUP = 0x0101;
    private const int WM_SYSKEYDOWN = 0x0104;
    private const int WM_SYSKEYUP = 0x0105;

    private const int VK_LWIN = 0x5B;
    private const int VK_RWIN = 0x5C;

    private static IntPtr _hookID = IntPtr.Zero;
    
    public static void BlockWinKey()
    {
        if (_hookID == IntPtr.Zero)
        {
            _hookID = SetHook(HookCallback);
        }
    }

    public static void UnblockWinKey()
    {
        if (_hookID != IntPtr.Zero)
        {
            UnhookWindowsHookEx(_hookID);
            _hookID = IntPtr.Zero;
        }
    }

    private static IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        return SetWindowsHookEx(WH_KEYBOARD_LL, proc, IntPtr.Zero, 0);

        /* Working same as above line.
        using (var curProcess = Process.GetCurrentProcess())
        {
            using (var curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }
        */
    }

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 &&
            (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN
            || wParam == (IntPtr)WM_KEYUP || wParam == (IntPtr)WM_SYSKEYUP))
        {
            int vkCode = Marshal.ReadInt32(lParam);

            UnityEngine.Debug.LogError($"Key: {vkCode} event: {wParam}");

            if (vkCode == VK_LWIN || vkCode == VK_RWIN)
            {
                return (IntPtr)1; // Block the key
            }
        }

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


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

    [DllImport("user32.dll")]
    public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll")]
    public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

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

Current behavior:

  1. When Unity has focus (in editor or player):
  • HookCallback is not being called
  • Windows key still opens the Start menu
  • Game loses focus
  1. When Unity loses focus:
  • HookCallback works properly
  • Windows key is blocked as intended

I’ve tried both approaches for setting up the hook:

  • Direct SetWindowsHookEx call
  • Using GetModuleHandle with the current process

The same code works flawlessly in a regular C# application, which suggests this might be Unity-specific. What could be causing this behavior in Unity, and what’s the proper way to block specific key inputs in this context?

It’s been some time since I last used SetWindowsHookEx. However I can tell that you’re using it wrong. You need to specify a module handle when your hook procedure is located in a separate DLL and this DLL will be injected into all applications. In your specific usecase you probably want to pass NULL to the module handle (or pass NULL to GetModuleHandle) and pass the actual native ThreadID of your main thread.

Also be aware of the fact that when testing inside the Editor you (your game) is just a guest inside the Unity editor application. I would not expect such hooks to work reliably inside the editor. Most such hooks can cause issues in the editor. So test in an actual build.

Also be careful when passing a callback to a native method. You should probably store the delegate in a persistent variable. Currently you pass your “HookCallback” directly to your SetHook method. The “proc” is actually a delegate that would be up for garbage collection after SetHook returns.

Finally watch out when using IL2CPP as backend as it requires the callback to be a static method and it should have the [MonoPInvokeCallback] attribute.

Some time ago I implemented a GET_MESSAGE hook over here. I’m not sure if I ever tried the LL keyboard hook.

1 Like

In my lunch break I quickly copied your code into my Unity test project, called “BlockWinKey” in OnEnable and “UnblockWinKey” in OnDisable. For me the win key gets completely blocked. I used an older Unity version (still have 2023.1.18f1 as I don’t use Unity much recently) and I’m on Win10. I only tested in playmore inside the editor but it worked just fine. The key seems to be even blocked when I unfocus Unity manually. The win key is of course also blocked inside Unity as the low level hook will intercept the key on the OS level.

So I can’t really replicate your issue. It seems to work just fine.

1 Like

I tested it with a different version and confirmed that it works as expected. Our project is currently using version 6, so I didn’t check other versions.
Your response was very helpful. Thank you!

1 Like