WindowsInput.cs 4.0 KB

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