Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture keypress in Unity when Unity doesn't have the input focus

I need Unity to capture ALL key presses, even if Unity doesn't have the focus.

I've tried to use:

Input.KeyPress()

But this seems to only work if Unity has the focus of the user's input. I need it to work when it doesn't have the focus, such as when I'm looking at / working with another Windows program.

PS: I already turned on the "Run in Background" option in player preferences.

like image 857
NERL Avatar asked Mar 31 '16 17:03

NERL


People also ask

How do I get the input from the spacebar in unity?

So you would instead use Input. GetButton("Jump") and define in Edit -> Project Settings -> Input that "Jump" is mapped to the positive key "space" (Unity will auto-supply some default keys, and Jump:Space is one). Additionally you can switch your Input. GetKey("a") / Input.


2 Answers

This is totally possible! Although, there is no way to do it by using only the tools in-built into Unity3D. You will have to use native libraries to do that.

The example below hooks the hook chain with a hook type of WH_KEYBOARD, which corresponds to a message-level keyboard hook. You can read more on SetWindowsHookEx and different types [ here][1].

You can check the parameters that are being received on hooking such a message type (WH_KEYBOARD) [here][2]

using UnityEngine;
using System;
using System.Collections;
using System.Runtime.InteropServices;

public class KBHooks : MonoBehaviour
{
    [DllImport("user32")]
    protected static extern IntPtr SetWindowsHookEx(
        HookType code, HookProc func, IntPtr hInstance, int threadID);

    [DllImport("user32")]
    protected static extern int UnhookWindowsHookEx(
        IntPtr hhook);

    [DllImport("user32")]
    protected static extern int CallNextHookEx(
        IntPtr hhook, int code, IntPtr wParam, IntPtr lParam);

    // Hook types. To hook the keyboard we only need WH_KEYBOARD
    protected enum HookType : int
    {
        WH_JOURNALRECORD = 0,
        WH_JOURNALPLAYBACK = 1,
        WH_KEYBOARD = 2,
        WH_GETMESSAGE = 3,
        WH_CALLWNDPROC = 4,
        WH_CBT = 5,
        WH_SYSMSGFILTER = 6,
        WH_MOUSE = 7,
        WH_HARDWARE = 8,
        WH_DEBUG = 9,
        WH_SHELL = 10,
        WH_FOREGROUNDIDLE = 11,
        WH_CALLWNDPROCRET = 12,
        WH_KEYBOARD_LL = 13,
        WH_MOUSE_LL = 14
    }

    protected IntPtr m_hhook = IntPtr.Zero;
    protected HookType m_hookType = HookType.WH_KEYBOARD;

    protected delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

    //We install the hook and hold on to the hook handle.
    //The handle will be need to unhook. 
    protected bool Install(HookProc cbFunc)
    {
        if (m_hhook == IntPtr.Zero)
            m_hhook = SetWindowsHookEx(
                m_hookType, 
                cbFunc, 
                IntPtr.Zero, 
                (int)AppDomain.GetCurrentThreadId());

        if (m_hhook == IntPtr.Zero)
            return false;

        return true;
    }

    protected void Uninstall()
    {
        if (m_hhook != IntPtr.Zero)
        {
            UnhookWindowsHookEx(m_hhook);
            m_hhook = IntPtr.Zero;
        }
    }

    protected int CoreHookProc(int code, IntPtr wParam, IntPtr lParam)
    {
        if (code < 0)
            return CallNextHookEx(m_hhook, code, wParam, lParam);

        Debug.Log(
            "hook code =" + code.ToString() + 
            " lparam=" + lParam.ToString() + 
            " wparam=" + wParam.ToString());

        // Yield to the next hook in the chain
        return CallNextHookEx(m_hhook, code, wParam, lParam);
    }

    // Use this for initialization
    void Start()
    {
        Debug.Log("install hook");
        Install(CoreHookProc);
    }

    void OnDisable()
    {
        Debug.Log("Uninstall hook");
        Uninstall();
    }

}

This example comes from [this blog][3].

Such a way of hooking will only work on Windows systems. If you need to make a separate hook on OS X or Linux you would need to do it in a native way in that operating system.

I cannot post more than 1 link because I lack reputation on SO. I hope one of the mods will edit my post accordingly.

 [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx
 [2]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644984(v=vs.85).aspx
 [3]: http://phardera.blogspot.com.es/2010/12/windows-hooks-in-unity3d.html
like image 94
boromak Avatar answered Oct 01 '22 20:10

boromak


I've assembled a Unity package based on the @boris-makogonyuk answer with some usability improvements. The package is available on GitHub (MIT license): https://github.com/Elringus/UnityRawInput

You can use it as follows:

Include package namespace.

using UnityRawInput;

Initialize the input service to start processing native input messages.

RawKeyInput.Start();

Optinally, you can specify whether input messages should be handled when the application is not in focus (disabled by default).

var workInBackground = true;
RawKeyInput.Start(workInBackground);

Add listeners for the input events.

RawKeyInput.OnKeyUp += HandleKeyUp;
RawKeyInput.OnKeyDown += HandleKeyDown;

private void HandleKeyUp (RawKey key) { ... }
private void HandleKeyDown (RawKey key) { ... }

You can also check whether specific key is currently pressed.

if (RawKeyInput.IsKeyDown(key)) { ... }

You can stop the service at any time.

RawKeyInput.Stop();

Don't forget to remove listeners when you no longer need them.

private void OnDisable ()
{
    RawKeyInput.OnKeyUp -= HandleKeyUp;
    RawKeyInput.OnKeyDown -= HandleKeyDown;
}
like image 28
Elringus Avatar answered Oct 01 '22 22:10

Elringus