diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index c8cd993..46495cb 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -150,7 +150,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 + m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &394699778 MonoBehaviour: @@ -165,78 +165,5 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: WorkInBackground: 1 + Async: 0 InterceptMessages: 0 ---- !u!1 &1620130889 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1620130893} - - component: {fileID: 1620130892} - m_Layer: 0 - m_Name: MainCamera - m_TagString: MainCamera - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!20 &1620130892 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1620130889} - m_Enabled: 1 - serializedVersion: 2 - m_ClearFlags: 1 - m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} - m_projectionMatrixMode: 1 - m_GateFitMode: 2 - m_FOVAxisMode: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 - m_NormalizedViewPortRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - near clip plane: 0.3 - far clip plane: 1000 - field of view: 60 - orthographic: 0 - orthographic size: 5 - m_Depth: -1 - m_CullingMask: - serializedVersion: 2 - m_Bits: 4294967295 - m_RenderingPath: -1 - m_TargetTexture: {fileID: 0} - m_TargetDisplay: 0 - m_TargetEye: 3 - m_HDR: 1 - m_AllowMSAA: 1 - m_AllowDynamicResolution: 0 - m_ForceIntoRT: 0 - m_OcclusionCulling: 1 - m_StereoConvergence: 10 - m_StereoSeparation: 0.022 ---- !u!4 &1620130893 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1620130889} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 1, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 0 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scripts/LogRawKeyInput.cs b/Assets/Scripts/LogRawKeyInput.cs index e3fc63d..96099e4 100644 --- a/Assets/Scripts/LogRawKeyInput.cs +++ b/Assets/Scripts/LogRawKeyInput.cs @@ -4,11 +4,12 @@ public class LogRawKeyInput : MonoBehaviour { public bool WorkInBackground; + public bool Async; public bool InterceptMessages; private void OnEnable () { - RawKeyInput.Start(WorkInBackground); + RawKeyInput.Start(WorkInBackground, Async); RawKeyInput.OnKeyUp += LogKeyUp; RawKeyInput.OnKeyDown += LogKeyDown; } diff --git a/Assets/UnityRawInput/Runtime/HookArgs.cs b/Assets/UnityRawInput/Runtime/HookArgs.cs new file mode 100644 index 0000000..90cecc5 --- /dev/null +++ b/Assets/UnityRawInput/Runtime/HookArgs.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.InteropServices; + +namespace UnityRawInput +{ + [StructLayout(LayoutKind.Sequential)] + public struct KeyboardArgs + { + public uint Code; + public uint ScanCode; + public KeyboardFlags Flags; + public uint Time; + public UIntPtr ExtraInfo; + + public static KeyboardArgs FromPtr (IntPtr ptr) + { + return (KeyboardArgs)Marshal.PtrToStructure(ptr, typeof(KeyboardArgs)); + } + } + + [Flags] + public enum KeyboardFlags : uint + { + Extended = 0x01, + Injected = 0x10, + AltDown = 0x20, + Up = 0x80, + } +} diff --git a/Assets/UnityRawInput/Runtime/KbDllHookStruct.cs.meta b/Assets/UnityRawInput/Runtime/HookArgs.cs.meta similarity index 100% rename from Assets/UnityRawInput/Runtime/KbDllHookStruct.cs.meta rename to Assets/UnityRawInput/Runtime/HookArgs.cs.meta diff --git a/Assets/UnityRawInput/Runtime/KbDllHookStruct.cs b/Assets/UnityRawInput/Runtime/KbDllHookStruct.cs deleted file mode 100644 index 4dadeca..0000000 --- a/Assets/UnityRawInput/Runtime/KbDllHookStruct.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace UnityRawInput -{ - [StructLayout(LayoutKind.Sequential)] - public struct KBDLLHOOKSTRUCT - { - public uint vkCode; - public uint scanCode; - public KBDLLHOOKSTRUCTFlags flags; - public uint time; - public UIntPtr dwExtraInfo; - - public static KBDLLHOOKSTRUCT CreateFromPtr (IntPtr ptr) - { - return (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(ptr, typeof(KBDLLHOOKSTRUCT)); - } - } - - [Flags] - public enum KBDLLHOOKSTRUCTFlags : uint - { - LLKHF_EXTENDED = 0x01, - LLKHF_INJECTED = 0x10, - LLKHF_ALTDOWN = 0x20, - LLKHF_UP = 0x80, - } -} diff --git a/Assets/UnityRawInput/Runtime/RawKeyInput.cs b/Assets/UnityRawInput/Runtime/RawKeyInput.cs index cf6f5fa..652e39d 100644 --- a/Assets/UnityRawInput/Runtime/RawKeyInput.cs +++ b/Assets/UnityRawInput/Runtime/RawKeyInput.cs @@ -1,5 +1,8 @@ -using System; +using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AOT; namespace UnityRawInput { @@ -17,7 +20,7 @@ public static class RawKeyInput /// /// Whether the service is running and input messages are being processed. /// - public static bool IsRunning => hookPtr != IntPtr.Zero; + public static bool IsRunning => cts != null && !cts.IsCancellationRequested; /// /// Whether any key is currently pressed. /// @@ -32,18 +35,26 @@ public static class RawKeyInput public static bool InterceptMessages { get; set; } private static readonly HashSet pressedKeys = new HashSet(); - private static IntPtr hookPtr = IntPtr.Zero; + private static readonly List hooks = new List(); + private static SynchronizationContext unityContext; + private static CancellationTokenSource cts; /// /// Initializes the service and starts processing input messages. /// /// Whether input messages should be handled when the application is not in focus. - /// Whether the service started successfully. - public static bool Start (bool workInBackground) + /// Whether to start the service in a background thread. + public static void Start (bool workInBackground, bool async) { - if (IsRunning) return false; + if (!InterceptMessages) return; + if (IsRunning) return; + cts = new CancellationTokenSource(); + unityContext = SynchronizationContext.Current; WorkInBackground = workInBackground; - return SetHook(); + if (async) + try { Task.Run(() => ListenHooksAsync(cts.Token)); } + catch (OperationCanceledException) { } + else ListenHooks(); } /// @@ -51,7 +62,8 @@ public static bool Start (bool workInBackground) /// public static void Stop () { - RemoveHook(); + cts?.Cancel(); + RemoveHooks(); pressedKeys.Clear(); } @@ -63,32 +75,43 @@ public static bool IsKeyDown (RawKey key) return pressedKeys.Contains(key); } - private static bool SetHook () + private static async Task ListenHooksAsync (CancellationToken token) { - if (hookPtr == IntPtr.Zero) - { - if (WorkInBackground) hookPtr = Win32API.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, HandleLowLevelHookProc, IntPtr.Zero, 0); - else hookPtr = Win32API.SetWindowsHookEx(HookType.WH_KEYBOARD, HandleHookProc, IntPtr.Zero, (int)Win32API.GetCurrentThreadId()); - } + ListenHooks(); + while (!token.IsCancellationRequested) + await Task.Delay(10, token); + } - if (hookPtr == IntPtr.Zero) return false; + private static void ListenHooks () + { + hooks.Add(SetKeyboardHook()); + hooks.Add(SetMouseHook()); + } - return true; + private static void RemoveHooks () + { + foreach (var pointer in hooks) + if (pointer != IntPtr.Zero) + Win32API.UnhookWindowsHookEx(pointer); + hooks.Clear(); } - private static void RemoveHook () + private static IntPtr SetKeyboardHook () { - if (hookPtr != IntPtr.Zero) - { - Win32API.UnhookWindowsHookEx(hookPtr); - hookPtr = IntPtr.Zero; - } + if (WorkInBackground) return Win32API.SetWindowsHookEx(HookType.WH_KEYBOARD_LL, HandleLowLevelKeyboardProc, IntPtr.Zero, 0); + return Win32API.SetWindowsHookEx(HookType.WH_KEYBOARD, HandleKeyboardProc, IntPtr.Zero, (int)Win32API.GetCurrentThreadId()); } - [AOT.MonoPInvokeCallback(typeof(Win32API.HookProc))] - private static int HandleHookProc (int code, IntPtr wParam, IntPtr lParam) + private static IntPtr SetMouseHook () { - if (code < 0) return Win32API.CallNextHookEx(hookPtr, code, wParam, lParam); + if (WorkInBackground) return Win32API.SetWindowsHookEx(HookType.WH_MOUSE_LL, HandleMouseProc, IntPtr.Zero, 0); + return Win32API.SetWindowsHookEx(HookType.WH_MOUSE, HandleMouseProc, IntPtr.Zero, (int)Win32API.GetCurrentThreadId()); + } + + [MonoPInvokeCallback(typeof(Win32API.HookProc))] + private static int HandleKeyboardProc (int code, IntPtr wParam, IntPtr lParam) + { + if (code < 0) return Win32API.CallNextHookEx(IntPtr.Zero, code, wParam, lParam); var isKeyDown = ((int)lParam & (1 << 31)) == 0; var key = (RawKey)wParam; @@ -96,34 +119,54 @@ private static int HandleHookProc (int code, IntPtr wParam, IntPtr lParam) if (isKeyDown) HandleKeyDown(key); else HandleKeyUp(key); - return InterceptMessages ? 1 : Win32API.CallNextHookEx(hookPtr, 0, wParam, lParam); + return Win32API.CallNextHookEx(IntPtr.Zero, 0, wParam, lParam); } - [AOT.MonoPInvokeCallback(typeof(Win32API.HookProc))] - private static int HandleLowLevelHookProc (int code, IntPtr wParam, IntPtr lParam) + [MonoPInvokeCallback(typeof(Win32API.HookProc))] + private static int HandleLowLevelKeyboardProc (int code, IntPtr wParam, IntPtr lParam) { - if (code < 0) return Win32API.CallNextHookEx(hookPtr, code, wParam, lParam); + if (code < 0) return Win32API.CallNextHookEx(IntPtr.Zero, code, wParam, lParam); - var kbd = KBDLLHOOKSTRUCT.CreateFromPtr(lParam); + var kbd = KeyboardArgs.FromPtr(lParam); var keyState = (RawKeyState)wParam; - var key = (RawKey)kbd.vkCode; + var key = (RawKey)kbd.Code; if (keyState == RawKeyState.KeyDown || keyState == RawKeyState.SysKeyDown) HandleKeyDown(key); else HandleKeyUp(key); - return InterceptMessages ? 1 : Win32API.CallNextHookEx(hookPtr, 0, wParam, lParam); + return Win32API.CallNextHookEx(IntPtr.Zero, 0, wParam, lParam); + } + + [MonoPInvokeCallback(typeof(Win32API.HookProc))] + private static int HandleMouseProc (int code, IntPtr wParam, IntPtr lParam) + { + if (code < 0) return Win32API.CallNextHookEx(IntPtr.Zero, code, wParam, lParam); + var state = (RawMouseState)wParam; + if (state == RawMouseState.LeftButtonDown) HandleKeyDown(RawKey.LeftButton); + else if (state == RawMouseState.MiddleButtonDown) HandleKeyDown(RawKey.MiddleButton); + else if (state == RawMouseState.RightButtonDown) HandleKeyDown(RawKey.RightButton); + else if (state == RawMouseState.LeftButtonUp) HandleKeyUp(RawKey.LeftButton); + else if (state == RawMouseState.MiddleButtonUp) HandleKeyUp(RawKey.MiddleButton); + else if (state == RawMouseState.RightButtonUp) HandleKeyUp(RawKey.RightButton); + else return Win32API.CallNextHookEx(IntPtr.Zero, code, wParam, lParam); + return Win32API.CallNextHookEx(IntPtr.Zero, 0, wParam, lParam); } private static void HandleKeyDown (RawKey key) { var added = pressedKeys.Add(key); - if (added && OnKeyDown != null) OnKeyDown.Invoke(key); + if (added) unityContext.Send(InvokeOnUnityThread, key); + void InvokeOnUnityThread (object obj) => OnKeyDown?.Invoke((RawKey)obj); } private static void HandleKeyUp (RawKey key) { + if (!pressedKeys.Contains(key)) + HandleKeyDown(key); + pressedKeys.Remove(key); - if (OnKeyUp != null) OnKeyUp.Invoke(key); + unityContext.Send(InvokeOnUnityThread, key); + void InvokeOnUnityThread (object obj) => OnKeyUp?.Invoke((RawKey)obj); } } } diff --git a/Assets/UnityRawInput/Runtime/RawMouseState.cs b/Assets/UnityRawInput/Runtime/RawMouseState.cs new file mode 100644 index 0000000..f687f65 --- /dev/null +++ b/Assets/UnityRawInput/Runtime/RawMouseState.cs @@ -0,0 +1,14 @@ +namespace UnityRawInput +{ + public enum RawMouseState + { + MouseMove = 0x0200, + LeftButtonDown = 0x0201, + LeftButtonUp = 0x0202, + MouseWheel = 0x020A, + MiddleButtonDown = 0x0207, + MiddleButtonUp = 0x0208, + RightButtonDown = 0x0204, + RightButtonUp = 0x0205 + } +} diff --git a/Assets/UnityRawInput/Runtime/RawMouseState.cs.meta b/Assets/UnityRawInput/Runtime/RawMouseState.cs.meta new file mode 100644 index 0000000..32553cd --- /dev/null +++ b/Assets/UnityRawInput/Runtime/RawMouseState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d98a1d9f11b44c84a258162e5ba58630 +timeCreated: 1661385339 \ No newline at end of file