NetEvents.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  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. if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
  70. {
  71. if (_retries > 1)
  72. {
  73. if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
  74. {
  75. lock (seqReqStatus.AnsiRequest._responseLock)
  76. {
  77. AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
  78. seqReqStatus.AnsiRequest.RaiseResponseFromInput (null);
  79. }
  80. }
  81. _retries = 0;
  82. }
  83. else
  84. {
  85. _retries++;
  86. }
  87. }
  88. else
  89. {
  90. _retries = 0;
  91. }
  92. if (!_forceRead)
  93. {
  94. Task.Delay (100, cancellationToken).Wait (cancellationToken);
  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. var ckiString = AnsiEscapeSequenceRequestUtils.ToString (cki);
  345. lock (seqReqStatus.AnsiRequest._responseLock)
  346. {
  347. seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString);
  348. }
  349. return;
  350. }
  351. if (!string.IsNullOrEmpty (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator))
  352. {
  353. if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result))
  354. {
  355. lock (result.AnsiRequest._responseLock)
  356. {
  357. result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator);
  358. AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null;
  359. }
  360. }
  361. return;
  362. }
  363. if (newConsoleKeyInfo != default)
  364. {
  365. HandleKeyboardEvent (newConsoleKeyInfo);
  366. }
  367. }
  368. [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
  369. private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
  370. {
  371. MouseButtonState mbs = default;
  372. foreach (object flag in Enum.GetValues (mouseFlags.GetType ()))
  373. {
  374. if (mouseFlags.HasFlag ((MouseFlags)flag))
  375. {
  376. switch (flag)
  377. {
  378. case MouseFlags.Button1Pressed:
  379. mbs |= MouseButtonState.Button1Pressed;
  380. break;
  381. case MouseFlags.Button1Released:
  382. mbs |= MouseButtonState.Button1Released;
  383. break;
  384. case MouseFlags.Button1Clicked:
  385. mbs |= MouseButtonState.Button1Clicked;
  386. break;
  387. case MouseFlags.Button1DoubleClicked:
  388. mbs |= MouseButtonState.Button1DoubleClicked;
  389. break;
  390. case MouseFlags.Button1TripleClicked:
  391. mbs |= MouseButtonState.Button1TripleClicked;
  392. break;
  393. case MouseFlags.Button2Pressed:
  394. mbs |= MouseButtonState.Button2Pressed;
  395. break;
  396. case MouseFlags.Button2Released:
  397. mbs |= MouseButtonState.Button2Released;
  398. break;
  399. case MouseFlags.Button2Clicked:
  400. mbs |= MouseButtonState.Button2Clicked;
  401. break;
  402. case MouseFlags.Button2DoubleClicked:
  403. mbs |= MouseButtonState.Button2DoubleClicked;
  404. break;
  405. case MouseFlags.Button2TripleClicked:
  406. mbs |= MouseButtonState.Button2TripleClicked;
  407. break;
  408. case MouseFlags.Button3Pressed:
  409. mbs |= MouseButtonState.Button3Pressed;
  410. break;
  411. case MouseFlags.Button3Released:
  412. mbs |= MouseButtonState.Button3Released;
  413. break;
  414. case MouseFlags.Button3Clicked:
  415. mbs |= MouseButtonState.Button3Clicked;
  416. break;
  417. case MouseFlags.Button3DoubleClicked:
  418. mbs |= MouseButtonState.Button3DoubleClicked;
  419. break;
  420. case MouseFlags.Button3TripleClicked:
  421. mbs |= MouseButtonState.Button3TripleClicked;
  422. break;
  423. case MouseFlags.WheeledUp:
  424. mbs |= MouseButtonState.ButtonWheeledUp;
  425. break;
  426. case MouseFlags.WheeledDown:
  427. mbs |= MouseButtonState.ButtonWheeledDown;
  428. break;
  429. case MouseFlags.WheeledLeft:
  430. mbs |= MouseButtonState.ButtonWheeledLeft;
  431. break;
  432. case MouseFlags.WheeledRight:
  433. mbs |= MouseButtonState.ButtonWheeledRight;
  434. break;
  435. case MouseFlags.Button4Pressed:
  436. mbs |= MouseButtonState.Button4Pressed;
  437. break;
  438. case MouseFlags.Button4Released:
  439. mbs |= MouseButtonState.Button4Released;
  440. break;
  441. case MouseFlags.Button4Clicked:
  442. mbs |= MouseButtonState.Button4Clicked;
  443. break;
  444. case MouseFlags.Button4DoubleClicked:
  445. mbs |= MouseButtonState.Button4DoubleClicked;
  446. break;
  447. case MouseFlags.Button4TripleClicked:
  448. mbs |= MouseButtonState.Button4TripleClicked;
  449. break;
  450. case MouseFlags.ButtonShift:
  451. mbs |= MouseButtonState.ButtonShift;
  452. break;
  453. case MouseFlags.ButtonCtrl:
  454. mbs |= MouseButtonState.ButtonCtrl;
  455. break;
  456. case MouseFlags.ButtonAlt:
  457. mbs |= MouseButtonState.ButtonAlt;
  458. break;
  459. case MouseFlags.ReportMousePosition:
  460. mbs |= MouseButtonState.ReportMousePosition;
  461. break;
  462. case MouseFlags.AllEvents:
  463. mbs |= MouseButtonState.AllEvents;
  464. break;
  465. }
  466. }
  467. }
  468. return mbs;
  469. }
  470. //private Point _lastCursorPosition;
  471. //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
  472. //{
  473. // if (terminating ==
  474. // // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
  475. // // The observation is correct because the response isn't immediate and this is useless
  476. // EscSeqUtils.CSI_RequestCursorPositionReport.Terminator)
  477. // {
  478. // var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
  479. // if (_lastCursorPosition.Y != point.Y)
  480. // {
  481. // _lastCursorPosition = point;
  482. // var eventType = EventType.WindowPosition;
  483. // var winPositionEv = new WindowPositionEvent { CursorPosition = point };
  484. // _inputQueue.Enqueue (
  485. // new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
  486. // );
  487. // }
  488. // else
  489. // {
  490. // return;
  491. // }
  492. // }
  493. // else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator)
  494. // {
  495. // if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value)
  496. // {
  497. // EnqueueWindowSizeEvent (
  498. // Math.Max (int.Parse (values [1]), 0),
  499. // Math.Max (int.Parse (values [2]), 0),
  500. // Math.Max (int.Parse (values [1]), 0),
  501. // Math.Max (int.Parse (values [2]), 0)
  502. // );
  503. // }
  504. // else
  505. // {
  506. // EnqueueRequestResponseEvent (c1Control, code, values, terminating);
  507. // }
  508. // }
  509. // else
  510. // {
  511. // EnqueueRequestResponseEvent (c1Control, code, values, terminating);
  512. // }
  513. // _inputReady.Set ();
  514. //}
  515. //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
  516. //{
  517. // var eventType = EventType.RequestResponse;
  518. // var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
  519. // _inputQueue.Enqueue (
  520. // new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
  521. // );
  522. //}
  523. private void HandleMouseEvent (MouseButtonState buttonState, Point pos)
  524. {
  525. var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
  526. _inputQueue.Enqueue (
  527. new () { EventType = EventType.Mouse, MouseEvent = mouseEvent }
  528. );
  529. }
  530. public enum EventType
  531. {
  532. Key = 1,
  533. Mouse = 2,
  534. WindowSize = 3,
  535. WindowPosition = 4,
  536. RequestResponse = 5
  537. }
  538. [Flags]
  539. public enum MouseButtonState
  540. {
  541. Button1Pressed = 0x1,
  542. Button1Released = 0x2,
  543. Button1Clicked = 0x4,
  544. Button1DoubleClicked = 0x8,
  545. Button1TripleClicked = 0x10,
  546. Button2Pressed = 0x20,
  547. Button2Released = 0x40,
  548. Button2Clicked = 0x80,
  549. Button2DoubleClicked = 0x100,
  550. Button2TripleClicked = 0x200,
  551. Button3Pressed = 0x400,
  552. Button3Released = 0x800,
  553. Button3Clicked = 0x1000,
  554. Button3DoubleClicked = 0x2000,
  555. Button3TripleClicked = 0x4000,
  556. ButtonWheeledUp = 0x8000,
  557. ButtonWheeledDown = 0x10000,
  558. ButtonWheeledLeft = 0x20000,
  559. ButtonWheeledRight = 0x40000,
  560. Button4Pressed = 0x80000,
  561. Button4Released = 0x100000,
  562. Button4Clicked = 0x200000,
  563. Button4DoubleClicked = 0x400000,
  564. Button4TripleClicked = 0x800000,
  565. ButtonShift = 0x1000000,
  566. ButtonCtrl = 0x2000000,
  567. ButtonAlt = 0x4000000,
  568. ReportMousePosition = 0x8000000,
  569. AllEvents = -1
  570. }
  571. public struct MouseEvent
  572. {
  573. public Point Position;
  574. public MouseButtonState ButtonState;
  575. }
  576. public struct WindowSizeEvent
  577. {
  578. public Size Size;
  579. }
  580. public struct WindowPositionEvent
  581. {
  582. public int Top;
  583. public int Left;
  584. public Point CursorPosition;
  585. }
  586. public struct RequestResponseEvent
  587. {
  588. public (string c1Control, string code, string [] values, string terminating) ResultTuple;
  589. }
  590. public struct InputResult
  591. {
  592. public EventType EventType;
  593. public ConsoleKeyInfo ConsoleKeyInfo;
  594. public MouseEvent MouseEvent;
  595. public WindowSizeEvent WindowSizeEvent;
  596. public WindowPositionEvent WindowPositionEvent;
  597. public RequestResponseEvent RequestResponseEvent;
  598. public readonly override string ToString ()
  599. {
  600. return (EventType switch
  601. {
  602. EventType.Key => ToString (ConsoleKeyInfo),
  603. EventType.Mouse => MouseEvent.ToString (),
  604. //EventType.WindowSize => WindowSize.ToString (),
  605. //EventType.RequestResponse => RequestResponse.ToString (),
  606. _ => "Unknown event type: " + EventType
  607. })!;
  608. }
  609. /// <summary>Prints a ConsoleKeyInfoEx structure</summary>
  610. /// <param name="cki"></param>
  611. /// <returns></returns>
  612. public readonly string ToString (ConsoleKeyInfo cki)
  613. {
  614. var ke = new Key ((KeyCode)cki.KeyChar);
  615. var sb = new StringBuilder ();
  616. sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
  617. sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
  618. sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
  619. sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
  620. sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
  621. string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
  622. return $"[ConsoleKeyInfo({s})]";
  623. }
  624. }
  625. private void HandleKeyboardEvent (ConsoleKeyInfo cki)
  626. {
  627. var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
  628. _inputQueue.Enqueue (inputResult);
  629. }
  630. public void Dispose ()
  631. {
  632. _inputReadyCancellationTokenSource?.Cancel ();
  633. _inputReadyCancellationTokenSource?.Dispose ();
  634. _inputReadyCancellationTokenSource = null;
  635. _inputReady.Dispose ();
  636. _waitForStart.Dispose ();
  637. try
  638. {
  639. // throws away any typeahead that has been typed by
  640. // the user and has not yet been read by the program.
  641. while (Console.KeyAvailable)
  642. {
  643. Console.ReadKey (true);
  644. }
  645. }
  646. catch (InvalidOperationException)
  647. {
  648. // Ignore - Console input has already been closed
  649. }
  650. }
  651. }