using System;
using System.Collections.Generic;
using AOT;
using UnityEngine;
namespace UnityRawInput
{
public static class RawInput
{
///
/// Event invoked when user presses a key.
///
public static event Action OnKeyDown;
///
/// Event invoked when user releases a key.
///
public static event Action OnKeyUp;
///
/// Whether the service is running and input messages are being processed.
///
public static bool IsRunning => hooks.Count > 0;
///
/// Whether any key is currently pressed.
///
public static bool AnyKeyDown => pressedKeys.Count > 0;
///
/// Whether input messages should be handled when the application is not in focus.
///
public static bool WorkInBackground { get; set; }
///
/// Whether handled input messages should not be propagated further.
///
public static bool InterceptMessages { get; set; }
///
/// Currently pressed keys.
///
public static IReadOnlyCollection PressedKeys => pressedKeys;
private static readonly HashSet pressedKeys = new HashSet();
private static readonly List hooks = new List();
///
/// Initializes the service and starts processing input messages.
///
/// Whether the service started successfully.
public static bool Start ()
{
if (IsRunning) return false;
EnsureRunInBackgroundEnabled();
SetHooks();
return hooks.TrueForAll(h => h != IntPtr.Zero);
}
///
/// Terminates the service and stops processing input messages.
///
public static void Stop ()
{
RemoveHooks();
pressedKeys.Clear();
}
///
/// Checks whether provided key is currently pressed.
///
public static bool IsKeyDown (RawKey key)
{
return pressedKeys.Contains(key);
}
private static void SetHooks ()
{
hooks.Add(Win32API.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, HandleKeyboardProc, IntPtr.Zero, 0));
hooks.Add(Win32API.SetWindowsHookEx(HookType.WH_MOUSE_LL, HandleMouseProc, IntPtr.Zero, 0));
}
private static void RemoveHooks ()
{
foreach (var pointer in hooks)
if (pointer != IntPtr.Zero)
Win32API.UnhookWindowsHookEx(pointer);
hooks.Clear();
}
[MonoPInvokeCallback(typeof(Win32API.HookProc))]
private static int HandleKeyboardProc (int code, IntPtr wParam, IntPtr lParam)
{
if (code < 0 || !CanHandleHook()) return Win32API.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
var args = (KeyboardArgs)lParam;
var state = (RawKeyState)wParam;
var key = (RawKey)args;
if (state == RawKeyState.KeyDown || state == RawKeyState.SysKeyDown) HandleKeyDown(key);
else HandleKeyUp(key);
return InterceptMessages ? 1 : Win32API.CallNextHookEx(IntPtr.Zero, 0, wParam, lParam);
}
[MonoPInvokeCallback(typeof(Win32API.HookProc))]
private static int HandleMouseProc (int code, IntPtr wParam, IntPtr lParam)
{
if (code < 0 || !CanHandleHook()) return Win32API.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
var args = (MouseArgs)lParam;
var state = (RawMouseState)wParam;
if (state == RawMouseState.LeftButtonDown) HandleKeyDown(RawKey.LeftButton);
else if (state == RawMouseState.LeftButtonUp) HandleKeyUp(RawKey.LeftButton);
else if (state == RawMouseState.RightButtonDown) HandleKeyDown(RawKey.RightButton);
else if (state == RawMouseState.RightButtonUp) HandleKeyUp(RawKey.RightButton);
else if (state == RawMouseState.MiddleButtonDown) HandleKeyDown(RawKey.MiddleButton);
else if (state == RawMouseState.MiddleButtonUp) HandleKeyUp(RawKey.MiddleButton);
else if (state == RawMouseState.ExtraButtonDown) HandleKeyDown(GetExtraButtonKey());
else if (state == RawMouseState.ExtraButtonUp) HandleKeyUp(GetExtraButtonKey());
else if (state == RawMouseState.MouseWheel) HandleKeyDownUp(GetWheelKey());
else if (state == RawMouseState.MouseWheelHorizontal) HandleKeyDownUp(GetWheelHorizontalKey());
return InterceptMessages ? 1 : Win32API.CallNextHookEx(IntPtr.Zero, 0, wParam, lParam);
short GetWheelDelta () => (short)(args.MouseData >> 16 & 0xFFFF);
RawKey GetWheelKey () => GetWheelDelta() < 0 ? RawKey.WheelDown : RawKey.WheelUp;
RawKey GetWheelHorizontalKey () => GetWheelDelta() < 0 ? RawKey.WheelLeft : RawKey.WheelRight;
RawKey GetExtraButtonKey () => (short)(args.MouseData >> 16 & 0xFFFF) == 1 ? RawKey.ExtraButton1 : RawKey.ExtraButton2;
}
private static void HandleKeyDown (RawKey key)
{
var added = pressedKeys.Add(key);
if (added) OnKeyDown?.Invoke(key);
}
private static void HandleKeyUp (RawKey key)
{
pressedKeys.Remove(key);
OnKeyUp?.Invoke(key);
}
private static void HandleKeyDownUp (RawKey key)
{
HandleKeyDown(key);
HandleKeyUp(key);
}
private static bool CanHandleHook ()
{
return WorkInBackground || Application.isFocused;
}
// https://github.com/Elringus/UnityRawInput/issues/19#issuecomment-1227462101
private static void EnsureRunInBackgroundEnabled ()
{
if (Application.runInBackground) return;
Debug.LogWarning("Application isn't set to run in background! Not enabling this option will " +
"cause severe mouse slowdown if the window isn't in focus. Enabling behavior for this play session, " +
"but you should explicitly enable this in \"Build Settings→Player Settings→Player→Resolution and " +
"Presentation→Run In Background\".");
Application.runInBackground = true;
}
}
}