WindowsConsole.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. #nullable enable
  2. using System.Collections.Concurrent;
  3. using System.ComponentModel;
  4. using System.Runtime.InteropServices;
  5. namespace Terminal.Gui.Drivers;
  6. public partial class WindowsConsole
  7. {
  8. private CancellationTokenSource? _inputReadyCancellationTokenSource;
  9. private readonly BlockingCollection<InputRecord> _inputQueue = new (new ConcurrentQueue<InputRecord> ());
  10. internal WindowsMainLoop? _mainLoop;
  11. public const int STD_OUTPUT_HANDLE = -11;
  12. public const int STD_INPUT_HANDLE = -10;
  13. private readonly nint _inputHandle;
  14. private nint _outputHandle;
  15. private nint _screenBuffer;
  16. private readonly uint _originalConsoleMode;
  17. private CursorVisibility? _initialCursorVisibility;
  18. private CursorVisibility? _currentCursorVisibility;
  19. private CursorVisibility? _pendingCursorVisibility;
  20. private readonly StringBuilder _stringBuilder = new (256 * 1024);
  21. private string _lastWrite = string.Empty;
  22. public WindowsConsole ()
  23. {
  24. _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
  25. _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
  26. _originalConsoleMode = ConsoleMode;
  27. uint newConsoleMode = _originalConsoleMode;
  28. newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
  29. newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
  30. newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
  31. ConsoleMode = newConsoleMode;
  32. _inputReadyCancellationTokenSource = new ();
  33. Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
  34. }
  35. public InputRecord? DequeueInput ()
  36. {
  37. while (_inputReadyCancellationTokenSource is { })
  38. {
  39. try
  40. {
  41. return _inputQueue.Take (_inputReadyCancellationTokenSource.Token);
  42. }
  43. catch (OperationCanceledException)
  44. {
  45. return null;
  46. }
  47. }
  48. return null;
  49. }
  50. public InputRecord? ReadConsoleInput ()
  51. {
  52. const int BUFFER_SIZE = 1;
  53. InputRecord inputRecord = default;
  54. uint numberEventsRead = 0;
  55. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  56. {
  57. try
  58. {
  59. // Peek to check if there is any input available
  60. if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0)
  61. {
  62. // Read the input since it is available
  63. ReadConsoleInput (
  64. _inputHandle,
  65. out inputRecord,
  66. BUFFER_SIZE,
  67. out numberEventsRead);
  68. }
  69. if (numberEventsRead > 0)
  70. {
  71. return inputRecord;
  72. }
  73. try
  74. {
  75. Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token);
  76. }
  77. catch (OperationCanceledException)
  78. {
  79. return null;
  80. }
  81. }
  82. catch (Exception ex)
  83. {
  84. if (ex is OperationCanceledException or ObjectDisposedException)
  85. {
  86. return null;
  87. }
  88. throw;
  89. }
  90. }
  91. return null;
  92. }
  93. private void ProcessInputQueue ()
  94. {
  95. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  96. {
  97. try
  98. {
  99. if (_inputQueue.Count == 0)
  100. {
  101. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  102. {
  103. try
  104. {
  105. InputRecord? inpRec = ReadConsoleInput ();
  106. if (inpRec is { })
  107. {
  108. _inputQueue.Add (inpRec.Value);
  109. break;
  110. }
  111. }
  112. catch (OperationCanceledException)
  113. {
  114. return;
  115. }
  116. }
  117. }
  118. }
  119. catch (OperationCanceledException)
  120. {
  121. return;
  122. }
  123. }
  124. }
  125. // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
  126. private TextStyle _redrawTextStyle = TextStyle.None;
  127. private CharInfo []? _originalStdOutChars;
  128. public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
  129. {
  130. //Debug.WriteLine ("WriteToConsole");
  131. if (!IsWindowsTerminal && _screenBuffer == nint.Zero)
  132. {
  133. ReadFromConsoleOutput (size, bufferSize, ref window);
  134. }
  135. SetInitialCursorVisibility ();
  136. var result = false;
  137. if (force16Colors)
  138. {
  139. var i = 0;
  140. CharInfo [] ci = new CharInfo [charInfoBuffer.Length];
  141. foreach (ExtendedCharInfo info in charInfoBuffer)
  142. {
  143. ci [i++] = new CharInfo
  144. {
  145. Char = new CharUnion { UnicodeChar = info.Char },
  146. Attributes =
  147. (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
  148. };
  149. }
  150. result = WriteConsoleOutput (IsWindowsTerminal ? _outputHandle : _screenBuffer, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window);
  151. }
  152. else
  153. {
  154. _stringBuilder.Clear ();
  155. _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
  156. EscSeqUtils.CSI_AppendCursorPosition (_stringBuilder, 0, 0);
  157. Attribute? prev = null;
  158. foreach (ExtendedCharInfo info in charInfoBuffer)
  159. {
  160. Attribute attr = info.Attribute;
  161. if (attr != prev)
  162. {
  163. prev = attr;
  164. EscSeqUtils.CSI_AppendForegroundColorRGB (_stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
  165. EscSeqUtils.CSI_AppendBackgroundColorRGB (_stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
  166. EscSeqUtils.CSI_AppendTextStyleChange (_stringBuilder, _redrawTextStyle, attr.Style);
  167. _redrawTextStyle = attr.Style;
  168. }
  169. if (info.Char != '\x1b')
  170. {
  171. if (!info.Empty)
  172. {
  173. _stringBuilder.Append (info.Char);
  174. }
  175. }
  176. else
  177. {
  178. _stringBuilder.Append (' ');
  179. }
  180. }
  181. _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
  182. _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
  183. var s = _stringBuilder.ToString ();
  184. // TODO: requires extensive testing if we go down this route
  185. // If console output has changed
  186. if (s != _lastWrite)
  187. {
  188. // supply console with the new content
  189. result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
  190. }
  191. _lastWrite = s;
  192. foreach (var sixel in Application.Sixel)
  193. {
  194. SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y));
  195. WriteConsole (IsWindowsTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
  196. }
  197. }
  198. if (!result)
  199. {
  200. int err = Marshal.GetLastWin32Error ();
  201. if (err != 0)
  202. {
  203. throw new Win32Exception (err);
  204. }
  205. }
  206. return result;
  207. }
  208. internal bool WriteANSI (string ansi)
  209. {
  210. if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero))
  211. {
  212. // Flush the output to make sure it's sent immediately
  213. return FlushFileBuffers (_outputHandle);
  214. }
  215. return false;
  216. }
  217. public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
  218. {
  219. _screenBuffer = CreateConsoleScreenBuffer (
  220. DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
  221. ShareMode.FileShareRead | ShareMode.FileShareWrite,
  222. nint.Zero,
  223. 1,
  224. nint.Zero
  225. );
  226. if (_screenBuffer == INVALID_HANDLE_VALUE)
  227. {
  228. int err = Marshal.GetLastWin32Error ();
  229. if (err != 0)
  230. {
  231. throw new Win32Exception (err);
  232. }
  233. }
  234. if (!SetConsoleActiveScreenBuffer (_screenBuffer))
  235. {
  236. throw new Win32Exception (Marshal.GetLastWin32Error ());
  237. }
  238. _originalStdOutChars = new CharInfo [size.Height * size.Width];
  239. if (!ReadConsoleOutput (_screenBuffer, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window))
  240. {
  241. throw new Win32Exception (Marshal.GetLastWin32Error ());
  242. }
  243. }
  244. public bool SetCursorPosition (Coord position)
  245. {
  246. return SetConsoleCursorPosition (IsWindowsTerminal ? _outputHandle : _screenBuffer, position);
  247. }
  248. public void SetInitialCursorVisibility ()
  249. {
  250. if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility))
  251. {
  252. _initialCursorVisibility = visibility;
  253. }
  254. }
  255. public bool GetCursorVisibility (out CursorVisibility visibility)
  256. {
  257. if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
  258. {
  259. visibility = CursorVisibility.Invisible;
  260. return false;
  261. }
  262. if (!GetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info))
  263. {
  264. int err = Marshal.GetLastWin32Error ();
  265. if (err != 0)
  266. {
  267. throw new Win32Exception (err);
  268. }
  269. visibility = CursorVisibility.Default;
  270. return false;
  271. }
  272. if (!info.bVisible)
  273. {
  274. visibility = CursorVisibility.Invisible;
  275. }
  276. else if (info.dwSize > 50)
  277. {
  278. visibility = CursorVisibility.Default;
  279. }
  280. else
  281. {
  282. visibility = CursorVisibility.Default;
  283. }
  284. return visibility != CursorVisibility.Invisible;
  285. }
  286. public bool EnsureCursorVisibility ()
  287. {
  288. if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value))
  289. {
  290. _pendingCursorVisibility = null;
  291. return true;
  292. }
  293. return false;
  294. }
  295. public void ForceRefreshCursorVisibility ()
  296. {
  297. if (_currentCursorVisibility.HasValue)
  298. {
  299. _pendingCursorVisibility = _currentCursorVisibility;
  300. _currentCursorVisibility = null;
  301. }
  302. }
  303. public bool SetCursorVisibility (CursorVisibility visibility)
  304. {
  305. if (_initialCursorVisibility.HasValue == false)
  306. {
  307. _pendingCursorVisibility = visibility;
  308. return false;
  309. }
  310. if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility)
  311. {
  312. var info = new ConsoleCursorInfo
  313. {
  314. dwSize = (uint)visibility & 0x00FF,
  315. bVisible = ((uint)visibility & 0xFF00) != 0
  316. };
  317. if (!SetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref info))
  318. {
  319. return false;
  320. }
  321. _currentCursorVisibility = visibility;
  322. }
  323. return true;
  324. }
  325. public void Cleanup ()
  326. {
  327. if (_initialCursorVisibility.HasValue)
  328. {
  329. SetCursorVisibility (_initialCursorVisibility.Value);
  330. }
  331. //SetConsoleOutputWindow (out _);
  332. ConsoleMode = _originalConsoleMode;
  333. _outputHandle = CreateConsoleScreenBuffer (
  334. DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
  335. ShareMode.FileShareRead | ShareMode.FileShareWrite,
  336. nint.Zero,
  337. 1,
  338. nint.Zero
  339. );
  340. if (!SetConsoleActiveScreenBuffer (_outputHandle))
  341. {
  342. int err = Marshal.GetLastWin32Error ();
  343. Console.WriteLine ("Error: {0}", err);
  344. }
  345. if (_screenBuffer != nint.Zero)
  346. {
  347. CloseHandle (_screenBuffer);
  348. }
  349. _screenBuffer = nint.Zero;
  350. _inputReadyCancellationTokenSource?.Cancel ();
  351. _inputReadyCancellationTokenSource?.Dispose ();
  352. _inputReadyCancellationTokenSource = null;
  353. }
  354. internal Size GetConsoleBufferWindow (out Point position)
  355. {
  356. if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
  357. {
  358. position = Point.Empty;
  359. return Size.Empty;
  360. }
  361. var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
  362. csbi.cbSize = (uint)Marshal.SizeOf (csbi);
  363. if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
  364. {
  365. //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
  366. position = Point.Empty;
  367. return Size.Empty;
  368. }
  369. Size sz = new (
  370. csbi.srWindow.Right - csbi.srWindow.Left + 1,
  371. csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
  372. position = new (csbi.srWindow.Left, csbi.srWindow.Top);
  373. return sz;
  374. }
  375. internal Size GetConsoleOutputWindow (out Point position)
  376. {
  377. var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
  378. csbi.cbSize = (uint)Marshal.SizeOf (csbi);
  379. if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
  380. {
  381. throw new Win32Exception (Marshal.GetLastWin32Error ());
  382. }
  383. Size sz = new (
  384. csbi.srWindow.Right - csbi.srWindow.Left + 1,
  385. csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
  386. position = new (csbi.srWindow.Left, csbi.srWindow.Top);
  387. return sz;
  388. }
  389. internal Size SetConsoleWindow (short cols, short rows)
  390. {
  391. var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
  392. csbi.cbSize = (uint)Marshal.SizeOf (csbi);
  393. if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
  394. {
  395. throw new Win32Exception (Marshal.GetLastWin32Error ());
  396. }
  397. Coord maxWinSize = GetLargestConsoleWindowSize (IsWindowsTerminal ? _outputHandle : _screenBuffer);
  398. short newCols = Math.Min (cols, maxWinSize.X);
  399. short newRows = Math.Min (rows, maxWinSize.Y);
  400. csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1));
  401. csbi.srWindow = new SmallRect (0, 0, newCols, newRows);
  402. csbi.dwMaximumWindowSize = new Coord (newCols, newRows);
  403. if (!SetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
  404. {
  405. throw new Win32Exception (Marshal.GetLastWin32Error ());
  406. }
  407. var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
  408. if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
  409. {
  410. //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
  411. return new (cols, rows);
  412. }
  413. SetConsoleOutputWindow (csbi);
  414. return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
  415. }
  416. private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
  417. {
  418. if ((IsWindowsTerminal
  419. ? _outputHandle
  420. : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
  421. {
  422. throw new Win32Exception (Marshal.GetLastWin32Error ());
  423. }
  424. }
  425. internal Size SetConsoleOutputWindow (out Point position)
  426. {
  427. if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
  428. {
  429. position = Point.Empty;
  430. return Size.Empty;
  431. }
  432. var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
  433. csbi.cbSize = (uint)Marshal.SizeOf (csbi);
  434. if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
  435. {
  436. throw new Win32Exception (Marshal.GetLastWin32Error ());
  437. }
  438. Size sz = new (
  439. csbi.srWindow.Right - csbi.srWindow.Left + 1,
  440. Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0));
  441. position = new (csbi.srWindow.Left, csbi.srWindow.Top);
  442. SetConsoleOutputWindow (csbi);
  443. var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
  444. if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
  445. {
  446. throw new Win32Exception (Marshal.GetLastWin32Error ());
  447. }
  448. if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
  449. {
  450. throw new Win32Exception (Marshal.GetLastWin32Error ());
  451. }
  452. return sz;
  453. }
  454. internal bool IsWindowsTerminal { get; set; }
  455. private uint ConsoleMode
  456. {
  457. get
  458. {
  459. GetConsoleMode (_inputHandle, out uint v);
  460. return v;
  461. }
  462. set => SetConsoleMode (_inputHandle, value);
  463. }
  464. [Flags]
  465. public enum ConsoleModes : uint
  466. {
  467. EnableProcessedInput = 1,
  468. EnableMouseInput = 16,
  469. EnableQuickEditMode = 64,
  470. EnableExtendedFlags = 128
  471. }
  472. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  473. public struct KeyEventRecord
  474. {
  475. [FieldOffset (0)]
  476. [MarshalAs (UnmanagedType.Bool)]
  477. public bool bKeyDown;
  478. [FieldOffset (4)]
  479. [MarshalAs (UnmanagedType.U2)]
  480. public ushort wRepeatCount;
  481. [FieldOffset (6)]
  482. [MarshalAs (UnmanagedType.U2)]
  483. public ConsoleKeyMapping.VK wVirtualKeyCode;
  484. [FieldOffset (8)]
  485. [MarshalAs (UnmanagedType.U2)]
  486. public ushort wVirtualScanCode;
  487. [FieldOffset (10)]
  488. public char UnicodeChar;
  489. [FieldOffset (12)]
  490. [MarshalAs (UnmanagedType.U4)]
  491. public ControlKeyState dwControlKeyState;
  492. public readonly override string ToString ()
  493. {
  494. return
  495. $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]";
  496. }
  497. }
  498. [Flags]
  499. public enum ButtonState
  500. {
  501. NoButtonPressed = 0,
  502. Button1Pressed = 1,
  503. Button2Pressed = 4,
  504. Button3Pressed = 8,
  505. Button4Pressed = 16,
  506. RightmostButtonPressed = 2
  507. }
  508. [Flags]
  509. public enum ControlKeyState
  510. {
  511. NoControlKeyPressed = 0,
  512. RightAltPressed = 1,
  513. LeftAltPressed = 2,
  514. RightControlPressed = 4,
  515. LeftControlPressed = 8,
  516. ShiftPressed = 16,
  517. NumlockOn = 32,
  518. ScrolllockOn = 64,
  519. CapslockOn = 128,
  520. EnhancedKey = 256
  521. }
  522. [Flags]
  523. public enum EventFlags
  524. {
  525. NoEvent = 0,
  526. MouseMoved = 1,
  527. DoubleClick = 2,
  528. MouseWheeled = 4,
  529. MouseHorizontalWheeled = 8
  530. }
  531. [StructLayout (LayoutKind.Explicit)]
  532. public struct MouseEventRecord
  533. {
  534. [FieldOffset (0)]
  535. public Coord MousePosition;
  536. [FieldOffset (4)]
  537. public ButtonState ButtonState;
  538. [FieldOffset (8)]
  539. public ControlKeyState ControlKeyState;
  540. [FieldOffset (12)]
  541. public EventFlags EventFlags;
  542. public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; }
  543. }
  544. public struct WindowBufferSizeRecord
  545. {
  546. public Coord _size;
  547. public WindowBufferSizeRecord (short x, short y) { _size = new Coord (x, y); }
  548. public readonly override string ToString () { return $"[WindowBufferSize{_size}"; }
  549. }
  550. [StructLayout (LayoutKind.Sequential)]
  551. public struct MenuEventRecord
  552. {
  553. public uint dwCommandId;
  554. }
  555. [StructLayout (LayoutKind.Sequential)]
  556. public struct FocusEventRecord
  557. {
  558. public uint bSetFocus;
  559. }
  560. public enum EventType : ushort
  561. {
  562. Focus = 0x10,
  563. Key = 0x1,
  564. Menu = 0x8,
  565. Mouse = 2,
  566. WindowBufferSize = 4
  567. }
  568. [StructLayout (LayoutKind.Explicit)]
  569. public struct InputRecord
  570. {
  571. [FieldOffset (0)]
  572. public EventType EventType;
  573. [FieldOffset (4)]
  574. public KeyEventRecord KeyEvent;
  575. [FieldOffset (4)]
  576. public MouseEventRecord MouseEvent;
  577. [FieldOffset (4)]
  578. public WindowBufferSizeRecord WindowBufferSizeEvent;
  579. [FieldOffset (4)]
  580. public MenuEventRecord MenuEvent;
  581. [FieldOffset (4)]
  582. public FocusEventRecord FocusEvent;
  583. public readonly override string ToString ()
  584. {
  585. return (EventType switch
  586. {
  587. EventType.Focus => FocusEvent.ToString (),
  588. EventType.Key => KeyEvent.ToString (),
  589. EventType.Menu => MenuEvent.ToString (),
  590. EventType.Mouse => MouseEvent.ToString (),
  591. EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
  592. _ => "Unknown event type: " + EventType
  593. })!;
  594. }
  595. }
  596. [Flags]
  597. private enum ShareMode : uint
  598. {
  599. FileShareRead = 1,
  600. FileShareWrite = 2
  601. }
  602. [Flags]
  603. private enum DesiredAccess : uint
  604. {
  605. GenericRead = 2147483648,
  606. GenericWrite = 1073741824
  607. }
  608. [StructLayout (LayoutKind.Sequential)]
  609. public struct ConsoleScreenBufferInfo
  610. {
  611. public Coord dwSize;
  612. public Coord dwCursorPosition;
  613. public ushort wAttributes;
  614. public SmallRect srWindow;
  615. public Coord dwMaximumWindowSize;
  616. }
  617. [StructLayout (LayoutKind.Sequential)]
  618. public struct Coord
  619. {
  620. public short X;
  621. public short Y;
  622. public Coord (short x, short y)
  623. {
  624. X = x;
  625. Y = y;
  626. }
  627. public readonly override string ToString () { return $"({X},{Y})"; }
  628. }
  629. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  630. public struct CharUnion
  631. {
  632. [FieldOffset (0)]
  633. public char UnicodeChar;
  634. [FieldOffset (0)]
  635. public byte AsciiChar;
  636. }
  637. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  638. public struct CharInfo
  639. {
  640. [FieldOffset (0)]
  641. public CharUnion Char;
  642. [FieldOffset (2)]
  643. public ushort Attributes;
  644. }
  645. public struct ExtendedCharInfo
  646. {
  647. public char Char { get; set; }
  648. public Attribute Attribute { get; set; }
  649. public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences
  650. public ExtendedCharInfo (char character, Attribute attribute)
  651. {
  652. Char = character;
  653. Attribute = attribute;
  654. Empty = false;
  655. }
  656. }
  657. [StructLayout (LayoutKind.Sequential)]
  658. public struct SmallRect
  659. {
  660. public short Left;
  661. public short Top;
  662. public short Right;
  663. public short Bottom;
  664. public SmallRect (short left, short top, short right, short bottom)
  665. {
  666. Left = left;
  667. Top = top;
  668. Right = right;
  669. Bottom = bottom;
  670. }
  671. public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; }
  672. public static void Update (ref SmallRect rect, short col, short row)
  673. {
  674. if (rect.Left == -1)
  675. {
  676. rect.Left = rect.Right = col;
  677. rect.Bottom = rect.Top = row;
  678. return;
  679. }
  680. if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom)
  681. {
  682. return;
  683. }
  684. if (col < rect.Left)
  685. {
  686. rect.Left = col;
  687. }
  688. if (col > rect.Right)
  689. {
  690. rect.Right = col;
  691. }
  692. if (row < rect.Top)
  693. {
  694. rect.Top = row;
  695. }
  696. if (row > rect.Bottom)
  697. {
  698. rect.Bottom = row;
  699. }
  700. }
  701. public readonly override string ToString () { return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; }
  702. }
  703. [StructLayout (LayoutKind.Sequential)]
  704. public struct ConsoleKeyInfoEx
  705. {
  706. public ConsoleKeyInfo ConsoleKeyInfo;
  707. public bool CapsLock;
  708. public bool NumLock;
  709. public bool ScrollLock;
  710. public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock)
  711. {
  712. ConsoleKeyInfo = consoleKeyInfo;
  713. CapsLock = capslock;
  714. NumLock = numlock;
  715. ScrollLock = scrolllock;
  716. }
  717. /// <summary>
  718. /// Prints a ConsoleKeyInfoEx structure
  719. /// </summary>
  720. /// <param name="ex"></param>
  721. /// <returns></returns>
  722. public readonly string ToString (ConsoleKeyInfoEx ex)
  723. {
  724. var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar);
  725. var sb = new StringBuilder ();
  726. sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})");
  727. sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
  728. sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
  729. sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
  730. sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) ");
  731. sb.Append (ex.CapsLock ? "caps," : string.Empty);
  732. sb.Append (ex.NumLock ? "num," : string.Empty);
  733. sb.Append (ex.ScrollLock ? "scroll," : string.Empty);
  734. string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
  735. return $"[ConsoleKeyInfoEx({s})]";
  736. }
  737. }
  738. [DllImport ("kernel32.dll", SetLastError = true)]
  739. private static extern nint GetStdHandle (int nStdHandle);
  740. [DllImport ("kernel32.dll", SetLastError = true)]
  741. private static extern bool CloseHandle (nint handle);
  742. [DllImport ("kernel32.dll", SetLastError = true)]
  743. public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead);
  744. [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
  745. public static extern bool ReadConsoleInput (
  746. nint hConsoleInput,
  747. out InputRecord lpBuffer,
  748. uint nLength,
  749. out uint lpNumberOfEventsRead
  750. );
  751. [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
  752. private static extern bool ReadConsoleOutput (
  753. nint hConsoleOutput,
  754. [Out] CharInfo [] lpBuffer,
  755. Coord dwBufferSize,
  756. Coord dwBufferCoord,
  757. ref SmallRect lpReadRegion
  758. );
  759. // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput
  760. [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)]
  761. public static extern bool WriteConsoleOutput (
  762. nint hConsoleOutput,
  763. CharInfo [] lpBuffer,
  764. Coord dwBufferSize,
  765. Coord dwBufferCoord,
  766. ref SmallRect lpWriteRegion
  767. );
  768. [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
  769. [return: MarshalAs (UnmanagedType.Bool)]
  770. private static partial bool WriteConsole (
  771. nint hConsoleOutput,
  772. ReadOnlySpan<char> lpbufer,
  773. uint NumberOfCharsToWriten,
  774. out uint lpNumberOfCharsWritten,
  775. nint lpReserved
  776. );
  777. [DllImport ("kernel32.dll", SetLastError = true)]
  778. static extern bool FlushFileBuffers (nint hFile);
  779. [DllImport ("kernel32.dll")]
  780. private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
  781. [StructLayout (LayoutKind.Sequential)]
  782. public struct ConsoleCursorInfo
  783. {
  784. /// <summary>
  785. /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100.
  786. /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal
  787. /// line at the bottom of the cell.
  788. /// </summary>
  789. public uint dwSize;
  790. public bool bVisible;
  791. }
  792. [DllImport ("kernel32.dll", SetLastError = true)]
  793. private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo);
  794. [DllImport ("kernel32.dll", SetLastError = true)]
  795. private static extern bool GetConsoleCursorInfo (nint hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo);
  796. [DllImport ("kernel32.dll")]
  797. private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
  798. [DllImport ("kernel32.dll")]
  799. private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
  800. [DllImport ("kernel32.dll", SetLastError = true)]
  801. private static extern nint CreateConsoleScreenBuffer (
  802. DesiredAccess dwDesiredAccess,
  803. ShareMode dwShareMode,
  804. nint secutiryAttributes,
  805. uint flags,
  806. nint screenBufferData
  807. );
  808. internal static nint INVALID_HANDLE_VALUE = new (-1);
  809. [DllImport ("kernel32.dll", SetLastError = true)]
  810. private static extern bool SetConsoleActiveScreenBuffer (nint handle);
  811. [DllImport ("kernel32.dll", SetLastError = true)]
  812. private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents);
  813. internal uint GetNumberOfConsoleInputEvents ()
  814. {
  815. if (!GetNumberOfConsoleInputEvents (_inputHandle, out uint numOfEvents))
  816. {
  817. Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
  818. return 0;
  819. }
  820. return numOfEvents;
  821. }
  822. [DllImport ("kernel32.dll", SetLastError = true)]
  823. private static extern bool FlushConsoleInputBuffer (nint handle);
  824. internal void FlushConsoleInputBuffer ()
  825. {
  826. if (!FlushConsoleInputBuffer (_inputHandle))
  827. {
  828. Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
  829. }
  830. }
  831. #if false // Not needed on the constructor. Perhaps could be used on resizing. To study.
  832. [DllImport ("kernel32.dll", ExactSpelling = true)]
  833. static extern IntPtr GetConsoleWindow ();
  834. [DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  835. static extern bool ShowWindow (IntPtr hWnd, int nCmdShow);
  836. public const int HIDE = 0;
  837. public const int MAXIMIZE = 3;
  838. public const int MINIMIZE = 6;
  839. public const int RESTORE = 9;
  840. internal void ShowWindow (int state)
  841. {
  842. IntPtr thisConsole = GetConsoleWindow ();
  843. ShowWindow (thisConsole, state);
  844. }
  845. #endif
  846. // See: https://github.com/gui-cs/Terminal.Gui/issues/357
  847. [StructLayout (LayoutKind.Sequential)]
  848. public struct CONSOLE_SCREEN_BUFFER_INFOEX
  849. {
  850. public uint cbSize;
  851. public Coord dwSize;
  852. public Coord dwCursorPosition;
  853. public ushort wAttributes;
  854. public SmallRect srWindow;
  855. public Coord dwMaximumWindowSize;
  856. public ushort wPopupAttributes;
  857. public bool bFullscreenSupported;
  858. [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
  859. public COLORREF [] ColorTable;
  860. }
  861. [StructLayout (LayoutKind.Explicit, Size = 4)]
  862. public struct COLORREF
  863. {
  864. public COLORREF (byte r, byte g, byte b)
  865. {
  866. Value = 0;
  867. R = r;
  868. G = g;
  869. B = b;
  870. }
  871. public COLORREF (uint value)
  872. {
  873. R = 0;
  874. G = 0;
  875. B = 0;
  876. Value = value & 0x00FFFFFF;
  877. }
  878. [FieldOffset (0)]
  879. public byte R;
  880. [FieldOffset (1)]
  881. public byte G;
  882. [FieldOffset (2)]
  883. public byte B;
  884. [FieldOffset (0)]
  885. public uint Value;
  886. }
  887. [DllImport ("kernel32.dll", SetLastError = true)]
  888. private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi);
  889. [DllImport ("kernel32.dll", SetLastError = true)]
  890. private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo);
  891. [DllImport ("kernel32.dll", SetLastError = true)]
  892. private static extern bool SetConsoleWindowInfo (
  893. nint hConsoleOutput,
  894. bool bAbsolute,
  895. [In] ref SmallRect lpConsoleWindow
  896. );
  897. [DllImport ("kernel32.dll", SetLastError = true)]
  898. private static extern Coord GetLargestConsoleWindowSize (
  899. nint hConsoleOutput
  900. );
  901. }