PageRenderTime 176ms CodeModel.GetById 77ms app.highlight 55ms RepoModel.GetById 35ms app.codeStats 0ms

/InputSystem/Windows/WindowsKeyboard.cs

#
C# | 955 lines | 494 code | 107 blank | 354 comment | 41 complexity | be7b9a678e5bfc7bc1c84f9f33867c6b MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using System.Diagnostics;
  4using System.IO;
  5using System.Runtime.InteropServices;
  6using System.Windows.Forms;
  7using System.Windows.Input;
  8using System.Windows.Interop;
  9using Delta.InputSystem.Devices;
 10using Delta.Platforms.Windows;
 11using Delta.Utilities;
 12using DeltaApplication = Delta.Engine.Application;
 13using KeyEventArgs = System.Windows.Forms.KeyEventArgs;
 14using WPFKeyEventArgs = System.Windows.Input.KeyEventArgs;
 15using WPFVisual = System.Windows.Media.Visual;
 16using WPFWindow = System.Windows.Window;
 17
 18namespace Delta.InputSystem.Windows
 19{
 20	/// <summary>
 21	/// Windows keyboard support is based on the Windows Forms or WPF events
 22	/// we receive for its main application window. These are collected each
 23	/// frame and then processed in the Update method. This implementation is
 24	/// better for typing text because we will not miss any states and the
 25	/// HandleInput method for text boxes is getting all the keyboard input
 26	/// while the XnaKeyboard implementation might miss some keys when the
 27	/// frame rate is too low (due the way it gets input via polling, nothing
 28	/// we can do about it, but this event driven implementation is fixing
 29	/// this problem on Windows).
 30	/// On platforms that can use WindowsKeyboard you should always use it, it
 31	/// even suppresses the Windows Key and handles keys and text input way
 32	/// better, XnaKeyboard should only be a fallback for platforms this
 33	/// </summary>
 34	public class WindowsKeyboard : BaseKeyboard, IDisposable
 35	{
 36		#region SP_DEVINFO_DATA Struct
 37		/// <summary>
 38		/// An SP_DEVINFO_DATA structure defines a device instance that is a
 39		/// member of a device information set.
 40		/// </summary>
 41		[StructLayout(LayoutKind.Sequential)]
 42		public struct SP_DEVINFO_DATA
 43		{
 44			#region cbSize (Public)
 45			/// <summary>
 46			/// The size, in bytes, of the SP_DEVINFO_DATA structure. For more
 47			/// information, see the following Remarks section.
 48			/// </summary>
 49			public int cbSize;
 50			#endregion
 51
 52			#region ClassGuid (Public)
 53			/// <summary>
 54			/// The GUID of the device's setup class.
 55			/// </summary>
 56			public Guid ClassGuid;
 57			#endregion
 58
 59			#region DevInst (Public)
 60			/// <summary>
 61			/// An opaque handle to the device instance (also known as a handle to 
 62			/// the devnode). 
 63			/// </summary>
 64			public int DevInst;
 65			#endregion
 66
 67			#region Reserved (Public)
 68			/// <summary>
 69			/// Reserved. For internal use only.
 70			/// </summary>
 71			public int Reserved;
 72			#endregion
 73		}
 74		#endregion
 75
 76		#region KBDLLHOOKSTRUCT Struct
 77		/// <summary>
 78		/// Structure contain information about low-level keyboard input event.
 79		/// More information can be found here: http://geekswithblogs.net/aghausman/archive/2009/04/26/disable-special-keys-in-win-app-c.aspx
 80		/// and http://msdn.microsoft.com/en-us/library/ms644967%28VS.85%29.aspx
 81		/// </summary>
 82		[StructLayout(LayoutKind.Sequential)]
 83		private struct KBDLLHOOKSTRUCT
 84		{
 85			#region key (Public)
 86			public readonly Keys key;
 87			#endregion
 88
 89			#region scanCode (Public)
 90			public readonly int scanCode;
 91			#endregion
 92
 93			#region flags (Public)
 94			public readonly int flags;
 95			#endregion
 96
 97			#region time (Public)
 98			public readonly int time;
 99			#endregion
100
101			#region extra (Public)
102			public readonly IntPtr extra;
103			#endregion
104		}
105		#endregion
106
107		#region Constants
108		/// <summary>
109		/// Return the devices that are currently present in the system
110		/// </summary>
111		/// <remarks>
112		/// see: http://msdn.microsoft.com/en-us/library/ff551069%28v=vs.85%29.aspx
113		/// </remarks>
114		protected const int DIGCF_PRESENT = 2;
115
116		/// <summary>
117		/// Used to define private messages for use by private window classes,
118		/// usually of the form WM_USER+X, where X is an integer value. 
119		/// </summary>
120		public const Int32 WM_USER = 1024;
121
122		/// <summary>
123		/// Windows Message to call On-screen-Keyboard window
124		/// </summary>
125		public const Int32 WM_CSKEYBOARD = WM_USER + 192;
126
127		/// <summary>
128		/// The title name of the On-Screen keyboard process.
129		/// </summary>
130		private const string oskProcessTitle = "On-Screen Keyboard";
131		#endregion
132
133		#region Delegates
134		/// <summary>
135		/// System level functions to be used for hook and unhook keyboard input
136		/// </summary>
137		/// <param name="nCode">Code</param>
138		/// <param name="wParam">Options</param>
139		/// <param name="lParam">Options</param>
140		/// <returns>delegate</returns>
141		private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam,
142			IntPtr lParam);
143		#endregion
144
145		#region IsConnected (Public)
146		/// <summary>
147		/// Is a keyboard connected? On windows we can always assume yes of course,
148		/// but it is checked anyway in IsKeyboardConnected to make sure we really
149		/// have one and do not need on-screen keyboards in Windows (also possible).
150		/// </summary>
151		public override bool IsConnected
152		{
153			get
154			{
155				return isConnected;
156			}
157		}
158		#endregion
159
160		#region MarkAllKeysAsHandled (Public)
161		/// <summary>
162		/// Helper property for marking all keys as handled when we receive key
163		/// events. By default this is on and this will prevent windows from
164		/// further handling these keys. An exception is made for editors, which
165		/// will still allow keyboard input and not mark them as handled here.
166		/// Some additional exceptions apply like WindowsKey, Ctrl+Alt+Del, etc.
167		/// Since we don't want the WindowsKey working on non-editor applications
168		/// we will create a windows hook and disable that key explicitly!
169		/// Note: This also disables Alt+F4 to close the window, so if you want
170		/// Escape and/or Alt+F4 to close the game or application, add it as a
171		/// global input command for the Exit event and it will work.
172		/// </summary>
173		/// <value>
174		/// 	<c>true</c> if [mark all keys as handled]; otherwise, <c>false</c>.
175		/// </value>
176		public bool MarkAllKeysAsHandled
177		{
178			get;
179			set;
180		}
181		#endregion
182
183		#region Protected
184
185		#region hWnd (Protected)
186		/// <summary>
187		/// Window handle for IsConnected 
188		/// </summary>
189		protected Int32 hWnd;
190		#endregion
191
192		#endregion
193
194		#region Private
195
196		#region GUID_DEVCLASS_KEYBOARD (Private)
197		/// <summary>
198		/// Keyboard GUID
199		/// </summary>
200		private static Guid GUID_DEVCLASS_KEYBOARD =
201			new Guid("4D36E96B-E325-11CE-BFC1-08002BE10318");
202		#endregion
203
204		#region oskProcessFilePath (Private)
205		/// <summary>
206		/// The absolute path of the located On-Screen keyboard process file which
207		/// will be started every time the On-Screen keyboard window is shown.
208		/// <para />
209		/// Note: This value will be set the first time when the showing is needed.
210		/// </summary>
211		private static string oskProcessFilePath;
212		#endregion
213
214		#region keysUp (Private)
215		/// <summary>
216		/// Helper array to handle all key up presses from the events in Update!
217		/// </summary>
218		private readonly List<InputButton> keysUp = new List<InputButton>();
219		#endregion
220
221		#region keysDown (Private)
222		/// <summary>
223		/// Helper array to handle all key down presses from the events in Update!
224		/// </summary>
225		private readonly List<InputButton> keysDown = new List<InputButton>();
226		#endregion
227
228		#region inputTextBuffer (Private)
229		/// <summary>
230		/// Helper to keep track of the input text received in KeyPress events.
231		/// </summary>
232		private string inputTextBuffer = "";
233		#endregion
234
235		#region numberOfTimesBackspaceWasDown (Private)
236		/// <summary>
237		/// A little helper to keep track how many times backspace was pressed.
238		/// This is not using the Released state because we want repeated key
239		/// presses too (to allow quickly erasing text).
240		/// </summary>
241		private int numberOfTimesBackspaceWasDown;
242		#endregion
243
244		#region isConnected (Private)
245		/// <summary>
246		/// Sets IsConnected
247		/// </summary>
248		private bool isConnected;
249		#endregion
250
251		#region oskProcess (Private)
252		private Process oskProcess;
253		#endregion
254
255		#region lowLevelKeyboardHook (Private)
256		private IntPtr lowLevelKeyboardHook;
257		#endregion
258
259		#region keyboardProcessObject (Private)
260		private readonly LowLevelKeyboardProc keyboardProcessObject;
261		#endregion
262
263		#endregion
264
265		#region Constructors
266		/// <summary>
267		/// Create windows keyboard
268		/// </summary>
269		public WindowsKeyboard()
270		{
271			// Check if there are any keyboards Connected in the system.
272			IsKeyboardConnected();
273
274			// Retrieve the window handle for OSK
275			hWnd = FindWindow("TFirstForm", "hvkFirstForm");
276
277			// Windows Presentation Foundation
278			IntPtr nativeWindowHande = DeltaApplication.Window.Handle;
279			bool isWpfWindow =
280				DeltaApplication.Window.GetType() == typeof(WindowWPF);
281
282			if (isWpfWindow)
283			{
284				WPFVisual visual = HwndSource.FromHwnd(
285					nativeWindowHande).CompositionTarget.RootVisual;
286				WPFWindow wpfWindow = WPFWindow.GetWindow(visual);
287				wpfWindow.KeyDown += OnWPFWindowKeyDown;
288				wpfWindow.KeyUp += OnWPFWindowKeyUp;
289				wpfWindow.PreviewTextInput += OnWPFWindowPreviewTextInput;
290			}
291				// Windows Forms
292			else
293			{
294				Control formsWindow = Control.FromHandle(nativeWindowHande);
295				formsWindow.KeyDown += OnFormsWindowKeyDown;
296				formsWindow.KeyUp += OnFormsWindowKeyUp;
297				formsWindow.KeyPress += OnFormsWindowKeyPress;
298			}
299
300			// Is this an editor window? Then still don't mark keys as handled.
301			if (isWpfWindow ||
302			    DeltaApplication.Window.Title.Contains("Editor") ||
303			    DeltaApplication.Window.Title.Contains("Tool"))
304			{
305				MarkAllKeysAsHandled = false;
306			}
307			else
308			{
309				// This will prevent Alt, Control, etc. being send to windows, thus
310				// disabling menus and Alt stealing the focus.
311				MarkAllKeysAsHandled = true;
312				// And also disable the WindowsKey (which is just plain annoying,
313				// especially for games, we can also use it as an working input key)!
314				// Get Current Module
315				ProcessModule currentModule =
316					Process.GetCurrentProcess().MainModule;
317				// Assign callback function each time keyboard process
318				keyboardProcessObject = LowLevelCaptureKey;
319				// Set Hook of Keyboard Process for current module
320				lowLevelKeyboardHook = SetWindowsHookEx(13, keyboardProcessObject,
321					GetModuleHandle(currentModule.ModuleName), 0);
322			} // else
323		}
324		#endregion
325
326		#region IDisposable Members
327		/// <summary>
328		/// Dispose, will just get rid of the keyboard hook if it was created.
329		/// </summary>
330		public void Dispose()
331		{
332			if (lowLevelKeyboardHook != IntPtr.Zero)
333			{
334				UnhookWindowsHookEx(lowLevelKeyboardHook);
335				lowLevelKeyboardHook = IntPtr.Zero;
336			}
337		}
338		#endregion
339
340		#region OnWPFWindowPreviewTextInput (Public)
341		/// <summary>
342		/// On WPF window preview text input
343		/// </summary>
344		/// <param name="sender">Sender</param>
345		/// <param name="e">Event</param>
346		public void OnWPFWindowPreviewTextInput(object sender,
347			TextCompositionEventArgs e)
348		{
349			inputTextBuffer += e.Text;
350		}
351		#endregion
352
353		#region HandleInput (Public)
354		/// <summary>
355		/// Handle input
356		/// </summary>
357		/// <param name="inputText">Input text</param>
358		public override void HandleInput(ref string inputText)
359		{
360			// This is pretty easy, we just need to append the text :)
361			if (inputTextBuffer.Length > 0)
362			{
363				inputText += inputTextBuffer;
364				inputTextBuffer = "";
365			}
366
367			// Was backspace pressed. Use the Pressed state here to get the repeated
368			// key presses too!
369			if (numberOfTimesBackspaceWasDown > 0 &&
370			    inputText.Length > 0)
371			{
372				// Completely kill all the input text?
373				if (numberOfTimesBackspaceWasDown >= inputText.Length)
374				{
375					inputText = "";
376				}
377				else
378				{
379					inputText = inputText.Remove(
380						inputText.Length - numberOfTimesBackspaceWasDown,
381						numberOfTimesBackspaceWasDown);
382				}
383			}
384
385			// Was enter pressed? Then add a newline
386			if (GetState(InputButton.Enter) == InputState.Released)
387			{
388				inputText += "\n";
389			}
390		}
391		#endregion
392
393		#region ForceShowOnScreenKeyboard (Public)
394		/// <summary>
395		/// Runs OSK.exe to simulate an on screen keyboard
396		/// </summary>
397		/// <remarks>
398		/// Check this article for more details
399		/// http://social.msdn.microsoft.com/Forums/da-DK/csharplanguage/thread/4e4511f8-ad50-4788-b5d7-2dd825b45665
400		/// </remarks>
401		public override void ForceShowOnScreenKeyboard()
402		{
403			if (oskProcessFilePath == null)
404			{
405				string winDirectory = Environment.GetEnvironmentVariable("WINDIR");
406
407				// Start with the default path of Windows7 (and Vista ? too)
408				string onScreenKeyboard = Path.Combine(winDirectory, "sysnative",
409					"osk.exe");
410				onScreenKeyboard = "";
411				// if the osk file does't exists at this loaction then we have probably
412				// an older Windows OS where the the file should be located in the
413				// 'system32' folder
414				if (File.Exists(onScreenKeyboard) == false)
415				{
416					onScreenKeyboard = Path.Combine(winDirectory, "system32", "osk.exe");
417				} // if
418
419				// if even this isn't the case then just ask the OS directly for this
420				// file
421				if (File.Exists(onScreenKeyboard) == false)
422				{
423					onScreenKeyboard = "osk.exe";
424				} // if
425
426				// Last remember the found path for the next time we need it
427				oskProcessFilePath = onScreenKeyboard;
428			} // if
429
430			try
431			{
432				//oskProcess = new Process();
433				//oskProcess.StartInfo.FileName = oskProcessFilePath;
434				//oskProcess.StartInfo.UseShellExecute = false;
435				//oskProcess.Start();
436				oskProcess = Process.Start(oskProcessFilePath);
437
438				// Some time the window is running but only in the back ground, this 
439				// happens in case we call HideOnScreenKeyboard, therefore we have 
440				// to bring it to the front again. 
441				//IntPtr windowHandle = (IntPtr)FindWindow(null, oskProcessTitle);
442				//ShowWindow(windowHandle, 1);
443				//ShowWindow(oskProcess.MainWindowHandle, 1);
444			} // try
445			catch (Exception ex)
446			{
447				Log.Warning("The OnScreenKeyboard couldn't be started because of:" +
448				            ex);
449			} // catch
450		}
451		#endregion
452
453		#region HideOnScreenKeyboard (Public)
454		/// <summary>
455		/// Kills the OSK.exe process thus hiding/ closing the On Screen Keyboard
456		/// </summary>
457		public override void HideOnScreenKeyboard()
458		{
459			if (oskProcess != null)
460			{
461				oskProcess.Kill();
462				oskProcess = null;
463			} // if
464
465			//IntPtr windowHandle = (IntPtr)FindWindow(null, oskProcessTitle);
466			//// Set the window handle to 0, it will disappear
467			//ShowWindow(windowHandle, 0);
468			// Set the window handle to 0, it will disappear
469			//ShowWindow(oskProcess.MainWindowHandle, 0);
470		}
471		#endregion
472
473		#region Methods (Private)
474
475		#region GetInputButtonFromKeyCode
476		/// <summary>
477		/// Get input button from key code
478		/// </summary>
479		private static InputButton GetInputButtonFromKeyCode(Keys keyCode)
480		{
481			// Basically we can just convert the key code straight away (see default)
482			// Some keys need special handling, they have different Xna key codes!
483			switch (keyCode)
484			{
485				case Keys.Control:
486				case Keys.ControlKey:
487					return InputButton.Control;
488
489				case Keys.LControlKey:
490					return InputButton.LeftControl;
491
492				case Keys.RControlKey:
493					return InputButton.RightControl;
494
495				case Keys.Alt:
496				case Keys.Menu:
497				case Keys.LMenu:
498				case Keys.RMenu:
499					return InputButton.Alt;
500
501				case Keys.Shift:
502				case Keys.ShiftKey:
503					return InputButton.Shift;
504
505				case Keys.LShiftKey:
506					return InputButton.LeftShift;
507
508				case Keys.RShiftKey:
509					return InputButton.RightShift;
510
511				case Keys.LWin:
512					return InputButton.LeftWindows;
513
514				case Keys.RWin:
515					return InputButton.RightWindows;
516
517				case Keys.Back:
518					return InputButton.BackSpace;
519
520				default:
521					return (InputButton)(int)keyCode;
522			}
523		}
524		#endregion
525
526		#region OnFormsWindowKeyDown
527		/// <summary>
528		/// On forms window key down
529		/// </summary>
530		/// <param name="sender">Sender</param>
531		/// <param name="e">Event args</param>
532		private void OnFormsWindowKeyDown(object sender,
533			KeyEventArgs e)
534		{
535			// Mark that we have data for the Update method.
536			HasData = true;
537
538			// Grab and convert the key code, which maps with our InputButton
539			InputButton key = GetInputButtonFromKeyCode(e.KeyCode);
540			keysDown.Add(key);
541
542			// Always mark this key as being used already, this way we can
543			// prevent windows from doing stuff for Alt, WindowsKey, etc.
544			// This works fine for alt, but seems to have no effect for WindowsKey
545			e.Handled = MarkAllKeysAsHandled;
546
547			//Log.Info("OnFormsWindowKeyDown e.KeyCode=" + e.KeyCode +
548			//  ", e.KeyData=" + e.KeyData +
549			//  ", e.KeyValue=" + e.KeyValue +
550			//  ", key=" + key);
551		}
552		#endregion
553
554		#region OnFormsWindowKeyUp
555		/// <summary>
556		/// On forms window key up
557		/// </summary>
558		/// <param name="sender">Sender</param>
559		/// <param name="e">Event args</param>
560		private void OnFormsWindowKeyUp(object sender,
561			KeyEventArgs e)
562		{
563			// Mark that we have data for the Update method.
564			HasData = true;
565
566			// Grab and convert the key code, which maps with our InputButton
567			InputButton key = GetInputButtonFromKeyCode(e.KeyCode);
568			keysUp.Add(key);
569
570			// Always mark this key as being used already, this way we can
571			// prevent windows from doing stuff for Alt, WindowsKey, etc.
572			// This works fine for alt, but seems to have no effect for WindowsKey
573			e.Handled = MarkAllKeysAsHandled;
574
575			//Log.Info("OnFormsWindowKeyUp e.KeyCode=" + e.KeyCode +
576			//  ", e.KeyData=" + e.KeyData +
577			//  ", e.KeyValue=" + e.KeyValue +
578			//  ", key=" + key);
579		}
580		#endregion
581
582		#region OnFormsWindowKeyPress
583		/// <summary>
584		/// On forms window key press
585		/// </summary>
586		/// <param name="sender">Event sender</param>
587		/// <param name="e">Event args</param>
588		private void OnFormsWindowKeyPress(object sender, KeyPressEventArgs e)
589		{
590			//Log.Info("OnFormsWindowKeyPress e.KeyChar='" + e.KeyChar + "'");
591			// Only handle real keys (above space)
592			if (e.KeyChar >= ' ')
593			{
594				inputTextBuffer += e.KeyChar;
595			}
596		}
597		#endregion
598
599		#region OnWPFWindowKeyDown
600		/// <summary>
601		/// On WPF window key down
602		/// </summary>
603		/// <param name="sender">Sender</param>
604		/// <param name="e">Event</param>
605		private void OnWPFWindowKeyDown(object sender, WPFKeyEventArgs e)
606		{
607			// Mark that we have data for the Update method.
608			HasData = true;
609			// We need to convert the WPF button to a virtual key, which then
610			// can be mapped to our InputButton enum.
611			InputButton key = GetInputButtonFromKeyCode(
612				(Keys)KeyInterop.VirtualKeyFromKey(e.Key));
613			keysDown.Add(key);
614			// Also add this key to the usedKeyIndices list
615			AddKeyIndex((int)key);
616		}
617		#endregion
618
619		#region OnWPFWindowKeyUp
620		/// <summary>
621		/// On WPF window key up
622		/// </summary>
623		/// <param name="sender">Sender</param>
624		/// <param name="e">Event</param>
625		private void OnWPFWindowKeyUp(object sender, WPFKeyEventArgs e)
626		{
627			// Mark that we have data for the Update method.
628			HasData = true;
629			// We need to convert the WPF button to a virtual key, which then
630			// can be mapped to our InputButton enum.
631			InputButton key = GetInputButtonFromKeyCode(
632				(Keys)KeyInterop.VirtualKeyFromKey(e.Key));
633			keysUp.Add(key);
634			// Also add this key to the usedKeyIndices list
635			AddKeyIndex((int)key);
636
637			// If this was the space key, also add it to the inputTextBuffer because
638			// OnWPFWindowPreviewTextInput does not seem to handle spaces:
639			// http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/446ec083-04c8-43f2-89dc-1e2521a31f6b
640			if (key == InputButton.Space)
641			{
642				inputTextBuffer += ' ';
643			}
644		}
645		#endregion
646
647		#region Update
648		/// <summary>
649		/// Update
650		/// </summary>
651		protected override void Update()
652		{
653			// No need to update if we have nothing connected!
654			if ( //can never be true here: IsConnected == false ||
655				// And skip we had no data before and still got nothing new
656				HasData == false)
657			{
658				return;
659			}
660
661			// First update all key states (so we can overwrite it easily below)
662			//for (int keyIndex = 0; keyIndex < NumberOfKeyboardKeys; keyIndex++)
663			int numberOfNotPressedKeys = 0;
664
665			// Time is stamped only when the key state is changed to Pressed
666			for (int index = 0; index < numberOfKeyIndices; index++)
667			{
668				#region IsPressed
669				int keyIndex = usedKeyIndices[index];
670				// If button was pressed before? Then just set to pressing.
671				if (keys[keyIndex] >= InputState.Pressed)
672				{
673					keys[keyIndex] = InputState.IsPressed;
674				}
675					#endregion
676
677					#region NoPressed
678					// Else set to NotPressed anymore
679				else
680				{
681					keys[keyIndex] = InputState.NotPressed;
682					numberOfNotPressedKeys++;
683				}
684				#endregion
685
686				// We need to handle combined keys too (Control makes LeftControl and
687				// RightControl key states to also be set, which is important for
688				// Windows input, but then again LeftControl from Xna needs also to
689				// set the Control key state in XnaKeyboard).
690				HandleCombinedKeys(keyIndex);
691			} // for
692
693			#region Pressed
694			// Now check if anything new was pressed
695			numberOfTimesBackspaceWasDown = 0;
696			foreach (InputButton newKey in keysDown)
697			{
698				int keyIndex = (int)newKey;
699				// 'IsPressed' state and will get the 'Pressed' all the time
700				// keysUp list for state checking, so only remove or add keys to the
701				// lists at the events
702				if (keys[keyIndex] != InputState.IsPressed)
703				{
704					keys[keyIndex] = InputState.Pressed;
705				} // if
706
707				// We need to handle combined keys too (Control makes LeftControl and
708				// RightControl key states to also be set, which is important for
709				// Windows input, but then again LeftControl from Xna needs also to
710				// set the Control key state in XnaKeyboard).
711				HandleCombinedKeys(keyIndex);
712
713				// Also add this key to the usedKeyIndices list
714				AddKeyIndex(keyIndex);
715
716				// Also check how many times we pressed BackSpace, which is important
717				// for HandleInput for textbox strings.
718				if (newKey == InputButton.BackSpace)
719				{
720					numberOfTimesBackspaceWasDown++;
721				}
722			}
723			#endregion
724
725			#region Released
726			// And finally go through all newly not anymore pressed keys
727			foreach (InputButton keyNotPressedAnymore in keysUp)
728			{
729				int keyIndex = (int)keyNotPressedAnymore;
730				keys[keyIndex] = InputState.Released;
731
732				// We need to handle combined keys too (Control makes LeftControl and
733				// RightControl key states to also be set, which is important for
734				// Windows input, but then again LeftControl from Xna needs also to
735				// set the Control key state in XnaKeyboard).
736				HandleCombinedKeys(keyIndex);
737
738				// Also add this key to the usedKeyIndices list
739				AddKeyIndex(keyIndex);
740			}
741			#endregion
742
743			// And handle the extra logic in the base class (e.g. virtual cursor)
744			base.Update();
745
746			// Check if we have no more pressed keys, then reset!
747			if (numberOfNotPressedKeys == numberOfKeyIndices &&
748			    keysDown.Count == 0 &&
749			    keysUp.Count == 0)
750			{
751				numberOfNotPressedKeys = 0;
752				// If we still have no data, no key is in any pressed state,
753				// then we can clear the usedKeyIndices to skip all checks
754				// for the next frame
755				HasData = false;
756			}
757			else
758			{
759				keysDown.Clear();
760				keysUp.Clear();
761			}
762		}
763		#endregion
764
765		#region IsKeyboardConnected
766		/// <summary>
767		/// This method will detect all the HIDs and record the keyboards that 
768		/// exists in the system
769		/// </summary>
770		/// <returns>
771		/// true if any keyboard was present, otherwise false.
772		/// </returns>
773		private void IsKeyboardConnected()
774		{
775			isConnected = false;
776			// Set the information you want to retrieve about the device you need 
777			// to detect through adding the GUID for the device.
778			IntPtr devinfo = SetupDiGetClassDevs(ref GUID_DEVCLASS_KEYBOARD,
779				IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT);
780
781			// Defines the class instance that sets the device information
782			SP_DEVINFO_DATA devInfoSet = new SP_DEVINFO_DATA
783			{
784				cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA))
785			};
786
787			// Check now if a keyboard is attached.
788			if (SetupDiEnumDeviceInfo(devinfo, 0, ref devInfoSet))
789			{
790				isConnected = true;
791			}
792		}
793		#endregion
794
795		#region SetWindowsHookEx
796		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
797		private static extern IntPtr SetWindowsHookEx(int id,
798			LowLevelKeyboardProc callback, IntPtr hMod, uint dwThreadId);
799		#endregion
800
801		#region UnhookWindowsHookEx
802		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
803		private static extern bool UnhookWindowsHookEx(IntPtr hook);
804		#endregion
805
806		#region CallNextHookEx
807		[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
808		private static extern IntPtr CallNextHookEx(IntPtr hook, int nCode,
809			IntPtr wp, IntPtr lp);
810		#endregion
811
812		#region GetModuleHandle
813		[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
814		private static extern IntPtr GetModuleHandle(string name);
815		#endregion
816
817		#region GetAsyncKeyState
818		[DllImport("user32.dll", CharSet = CharSet.Auto)]
819		private static extern short GetAsyncKeyState(Keys key);
820		#endregion
821
822		// Global objects for the keyboard hook
823
824		#region LowLevelCaptureKey
825		/// <summary>
826		/// Low level capture key
827		/// </summary>
828		/// <param name="nCode">Capture code to handle (we handle all above 0).
829		/// </param>
830		/// <param name="wp">Pointer to the first pointer (unused here)</param>
831		/// <param name="lp">Pointer to the KBDLLHOOKSTRUCT</param>
832		/// <returns>Pointer, but basically 0 if not successful, otherwise this
833		/// method handled the capture key.</returns>
834		private IntPtr LowLevelCaptureKey(int nCode, IntPtr wp, IntPtr lp)
835		{
836			if (nCode >= 0)
837			{
838				KBDLLHOOKSTRUCT objKeyInfo = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(
839					lp, typeof(KBDLLHOOKSTRUCT));
840
841				// Disable the Windows keys from stealing our app focus
842				if (objKeyInfo.key == Keys.RWin ||
843				    objKeyInfo.key == Keys.LWin)
844				{
845					// Mark that we have data for the Update method.
846					HasData = true;
847					// Grab and convert the key code, which maps with our InputButton
848					InputButton key = GetInputButtonFromKeyCode(objKeyInfo.key);
849					// Is the key up or down?
850					if ((objKeyInfo.flags & 0x80) != 0)
851					{
852						keysUp.Add(key);
853					}
854					else
855					{
856						keysDown.Add(key);
857					}
858
859					// We need to handle combined keys too (Control makes LeftControl and
860					// RightControl key states to also be set, which is important for
861					// Windows input, but then again LeftControl from Xna needs also to
862					// set the Control key state in XnaKeyboard).
863					HandleCombinedKeys((int)key);
864
865					// Also add this key to the usedKeyIndices list
866					AddKeyIndex((int)key);
867
868					// And don't handle it any further (will not be handled by Windows
869					// and also won't get to the keyboard events below).
870					return (IntPtr)1;
871				}
872			} // if
873
874			// Else use the normal keyboard handling hook
875			return CallNextHookEx(lowLevelKeyboardHook, nCode, wp, lp);
876		}
877		#endregion
878
879		#region SetupDiGetClassDevs
880		/// <summary>
881		/// Returns a handle to a device information set that contains requested
882		/// device information elements for a local computer. 
883		/// </summary>
884		/// <param name="classGuid">KeyboardGUID</param>
885		/// <param name="enumerator">An identifier (ID) of a Plug and Play (PnP)
886		/// enumerator.</param>
887		/// <param name="hWndParent">A handle to the top-level window to be used
888		/// for a user interface that is associated with installing a device 
889		/// instance in the device information set. This handle is optional and 
890		/// can be NULL.</param>
891		/// <param name="flags">device information elements that are added to the
892		/// device information set. </param>
893		/// <returns>Pointer to the device information handle.</returns>
894		[DllImport("setupapi.dll")]
895		private static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid,
896			IntPtr enumerator, IntPtr hWndParent, int flags);
897		#endregion
898
899		#region SetupDiEnumDeviceInfo
900		/// <summary>
901		/// The SetupDiEnumDeviceInfo function returns a SP_DEVINFO_DATA
902		/// structure that specifies a device information element in a device 
903		/// information set. 
904		/// </summary>
905		/// <param name="deviceInfoSet">A handle to the device information set 
906		/// for which to return an SP_DEVINFO_DATA structure that represents a 
907		/// device information element.</param>
908		/// <param name="supplies">A zero-based index of the device information 
909		/// element to retrieve.</param>
910		/// <param name="deviceInfoData">A pointer to an SP_DEVINFO_DATA structure 
911		/// to receive information about an enumerated device information 
912		/// element. The caller must set DeviceInfoData.cbSize to 
913		/// sizeof(SP_DEVINFO_DATA).</param>
914		/// <returns>True if this call succeeded, false otherwise.</returns>
915		[DllImport("setupapi.dll")]
916		private static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet,
917			int supplies, ref SP_DEVINFO_DATA deviceInfoData);
918		#endregion
919
920		#region FindWindow
921		/// <summary>
922		/// Retrieves a handle to the top-level window whose class name and window 
923		/// name match the specified strings. This function does not search child
924		/// windows. This function does not perform a case-sensitive search.
925		/// </summary>
926		/// <param name="_ClassName">
927		/// The class name or a class atom created by a previous call to the 
928		/// RegisterClass or RegisterClassEx function.
929		/// </param>
930		/// <param name="_WindowName">
931		/// The window name (the window's title)
932		/// </param>
933		/// <returns>
934		/// Handle to the window that has the specified class name and 
935		/// window name.</returns>
936		[DllImport("user32.dll", EntryPoint = "FindWindow")]
937		private static extern Int32 FindWindow(string _ClassName,
938			string _WindowName);
939		#endregion
940
941		#region ShowWindow
942		/// <summary>
943		/// Finds a specific runnin window for a given process
944		/// </summary>
945		/// <param name="hWnd">The window handler</param>
946		/// <param name="nCmdShow">Specifies the view options, 1 = view, 
947		/// 0 = hide</param>
948		/// <returns>true if successfully invoked</returns>
949		[DllImport("user32.dll")]
950		private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
951		#endregion
952
953		#endregion
954	}
955}