PageRenderTime 46ms CodeModel.GetById 27ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/InputSystem/Windows/WindowsTouch.cs

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