PageRenderTime 40ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/InputSystem/Windows/WindowsTouch.cs

#
C# | 390 lines | 208 code | 53 blank | 129 comment | 24 complexity | 15253c6cd4ae43a7e942130c7a94d11d MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Runtime.InteropServices;
  4. using Delta.Engine;
  5. using Delta.InputSystem.Devices;
  6. using Delta.Platforms.Windows;
  7. using Delta.Utilities;
  8. using Delta.Utilities.Datatypes;
  9. using Delta.Utilities.Helpers;
  10. using WinMessage = System.Windows.Forms.Message;
  11. namespace Delta.InputSystem.Windows
  12. {
  13. /// <summary>
  14. /// Windows multi-touch implementation. Only works on Windows 7 because this
  15. /// is part of the Windows 7 SDK. For references and getting started see:
  16. /// http://msdn.microsoft.com/en-us/library/dd371581%28VS.85%29.aspx
  17. /// </summary>
  18. public class WindowsTouch : BaseTouch
  19. {
  20. #region NativeTouchInput Struct
  21. /// <summary>
  22. /// Touch API defined structures [winuser.h]
  23. /// @see: http://msdn.microsoft.com/en-us/library/dd562194%28VS.85%29.aspx
  24. /// Note: We changed the naming a bit, we don't want C++ naming here.
  25. /// </summary>
  26. [StructLayout(LayoutKind.Sequential)]
  27. private struct NativeTouchInput
  28. {
  29. #region x (Public)
  30. public readonly int x;
  31. #endregion
  32. #region y (Public)
  33. public readonly int y;
  34. #endregion
  35. #region sourcePointer (Public)
  36. public readonly IntPtr sourcePointer;
  37. #endregion
  38. #region id (Public)
  39. public readonly int id;
  40. #endregion
  41. #region flags (Public)
  42. public readonly int flags;
  43. #endregion
  44. #region mask (Public)
  45. public readonly int mask;
  46. #endregion
  47. #region timeStamp (Public)
  48. public readonly int timeStamp;
  49. #endregion
  50. #region extraInfo (Public)
  51. public readonly IntPtr extraInfo;
  52. #endregion
  53. #region contactX (Public)
  54. public readonly int contactX;
  55. #endregion
  56. #region contactY (Public)
  57. public readonly int contactY;
  58. #endregion
  59. }
  60. #endregion
  61. #region Constants
  62. /// <summary>
  63. /// Touch input flag for a Move event. (-> @see WinUser.h)
  64. /// </summary>
  65. private const int TouchInputMove = 0x0001;
  66. /// <summary>
  67. /// Touch input flag for a Down event. (-> @see WinUser.h)
  68. /// </summary>
  69. private const int TouchInputDown = 0x0002;
  70. /// <summary>
  71. /// Touch input flag for an Up event. (-> @see WinUser.h)
  72. /// </summary>
  73. private const int TouchInputUp = 0x0004;
  74. /// <summary>
  75. /// WM_TOUCH event that will be enabled in the constructor via the native
  76. /// RegisterTouchWindow method.
  77. /// </summary>
  78. private const int WM_TOUCH = 0x0240;
  79. #endregion
  80. #region IsConnected (Public)
  81. /// <summary>
  82. /// Is connected
  83. /// </summary>
  84. public override bool IsConnected
  85. {
  86. get
  87. {
  88. return isTouchEnabled &&
  89. //Note: Currently a one-time thingy, see OnNativeEvent
  90. // This should not be here, but for testing it is..
  91. HasData;
  92. } // get
  93. }
  94. #endregion
  95. #region Private
  96. #region TouchCursorByteSize (Private)
  97. /// <summary>
  98. /// Constant size in bytes of the "TouchCursor" helper struct.
  99. /// </summary>
  100. private static int TouchCursorByteSize;
  101. #endregion
  102. // IsConnected
  103. #region win7Window (Private)
  104. /// <summary>
  105. /// The native windows from where we will receive the touch data updates.
  106. /// </summary>
  107. private readonly Window win7Window;
  108. #endregion
  109. #region isTouchEnabled (Private)
  110. /// <summary>
  111. /// Is touch enabled? If this is true we can use this class (IsConnected
  112. /// will return true if Settings.Debug.IsTouchEnabled is not false).
  113. /// </summary>
  114. private readonly bool isTouchEnabled;
  115. #endregion
  116. #region nativeTouches (Private)
  117. /// <summary>
  118. /// Cached list of native touches we receive in the OnNativeEvent event
  119. /// (if it is a WM_TOUCH). We can receive multiple native touches at once
  120. /// and even multiple times per frame, so we just go through this list
  121. /// from start to finish in the Update method and clear it out again!
  122. /// </summary>
  123. private readonly List<NativeTouchInput> nativeTouches =
  124. new List<NativeTouchInput>();
  125. #endregion
  126. #endregion
  127. #region Constructors
  128. /// <summary>
  129. /// Create windows touch
  130. /// </summary>
  131. public WindowsTouch()
  132. {
  133. // If touch input is disabled in settings, we don't need to continue
  134. // here, just abort. isTouchEnabled defaults to false.
  135. if (Settings.Modules.IsTouchEnabled == false)
  136. {
  137. return;
  138. }
  139. try
  140. {
  141. win7Window = Application.Window as Window;
  142. // Note: If we are not in Windows 7, we don't even try this!
  143. if (win7Window != null &&
  144. // Windows 7 is Windows NT 6.1 internally
  145. Environment.OSVersion.VersionString.Contains("Windows NT 6.1."))
  146. {
  147. // Register our window form on Windows 7, but don't use any extra
  148. // parameters yet. For more details see:
  149. // http://msdn.microsoft.com/en-us/library/dd317326%28VS.85%29.aspx
  150. isTouchEnabled = RegisterTouchWindow(win7Window.Handle, 0);
  151. TouchCursorByteSize = Marshal.SizeOf(typeof(NativeTouchInput));
  152. win7Window.NativeEvent += OnNativeEvent;
  153. } // if
  154. } // try
  155. catch (Exception ex)
  156. {
  157. // Note: This warning should only be outputted on Windows 7!
  158. Log.Warning("Failed to initialize TouchStateUpdater for " +
  159. "WindowsTouch, unable to use touch input from WIndows 7 " +
  160. "multi-touch support! " + ex);
  161. }
  162. }
  163. #endregion
  164. #region Methods (Private)
  165. // These DLLImports are only available on Windows 7.
  166. // What happens when we try to use this on other platforms like XP?
  167. #region RegisterTouchWindow
  168. [DllImport("User32")]
  169. private static extern bool RegisterTouchWindow(IntPtr handle,
  170. UInt32 flags);
  171. #endregion
  172. #region UnregisterTouchWindow
  173. [DllImport("User32")]
  174. private static extern bool UnregisterTouchWindow(IntPtr handle);
  175. #endregion
  176. #region CloseTouchInputHandle
  177. [DllImport("user32")]
  178. private static extern void CloseTouchInputHandle(IntPtr lParam);
  179. #endregion
  180. #region GetTouchInputInfo
  181. [DllImport("user32")]
  182. private static extern bool GetTouchInputInfo(IntPtr hTouchInput,
  183. int cInputs, [In, Out] NativeTouchInput[] pInputs, int cbSize);
  184. #endregion
  185. #region OnNativeEvent
  186. /// <summary>
  187. /// Handle windows message, processes only if something happens
  188. /// </summary>
  189. /// <param name="message">Message</param>
  190. private void OnNativeEvent(ref WinMessage message)
  191. {
  192. // Try catch up the (for us interesting) touch events
  193. switch (message.Msg)
  194. {
  195. // WM_TOUCH (-> @see "winuser.h" for the window message constants)
  196. case WM_TOUCH:
  197. // Acknowledge event that we will handle it now
  198. message.Result = new IntPtr(1);
  199. break;
  200. default:
  201. return;
  202. }
  203. // We got new data!
  204. HasData = true;
  205. // Extract the current number of active touch points
  206. int usedNativeTouches = (message.WParam.ToInt32() & 0xffff);
  207. /*don't care anymore
  208. // Make sure we never go above MaxNumberOfTouches
  209. if (usedNativeTouches > MaxNumberOfTouches)
  210. {
  211. usedNativeTouches = MaxNumberOfTouches;
  212. }
  213. */
  214. // So we know how many current touch inputs we will receive now
  215. //Log.Info("nativeTouches received: usedNativeTouches="+usedNativeTouches);
  216. NativeTouchInput[] newTouches = new NativeTouchInput[usedNativeTouches];
  217. bool isTouchInputProcessed = GetTouchInputInfo(message.LParam,
  218. usedNativeTouches, newTouches, TouchCursorByteSize);
  219. // If we don't get the data (shouldn't normally happen)
  220. if (isTouchInputProcessed == false)
  221. {
  222. // Happens if something was bad like, invalid params, but will not
  223. // execute if nothing happen resp. noone touches....
  224. Log.Warning("Windows 7 Touch error, no input processed " +
  225. "(we got no touches now)");
  226. return;
  227. } // if
  228. // Just add the received touches to our nativeTouches list
  229. nativeTouches.AddRange(newTouches);
  230. // Before we finally close up the handling, we notify the OS that we
  231. // have finished our "work"
  232. CloseTouchInputHandle(message.LParam);
  233. //Log.Info("nativeTouches end: usedNativeTouches=" + usedNativeTouches);
  234. }
  235. #endregion
  236. #region Update
  237. /// <summary>
  238. /// Update
  239. /// </summary>
  240. protected override void Update()
  241. {
  242. if (IsConnected == false)
  243. {
  244. return;
  245. }
  246. //Log.Info("nativeTouches=" + nativeTouches.Count +
  247. // ", ActiveTouches=" + ActiveTouches);
  248. // In any case (receive any touches this frame or not), we need to check
  249. // which touches are still active and update their states (mostly just
  250. // going from Pressed to IsPressed and Release to NotPressed).
  251. base.UpdateAndResetTouchStates();
  252. // Handle all collected native touch events
  253. for (int index = 0; index < nativeTouches.Count; index++)
  254. {
  255. // Note: We could have received several native touches with the same
  256. // id, then we will update the same Touches in our array (see below).
  257. NativeTouchInput touch = nativeTouches[index];
  258. // Touch space conversion to Quadratic Space. We have to convert the
  259. // raw touch coordinates first in Pixel Space of the "OS Desktop Area"
  260. // because they are scale by 100x (@see: WinUser.h (line 5382))
  261. // "#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)"
  262. // And finally convert in our Quadratic Space for the Viewport
  263. Point quadPos = ScreenSpace.ToQuadraticSpace(win7Window.DesktopToViewport(
  264. touch.x / 100, touch.y / 100));
  265. // Search for the touch id in the existing list
  266. int existingIndex = base.GetOrCreateIndexFromTouchId(touch.id);
  267. if (existingIndex == MathHelper.InvalidIndex)
  268. {
  269. continue;
  270. }
  271. // Convert into our touch state
  272. InputState touchState = InputState.NotPressed;
  273. if ((touch.flags & TouchInputDown) != 0)
  274. {
  275. // The initial touch input down is Pressed, then it goes to
  276. // IsPressed! This is because we sometimes get TouchInputDown
  277. // several times for the same press (and this would mess up our
  278. // state logic).
  279. // InputState.Pressed : InputState.IsPressed;
  280. touchState = InputState.Pressed;
  281. // And store some extra states for gestures detection.
  282. base.SetInitialTouchPressData(quadPos);
  283. }
  284. //Note: Move is also handled separately below!
  285. else if ((touch.flags & TouchInputMove) != 0)
  286. {
  287. // Only process this move if we did not just entered Pressed state.
  288. // Please note that Pressed will be changed to IsPressed
  289. // automatically above.
  290. touchState = Touches[existingIndex].State == InputState.Pressed
  291. ? InputState.Pressed
  292. : InputState.IsPressed;
  293. }
  294. else if ((touch.flags & TouchInputUp) != 0)
  295. {
  296. touchState = InputState.Released;
  297. }
  298. else
  299. {
  300. Log.Warning("Invalid touch flags, can't handle: " + touch.flags);
  301. }
  302. //Log.Info("native touch state " + existingIndex + "=" + touchState +
  303. // ", id=" + touch.id +
  304. // ", native TouchInputDown=" + (touch.flags & TouchInputDown) +
  305. // ", native TouchInputMove=" + (touch.flags & TouchInputMove) +
  306. // ", native TouchInputUp=" + (touch.flags & TouchInputUp));
  307. // This either updates or creates a new touch
  308. Touches[existingIndex].Set(touch.id,
  309. // We need to convert from screen space to quadratic space
  310. // note Check if the device orientation working properly??
  311. quadPos,
  312. // Convert from TouchLocationState to our InputState enum.
  313. touchState,
  314. (touch.flags & TouchInputMove) != 0,
  315. // We got no tap count data in Windows 7
  316. 0,
  317. // But we can use the touch time (in ms)
  318. Time.Milliseconds);
  319. if (touchState == InputState.Pressed)
  320. {
  321. SetInitialTouchPressData(quadPos);
  322. }
  323. } // for
  324. // We handled all the caught events, reset for the next update.
  325. nativeTouches.Clear();
  326. // Tell us how many touches are currently active, this is very important
  327. // to make sure we only iterate this far through the Touches array.
  328. // Note: We also do not need to clear the rest of the Touches array
  329. // because it is not supposed to be used anyway!
  330. //disabled for now: ActiveTouches = numberOfTouches;
  331. // The rest of the code is the same for all touch device implementations
  332. base.HandleStatesAndGestures();
  333. }
  334. #endregion
  335. #endregion
  336. }
  337. }