Add project files.
This commit is contained in:
360
Assets/Cryville/Common/Unity/Input/WindowsPointerHandler.cs
Normal file
360
Assets/Cryville/Common/Unity/Input/WindowsPointerHandler.cs
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* @author Valentin Simonov / http://va.lent.in/
|
||||
* @author Valentin Frolov
|
||||
* @author Andrew David Griffiths
|
||||
* @author Cryville:Lime
|
||||
*/
|
||||
using AOT;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cryville.Common.Unity.Input {
|
||||
public class WindowsPointerHandler : InputHandler {
|
||||
static WindowsPointerHandler Instance;
|
||||
|
||||
const string PRESS_AND_HOLD_ATOM = "MicrosoftTabletPenServiceProperty";
|
||||
delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
public const int MONITOR_DEFAULTTONEAREST = 2;
|
||||
readonly IntPtr hMainWindow;
|
||||
IntPtr oldWndProcPtr;
|
||||
IntPtr newWndProcPtr;
|
||||
WndProcDelegate newWndProc;
|
||||
ushort pressAndHoldAtomID;
|
||||
|
||||
readonly int touchInputSize;
|
||||
readonly long freq;
|
||||
static bool usePointerMessage;
|
||||
|
||||
public WindowsPointerHandler() {
|
||||
if (Instance != null)
|
||||
throw new InvalidOperationException("WindowsPointerHandler already created");
|
||||
Instance = this;
|
||||
usePointerMessage = true;
|
||||
|
||||
hMainWindow = GetUnityWindowHandle();
|
||||
if (hMainWindow == IntPtr.Zero) {
|
||||
throw new InvalidOperationException("Cannot find the game window");
|
||||
}
|
||||
NativeMethods.QueryPerformanceFrequency(out freq);
|
||||
retry:
|
||||
if (usePointerMessage) {
|
||||
try {
|
||||
NativeMethods.EnableMouseInPointer(true);
|
||||
Logger.Log("main", 0, "Input", "Touch input frequency: {0}", freq);
|
||||
}
|
||||
catch (TypeLoadException) {
|
||||
usePointerMessage = false;
|
||||
Logger.Log("main", 2, "Input", "Advanced touch handling not supported");
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
else {
|
||||
touchInputSize = Marshal.SizeOf(typeof(NativeMethods.TOUCHINPUT));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Activate() {
|
||||
RegisterWindowProc(WndProc);
|
||||
if (!NativeMethods.RegisterTouchWindow(hMainWindow, NativeMethods.TOUCH_WINDOW_FLAGS.TWF_WANTPALM)) {
|
||||
throw new InvalidOperationException("Failed to register touch window");
|
||||
}
|
||||
DisablePressAndHold();
|
||||
}
|
||||
|
||||
public override void Deactivate() {
|
||||
EnablePressAndHold();
|
||||
if (!NativeMethods.UnregisterTouchWindow(hMainWindow)) {
|
||||
throw new InvalidOperationException("Failed to unregister touch window");
|
||||
}
|
||||
UnregisterWindowProc();
|
||||
}
|
||||
|
||||
void RegisterWindowProc(WndProcDelegate windowProc) {
|
||||
newWndProc = windowProc;
|
||||
newWndProcPtr = Marshal.GetFunctionPointerForDelegate(newWndProc);
|
||||
oldWndProcPtr = SetWindowLongPtr(hMainWindow, -4, newWndProcPtr);
|
||||
}
|
||||
|
||||
void UnregisterWindowProc() {
|
||||
SetWindowLongPtr(hMainWindow, -4, oldWndProcPtr);
|
||||
newWndProcPtr = IntPtr.Zero;
|
||||
newWndProc = null;
|
||||
}
|
||||
|
||||
public const int TABLET_DISABLE_PRESSANDHOLD = 0x00000001;
|
||||
public const int TABLET_DISABLE_PENTAPFEEDBACK = 0x00000008;
|
||||
public const int TABLET_DISABLE_PENBARRELFEEDBACK = 0x00000010;
|
||||
public const int TABLET_DISABLE_FLICKS = 0x00010000;
|
||||
|
||||
protected void DisablePressAndHold() {
|
||||
pressAndHoldAtomID = NativeMethods.GlobalAddAtom(PRESS_AND_HOLD_ATOM);
|
||||
NativeMethods.SetProp(hMainWindow, PRESS_AND_HOLD_ATOM,
|
||||
TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click) gesture
|
||||
TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves)
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button down (circle)
|
||||
TABLET_DISABLE_FLICKS // disables pen flicks (back, forward, drag down, drag up);
|
||||
);
|
||||
}
|
||||
|
||||
protected void EnablePressAndHold() {
|
||||
if (pressAndHoldAtomID != 0) {
|
||||
NativeMethods.RemoveProp(hMainWindow, PRESS_AND_HOLD_ATOM);
|
||||
NativeMethods.GlobalDeleteAtom(pressAndHoldAtomID);
|
||||
}
|
||||
}
|
||||
|
||||
const string UnityWindowClassName = "UnityWndClass";
|
||||
|
||||
static IntPtr _windowHandle;
|
||||
public static IntPtr GetUnityWindowHandle() {
|
||||
uint threadId = NativeMethods.GetCurrentThreadId();
|
||||
NativeMethods.EnumThreadWindows(
|
||||
threadId,
|
||||
UnityWindowFilter,
|
||||
IntPtr.Zero
|
||||
);
|
||||
return _windowHandle;
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(NativeMethods.EnumWindowsProc))]
|
||||
public static bool UnityWindowFilter(IntPtr hWnd, IntPtr lParam) {
|
||||
var classText = new StringBuilder(UnityWindowClassName.Length + 1);
|
||||
NativeMethods.GetClassName(hWnd, classText, classText.Capacity);
|
||||
if (classText.ToString() == UnityWindowClassName) {
|
||||
_windowHandle = hWnd;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsNullable(int type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override byte GetDimension(int type) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public override string GetTypeName(int type) {
|
||||
switch (type) {
|
||||
case 0: return "Windows Pointer Unknown";
|
||||
case 1: return "Windows Pointer Pointer";
|
||||
case 2: return "Windows Pointer Touch";
|
||||
case 3: return "Windows Pointer Pen";
|
||||
case 4: return "Windows Pointer Mouse";
|
||||
case 5: return "Windows Pointer Touch Pad";
|
||||
case 255: return "Windows Touch";
|
||||
default: throw new ArgumentOutOfRangeException(nameof(type));
|
||||
}
|
||||
}
|
||||
|
||||
public override double GetCurrentTimestamp() {
|
||||
long tick;
|
||||
NativeMethods.QueryPerformanceCounter(out tick);
|
||||
return (double)tick / freq;
|
||||
}
|
||||
|
||||
public override void Dispose(bool disposing) {
|
||||
Deactivate();
|
||||
if (usePointerMessage)
|
||||
NativeMethods.EnableMouseInPointer(false);
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(WndProcDelegate))]
|
||||
static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) {
|
||||
try {
|
||||
var m = (WindowMessages)msg;
|
||||
/*if (m >= WindowMessages.WM_MOUSEFIRST && m <= WindowMessages.WM_MOUSELAST) {
|
||||
if (!usePointerMessage)
|
||||
Instance.handleMouseMsg(msg, wParam, lParam);
|
||||
}
|
||||
else*/
|
||||
switch (m) {
|
||||
case WindowMessages.WM_TOUCH:
|
||||
if (usePointerMessage)
|
||||
NativeMethods.CloseTouchInputHandle(lParam);
|
||||
else
|
||||
Instance.HandleTouchMsg(wParam, lParam);
|
||||
break;
|
||||
case WindowMessages.WM_POINTERDOWN:
|
||||
case WindowMessages.WM_POINTERUP:
|
||||
case WindowMessages.WM_POINTERUPDATE:
|
||||
if (usePointerMessage)
|
||||
Instance.HandlePointerMsg(msg, wParam, lParam);
|
||||
break;
|
||||
case WindowMessages.WM_CLOSE:
|
||||
Instance.Dispose();
|
||||
// Calling oldWndProcPtr here would crash the application
|
||||
Application.Quit();
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
return NativeMethods.CallWindowProc(Instance.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Logger.Log("main", 4, "Input", "An error occured while handling pointer:\n{0}", ex);
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
void HandlePointerMsg(uint msg, IntPtr wParam, IntPtr lParam) {
|
||||
int id = LOWORD(wParam.ToInt32());
|
||||
|
||||
var rawpinfo = new NativeMethods.POINTER_INFO();
|
||||
if (!NativeMethods.GetPointerInfo(id, ref rawpinfo))
|
||||
throw new InvalidOperationException("Failed to get pointer info");
|
||||
|
||||
/*
|
||||
Vector2? _cs = null;
|
||||
uint? orientation = null;
|
||||
uint? pressure = null;
|
||||
if (pointerInfo.pointerType == NativeMethods.POINTER_INPUT_TYPE.PT_TOUCH) {
|
||||
var pointerTouchInfo = new NativeMethods.POINTER_TOUCH_INFO();
|
||||
NativeMethods.GetPointerTouchInfo(pointerId, ref pointerTouchInfo);
|
||||
if (pointerTouchInfo.touchMask.HasFlag(NativeMethods.TOUCH_MASK.TOUCH_MASK_CONTACTAREA)) {
|
||||
Vector2 cs;
|
||||
cs.x = pointerTouchInfo.rcContact.Width;
|
||||
cs.y = pointerTouchInfo.rcContact.Height;
|
||||
_cs = cs;
|
||||
}
|
||||
if (pointerTouchInfo.touchMask.HasFlag(NativeMethods.TOUCH_MASK.TOUCH_MASK_ORIENTATION)) {
|
||||
orientation = pointerTouchInfo.orientation;
|
||||
}
|
||||
if (pointerTouchInfo.touchMask.HasFlag(NativeMethods.TOUCH_MASK.TOUCH_MASK_PRESSURE)) {
|
||||
pressure = pointerTouchInfo.pressure;
|
||||
}
|
||||
}*/
|
||||
|
||||
NativeMethods.POINT p = rawpinfo.ptPixelLocation;
|
||||
NativeMethods.ScreenToClient(hMainWindow, ref p);
|
||||
Vector2 _p = new Vector2(p.X, p.Y);
|
||||
|
||||
double time = (double)rawpinfo.PerformanceCount / freq;
|
||||
|
||||
int type;
|
||||
switch (rawpinfo.pointerType) {
|
||||
case NativeMethods.POINTER_INPUT_TYPE.PT_POINTER : type = 1; break;
|
||||
case NativeMethods.POINTER_INPUT_TYPE.PT_TOUCH : type = 2; break;
|
||||
case NativeMethods.POINTER_INPUT_TYPE.PT_PEN : type = 3; break;
|
||||
case NativeMethods.POINTER_INPUT_TYPE.PT_MOUSE : type = 4; break;
|
||||
case NativeMethods.POINTER_INPUT_TYPE.PT_TOUCHPAD: type = 5; break;
|
||||
default: type = 0; break;
|
||||
}
|
||||
if (rawpinfo.pointerFlags.HasFlag(NativeMethods.POINTER_FLAGS.POINTER_FLAG_CANCELED)) {
|
||||
OnInput(type, id, new InputVector(time));
|
||||
return;
|
||||
}
|
||||
|
||||
InputVector vec = new InputVector(time, _p);
|
||||
|
||||
switch ((WindowMessages)msg) {
|
||||
case WindowMessages.WM_POINTERDOWN:
|
||||
case WindowMessages.WM_POINTERUPDATE:
|
||||
OnInput(type, id, vec);
|
||||
break;
|
||||
case WindowMessages.WM_POINTERUP:
|
||||
OnInput(type, id, vec);
|
||||
OnInput(type, id, new InputVector(time));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*void handleMouseMsg(uint msg, IntPtr wParam, IntPtr lParam) {
|
||||
int id = mouseId;
|
||||
double procTime = diag::Stopwatch.GetTimestamp()
|
||||
/ (double)diag::Stopwatch.Frequency;
|
||||
Vector2 _p;
|
||||
_p.x = ((int)(short)LOWORD(lParam.ToInt32()));
|
||||
_p.y = ((int)(short)HIWORD(lParam.ToInt32()));
|
||||
PointerPhase phase;
|
||||
switch ((WindowMessages)msg) {
|
||||
case WindowMessages.WM_LBUTTONDOWN:
|
||||
case WindowMessages.WM_LBUTTONDBLCLK:
|
||||
phase = PointerPhase.Begin;
|
||||
id = mouseId = newIdCallback();
|
||||
break;
|
||||
case WindowMessages.WM_MOUSEMOVE:
|
||||
if (mouseId == 0)
|
||||
return;
|
||||
phase = PointerPhase.Update;
|
||||
break;
|
||||
case WindowMessages.WM_LBUTTONUP:
|
||||
phase = PointerPhase.End;
|
||||
mouseId = 0;
|
||||
break;
|
||||
case WindowMessages.WM_MOUSELEAVE:
|
||||
phase = PointerPhase.Cancel;
|
||||
mouseId = 0;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
callback(id, new PointerInfo() {
|
||||
Id = id,
|
||||
ProcessTime = procTime,
|
||||
Phase = phase,
|
||||
Type = PointerType.Mouse,
|
||||
Position = _p,
|
||||
});
|
||||
}*/
|
||||
|
||||
void HandleTouchMsg(IntPtr wParam, IntPtr lParam) {
|
||||
int inputCount = LOWORD(wParam.ToInt32());
|
||||
NativeMethods.TOUCHINPUT[] inputs = new NativeMethods.TOUCHINPUT[inputCount];
|
||||
|
||||
if (!NativeMethods.GetTouchInputInfo(lParam, inputCount, inputs, touchInputSize))
|
||||
throw new InvalidOperationException("Failed to get touch input info");
|
||||
|
||||
for (int i = 0; i < inputCount; i++) {
|
||||
NativeMethods.TOUCHINPUT touch = inputs[i];
|
||||
|
||||
NativeMethods.POINT p = new NativeMethods.POINT {
|
||||
X = touch.x / 100,
|
||||
Y = touch.y / 100
|
||||
};
|
||||
NativeMethods.ScreenToClient(hMainWindow, ref p);
|
||||
Vector2 _p = new Vector2(p.X, p.Y);
|
||||
|
||||
/*Vector2? _cs = null;
|
||||
if (touch.dwMask.HasFlag(NativeMethods.TOUCHINPUT_Mask.TOUCHINPUTMASKF_CONTACTAREA)) {
|
||||
Vector2 cs;
|
||||
cs.x = touch.cxContact / 100;
|
||||
cs.y = touch.cyContact / 100;
|
||||
_cs = cs;
|
||||
}*/
|
||||
|
||||
int id = touch.dwID;
|
||||
double time = touch.dwTime / 1000.0;
|
||||
InputVector vec = new InputVector(time, _p);
|
||||
|
||||
if (touch.dwFlags.HasFlag(NativeMethods.TOUCHINPUT_Flags.TOUCHEVENTF_MOVE) ||
|
||||
touch.dwFlags.HasFlag(NativeMethods.TOUCHINPUT_Flags.TOUCHEVENTF_DOWN)) {
|
||||
OnInput(255, id, vec);
|
||||
}
|
||||
else if (touch.dwFlags.HasFlag(NativeMethods.TOUCHINPUT_Flags.TOUCHEVENTF_UP)) {
|
||||
OnInput(255, id, vec);
|
||||
OnInput(255, id, new InputVector(time));
|
||||
}
|
||||
}
|
||||
|
||||
NativeMethods.CloseTouchInputHandle(lParam);
|
||||
}
|
||||
|
||||
public static int LOWORD(int value) {
|
||||
return value & 0xffff;
|
||||
}
|
||||
|
||||
public static int HIWORD(int value) {
|
||||
return (value & (0xffff << 16)) >> 16;
|
||||
}
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong) {
|
||||
if (IntPtr.Size == 8)
|
||||
return NativeMethods.SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
|
||||
return new IntPtr(NativeMethods.SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user