NetEvents.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. #nullable enable
  2. using System.Diagnostics.CodeAnalysis;
  3. namespace Terminal.Gui;
  4. internal class NetEvents : IDisposable
  5. {
  6. private readonly ManualResetEventSlim _inputReady = new (false);
  7. private CancellationTokenSource? _inputReadyCancellationTokenSource;
  8. internal readonly ManualResetEventSlim _waitForStart = new (false);
  9. private readonly Queue<InputResult> _inputQueue = new ();
  10. private readonly ConsoleDriver _consoleDriver;
  11. private ConsoleKeyInfo []? _cki;
  12. private bool _isEscSeq;
  13. #if PROCESS_REQUEST
  14. bool _neededProcessRequest;
  15. #endif
  16. public NetEvents (ConsoleDriver consoleDriver)
  17. {
  18. _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
  19. _inputReadyCancellationTokenSource = new ();
  20. Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
  21. Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
  22. }
  23. public InputResult? DequeueInput ()
  24. {
  25. while (_inputReadyCancellationTokenSource is { Token.IsCancellationRequested: false })
  26. {
  27. _waitForStart.Set ();
  28. try
  29. {
  30. if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
  31. {
  32. if (_inputQueue.Count == 0)
  33. {
  34. _inputReady.Wait (_inputReadyCancellationTokenSource.Token);
  35. }
  36. }
  37. if (_inputQueue.Count > 0)
  38. {
  39. return _inputQueue.Dequeue ();
  40. }
  41. }
  42. catch (OperationCanceledException)
  43. {
  44. return null;
  45. }
  46. finally
  47. {
  48. if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  49. {
  50. _inputReady.Reset ();
  51. }
  52. }
  53. #if PROCESS_REQUEST
  54. _neededProcessRequest = false;
  55. #endif
  56. }
  57. return null;
  58. }
  59. private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
  60. {
  61. while (!cancellationToken.IsCancellationRequested)
  62. {
  63. // if there is a key available, return it without waiting
  64. // (or dispatching work to the thread queue)
  65. if (Console.KeyAvailable)
  66. {
  67. return Console.ReadKey (intercept);
  68. }
  69. // The delay must be here because it may have a request response after a while
  70. // In WSL it takes longer for keys to be available.
  71. Task.Delay (100, cancellationToken).Wait (cancellationToken);
  72. if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
  73. {
  74. if (_retries > 1)
  75. {
  76. if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus)
  77. && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
  78. {
  79. lock (seqReqStatus.AnsiRequest._responseLock)
  80. {
  81. AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
  82. seqReqStatus.AnsiRequest.RaiseResponseFromInput (null);
  83. }
  84. }
  85. _retries = 0;
  86. }
  87. else
  88. {
  89. _retries++;
  90. }
  91. }
  92. else
  93. {
  94. _retries = 0;
  95. }
  96. }
  97. cancellationToken.ThrowIfCancellationRequested ();
  98. return default (ConsoleKeyInfo);
  99. }
  100. internal bool _forceRead;
  101. private int _retries;
  102. private void ProcessInputQueue ()
  103. {
  104. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  105. {
  106. try
  107. {
  108. if (!_forceRead)
  109. {
  110. _waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
  111. }
  112. }
  113. catch (OperationCanceledException)
  114. {
  115. return;
  116. }
  117. finally
  118. {
  119. if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  120. {
  121. _waitForStart.Reset ();
  122. }
  123. }
  124. try
  125. {
  126. if (_inputQueue.Count == 0 || _forceRead)
  127. {
  128. ConsoleKey key = 0;
  129. ConsoleModifiers mod = 0;
  130. ConsoleKeyInfo newConsoleKeyInfo = default;
  131. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  132. {
  133. ConsoleKeyInfo consoleKeyInfo;
  134. try
  135. {
  136. consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token);
  137. }
  138. catch (OperationCanceledException)
  139. {
  140. return;
  141. }
  142. var ckiAlreadyResized = false;
  143. if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { })
  144. {
  145. ckiAlreadyResized = true;
  146. _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki);
  147. _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki);
  148. AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null;
  149. if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B')
  150. {
  151. _isEscSeq = true;
  152. }
  153. }
  154. if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
  155. || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq))
  156. {
  157. if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
  158. {
  159. _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (
  160. new (
  161. (char)KeyCode.Esc,
  162. 0,
  163. false,
  164. false,
  165. false
  166. ),
  167. _cki
  168. );
  169. }
  170. _isEscSeq = true;
  171. if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
  172. || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127)
  173. || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))
  174. || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar))
  175. || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar))
  176. || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar)))
  177. {
  178. ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
  179. _cki = null;
  180. _isEscSeq = false;
  181. ProcessMapConsoleKeyInfo (consoleKeyInfo);
  182. }
  183. else
  184. {
  185. newConsoleKeyInfo = consoleKeyInfo;
  186. if (!ckiAlreadyResized)
  187. {
  188. _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki);
  189. }
  190. if (Console.KeyAvailable)
  191. {
  192. continue;
  193. }
  194. ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
  195. _cki = null;
  196. _isEscSeq = false;
  197. }
  198. break;
  199. }
  200. if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { })
  201. {
  202. ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
  203. _cki = null;
  204. if (Console.KeyAvailable)
  205. {
  206. _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki);
  207. }
  208. else
  209. {
  210. ProcessMapConsoleKeyInfo (consoleKeyInfo);
  211. }
  212. break;
  213. }
  214. ProcessMapConsoleKeyInfo (consoleKeyInfo);
  215. if (_retries > 0)
  216. {
  217. _retries = 0;
  218. }
  219. break;
  220. }
  221. }
  222. _inputReady.Set ();
  223. }
  224. catch (OperationCanceledException)
  225. {
  226. return;
  227. }
  228. }
  229. void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
  230. {
  231. _inputQueue.Enqueue (
  232. new ()
  233. {
  234. EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo)
  235. }
  236. );
  237. _isEscSeq = false;
  238. }
  239. }
  240. private void CheckWindowSizeChange ()
  241. {
  242. void RequestWindowSize (CancellationToken cancellationToken)
  243. {
  244. while (!cancellationToken.IsCancellationRequested)
  245. {
  246. // Wait for a while then check if screen has changed sizes
  247. Task.Delay (500, cancellationToken).Wait (cancellationToken);
  248. int buffHeight, buffWidth;
  249. if (((NetDriver)_consoleDriver).IsWinPlatform)
  250. {
  251. buffHeight = Math.Max (Console.BufferHeight, 0);
  252. buffWidth = Math.Max (Console.BufferWidth, 0);
  253. }
  254. else
  255. {
  256. buffHeight = _consoleDriver.Rows;
  257. buffWidth = _consoleDriver.Cols;
  258. }
  259. if (EnqueueWindowSizeEvent (
  260. Math.Max (Console.WindowHeight, 0),
  261. Math.Max (Console.WindowWidth, 0),
  262. buffHeight,
  263. buffWidth
  264. ))
  265. {
  266. return;
  267. }
  268. }
  269. cancellationToken.ThrowIfCancellationRequested ();
  270. }
  271. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  272. {
  273. try
  274. {
  275. RequestWindowSize (_inputReadyCancellationTokenSource.Token);
  276. if (_inputQueue.Count > 0)
  277. {
  278. _inputReady.Set ();
  279. }
  280. }
  281. catch (OperationCanceledException)
  282. {
  283. return;
  284. }
  285. }
  286. }
  287. /// <summary>Enqueue a window size event if the window size has changed.</summary>
  288. /// <param name="winHeight"></param>
  289. /// <param name="winWidth"></param>
  290. /// <param name="buffHeight"></param>
  291. /// <param name="buffWidth"></param>
  292. /// <returns></returns>
  293. private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
  294. {
  295. if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows)
  296. {
  297. return false;
  298. }
  299. int w = Math.Max (winWidth, 0);
  300. int h = Math.Max (winHeight, 0);
  301. _inputQueue.Enqueue (
  302. new ()
  303. {
  304. EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (w, h) }
  305. }
  306. );
  307. return true;
  308. }
  309. // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
  310. private void ProcessRequestResponse (
  311. ref ConsoleKeyInfo newConsoleKeyInfo,
  312. ref ConsoleKey key,
  313. ConsoleKeyInfo [] cki,
  314. ref ConsoleModifiers mod
  315. )
  316. {
  317. // isMouse is true if it's CSI<, false otherwise
  318. AnsiEscapeSequenceRequestUtils.DecodeEscSeq (
  319. ref newConsoleKeyInfo,
  320. ref key,
  321. cki,
  322. ref mod,
  323. out string c1Control,
  324. out string code,
  325. out string [] values,
  326. out string terminating,
  327. out bool isMouse,
  328. out List<MouseFlags> mouseFlags,
  329. out Point pos,
  330. out AnsiEscapeSequenceRequestStatus? seqReqStatus,
  331. (f, p) => HandleMouseEvent (MapMouseFlags (f), p)
  332. );
  333. if (isMouse)
  334. {
  335. foreach (MouseFlags mf in mouseFlags)
  336. {
  337. HandleMouseEvent (MapMouseFlags (mf), pos);
  338. }
  339. return;
  340. }
  341. //if (seqReqStatus is { })
  342. //{
  343. // HandleRequestResponseEvent (c1Control, code, values, terminating);
  344. // return;
  345. //}
  346. if (newConsoleKeyInfo != default)
  347. {
  348. HandleKeyboardEvent (newConsoleKeyInfo);
  349. }
  350. }
  351. [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
  352. private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
  353. {
  354. MouseButtonState mbs = default;
  355. foreach (object flag in Enum.GetValues (mouseFlags.GetType ()))
  356. {
  357. if (mouseFlags.HasFlag ((MouseFlags)flag))
  358. {
  359. switch (flag)
  360. {
  361. case MouseFlags.Button1Pressed:
  362. mbs |= MouseButtonState.Button1Pressed;
  363. break;
  364. case MouseFlags.Button1Released:
  365. mbs |= MouseButtonState.Button1Released;
  366. break;
  367. case MouseFlags.Button1Clicked:
  368. mbs |= MouseButtonState.Button1Clicked;
  369. break;
  370. case MouseFlags.Button1DoubleClicked:
  371. mbs |= MouseButtonState.Button1DoubleClicked;
  372. break;
  373. case MouseFlags.Button1TripleClicked:
  374. mbs |= MouseButtonState.Button1TripleClicked;
  375. break;
  376. case MouseFlags.Button2Pressed:
  377. mbs |= MouseButtonState.Button2Pressed;
  378. break;
  379. case MouseFlags.Button2Released:
  380. mbs |= MouseButtonState.Button2Released;
  381. break;
  382. case MouseFlags.Button2Clicked:
  383. mbs |= MouseButtonState.Button2Clicked;
  384. break;
  385. case MouseFlags.Button2DoubleClicked:
  386. mbs |= MouseButtonState.Button2DoubleClicked;
  387. break;
  388. case MouseFlags.Button2TripleClicked:
  389. mbs |= MouseButtonState.Button2TripleClicked;
  390. break;
  391. case MouseFlags.Button3Pressed:
  392. mbs |= MouseButtonState.Button3Pressed;
  393. break;
  394. case MouseFlags.Button3Released:
  395. mbs |= MouseButtonState.Button3Released;
  396. break;
  397. case MouseFlags.Button3Clicked:
  398. mbs |= MouseButtonState.Button3Clicked;
  399. break;
  400. case MouseFlags.Button3DoubleClicked:
  401. mbs |= MouseButtonState.Button3DoubleClicked;
  402. break;
  403. case MouseFlags.Button3TripleClicked:
  404. mbs |= MouseButtonState.Button3TripleClicked;
  405. break;
  406. case MouseFlags.WheeledUp:
  407. mbs |= MouseButtonState.ButtonWheeledUp;
  408. break;
  409. case MouseFlags.WheeledDown:
  410. mbs |= MouseButtonState.ButtonWheeledDown;
  411. break;
  412. case MouseFlags.WheeledLeft:
  413. mbs |= MouseButtonState.ButtonWheeledLeft;
  414. break;
  415. case MouseFlags.WheeledRight:
  416. mbs |= MouseButtonState.ButtonWheeledRight;
  417. break;
  418. case MouseFlags.Button4Pressed:
  419. mbs |= MouseButtonState.Button4Pressed;
  420. break;
  421. case MouseFlags.Button4Released:
  422. mbs |= MouseButtonState.Button4Released;
  423. break;
  424. case MouseFlags.Button4Clicked:
  425. mbs |= MouseButtonState.Button4Clicked;
  426. break;
  427. case MouseFlags.Button4DoubleClicked:
  428. mbs |= MouseButtonState.Button4DoubleClicked;
  429. break;
  430. case MouseFlags.Button4TripleClicked:
  431. mbs |= MouseButtonState.Button4TripleClicked;
  432. break;
  433. case MouseFlags.ButtonShift:
  434. mbs |= MouseButtonState.ButtonShift;
  435. break;
  436. case MouseFlags.ButtonCtrl:
  437. mbs |= MouseButtonState.ButtonCtrl;
  438. break;
  439. case MouseFlags.ButtonAlt:
  440. mbs |= MouseButtonState.ButtonAlt;
  441. break;
  442. case MouseFlags.ReportMousePosition:
  443. mbs |= MouseButtonState.ReportMousePosition;
  444. break;
  445. case MouseFlags.AllEvents:
  446. mbs |= MouseButtonState.AllEvents;
  447. break;
  448. }
  449. }
  450. }
  451. return mbs;
  452. }
  453. //private Point _lastCursorPosition;
  454. //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
  455. //{
  456. // if (terminating ==
  457. // // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
  458. // // The observation is correct because the response isn't immediate and this is useless
  459. // EscSeqUtils.CSI_RequestCursorPositionReport.Terminator)
  460. // {
  461. // var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
  462. // if (_lastCursorPosition.Y != point.Y)
  463. // {
  464. // _lastCursorPosition = point;
  465. // var eventType = EventType.WindowPosition;
  466. // var winPositionEv = new WindowPositionEvent { CursorPosition = point };
  467. // _inputQueue.Enqueue (
  468. // new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
  469. // );
  470. // }
  471. // else
  472. // {
  473. // return;
  474. // }
  475. // }
  476. // else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator)
  477. // {
  478. // if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value)
  479. // {
  480. // EnqueueWindowSizeEvent (
  481. // Math.Max (int.Parse (values [1]), 0),
  482. // Math.Max (int.Parse (values [2]), 0),
  483. // Math.Max (int.Parse (values [1]), 0),
  484. // Math.Max (int.Parse (values [2]), 0)
  485. // );
  486. // }
  487. // else
  488. // {
  489. // EnqueueRequestResponseEvent (c1Control, code, values, terminating);
  490. // }
  491. // }
  492. // else
  493. // {
  494. // EnqueueRequestResponseEvent (c1Control, code, values, terminating);
  495. // }
  496. // _inputReady.Set ();
  497. //}
  498. //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
  499. //{
  500. // var eventType = EventType.RequestResponse;
  501. // var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
  502. // _inputQueue.Enqueue (
  503. // new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
  504. // );
  505. //}
  506. private void HandleMouseEvent (MouseButtonState buttonState, Point pos)
  507. {
  508. var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
  509. _inputQueue.Enqueue (
  510. new () { EventType = EventType.Mouse, MouseEvent = mouseEvent }
  511. );
  512. }
  513. public enum EventType
  514. {
  515. Key = 1,
  516. Mouse = 2,
  517. WindowSize = 3,
  518. WindowPosition = 4,
  519. RequestResponse = 5
  520. }
  521. [Flags]
  522. public enum MouseButtonState
  523. {
  524. Button1Pressed = 0x1,
  525. Button1Released = 0x2,
  526. Button1Clicked = 0x4,
  527. Button1DoubleClicked = 0x8,
  528. Button1TripleClicked = 0x10,
  529. Button2Pressed = 0x20,
  530. Button2Released = 0x40,
  531. Button2Clicked = 0x80,
  532. Button2DoubleClicked = 0x100,
  533. Button2TripleClicked = 0x200,
  534. Button3Pressed = 0x400,
  535. Button3Released = 0x800,
  536. Button3Clicked = 0x1000,
  537. Button3DoubleClicked = 0x2000,
  538. Button3TripleClicked = 0x4000,
  539. ButtonWheeledUp = 0x8000,
  540. ButtonWheeledDown = 0x10000,
  541. ButtonWheeledLeft = 0x20000,
  542. ButtonWheeledRight = 0x40000,
  543. Button4Pressed = 0x80000,
  544. Button4Released = 0x100000,
  545. Button4Clicked = 0x200000,
  546. Button4DoubleClicked = 0x400000,
  547. Button4TripleClicked = 0x800000,
  548. ButtonShift = 0x1000000,
  549. ButtonCtrl = 0x2000000,
  550. ButtonAlt = 0x4000000,
  551. ReportMousePosition = 0x8000000,
  552. AllEvents = -1
  553. }
  554. public struct MouseEvent
  555. {
  556. public Point Position;
  557. public MouseButtonState ButtonState;
  558. }
  559. public struct WindowSizeEvent
  560. {
  561. public Size Size;
  562. }
  563. public struct WindowPositionEvent
  564. {
  565. public int Top;
  566. public int Left;
  567. public Point CursorPosition;
  568. }
  569. public struct RequestResponseEvent
  570. {
  571. public (string c1Control, string code, string [] values, string terminating) ResultTuple;
  572. }
  573. public struct InputResult
  574. {
  575. public EventType EventType;
  576. public ConsoleKeyInfo ConsoleKeyInfo;
  577. public MouseEvent MouseEvent;
  578. public WindowSizeEvent WindowSizeEvent;
  579. public WindowPositionEvent WindowPositionEvent;
  580. public RequestResponseEvent RequestResponseEvent;
  581. public readonly override string ToString ()
  582. {
  583. return (EventType switch
  584. {
  585. EventType.Key => ToString (ConsoleKeyInfo),
  586. EventType.Mouse => MouseEvent.ToString (),
  587. //EventType.WindowSize => WindowSize.ToString (),
  588. //EventType.RequestResponse => RequestResponse.ToString (),
  589. _ => "Unknown event type: " + EventType
  590. })!;
  591. }
  592. /// <summary>Prints a ConsoleKeyInfoEx structure</summary>
  593. /// <param name="cki"></param>
  594. /// <returns></returns>
  595. public readonly string ToString (ConsoleKeyInfo cki)
  596. {
  597. var ke = new Key ((KeyCode)cki.KeyChar);
  598. var sb = new StringBuilder ();
  599. sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
  600. sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
  601. sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
  602. sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
  603. sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
  604. string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
  605. return $"[ConsoleKeyInfo({s})]";
  606. }
  607. }
  608. private void HandleKeyboardEvent (ConsoleKeyInfo cki)
  609. {
  610. var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
  611. _inputQueue.Enqueue (inputResult);
  612. }
  613. public void Dispose ()
  614. {
  615. _inputReadyCancellationTokenSource?.Cancel ();
  616. _inputReadyCancellationTokenSource?.Dispose ();
  617. _inputReadyCancellationTokenSource = null;
  618. _inputReady.Dispose ();
  619. _waitForStart.Dispose ();
  620. try
  621. {
  622. // throws away any typeahead that has been typed by
  623. // the user and has not yet been read by the program.
  624. while (Console.KeyAvailable)
  625. {
  626. Console.ReadKey (true);
  627. }
  628. }
  629. catch (InvalidOperationException)
  630. {
  631. // Ignore - Console input has already been closed
  632. }
  633. }
  634. }