WindowsInput.cs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 : ConsoleInput<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. if (ConsoleDriver.RunningUnitTests)
  36. {
  37. return;
  38. }
  39. _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
  40. GetConsoleMode (_inputHandle, out uint v);
  41. _originalConsoleMode = v;
  42. uint newConsoleMode = _originalConsoleMode;
  43. newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
  44. newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
  45. newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
  46. SetConsoleMode (_inputHandle, newConsoleMode);
  47. }
  48. protected override bool Peek ()
  49. {
  50. if (ConsoleDriver.RunningUnitTests)
  51. {
  52. return false;
  53. }
  54. const int BUFFER_SIZE = 1; // We only need to check if there's at least one event
  55. nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * BUFFER_SIZE);
  56. try
  57. {
  58. // Use PeekConsoleInput to inspect the input buffer without removing events
  59. if (PeekConsoleInput (_inputHandle, pRecord, BUFFER_SIZE, out uint numberOfEventsRead))
  60. {
  61. // Return true if there's at least one event in the buffer
  62. return numberOfEventsRead > 0;
  63. }
  64. else
  65. {
  66. // Handle the failure of PeekConsoleInput
  67. throw new InvalidOperationException ("Failed to peek console input.");
  68. }
  69. }
  70. catch (Exception ex)
  71. {
  72. // Optionally log the exception
  73. Console.WriteLine (@$"Error in Peek: {ex.Message}");
  74. return false;
  75. }
  76. finally
  77. {
  78. // Free the allocated memory
  79. Marshal.FreeHGlobal (pRecord);
  80. }
  81. }
  82. protected override IEnumerable<InputRecord> Read ()
  83. {
  84. const int BUFFER_SIZE = 1;
  85. nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * BUFFER_SIZE);
  86. try
  87. {
  88. ReadConsoleInput (
  89. _inputHandle,
  90. pRecord,
  91. BUFFER_SIZE,
  92. out uint numberEventsRead);
  93. return numberEventsRead == 0
  94. ? []
  95. : new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
  96. }
  97. catch (Exception)
  98. {
  99. return [];
  100. }
  101. finally
  102. {
  103. Marshal.FreeHGlobal (pRecord);
  104. }
  105. }
  106. public override void Dispose ()
  107. {
  108. if (ConsoleDriver.RunningUnitTests)
  109. {
  110. return;
  111. }
  112. if (!FlushConsoleInputBuffer (_inputHandle))
  113. {
  114. throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
  115. }
  116. SetConsoleMode (_inputHandle, _originalConsoleMode);
  117. }
  118. }