2
0

WindowsInput.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. if (ConsoleDriver.RunningUnitTests)
  50. {
  51. return false;
  52. }
  53. const int bufferSize = 1; // We only need to check if there's at least one event
  54. nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
  55. try
  56. {
  57. // Use PeekConsoleInput to inspect the input buffer without removing events
  58. if (PeekConsoleInput (_inputHandle, pRecord, bufferSize, 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. Console.WriteLine ($"Error in Peek: {ex.Message}");
  73. return false;
  74. }
  75. finally
  76. {
  77. // Free the allocated memory
  78. Marshal.FreeHGlobal (pRecord);
  79. }
  80. }
  81. protected override IEnumerable<InputRecord> Read ()
  82. {
  83. const int bufferSize = 1;
  84. nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
  85. try
  86. {
  87. ReadConsoleInput (
  88. _inputHandle,
  89. pRecord,
  90. bufferSize,
  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. if (ConsoleDriver.RunningUnitTests)
  108. {
  109. return;
  110. }
  111. if (!FlushConsoleInputBuffer (_inputHandle))
  112. {
  113. throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
  114. }
  115. SetConsoleMode (_inputHandle, _originalConsoleMode);
  116. }
  117. }