WindowsConsole.cs 38 KB

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