345 lines
11 KiB
C#
345 lines
11 KiB
C#
/*
|
|
* @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 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 override void ActivateImpl() {
|
|
newWndProc = WndProc;
|
|
newWndProcPtr = Marshal.GetFunctionPointerForDelegate(newWndProc);
|
|
oldWndProcPtr = SetWindowLongPtr(hMainWindow, -4, newWndProcPtr);
|
|
if (!NativeMethods.RegisterTouchWindow(hMainWindow, NativeMethods.TOUCH_WINDOW_FLAGS.TWF_WANTPALM)) {
|
|
throw new InvalidOperationException("Failed to register touch window");
|
|
}
|
|
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 override void DeactivateImpl() {
|
|
if (pressAndHoldAtomID != 0) {
|
|
NativeMethods.RemoveProp(hMainWindow, PRESS_AND_HOLD_ATOM);
|
|
NativeMethods.GlobalDeleteAtom(pressAndHoldAtomID);
|
|
}
|
|
if (!NativeMethods.UnregisterTouchWindow(hMainWindow)) {
|
|
throw new InvalidOperationException("Failed to unregister touch window");
|
|
}
|
|
SetWindowLongPtr(hMainWindow, -4, oldWndProcPtr);
|
|
newWndProcPtr = IntPtr.Zero;
|
|
newWndProc = null;
|
|
}
|
|
|
|
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) {
|
|
DeactivateImpl();
|
|
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 = UnityCameraUtils.ScreenToWorldPoint(new Vector2(p.X, Screen.height - 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)) {
|
|
Feed(type, id, new InputVector(time));
|
|
return;
|
|
}
|
|
|
|
InputVector vec = new InputVector(time, _p);
|
|
|
|
switch ((WindowMessages)msg) {
|
|
case WindowMessages.WM_POINTERDOWN:
|
|
case WindowMessages.WM_POINTERUPDATE:
|
|
Feed(type, id, vec);
|
|
break;
|
|
case WindowMessages.WM_POINTERUP:
|
|
Feed(type, id, vec);
|
|
Feed(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 = UnityCameraUtils.ScreenToWorldPoint(new Vector2(p.X, Screen.height - 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)) {
|
|
Feed(255, id, vec);
|
|
}
|
|
else if (touch.dwFlags.HasFlag(NativeMethods.TOUCHINPUT_Flags.TOUCHEVENTF_UP)) {
|
|
Feed(255, id, vec);
|
|
Feed(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()));
|
|
}
|
|
}
|
|
}
|