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