WindowsConsole.cs 33 KB

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