PageRenderTime 139ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/InputSystem/Windows/WindowsKeyboard.cs

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