WindowsConsole.cs 38 KB

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