WindowsInput.cs 3.6 KB

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