WindowsInput.cs 4.3 KB

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