/*
* @author Valentin Simonov / http://va.lent.in/
* @author Valentin Frolov
* @author Andrew David Griffiths
*/
#if UNITY_STANDALONE_WIN
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using TouchScript.Pointers;
using TouchScript.Utils;
using TouchScript.Utils.Platform;
using UnityEngine;
namespace TouchScript.InputSources.InputHandlers
{
///
/// Windows 8 pointer handling implementation which can be embedded to other (input) classes. Uses WindowsTouch.dll to query native touches with WM_TOUCH or WM_POINTER APIs.
///
public class Windows8PointerHandler : WindowsPointerHandler
{
#region Public properties
///
/// Should the primary pointer also dispatch a mouse pointer.
///
public bool MouseInPointer
{
get { return mouseInPointer; }
set
{
WindowsUtils.EnableMouseInPointer(value);
mouseInPointer = value;
if (mouseInPointer)
{
if (mousePointer == null) mousePointer = internalAddMousePointer(Vector3.zero);
}
else
{
if (mousePointer != null)
{
if ((mousePointer.Buttons & Pointer.PointerButtonState.AnyButtonPressed) != 0)
{
mousePointer.Buttons = PointerUtils.UpPressedButtons(mousePointer.Buttons);
releasePointer(mousePointer);
}
removePointer(mousePointer);
}
}
}
}
#endregion
#region Private variables
private bool mouseInPointer = true;
#endregion
#region Constructor
///
public Windows8PointerHandler(PointerDelegate addPointer, PointerDelegate updatePointer, PointerDelegate pressPointer, PointerDelegate releasePointer, PointerDelegate removePointer, PointerDelegate cancelPointer) : base(addPointer, updatePointer, pressPointer, releasePointer, removePointer, cancelPointer)
{
mousePool = new ObjectPool(4, () => new MousePointer(this), null, resetPointer);
penPool = new ObjectPool(2, () => new PenPointer(this), null, resetPointer);
mousePointer = internalAddMousePointer(Vector3.zero);
init(TOUCH_API.WIN8);
}
#endregion
#region Public methods
///
public override bool UpdateInput()
{
base.UpdateInput();
return true;
}
///
public override bool CancelPointer(Pointer pointer, bool shouldReturn)
{
if (pointer.Equals(mousePointer))
{
cancelPointer(mousePointer);
if (shouldReturn) mousePointer = internalReturnMousePointer(mousePointer);
else mousePointer = internalAddMousePointer(pointer.Position); // can't totally cancel mouse pointer
return true;
}
if (pointer.Equals(penPointer))
{
cancelPointer(penPointer);
if (shouldReturn) penPointer = internalReturnPenPointer(penPointer);
return true;
}
return base.CancelPointer(pointer, shouldReturn);
}
///
public override void Dispose()
{
if (mousePointer != null)
{
cancelPointer(mousePointer);
mousePointer = null;
}
if (penPointer != null)
{
cancelPointer(penPointer);
penPointer = null;
}
WindowsUtils.EnableMouseInPointer(false);
base.Dispose();
}
#endregion
#region Internal methods
///
public override void INTERNAL_DiscardPointer(Pointer pointer)
{
if (pointer is MousePointer) mousePool.Release(pointer as MousePointer);
else if (pointer is PenPointer) penPool.Release(pointer as PenPointer);
else base.INTERNAL_DiscardPointer(pointer);
}
#endregion
}
public class Windows7PointerHandler : WindowsPointerHandler
{
///
public Windows7PointerHandler(PointerDelegate addPointer, PointerDelegate updatePointer, PointerDelegate pressPointer, PointerDelegate releasePointer, PointerDelegate removePointer, PointerDelegate cancelPointer) : base(addPointer, updatePointer, pressPointer, releasePointer, removePointer, cancelPointer)
{
init(TOUCH_API.WIN7);
}
#region Public methods
///
public override bool UpdateInput()
{
base.UpdateInput();
return winTouchToInternalId.Count > 0;
}
#endregion
}
///
/// Base class for Windows 8 and Windows 7 input handlers.
///
public abstract class WindowsPointerHandler : IInputSource, IDisposable
{
#region Consts
///
/// Windows constant to turn off press and hold visual effect.
///
public const string PRESS_AND_HOLD_ATOM = "MicrosoftTabletPenServiceProperty";
///
/// The method delegate used to pass data from the native DLL.
///
/// Pointer id.
/// Current event.
/// Pointer type.
/// Pointer position.
/// Pointer data.
protected delegate void NativePointerDelegate(int id, PointerEvent evt, PointerType type, Vector2 position, PointerData data);
///
/// The method delegate used to pass log messages from the native DLL.
///
/// The log message.
protected delegate void NativeLog([MarshalAs(UnmanagedType.BStr)] string log);
#endregion
#region Public properties
///
public ICoordinatesRemapper CoordinatesRemapper { get; set; }
#endregion
#region Private variables
private NativePointerDelegate nativePointerDelegate;
private NativeLog nativeLogDelegate;
protected PointerDelegate addPointer;
protected PointerDelegate updatePointer;
protected PointerDelegate pressPointer;
protected PointerDelegate releasePointer;
protected PointerDelegate removePointer;
protected PointerDelegate cancelPointer;
protected IntPtr hMainWindow;
protected ushort pressAndHoldAtomID;
protected Dictionary winTouchToInternalId = new Dictionary(10);
protected ObjectPool touchPool;
protected ObjectPool mousePool;
protected ObjectPool penPool;
protected MousePointer mousePointer;
protected PenPointer penPointer;
#endregion
#region Constructor
///
/// Initializes a new instance of the class.
///
/// A function called when a new pointer is detected.
/// A function called when a pointer is moved or its parameter is updated.
/// A function called when a pointer touches the surface.
/// A function called when a pointer is lifted off.
/// A function called when a pointer is removed.
/// A function called when a pointer is cancelled.
public WindowsPointerHandler(PointerDelegate addPointer, PointerDelegate updatePointer, PointerDelegate pressPointer, PointerDelegate releasePointer, PointerDelegate removePointer, PointerDelegate cancelPointer)
{
this.addPointer = addPointer;
this.updatePointer = updatePointer;
this.pressPointer = pressPointer;
this.releasePointer = releasePointer;
this.removePointer = removePointer;
this.cancelPointer = cancelPointer;
nativeLogDelegate = nativeLog;
nativePointerDelegate = nativePointer;
touchPool = new ObjectPool(10, () => new TouchPointer(this), null, resetPointer);
hMainWindow = WindowsUtils.GetActiveWindow();
disablePressAndHold();
setScaling();
}
#endregion
#region Public methods
///
public virtual bool UpdateInput()
{
return false;
}
///
public virtual void UpdateResolution()
{
setScaling();
if (mousePointer != null) TouchManager.Instance.CancelPointer(mousePointer.Id);
}
///
public virtual bool CancelPointer(Pointer pointer, bool shouldReturn)
{
var touch = pointer as TouchPointer;
if (touch == null) return false;
int internalTouchId = -1;
foreach (var t in winTouchToInternalId)
{
if (t.Value == touch)
{
internalTouchId = t.Key;
break;
}
}
if (internalTouchId > -1)
{
cancelPointer(touch);
winTouchToInternalId.Remove(internalTouchId);
if (shouldReturn) winTouchToInternalId[internalTouchId] = internalReturnTouchPointer(touch);
return true;
}
return false;
}
///
/// Releases resources.
///
public virtual void Dispose()
{
foreach (var i in winTouchToInternalId) cancelPointer(i.Value);
winTouchToInternalId.Clear();
enablePressAndHold();
DisposePlugin();
}
#endregion
#region Internal methods
///
public virtual void INTERNAL_DiscardPointer(Pointer pointer)
{
var p = pointer as TouchPointer;
if (p == null) return;
touchPool.Release(p);
}
#endregion
#region Protected methods
protected TouchPointer internalAddTouchPointer(Vector2 position)
{
var pointer = touchPool.Get();
pointer.Position = remapCoordinates(position);
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonDown | Pointer.PointerButtonState.FirstButtonPressed;
addPointer(pointer);
pressPointer(pointer);
return pointer;
}
protected TouchPointer internalReturnTouchPointer(TouchPointer pointer)
{
var newPointer = touchPool.Get();
newPointer.CopyFrom(pointer);
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonDown | Pointer.PointerButtonState.FirstButtonPressed;
newPointer.Flags |= Pointer.FLAG_RETURNED;
addPointer(newPointer);
pressPointer(newPointer);
return newPointer;
}
protected void internalRemoveTouchPointer(TouchPointer pointer)
{
pointer.Buttons &= ~Pointer.PointerButtonState.FirstButtonPressed;
pointer.Buttons |= Pointer.PointerButtonState.FirstButtonUp;
releasePointer(pointer);
removePointer(pointer);
}
protected MousePointer internalAddMousePointer(Vector2 position)
{
var pointer = mousePool.Get();
pointer.Position = remapCoordinates(position);
addPointer(pointer);
return pointer;
}
protected MousePointer internalReturnMousePointer(MousePointer pointer)
{
var newPointer = mousePool.Get();
newPointer.CopyFrom(pointer);
newPointer.Flags |= Pointer.FLAG_RETURNED;
addPointer(newPointer);
if ((newPointer.Buttons & Pointer.PointerButtonState.AnyButtonPressed) != 0)
{
// Adding down state this frame
newPointer.Buttons = PointerUtils.DownPressedButtons(newPointer.Buttons);
pressPointer(newPointer);
}
return newPointer;
}
protected PenPointer internalAddPenPointer(Vector2 position)
{
if (penPointer != null) throw new InvalidOperationException("One pen pointer is already registered! Trying to add another one.");
var pointer = penPool.Get();
pointer.Position = remapCoordinates(position);
addPointer(pointer);
return pointer;
}
protected void internalRemovePenPointer(PenPointer pointer)
{
removePointer(pointer);
penPointer = null;
}
protected PenPointer internalReturnPenPointer(PenPointer pointer)
{
var newPointer = penPool.Get();
newPointer.CopyFrom(pointer);
newPointer.Flags |= Pointer.FLAG_RETURNED;
addPointer(newPointer);
if ((newPointer.Buttons & Pointer.PointerButtonState.AnyButtonPressed) != 0)
{
// Adding down state this frame
newPointer.Buttons = PointerUtils.DownPressedButtons(newPointer.Buttons);
pressPointer(newPointer);
}
return newPointer;
}
protected void init(TOUCH_API api)
{
Init(api, nativeLogDelegate, nativePointerDelegate);
}
protected Vector2 remapCoordinates(Vector2 position)
{
if (CoordinatesRemapper != null) return CoordinatesRemapper.Remap(position);
return position;
}
protected void resetPointer(Pointer p)
{
p.INTERNAL_Reset();
}
#endregion
#region Private functions
private void disablePressAndHold()
{
// https://msdn.microsoft.com/en-us/library/bb969148(v=vs.85).aspx
pressAndHoldAtomID = WindowsUtils.GlobalAddAtom(PRESS_AND_HOLD_ATOM);
WindowsUtils.SetProp(hMainWindow, PRESS_AND_HOLD_ATOM,
WindowsUtils.TABLET_DISABLE_PRESSANDHOLD | // disables press and hold (right-click) gesture
WindowsUtils.TABLET_DISABLE_PENTAPFEEDBACK | // disables UI feedback on pen up (waves)
WindowsUtils.TABLET_DISABLE_PENBARRELFEEDBACK | // disables UI feedback on pen button down (circle)
WindowsUtils.TABLET_DISABLE_FLICKS // disables pen flicks (back, forward, drag down, drag up);
);
}
private void enablePressAndHold()
{
if (pressAndHoldAtomID != 0)
{
WindowsUtils.RemoveProp(hMainWindow, PRESS_AND_HOLD_ATOM);
WindowsUtils.GlobalDeleteAtom(pressAndHoldAtomID);
}
}
private void setScaling()
{
var screenWidth = Screen.width;
var screenHeight = Screen.height;
if (!Screen.fullScreen)
{
SetScreenParams(screenWidth, screenHeight, 0, 0, 1, 1);
return;
}
int width, height;
WindowsUtils.GetNativeMonitorResolution(out width, out height);
float scale = Mathf.Max(screenWidth / ((float) width), screenHeight / ((float) height));
SetScreenParams(screenWidth, screenHeight, (width - screenWidth / scale) * .5f, (height - screenHeight / scale) * .5f, scale, scale);
}
#endregion
#region Pointer callbacks
private void nativeLog(string log)
{
Debug.Log("[WindowsTouch.dll]: " + log);
}
private void nativePointer(int id, PointerEvent evt, PointerType type, Vector2 position, PointerData data)
{
switch (type)
{
case PointerType.Mouse:
switch (evt)
{
// Enter and Exit are not used - mouse is always present
// TODO: how does it work with 2+ mice?
case PointerEvent.Enter:
throw new NotImplementedException("This is not supposed to be called o.O");
case PointerEvent.Leave:
break;
case PointerEvent.Down:
mousePointer.Buttons = updateButtons(mousePointer.Buttons, data.PointerFlags, data.ChangedButtons);
pressPointer(mousePointer);
break;
case PointerEvent.Up:
mousePointer.Buttons = updateButtons(mousePointer.Buttons, data.PointerFlags, data.ChangedButtons);
releasePointer(mousePointer);
break;
case PointerEvent.Update:
mousePointer.Position = position;
mousePointer.Buttons = updateButtons(mousePointer.Buttons, data.PointerFlags, data.ChangedButtons);
updatePointer(mousePointer);
break;
case PointerEvent.Cancelled:
cancelPointer(mousePointer);
// can't cancel the mouse pointer, it is always present
mousePointer = internalAddMousePointer(mousePointer.Position);
break;
}
break;
case PointerType.Touch:
TouchPointer touchPointer;
switch (evt)
{
case PointerEvent.Enter:
break;
case PointerEvent.Leave:
// Sometimes Windows might not send Up, so have to execute touch release logic here.
// Has been working fine on test devices so far.
if (winTouchToInternalId.TryGetValue(id, out touchPointer))
{
winTouchToInternalId.Remove(id);
internalRemoveTouchPointer(touchPointer);
}
break;
case PointerEvent.Down:
touchPointer = internalAddTouchPointer(position);
touchPointer.Rotation = getTouchRotation(ref data);
touchPointer.Pressure = getTouchPressure(ref data);
winTouchToInternalId.Add(id, touchPointer);
break;
case PointerEvent.Up:
break;
case PointerEvent.Update:
if (!winTouchToInternalId.TryGetValue(id, out touchPointer)) return;
touchPointer.Position = position;
touchPointer.Rotation = getTouchRotation(ref data);
touchPointer.Pressure = getTouchPressure(ref data);
updatePointer(touchPointer);
break;
case PointerEvent.Cancelled:
if (winTouchToInternalId.TryGetValue(id, out touchPointer))
{
winTouchToInternalId.Remove(id);
cancelPointer(touchPointer);
}
break;
}
break;
case PointerType.Pen:
switch (evt)
{
case PointerEvent.Enter:
penPointer = internalAddPenPointer(position);
penPointer.Pressure = getPenPressure(ref data);
penPointer.Rotation = getPenRotation(ref data);
break;
case PointerEvent.Leave:
if (penPointer == null) break;
internalRemovePenPointer(penPointer);
break;
case PointerEvent.Down:
if (penPointer == null) break;
penPointer.Buttons = updateButtons(penPointer.Buttons, data.PointerFlags, data.ChangedButtons);
penPointer.Pressure = getPenPressure(ref data);
penPointer.Rotation = getPenRotation(ref data);
pressPointer(penPointer);
break;
case PointerEvent.Up:
if (penPointer == null) break;
mousePointer.Buttons = updateButtons(penPointer.Buttons, data.PointerFlags, data.ChangedButtons);
releasePointer(penPointer);
break;
case PointerEvent.Update:
if (penPointer == null) break;
penPointer.Position = position;
penPointer.Pressure = getPenPressure(ref data);
penPointer.Rotation = getPenRotation(ref data);
penPointer.Buttons = updateButtons(penPointer.Buttons, data.PointerFlags, data.ChangedButtons);
updatePointer(penPointer);
break;
case PointerEvent.Cancelled:
if (penPointer == null) break;
cancelPointer(penPointer);
break;
}
break;
}
}
private Pointer.PointerButtonState updateButtons(Pointer.PointerButtonState current, PointerFlags flags, ButtonChangeType change)
{
var currentUpDown = ((uint) current) & 0xFFFFFC00;
var pressed = ((uint) flags >> 4) & 0x1F;
var newUpDown = 0U;
if (change != ButtonChangeType.None) newUpDown = 1U << (10 + (int) change);
var combined = (Pointer.PointerButtonState) (pressed | newUpDown | currentUpDown);
return combined;
}
private float getTouchPressure(ref PointerData data)
{
var reliable = (data.Mask & (uint) TouchMask.Pressure) > 0;
if (reliable) return data.Pressure / 1024f;
return TouchPointer.DEFAULT_PRESSURE;
}
private float getTouchRotation(ref PointerData data)
{
var reliable = (data.Mask & (uint) TouchMask.Orientation) > 0;
if (reliable) return data.Rotation / 180f * Mathf.PI;
return TouchPointer.DEFAULT_ROTATION;
}
private float getPenPressure(ref PointerData data)
{
var reliable = (data.Mask & (uint) PenMask.Pressure) > 0;
if (reliable) return data.Pressure / 1024f;
return PenPointer.DEFAULT_PRESSURE;
}
private float getPenRotation(ref PointerData data)
{
var reliable = (data.Mask & (uint) PenMask.Rotation) > 0;
if (reliable) return data.Rotation / 180f * Mathf.PI;
return PenPointer.DEFAULT_ROTATION;
}
#endregion
#region p/invoke
protected enum TOUCH_API
{
WIN7,
WIN8
}
protected enum PointerEvent : uint
{
Enter = 0x0249,
Leave = 0x024A,
Update = 0x0245,
Down = 0x0246,
Up = 0x0247,
Cancelled = 0x1000
}
protected enum PointerType
{
Pointer = 0x00000001,
Touch = 0x00000002,
Pen = 0x00000003,
Mouse = 0x00000004,
TouchPad = 0x00000005
}
[Flags]
protected enum PointerFlags
{
None = 0x00000000,
New = 0x00000001,
InRange = 0x00000002,
InContact = 0x00000004,
FirstButton = 0x00000010,
SecondButton = 0x00000020,
ThirdButton = 0x00000040,
FourthButton = 0x00000080,
FifthButton = 0x00000100,
Primary = 0x00002000,
Confidence = 0x00004000,
Canceled = 0x00008000,
Down = 0x00010000,
Update = 0x00020000,
Up = 0x00040000,
Wheel = 0x00080000,
HWheel = 0x00100000,
CaptureChanged = 0x00200000,
HasTransform = 0x00400000
}
protected enum ButtonChangeType
{
None,
FirstDown,
FirstUp,
SecondDown,
SecondUp,
ThirdDown,
ThirdUp,
FourthDown,
FourthUp,
FifthDown,
FifthUp
}
[Flags]
protected enum TouchFlags
{
None = 0x00000000
}
[Flags]
protected enum TouchMask
{
None = 0x00000000,
ContactArea = 0x00000001,
Orientation = 0x00000002,
Pressure = 0x00000004
}
[Flags]
protected enum PenFlags
{
None = 0x00000000,
Barrel = 0x00000001,
Inverted = 0x00000002,
Eraser = 0x00000004
}
[Flags]
protected enum PenMask
{
None = 0x00000000,
Pressure = 0x00000001,
Rotation = 0x00000002,
TiltX = 0x00000004,
TiltY = 0x00000008
}
[StructLayout(LayoutKind.Sequential)]
protected struct PointerData
{
public PointerFlags PointerFlags;
public uint Flags;
public uint Mask;
public ButtonChangeType ChangedButtons;
public uint Rotation;
public uint Pressure;
public int TiltX;
public int TiltY;
}
[DllImport("WindowsTouch", CallingConvention = CallingConvention.StdCall)]
private static extern void Init(TOUCH_API api, NativeLog log, NativePointerDelegate pointerDelegate);
[DllImport("WindowsTouch", EntryPoint = "Dispose", CallingConvention = CallingConvention.StdCall)]
private static extern void DisposePlugin();
[DllImport("WindowsTouch", CallingConvention = CallingConvention.StdCall)]
private static extern void SetScreenParams(int width, int height, float offsetX, float offsetY, float scaleX, float scaleY);
#endregion
}
}
#endif