Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 2 additions & 75 deletions Assets/Scenes/SampleScene.unity
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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}
3 changes: 2 additions & 1 deletion Assets/Scripts/LogRawKeyInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
29 changes: 29 additions & 0 deletions Assets/UnityRawInput/Runtime/HookArgs.cs
Original file line number Diff line number Diff line change
@@ -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,
}
}
29 changes: 0 additions & 29 deletions Assets/UnityRawInput/Runtime/KbDllHookStruct.cs

This file was deleted.

111 changes: 77 additions & 34 deletions Assets/UnityRawInput/Runtime/RawKeyInput.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using AOT;

namespace UnityRawInput
{
Expand All @@ -17,7 +20,7 @@ public static class RawKeyInput
/// <summary>
/// Whether the service is running and input messages are being processed.
/// </summary>
public static bool IsRunning => hookPtr != IntPtr.Zero;
public static bool IsRunning => cts != null && !cts.IsCancellationRequested;
/// <summary>
/// Whether any key is currently pressed.
/// </summary>
Expand All @@ -32,26 +35,35 @@ public static class RawKeyInput
public static bool InterceptMessages { get; set; }

private static readonly HashSet<RawKey> pressedKeys = new HashSet<RawKey>();
private static IntPtr hookPtr = IntPtr.Zero;
private static readonly List<IntPtr> hooks = new List<IntPtr>();
private static SynchronizationContext unityContext;
private static CancellationTokenSource cts;

/// <summary>
/// Initializes the service and starts processing input messages.
/// </summary>
/// <param name="workInBackground">Whether input messages should be handled when the application is not in focus.</param>
/// <returns>Whether the service started successfully.</returns>
public static bool Start (bool workInBackground)
/// <param name="async">Whether to start the service in a background thread.</param>
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();
}

/// <summary>
/// Terminates the service and stops processing input messages.
/// </summary>
public static void Stop ()
{
RemoveHook();
cts?.Cancel();
RemoveHooks();
pressedKeys.Clear();
}

Expand All @@ -63,67 +75,98 @@ 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;

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);
}
}
}
14 changes: 14 additions & 0 deletions Assets/UnityRawInput/Runtime/RawMouseState.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
3 changes: 3 additions & 0 deletions Assets/UnityRawInput/Runtime/RawMouseState.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.