WindowsInput.cs 4.1 KB

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