WindowsInput.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. using System.Runtime.InteropServices;
  2. using Microsoft.Extensions.Logging;
  3. using static Terminal.Gui.Drivers.WindowsConsole;
  4. namespace Terminal.Gui.Drivers;
  5. internal class WindowsInput : InputImpl<InputRecord>, IWindowsInput
  6. {
  7. private readonly nint _inputHandle;
  8. [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
  9. public static extern bool ReadConsoleInput (
  10. nint hConsoleInput,
  11. nint lpBuffer,
  12. uint nLength,
  13. out uint lpNumberOfEventsRead
  14. );
  15. [DllImport ("kernel32.dll", EntryPoint = "PeekConsoleInputW", CharSet = CharSet.Unicode)]
  16. public static extern bool PeekConsoleInput (
  17. nint hConsoleInput,
  18. nint lpBuffer,
  19. uint nLength,
  20. out uint lpNumberOfEventsRead
  21. );
  22. [DllImport ("kernel32.dll", SetLastError = true)]
  23. private static extern nint GetStdHandle (int nStdHandle);
  24. [DllImport ("kernel32.dll")]
  25. private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
  26. [DllImport ("kernel32.dll")]
  27. private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
  28. private readonly uint _originalConsoleMode;
  29. [DllImport ("kernel32.dll", SetLastError = true)]
  30. private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
  31. public WindowsInput ()
  32. {
  33. Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
  34. try
  35. {
  36. _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
  37. GetConsoleMode (_inputHandle, out uint v);
  38. _originalConsoleMode = v;
  39. uint newConsoleMode = _originalConsoleMode;
  40. newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
  41. newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
  42. newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
  43. SetConsoleMode (_inputHandle, newConsoleMode);
  44. }
  45. catch
  46. {
  47. // ignore errors during unit tests
  48. }
  49. }
  50. public override bool Peek ()
  51. {
  52. const int BUFFER_SIZE = 1; // We only need to check if there's at least one event
  53. nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * BUFFER_SIZE);
  54. try
  55. {
  56. // Use PeekConsoleInput to inspect the input buffer without removing events
  57. if (PeekConsoleInput (_inputHandle, pRecord, BUFFER_SIZE, out uint numberOfEventsRead))
  58. {
  59. // Return true if there's at least one event in the buffer
  60. return numberOfEventsRead > 0;
  61. }
  62. else
  63. {
  64. // Handle the failure of PeekConsoleInput
  65. throw new InvalidOperationException ("Failed to peek console input.");
  66. }
  67. }
  68. catch (Exception ex)
  69. {
  70. // Optionally log the exception
  71. Logging.Error (@$"Error in Peek: {ex.Message}");
  72. return false;
  73. }
  74. finally
  75. {
  76. // Free the allocated memory
  77. Marshal.FreeHGlobal (pRecord);
  78. }
  79. }
  80. public override IEnumerable<InputRecord> Read ()
  81. {
  82. const int BUFFER_SIZE = 1;
  83. nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * BUFFER_SIZE);
  84. try
  85. {
  86. ReadConsoleInput (
  87. _inputHandle,
  88. pRecord,
  89. BUFFER_SIZE,
  90. out uint numberEventsRead);
  91. return numberEventsRead == 0
  92. ? []
  93. : new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
  94. }
  95. catch (Exception)
  96. {
  97. return [];
  98. }
  99. finally
  100. {
  101. Marshal.FreeHGlobal (pRecord);
  102. }
  103. }
  104. public override void Dispose ()
  105. {
  106. try
  107. {
  108. if (!FlushConsoleInputBuffer (_inputHandle))
  109. {
  110. throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
  111. }
  112. SetConsoleMode (_inputHandle, _originalConsoleMode);
  113. }
  114. catch
  115. {
  116. // ignore errors during unit tests
  117. }
  118. }
  119. }