NetDriver.cs 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800
  1. //
  2. // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
  3. //
  4. using System.Collections.Concurrent;
  5. using System.Diagnostics;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Runtime.InteropServices;
  8. using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
  9. using static Terminal.Gui.NetEvents;
  10. namespace Terminal.Gui;
  11. internal class NetWinVTConsole
  12. {
  13. private const uint DISABLE_NEWLINE_AUTO_RETURN = 8;
  14. private const uint ENABLE_ECHO_INPUT = 4;
  15. private const uint ENABLE_EXTENDED_FLAGS = 128;
  16. private const uint ENABLE_INSERT_MODE = 32;
  17. private const uint ENABLE_LINE_INPUT = 2;
  18. private const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
  19. private const uint ENABLE_MOUSE_INPUT = 16;
  20. // Input modes.
  21. private const uint ENABLE_PROCESSED_INPUT = 1;
  22. // Output modes.
  23. private const uint ENABLE_PROCESSED_OUTPUT = 1;
  24. private const uint ENABLE_QUICK_EDIT_MODE = 64;
  25. private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
  26. private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
  27. private const uint ENABLE_WINDOW_INPUT = 8;
  28. private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2;
  29. private const int STD_ERROR_HANDLE = -12;
  30. private const int STD_INPUT_HANDLE = -10;
  31. private const int STD_OUTPUT_HANDLE = -11;
  32. private readonly nint _errorHandle;
  33. private readonly nint _inputHandle;
  34. private readonly uint _originalErrorConsoleMode;
  35. private readonly uint _originalInputConsoleMode;
  36. private readonly uint _originalOutputConsoleMode;
  37. private readonly nint _outputHandle;
  38. public NetWinVTConsole ()
  39. {
  40. _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
  41. if (!GetConsoleMode (_inputHandle, out uint mode))
  42. {
  43. throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}.");
  44. }
  45. _originalInputConsoleMode = mode;
  46. if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT)
  47. {
  48. mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
  49. if (!SetConsoleMode (_inputHandle, mode))
  50. {
  51. throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}.");
  52. }
  53. }
  54. _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
  55. if (!GetConsoleMode (_outputHandle, out mode))
  56. {
  57. throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}.");
  58. }
  59. _originalOutputConsoleMode = mode;
  60. if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN)
  61. {
  62. mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
  63. if (!SetConsoleMode (_outputHandle, mode))
  64. {
  65. throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}.");
  66. }
  67. }
  68. _errorHandle = GetStdHandle (STD_ERROR_HANDLE);
  69. if (!GetConsoleMode (_errorHandle, out mode))
  70. {
  71. throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
  72. }
  73. _originalErrorConsoleMode = mode;
  74. if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN)
  75. {
  76. mode |= DISABLE_NEWLINE_AUTO_RETURN;
  77. if (!SetConsoleMode (_errorHandle, mode))
  78. {
  79. throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
  80. }
  81. }
  82. }
  83. public void Cleanup ()
  84. {
  85. if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
  86. {
  87. throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
  88. }
  89. if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode))
  90. {
  91. throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}.");
  92. }
  93. if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode))
  94. {
  95. throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}.");
  96. }
  97. }
  98. [DllImport ("kernel32.dll")]
  99. private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
  100. [DllImport ("kernel32.dll")]
  101. private static extern uint GetLastError ();
  102. [DllImport ("kernel32.dll", SetLastError = true)]
  103. private static extern nint GetStdHandle (int nStdHandle);
  104. [DllImport ("kernel32.dll")]
  105. private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
  106. }
  107. internal class NetEvents : IDisposable
  108. {
  109. private CancellationTokenSource _inputReadyCancellationTokenSource;
  110. private readonly ManualResetEventSlim _waitForStart = new (false);
  111. //CancellationTokenSource _waitForStartCancellationTokenSource;
  112. private readonly ManualResetEventSlim _winChange = new (false);
  113. private readonly BlockingCollection<InputResult?> _inputQueue = new (new ConcurrentQueue<InputResult?> ());
  114. private readonly ConsoleDriver _consoleDriver;
  115. private ConsoleKeyInfo [] _cki;
  116. private bool _isEscSeq;
  117. #if PROCESS_REQUEST
  118. bool _neededProcessRequest;
  119. #endif
  120. public EscSeqRequests EscSeqRequests { get; } = new ();
  121. public AnsiResponseParser<ConsoleKeyInfo> Parser { get; private set; } = new ();
  122. public NetEvents (ConsoleDriver consoleDriver)
  123. {
  124. _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
  125. _inputReadyCancellationTokenSource = new CancellationTokenSource ();
  126. Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
  127. Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
  128. Parser.UnexpectedResponseHandler = ProcessRequestResponse;
  129. }
  130. public InputResult? DequeueInput ()
  131. {
  132. while (_inputReadyCancellationTokenSource != null
  133. && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
  134. {
  135. _waitForStart.Set ();
  136. _winChange.Set ();
  137. if (_inputQueue.TryTake (out var item,-1,_inputReadyCancellationTokenSource.Token))
  138. {
  139. return item;
  140. }
  141. }
  142. return null;
  143. }
  144. private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
  145. {
  146. // if there is a key available, return it without waiting
  147. // (or dispatching work to the thread queue)
  148. if (Console.KeyAvailable)
  149. {
  150. return Console.ReadKey (intercept);
  151. }
  152. while (!cancellationToken.IsCancellationRequested)
  153. {
  154. Task.Delay (100, cancellationToken).Wait (cancellationToken);
  155. foreach (var k in ShouldRelease ())
  156. {
  157. ProcessMapConsoleKeyInfo (k);
  158. }
  159. if (Console.KeyAvailable)
  160. {
  161. return Console.ReadKey (intercept);
  162. }
  163. }
  164. cancellationToken.ThrowIfCancellationRequested ();
  165. return default (ConsoleKeyInfo);
  166. }
  167. public IEnumerable<ConsoleKeyInfo> ShouldRelease ()
  168. {
  169. if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
  170. DateTime.Now - Parser.StateChangedAt > _consoleDriver.EscTimeout)
  171. {
  172. return Parser.Release ().Select (o => o.Item2);
  173. }
  174. return [];
  175. }
  176. private void ProcessInputQueue ()
  177. {
  178. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  179. {
  180. try
  181. {
  182. _waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
  183. }
  184. catch (OperationCanceledException)
  185. {
  186. return;
  187. }
  188. _waitForStart.Reset ();
  189. if (_inputQueue.Count == 0)
  190. {
  191. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  192. {
  193. ConsoleKeyInfo consoleKeyInfo;
  194. try
  195. {
  196. consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token);
  197. }
  198. catch (OperationCanceledException)
  199. {
  200. return;
  201. }
  202. // Parse
  203. foreach (var k in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
  204. {
  205. ProcessMapConsoleKeyInfo (k.Item2);
  206. }
  207. }
  208. }
  209. }
  210. }
  211. private void ProcessInputAfterParsing(ConsoleKeyInfo consoleKeyInfo,
  212. ref ConsoleKey key,
  213. ref ConsoleModifiers mod,
  214. ref ConsoleKeyInfo newConsoleKeyInfo)
  215. {
  216. if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
  217. || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq))
  218. {
  219. if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
  220. {
  221. _cki = EscSeqUtils.ResizeArray (
  222. new ConsoleKeyInfo (
  223. (char)KeyCode.Esc,
  224. 0,
  225. false,
  226. false,
  227. false
  228. ),
  229. _cki
  230. );
  231. }
  232. _isEscSeq = true;
  233. newConsoleKeyInfo = consoleKeyInfo;
  234. _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
  235. if (Console.KeyAvailable)
  236. {
  237. return;
  238. }
  239. ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
  240. _cki = null;
  241. _isEscSeq = false;
  242. return;
  243. }
  244. if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { })
  245. {
  246. ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
  247. _cki = null;
  248. if (Console.KeyAvailable)
  249. {
  250. _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
  251. }
  252. else
  253. {
  254. ProcessMapConsoleKeyInfo (consoleKeyInfo);
  255. }
  256. return;
  257. }
  258. ProcessMapConsoleKeyInfo (consoleKeyInfo);
  259. }
  260. void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
  261. {
  262. _inputQueue.Add (
  263. new InputResult
  264. {
  265. EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
  266. }
  267. );
  268. _isEscSeq = false;
  269. }
  270. private void CheckWindowSizeChange ()
  271. {
  272. void RequestWindowSize (CancellationToken cancellationToken)
  273. {
  274. while (!cancellationToken.IsCancellationRequested)
  275. {
  276. // Wait for a while then check if screen has changed sizes
  277. Task.Delay (500, cancellationToken).Wait (cancellationToken);
  278. int buffHeight, buffWidth;
  279. if (((NetDriver)_consoleDriver).IsWinPlatform)
  280. {
  281. buffHeight = Math.Max (Console.BufferHeight, 0);
  282. buffWidth = Math.Max (Console.BufferWidth, 0);
  283. }
  284. else
  285. {
  286. buffHeight = _consoleDriver.Rows;
  287. buffWidth = _consoleDriver.Cols;
  288. }
  289. if (EnqueueWindowSizeEvent (
  290. Math.Max (Console.WindowHeight, 0),
  291. Math.Max (Console.WindowWidth, 0),
  292. buffHeight,
  293. buffWidth
  294. ))
  295. {
  296. return;
  297. }
  298. }
  299. cancellationToken.ThrowIfCancellationRequested ();
  300. }
  301. while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
  302. {
  303. try
  304. {
  305. _winChange.Wait (_inputReadyCancellationTokenSource.Token);
  306. _winChange.Reset ();
  307. RequestWindowSize (_inputReadyCancellationTokenSource.Token);
  308. }
  309. catch (OperationCanceledException)
  310. {
  311. return;
  312. }
  313. }
  314. }
  315. /// <summary>Enqueue a window size event if the window size has changed.</summary>
  316. /// <param name="winHeight"></param>
  317. /// <param name="winWidth"></param>
  318. /// <param name="buffHeight"></param>
  319. /// <param name="buffWidth"></param>
  320. /// <returns></returns>
  321. private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
  322. {
  323. if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows)
  324. {
  325. return false;
  326. }
  327. int w = Math.Max (winWidth, 0);
  328. int h = Math.Max (winHeight, 0);
  329. _inputQueue.Add (
  330. new InputResult
  331. {
  332. EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) }
  333. }
  334. );
  335. return true;
  336. }
  337. private bool ProcessRequestResponse (IEnumerable<Tuple<char, ConsoleKeyInfo>> obj)
  338. {
  339. // Added for signature compatibility with existing method, not sure what they are even for.
  340. ConsoleKeyInfo newConsoleKeyInfo = default;
  341. ConsoleKey key = default;
  342. ConsoleModifiers mod = default;
  343. ProcessRequestResponse (ref newConsoleKeyInfo, ref key, obj.Select (v=>v.Item2).ToArray (),ref mod);
  344. // Handled
  345. return true;
  346. }
  347. // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
  348. private void ProcessRequestResponse (
  349. ref ConsoleKeyInfo newConsoleKeyInfo,
  350. ref ConsoleKey key,
  351. ConsoleKeyInfo [] cki,
  352. ref ConsoleModifiers mod
  353. )
  354. {
  355. // isMouse is true if it's CSI<, false otherwise
  356. EscSeqUtils.DecodeEscSeq (
  357. EscSeqRequests,
  358. ref newConsoleKeyInfo,
  359. ref key,
  360. cki,
  361. ref mod,
  362. out string c1Control,
  363. out string code,
  364. out string [] values,
  365. out string terminating,
  366. out bool isMouse,
  367. out List<MouseFlags> mouseFlags,
  368. out Point pos,
  369. out bool isReq,
  370. (f, p) => HandleMouseEvent (MapMouseFlags (f), p)
  371. );
  372. if (isMouse)
  373. {
  374. foreach (MouseFlags mf in mouseFlags)
  375. {
  376. HandleMouseEvent (MapMouseFlags (mf), pos);
  377. }
  378. return;
  379. }
  380. if (isReq)
  381. {
  382. HandleRequestResponseEvent (c1Control, code, values, terminating);
  383. return;
  384. }
  385. HandleKeyboardEvent (newConsoleKeyInfo);
  386. }
  387. [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
  388. private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
  389. {
  390. MouseButtonState mbs = default;
  391. foreach (object flag in Enum.GetValues (mouseFlags.GetType ()))
  392. {
  393. if (mouseFlags.HasFlag ((MouseFlags)flag))
  394. {
  395. switch (flag)
  396. {
  397. case MouseFlags.Button1Pressed:
  398. mbs |= MouseButtonState.Button1Pressed;
  399. break;
  400. case MouseFlags.Button1Released:
  401. mbs |= MouseButtonState.Button1Released;
  402. break;
  403. case MouseFlags.Button1Clicked:
  404. mbs |= MouseButtonState.Button1Clicked;
  405. break;
  406. case MouseFlags.Button1DoubleClicked:
  407. mbs |= MouseButtonState.Button1DoubleClicked;
  408. break;
  409. case MouseFlags.Button1TripleClicked:
  410. mbs |= MouseButtonState.Button1TripleClicked;
  411. break;
  412. case MouseFlags.Button2Pressed:
  413. mbs |= MouseButtonState.Button2Pressed;
  414. break;
  415. case MouseFlags.Button2Released:
  416. mbs |= MouseButtonState.Button2Released;
  417. break;
  418. case MouseFlags.Button2Clicked:
  419. mbs |= MouseButtonState.Button2Clicked;
  420. break;
  421. case MouseFlags.Button2DoubleClicked:
  422. mbs |= MouseButtonState.Button2DoubleClicked;
  423. break;
  424. case MouseFlags.Button2TripleClicked:
  425. mbs |= MouseButtonState.Button2TripleClicked;
  426. break;
  427. case MouseFlags.Button3Pressed:
  428. mbs |= MouseButtonState.Button3Pressed;
  429. break;
  430. case MouseFlags.Button3Released:
  431. mbs |= MouseButtonState.Button3Released;
  432. break;
  433. case MouseFlags.Button3Clicked:
  434. mbs |= MouseButtonState.Button3Clicked;
  435. break;
  436. case MouseFlags.Button3DoubleClicked:
  437. mbs |= MouseButtonState.Button3DoubleClicked;
  438. break;
  439. case MouseFlags.Button3TripleClicked:
  440. mbs |= MouseButtonState.Button3TripleClicked;
  441. break;
  442. case MouseFlags.WheeledUp:
  443. mbs |= MouseButtonState.ButtonWheeledUp;
  444. break;
  445. case MouseFlags.WheeledDown:
  446. mbs |= MouseButtonState.ButtonWheeledDown;
  447. break;
  448. case MouseFlags.WheeledLeft:
  449. mbs |= MouseButtonState.ButtonWheeledLeft;
  450. break;
  451. case MouseFlags.WheeledRight:
  452. mbs |= MouseButtonState.ButtonWheeledRight;
  453. break;
  454. case MouseFlags.Button4Pressed:
  455. mbs |= MouseButtonState.Button4Pressed;
  456. break;
  457. case MouseFlags.Button4Released:
  458. mbs |= MouseButtonState.Button4Released;
  459. break;
  460. case MouseFlags.Button4Clicked:
  461. mbs |= MouseButtonState.Button4Clicked;
  462. break;
  463. case MouseFlags.Button4DoubleClicked:
  464. mbs |= MouseButtonState.Button4DoubleClicked;
  465. break;
  466. case MouseFlags.Button4TripleClicked:
  467. mbs |= MouseButtonState.Button4TripleClicked;
  468. break;
  469. case MouseFlags.ButtonShift:
  470. mbs |= MouseButtonState.ButtonShift;
  471. break;
  472. case MouseFlags.ButtonCtrl:
  473. mbs |= MouseButtonState.ButtonCtrl;
  474. break;
  475. case MouseFlags.ButtonAlt:
  476. mbs |= MouseButtonState.ButtonAlt;
  477. break;
  478. case MouseFlags.ReportMousePosition:
  479. mbs |= MouseButtonState.ReportMousePosition;
  480. break;
  481. case MouseFlags.AllEvents:
  482. mbs |= MouseButtonState.AllEvents;
  483. break;
  484. }
  485. }
  486. }
  487. return mbs;
  488. }
  489. private Point _lastCursorPosition;
  490. private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
  491. {
  492. switch (terminating)
  493. {
  494. // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
  495. case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
  496. var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
  497. if (_lastCursorPosition.Y != point.Y)
  498. {
  499. _lastCursorPosition = point;
  500. var eventType = EventType.WindowPosition;
  501. var winPositionEv = new WindowPositionEvent { CursorPosition = point };
  502. _inputQueue.Add (
  503. new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
  504. );
  505. }
  506. else
  507. {
  508. return;
  509. }
  510. break;
  511. case EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator:
  512. switch (values [0])
  513. {
  514. case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue:
  515. EnqueueWindowSizeEvent (
  516. Math.Max (int.Parse (values [1]), 0),
  517. Math.Max (int.Parse (values [2]), 0),
  518. Math.Max (int.Parse (values [1]), 0),
  519. Math.Max (int.Parse (values [2]), 0)
  520. );
  521. break;
  522. default:
  523. EnqueueRequestResponseEvent (c1Control, code, values, terminating);
  524. break;
  525. }
  526. break;
  527. default:
  528. EnqueueRequestResponseEvent (c1Control, code, values, terminating);
  529. break;
  530. }
  531. }
  532. private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
  533. {
  534. var eventType = EventType.RequestResponse;
  535. var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
  536. _inputQueue.Add (
  537. new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
  538. );
  539. }
  540. private void HandleMouseEvent (MouseButtonState buttonState, Point pos)
  541. {
  542. var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
  543. _inputQueue.Add (
  544. new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent }
  545. );
  546. }
  547. public enum EventType
  548. {
  549. Key = 1,
  550. Mouse = 2,
  551. WindowSize = 3,
  552. WindowPosition = 4,
  553. RequestResponse = 5
  554. }
  555. [Flags]
  556. public enum MouseButtonState
  557. {
  558. Button1Pressed = 0x1,
  559. Button1Released = 0x2,
  560. Button1Clicked = 0x4,
  561. Button1DoubleClicked = 0x8,
  562. Button1TripleClicked = 0x10,
  563. Button2Pressed = 0x20,
  564. Button2Released = 0x40,
  565. Button2Clicked = 0x80,
  566. Button2DoubleClicked = 0x100,
  567. Button2TripleClicked = 0x200,
  568. Button3Pressed = 0x400,
  569. Button3Released = 0x800,
  570. Button3Clicked = 0x1000,
  571. Button3DoubleClicked = 0x2000,
  572. Button3TripleClicked = 0x4000,
  573. ButtonWheeledUp = 0x8000,
  574. ButtonWheeledDown = 0x10000,
  575. ButtonWheeledLeft = 0x20000,
  576. ButtonWheeledRight = 0x40000,
  577. Button4Pressed = 0x80000,
  578. Button4Released = 0x100000,
  579. Button4Clicked = 0x200000,
  580. Button4DoubleClicked = 0x400000,
  581. Button4TripleClicked = 0x800000,
  582. ButtonShift = 0x1000000,
  583. ButtonCtrl = 0x2000000,
  584. ButtonAlt = 0x4000000,
  585. ReportMousePosition = 0x8000000,
  586. AllEvents = -1
  587. }
  588. public struct MouseEvent
  589. {
  590. public Point Position;
  591. public MouseButtonState ButtonState;
  592. }
  593. public struct WindowSizeEvent
  594. {
  595. public Size Size;
  596. }
  597. public struct WindowPositionEvent
  598. {
  599. public int Top;
  600. public int Left;
  601. public Point CursorPosition;
  602. }
  603. public struct RequestResponseEvent
  604. {
  605. public (string c1Control, string code, string [] values, string terminating) ResultTuple;
  606. }
  607. public struct InputResult
  608. {
  609. public EventType EventType;
  610. public ConsoleKeyInfo ConsoleKeyInfo;
  611. public MouseEvent MouseEvent;
  612. public WindowSizeEvent WindowSizeEvent;
  613. public WindowPositionEvent WindowPositionEvent;
  614. public RequestResponseEvent RequestResponseEvent;
  615. public readonly override string ToString ()
  616. {
  617. return EventType switch
  618. {
  619. EventType.Key => ToString (ConsoleKeyInfo),
  620. EventType.Mouse => MouseEvent.ToString (),
  621. //EventType.WindowSize => WindowSize.ToString (),
  622. //EventType.RequestResponse => RequestResponse.ToString (),
  623. _ => "Unknown event type: " + EventType
  624. };
  625. }
  626. /// <summary>Prints a ConsoleKeyInfoEx structure</summary>
  627. /// <param name="cki"></param>
  628. /// <returns></returns>
  629. public readonly string ToString (ConsoleKeyInfo cki)
  630. {
  631. var ke = new Key ((KeyCode)cki.KeyChar);
  632. var sb = new StringBuilder ();
  633. sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
  634. sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
  635. sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
  636. sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
  637. sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
  638. string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
  639. return $"[ConsoleKeyInfo({s})]";
  640. }
  641. }
  642. private void HandleKeyboardEvent (ConsoleKeyInfo cki)
  643. {
  644. var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
  645. _inputQueue.Add (inputResult);
  646. }
  647. public void Dispose ()
  648. {
  649. _inputReadyCancellationTokenSource?.Cancel ();
  650. _inputReadyCancellationTokenSource?.Dispose ();
  651. _inputReadyCancellationTokenSource = null;
  652. try
  653. {
  654. // throws away any typeahead that has been typed by
  655. // the user and has not yet been read by the program.
  656. while (Console.KeyAvailable)
  657. {
  658. Console.ReadKey (true);
  659. }
  660. }
  661. catch (InvalidOperationException)
  662. {
  663. // Ignore - Console input has already been closed
  664. }
  665. }
  666. }
  667. internal class NetDriver : ConsoleDriver
  668. {
  669. private const int COLOR_BLACK = 30;
  670. private const int COLOR_BLUE = 34;
  671. private const int COLOR_BRIGHT_BLACK = 90;
  672. private const int COLOR_BRIGHT_BLUE = 94;
  673. private const int COLOR_BRIGHT_CYAN = 96;
  674. private const int COLOR_BRIGHT_GREEN = 92;
  675. private const int COLOR_BRIGHT_MAGENTA = 95;
  676. private const int COLOR_BRIGHT_RED = 91;
  677. private const int COLOR_BRIGHT_WHITE = 97;
  678. private const int COLOR_BRIGHT_YELLOW = 93;
  679. private const int COLOR_CYAN = 36;
  680. private const int COLOR_GREEN = 32;
  681. private const int COLOR_MAGENTA = 35;
  682. private const int COLOR_RED = 31;
  683. private const int COLOR_WHITE = 37;
  684. private const int COLOR_YELLOW = 33;
  685. private NetMainLoop _mainLoopDriver;
  686. public bool IsWinPlatform { get; private set; }
  687. public NetWinVTConsole NetWinConsole { get; private set; }
  688. public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix
  689. || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
  690. public override void Refresh ()
  691. {
  692. UpdateScreen ();
  693. UpdateCursor ();
  694. }
  695. public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
  696. {
  697. var input = new InputResult
  698. {
  699. EventType = EventType.Key, ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
  700. };
  701. try
  702. {
  703. ProcessInput (input);
  704. }
  705. catch (OverflowException)
  706. { }
  707. }
  708. public override void Suspend ()
  709. {
  710. if (Environment.OSVersion.Platform != PlatformID.Unix)
  711. {
  712. return;
  713. }
  714. StopReportingMouseMoves ();
  715. if (!RunningUnitTests)
  716. {
  717. Console.ResetColor ();
  718. Console.Clear ();
  719. //Disable alternative screen buffer.
  720. Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
  721. //Set cursor key to cursor.
  722. Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
  723. Platform.Suspend ();
  724. //Enable alternative screen buffer.
  725. Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
  726. SetContentsAsDirty ();
  727. Refresh ();
  728. }
  729. StartReportingMouseMoves ();
  730. }
  731. public override void UpdateScreen ()
  732. {
  733. if (RunningUnitTests
  734. || _winSizeChanging
  735. || Console.WindowHeight < 1
  736. || Contents.Length != Rows * Cols
  737. || Rows != Console.WindowHeight)
  738. {
  739. return;
  740. }
  741. var top = 0;
  742. var left = 0;
  743. int rows = Rows;
  744. int cols = Cols;
  745. var output = new StringBuilder ();
  746. Attribute? redrawAttr = null;
  747. int lastCol = -1;
  748. CursorVisibility? savedVisibility = _cachedCursorVisibility;
  749. SetCursorVisibility (CursorVisibility.Invisible);
  750. for (int row = top; row < rows; row++)
  751. {
  752. if (Console.WindowHeight < 1)
  753. {
  754. return;
  755. }
  756. if (!_dirtyLines [row])
  757. {
  758. continue;
  759. }
  760. if (!SetCursorPosition (0, row))
  761. {
  762. return;
  763. }
  764. _dirtyLines [row] = false;
  765. output.Clear ();
  766. for (int col = left; col < cols; col++)
  767. {
  768. lastCol = -1;
  769. var outputWidth = 0;
  770. for (; col < cols; col++)
  771. {
  772. if (!Contents [row, col].IsDirty)
  773. {
  774. if (output.Length > 0)
  775. {
  776. WriteToConsole (output, ref lastCol, row, ref outputWidth);
  777. }
  778. else if (lastCol == -1)
  779. {
  780. lastCol = col;
  781. }
  782. if (lastCol + 1 < cols)
  783. {
  784. lastCol++;
  785. }
  786. continue;
  787. }
  788. if (lastCol == -1)
  789. {
  790. lastCol = col;
  791. }
  792. Attribute attr = Contents [row, col].Attribute.Value;
  793. // Performance: Only send the escape sequence if the attribute has changed.
  794. if (attr != redrawAttr)
  795. {
  796. redrawAttr = attr;
  797. if (Force16Colors)
  798. {
  799. output.Append (
  800. EscSeqUtils.CSI_SetGraphicsRendition (
  801. MapColors (
  802. (ConsoleColor)attr.Background.GetClosestNamedColor16 (),
  803. false
  804. ),
  805. MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ())
  806. )
  807. );
  808. }
  809. else
  810. {
  811. output.Append (
  812. EscSeqUtils.CSI_SetForegroundColorRGB (
  813. attr.Foreground.R,
  814. attr.Foreground.G,
  815. attr.Foreground.B
  816. )
  817. );
  818. output.Append (
  819. EscSeqUtils.CSI_SetBackgroundColorRGB (
  820. attr.Background.R,
  821. attr.Background.G,
  822. attr.Background.B
  823. )
  824. );
  825. }
  826. }
  827. outputWidth++;
  828. Rune rune = Contents [row, col].Rune;
  829. output.Append (rune);
  830. if (Contents [row, col].CombiningMarks.Count > 0)
  831. {
  832. // AtlasEngine does not support NON-NORMALIZED combining marks in a way
  833. // compatible with the driver architecture. Any CMs (except in the first col)
  834. // are correctly combined with the base char, but are ALSO treated as 1 column
  835. // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
  836. //
  837. // For now, we just ignore the list of CMs.
  838. //foreach (var combMark in Contents [row, col].CombiningMarks) {
  839. // output.Append (combMark);
  840. //}
  841. // WriteToConsole (output, ref lastCol, row, ref outputWidth);
  842. }
  843. else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
  844. {
  845. WriteToConsole (output, ref lastCol, row, ref outputWidth);
  846. SetCursorPosition (col - 1, row);
  847. }
  848. Contents [row, col].IsDirty = false;
  849. }
  850. }
  851. if (output.Length > 0)
  852. {
  853. SetCursorPosition (lastCol, row);
  854. Console.Write (output);
  855. }
  856. }
  857. SetCursorPosition (0, 0);
  858. _cachedCursorVisibility = savedVisibility;
  859. void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
  860. {
  861. SetCursorPosition (lastCol, row);
  862. Console.Write (output);
  863. output.Clear ();
  864. lastCol += outputWidth;
  865. outputWidth = 0;
  866. }
  867. }
  868. /// <inheritdoc />
  869. protected override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
  870. /// <inheritdoc />
  871. internal override void RawWrite (string str)
  872. {
  873. Console.Write (str);
  874. }
  875. internal override void End ()
  876. {
  877. if (IsWinPlatform)
  878. {
  879. NetWinConsole?.Cleanup ();
  880. }
  881. StopReportingMouseMoves ();
  882. if (!RunningUnitTests)
  883. {
  884. Console.ResetColor ();
  885. //Disable alternative screen buffer.
  886. Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
  887. //Set cursor key to cursor.
  888. Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
  889. Console.Out.Close ();
  890. }
  891. }
  892. internal override MainLoop Init ()
  893. {
  894. PlatformID p = Environment.OSVersion.Platform;
  895. if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
  896. {
  897. IsWinPlatform = true;
  898. try
  899. {
  900. NetWinConsole = new NetWinVTConsole ();
  901. }
  902. catch (ApplicationException)
  903. {
  904. // Likely running as a unit test, or in a non-interactive session.
  905. }
  906. }
  907. if (IsWinPlatform)
  908. {
  909. Clipboard = new WindowsClipboard ();
  910. }
  911. else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  912. {
  913. Clipboard = new MacOSXClipboard ();
  914. }
  915. else
  916. {
  917. if (CursesDriver.Is_WSL_Platform ())
  918. {
  919. Clipboard = new WSLClipboard ();
  920. }
  921. else
  922. {
  923. Clipboard = new CursesClipboard ();
  924. }
  925. }
  926. if (!RunningUnitTests)
  927. {
  928. Console.TreatControlCAsInput = true;
  929. Cols = Console.WindowWidth;
  930. Rows = Console.WindowHeight;
  931. //Enable alternative screen buffer.
  932. Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
  933. //Set cursor key to application.
  934. Console.Out.Write (EscSeqUtils.CSI_HideCursor);
  935. }
  936. else
  937. {
  938. // We are being run in an environment that does not support a console
  939. // such as a unit test, or a pipe.
  940. Cols = 80;
  941. Rows = 24;
  942. }
  943. ResizeScreen ();
  944. ClearContents ();
  945. CurrentAttribute = new Attribute (Color.White, Color.Black);
  946. StartReportingMouseMoves ();
  947. _mainLoopDriver = new NetMainLoop (this);
  948. _mainLoopDriver.ProcessInput = ProcessInput;
  949. return new MainLoop (_mainLoopDriver);
  950. }
  951. private void ProcessInput (InputResult inputEvent)
  952. {
  953. switch (inputEvent.EventType)
  954. {
  955. case EventType.Key:
  956. ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
  957. //if (consoleKeyInfo.Key == ConsoleKey.Packet) {
  958. // consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
  959. //}
  960. //Debug.WriteLine ($"event: {inputEvent}");
  961. KeyCode map = MapKey (consoleKeyInfo);
  962. if (map == KeyCode.Null)
  963. {
  964. break;
  965. }
  966. OnKeyDown (new Key (map));
  967. OnKeyUp (new Key (map));
  968. break;
  969. case EventType.Mouse:
  970. MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
  971. //Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
  972. OnMouseEvent (me);
  973. break;
  974. case EventType.WindowSize:
  975. _winSizeChanging = true;
  976. Top = 0;
  977. Left = 0;
  978. Cols = inputEvent.WindowSizeEvent.Size.Width;
  979. Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0);
  980. ;
  981. ResizeScreen ();
  982. ClearContents ();
  983. _winSizeChanging = false;
  984. OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows)));
  985. break;
  986. case EventType.RequestResponse:
  987. break;
  988. case EventType.WindowPosition:
  989. break;
  990. default:
  991. throw new ArgumentOutOfRangeException ();
  992. }
  993. }
  994. #region Size and Position Handling
  995. private volatile bool _winSizeChanging;
  996. private void SetWindowPosition (int col, int row)
  997. {
  998. if (!RunningUnitTests)
  999. {
  1000. Top = Console.WindowTop;
  1001. Left = Console.WindowLeft;
  1002. }
  1003. else
  1004. {
  1005. Top = row;
  1006. Left = col;
  1007. }
  1008. }
  1009. public virtual void ResizeScreen ()
  1010. {
  1011. // Not supported on Unix.
  1012. if (IsWinPlatform)
  1013. {
  1014. // Can raise an exception while is still resizing.
  1015. try
  1016. {
  1017. #pragma warning disable CA1416
  1018. if (Console.WindowHeight > 0)
  1019. {
  1020. Console.CursorTop = 0;
  1021. Console.CursorLeft = 0;
  1022. Console.WindowTop = 0;
  1023. Console.WindowLeft = 0;
  1024. if (Console.WindowHeight > Rows)
  1025. {
  1026. Console.SetWindowSize (Cols, Rows);
  1027. }
  1028. Console.SetBufferSize (Cols, Rows);
  1029. }
  1030. #pragma warning restore CA1416
  1031. }
  1032. // INTENT: Why are these eating the exceptions?
  1033. // Comments would be good here.
  1034. catch (IOException)
  1035. {
  1036. // CONCURRENCY: Unsynchronized access to Clip is not safe.
  1037. Clip = new (0, 0, Cols, Rows);
  1038. }
  1039. catch (ArgumentOutOfRangeException)
  1040. {
  1041. // CONCURRENCY: Unsynchronized access to Clip is not safe.
  1042. Clip = new (0, 0, Cols, Rows);
  1043. }
  1044. }
  1045. else
  1046. {
  1047. Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
  1048. }
  1049. // CONCURRENCY: Unsynchronized access to Clip is not safe.
  1050. Clip = new (0, 0, Cols, Rows);
  1051. }
  1052. #endregion
  1053. #region Color Handling
  1054. // Cache the list of ConsoleColor values.
  1055. [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
  1056. private static readonly HashSet<int> ConsoleColorValues = new (
  1057. Enum.GetValues (typeof (ConsoleColor))
  1058. .OfType<ConsoleColor> ()
  1059. .Select (c => (int)c)
  1060. );
  1061. // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
  1062. private static readonly Dictionary<ConsoleColor, int> colorMap = new ()
  1063. {
  1064. { ConsoleColor.Black, COLOR_BLACK },
  1065. { ConsoleColor.DarkBlue, COLOR_BLUE },
  1066. { ConsoleColor.DarkGreen, COLOR_GREEN },
  1067. { ConsoleColor.DarkCyan, COLOR_CYAN },
  1068. { ConsoleColor.DarkRed, COLOR_RED },
  1069. { ConsoleColor.DarkMagenta, COLOR_MAGENTA },
  1070. { ConsoleColor.DarkYellow, COLOR_YELLOW },
  1071. { ConsoleColor.Gray, COLOR_WHITE },
  1072. { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
  1073. { ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
  1074. { ConsoleColor.Green, COLOR_BRIGHT_GREEN },
  1075. { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
  1076. { ConsoleColor.Red, COLOR_BRIGHT_RED },
  1077. { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
  1078. { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
  1079. { ConsoleColor.White, COLOR_BRIGHT_WHITE }
  1080. };
  1081. // Map a ConsoleColor to a platform dependent value.
  1082. private int MapColors (ConsoleColor color, bool isForeground = true)
  1083. {
  1084. return colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
  1085. }
  1086. ///// <remarks>
  1087. ///// In the NetDriver, colors are encoded as an int.
  1088. ///// However, the foreground color is stored in the most significant 16 bits,
  1089. ///// and the background color is stored in the least significant 16 bits.
  1090. ///// </remarks>
  1091. //public override Attribute MakeColor (Color foreground, Color background)
  1092. //{
  1093. // // Encode the colors into the int value.
  1094. // return new Attribute (
  1095. // platformColor: ((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
  1096. // foreground: foreground,
  1097. // background: background
  1098. // );
  1099. //}
  1100. #endregion
  1101. #region Cursor Handling
  1102. private bool SetCursorPosition (int col, int row)
  1103. {
  1104. if (IsWinPlatform)
  1105. {
  1106. // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
  1107. try
  1108. {
  1109. Console.SetCursorPosition (col, row);
  1110. return true;
  1111. }
  1112. catch (Exception)
  1113. {
  1114. return false;
  1115. }
  1116. }
  1117. // + 1 is needed because non-Windows is based on 1 instead of 0 and
  1118. // Console.CursorTop/CursorLeft isn't reliable.
  1119. Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
  1120. return true;
  1121. }
  1122. private CursorVisibility? _cachedCursorVisibility;
  1123. public override void UpdateCursor ()
  1124. {
  1125. EnsureCursorVisibility ();
  1126. if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows)
  1127. {
  1128. SetCursorPosition (Col, Row);
  1129. SetWindowPosition (0, Row);
  1130. }
  1131. }
  1132. public override bool GetCursorVisibility (out CursorVisibility visibility)
  1133. {
  1134. visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
  1135. return visibility == CursorVisibility.Default;
  1136. }
  1137. public override bool SetCursorVisibility (CursorVisibility visibility)
  1138. {
  1139. _cachedCursorVisibility = visibility;
  1140. Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
  1141. return visibility == CursorVisibility.Default;
  1142. }
  1143. public override bool EnsureCursorVisibility ()
  1144. {
  1145. if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
  1146. {
  1147. GetCursorVisibility (out CursorVisibility cursorVisibility);
  1148. _cachedCursorVisibility = cursorVisibility;
  1149. SetCursorVisibility (CursorVisibility.Invisible);
  1150. return false;
  1151. }
  1152. SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
  1153. return _cachedCursorVisibility == CursorVisibility.Default;
  1154. }
  1155. #endregion
  1156. #region Mouse Handling
  1157. public void StartReportingMouseMoves ()
  1158. {
  1159. if (!RunningUnitTests)
  1160. {
  1161. Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
  1162. }
  1163. }
  1164. public void StopReportingMouseMoves ()
  1165. {
  1166. if (!RunningUnitTests)
  1167. {
  1168. Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
  1169. }
  1170. }
  1171. private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me)
  1172. {
  1173. //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
  1174. MouseFlags mouseFlag = 0;
  1175. if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0)
  1176. {
  1177. mouseFlag |= MouseFlags.Button1Pressed;
  1178. }
  1179. if ((me.ButtonState & MouseButtonState.Button1Released) != 0)
  1180. {
  1181. mouseFlag |= MouseFlags.Button1Released;
  1182. }
  1183. if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0)
  1184. {
  1185. mouseFlag |= MouseFlags.Button1Clicked;
  1186. }
  1187. if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0)
  1188. {
  1189. mouseFlag |= MouseFlags.Button1DoubleClicked;
  1190. }
  1191. if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0)
  1192. {
  1193. mouseFlag |= MouseFlags.Button1TripleClicked;
  1194. }
  1195. if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0)
  1196. {
  1197. mouseFlag |= MouseFlags.Button2Pressed;
  1198. }
  1199. if ((me.ButtonState & MouseButtonState.Button2Released) != 0)
  1200. {
  1201. mouseFlag |= MouseFlags.Button2Released;
  1202. }
  1203. if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0)
  1204. {
  1205. mouseFlag |= MouseFlags.Button2Clicked;
  1206. }
  1207. if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0)
  1208. {
  1209. mouseFlag |= MouseFlags.Button2DoubleClicked;
  1210. }
  1211. if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0)
  1212. {
  1213. mouseFlag |= MouseFlags.Button2TripleClicked;
  1214. }
  1215. if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0)
  1216. {
  1217. mouseFlag |= MouseFlags.Button3Pressed;
  1218. }
  1219. if ((me.ButtonState & MouseButtonState.Button3Released) != 0)
  1220. {
  1221. mouseFlag |= MouseFlags.Button3Released;
  1222. }
  1223. if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0)
  1224. {
  1225. mouseFlag |= MouseFlags.Button3Clicked;
  1226. }
  1227. if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0)
  1228. {
  1229. mouseFlag |= MouseFlags.Button3DoubleClicked;
  1230. }
  1231. if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0)
  1232. {
  1233. mouseFlag |= MouseFlags.Button3TripleClicked;
  1234. }
  1235. if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0)
  1236. {
  1237. mouseFlag |= MouseFlags.WheeledUp;
  1238. }
  1239. if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0)
  1240. {
  1241. mouseFlag |= MouseFlags.WheeledDown;
  1242. }
  1243. if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0)
  1244. {
  1245. mouseFlag |= MouseFlags.WheeledLeft;
  1246. }
  1247. if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0)
  1248. {
  1249. mouseFlag |= MouseFlags.WheeledRight;
  1250. }
  1251. if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0)
  1252. {
  1253. mouseFlag |= MouseFlags.Button4Pressed;
  1254. }
  1255. if ((me.ButtonState & MouseButtonState.Button4Released) != 0)
  1256. {
  1257. mouseFlag |= MouseFlags.Button4Released;
  1258. }
  1259. if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0)
  1260. {
  1261. mouseFlag |= MouseFlags.Button4Clicked;
  1262. }
  1263. if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0)
  1264. {
  1265. mouseFlag |= MouseFlags.Button4DoubleClicked;
  1266. }
  1267. if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0)
  1268. {
  1269. mouseFlag |= MouseFlags.Button4TripleClicked;
  1270. }
  1271. if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0)
  1272. {
  1273. mouseFlag |= MouseFlags.ReportMousePosition;
  1274. }
  1275. if ((me.ButtonState & MouseButtonState.ButtonShift) != 0)
  1276. {
  1277. mouseFlag |= MouseFlags.ButtonShift;
  1278. }
  1279. if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0)
  1280. {
  1281. mouseFlag |= MouseFlags.ButtonCtrl;
  1282. }
  1283. if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0)
  1284. {
  1285. mouseFlag |= MouseFlags.ButtonAlt;
  1286. }
  1287. return new MouseEventArgs { Position = me.Position, Flags = mouseFlag };
  1288. }
  1289. #endregion Mouse Handling
  1290. #region Keyboard Handling
  1291. private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
  1292. {
  1293. if (consoleKeyInfo.Key != ConsoleKey.Packet)
  1294. {
  1295. return consoleKeyInfo;
  1296. }
  1297. ConsoleModifiers mod = consoleKeyInfo.Modifiers;
  1298. bool shift = (mod & ConsoleModifiers.Shift) != 0;
  1299. bool alt = (mod & ConsoleModifiers.Alt) != 0;
  1300. bool control = (mod & ConsoleModifiers.Control) != 0;
  1301. ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
  1302. return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
  1303. }
  1304. private KeyCode MapKey (ConsoleKeyInfo keyInfo)
  1305. {
  1306. switch (keyInfo.Key)
  1307. {
  1308. case ConsoleKey.OemPeriod:
  1309. case ConsoleKey.OemComma:
  1310. case ConsoleKey.OemPlus:
  1311. case ConsoleKey.OemMinus:
  1312. case ConsoleKey.Packet:
  1313. case ConsoleKey.Oem1:
  1314. case ConsoleKey.Oem2:
  1315. case ConsoleKey.Oem3:
  1316. case ConsoleKey.Oem4:
  1317. case ConsoleKey.Oem5:
  1318. case ConsoleKey.Oem6:
  1319. case ConsoleKey.Oem7:
  1320. case ConsoleKey.Oem8:
  1321. case ConsoleKey.Oem102:
  1322. if (keyInfo.KeyChar == 0)
  1323. {
  1324. // If the keyChar is 0, keyInfo.Key value is not a printable character.
  1325. return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
  1326. }
  1327. if (keyInfo.Modifiers != ConsoleModifiers.Shift)
  1328. {
  1329. // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
  1330. return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
  1331. }
  1332. // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
  1333. // and passing on Shift would be redundant.
  1334. return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
  1335. }
  1336. // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
  1337. if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
  1338. {
  1339. if (keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I)
  1340. {
  1341. return KeyCode.Tab;
  1342. }
  1343. return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key));
  1344. }
  1345. // Handle control keys (e.g. CursorUp)
  1346. if (keyInfo.Key != ConsoleKey.None
  1347. && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))
  1348. {
  1349. return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
  1350. }
  1351. if (((ConsoleKey)keyInfo.KeyChar) is >= ConsoleKey.A and <= ConsoleKey.Z)
  1352. {
  1353. // Shifted
  1354. keyInfo = new ConsoleKeyInfo (
  1355. keyInfo.KeyChar,
  1356. (ConsoleKey)keyInfo.KeyChar,
  1357. true,
  1358. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
  1359. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
  1360. }
  1361. if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z)
  1362. {
  1363. // Unshifted
  1364. keyInfo = new ConsoleKeyInfo (
  1365. keyInfo.KeyChar,
  1366. (ConsoleKey)(keyInfo.KeyChar - 32),
  1367. false,
  1368. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
  1369. keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
  1370. }
  1371. if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z )
  1372. {
  1373. if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
  1374. || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
  1375. {
  1376. // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos
  1377. return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
  1378. }
  1379. if (keyInfo.Modifiers == ConsoleModifiers.Shift)
  1380. {
  1381. // If ShiftMask is on add the ShiftMask
  1382. if (char.IsUpper (keyInfo.KeyChar))
  1383. {
  1384. return (KeyCode)keyInfo.Key | KeyCode.ShiftMask;
  1385. }
  1386. }
  1387. return (KeyCode)keyInfo.Key;
  1388. }
  1389. return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
  1390. }
  1391. #endregion Keyboard Handling
  1392. }
  1393. /// <summary>
  1394. /// Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is
  1395. /// cross-platform but lacks things like file descriptor monitoring.
  1396. /// </summary>
  1397. /// <remarks>This implementation is used for NetDriver.</remarks>
  1398. internal class NetMainLoop : IMainLoopDriver
  1399. {
  1400. internal NetEvents _netEvents;
  1401. /// <summary>Invoked when a Key is pressed.</summary>
  1402. internal Action<InputResult> ProcessInput;
  1403. private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
  1404. // Wrap ConcurrentQueue in a BlockingCollection to enable blocking with timeout
  1405. private readonly BlockingCollection<InputResult> _resultQueue = new (new ConcurrentQueue<InputResult> ());
  1406. private readonly ManualResetEventSlim _waitForProbe = new (false);
  1407. private MainLoop _mainLoop;
  1408. /// <summary>Initializes the class with the console driver.</summary>
  1409. /// <remarks>Passing a consoleDriver is provided to capture windows resizing.</remarks>
  1410. /// <param name="consoleDriver">The console driver used by this Net main loop.</param>
  1411. /// <exception cref="ArgumentNullException"></exception>
  1412. public NetMainLoop (ConsoleDriver consoleDriver = null)
  1413. {
  1414. if (consoleDriver is null)
  1415. {
  1416. throw new ArgumentNullException (nameof (consoleDriver));
  1417. }
  1418. _netEvents = new NetEvents (consoleDriver);
  1419. }
  1420. void IMainLoopDriver.Setup (MainLoop mainLoop)
  1421. {
  1422. _mainLoop = mainLoop;
  1423. Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
  1424. }
  1425. void IMainLoopDriver.Wakeup () { }
  1426. bool IMainLoopDriver.EventsPending ()
  1427. {
  1428. _waitForProbe.Set ();
  1429. if (_mainLoop.CheckTimersAndIdleHandlers (out _))
  1430. {
  1431. return true;
  1432. }
  1433. return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
  1434. }
  1435. void IMainLoopDriver.Iteration ()
  1436. {
  1437. while (_resultQueue.TryTake (out InputResult v))
  1438. {
  1439. ProcessInput?.Invoke (v);
  1440. }
  1441. }
  1442. void IMainLoopDriver.TearDown ()
  1443. {
  1444. _inputHandlerTokenSource?.Cancel ();
  1445. _inputHandlerTokenSource?.Dispose ();
  1446. _waitForProbe?.Dispose ();
  1447. _netEvents?.Dispose ();
  1448. _netEvents = null;
  1449. _mainLoop = null;
  1450. }
  1451. private void NetInputHandler ()
  1452. {
  1453. while (_mainLoop is { })
  1454. {
  1455. try
  1456. {
  1457. if (!_inputHandlerTokenSource.IsCancellationRequested)
  1458. {
  1459. _waitForProbe.Wait (_inputHandlerTokenSource.Token);
  1460. }
  1461. }
  1462. catch (OperationCanceledException)
  1463. {
  1464. return;
  1465. }
  1466. finally
  1467. {
  1468. if (_waitForProbe.IsSet)
  1469. {
  1470. _waitForProbe.Reset ();
  1471. }
  1472. }
  1473. if (_inputHandlerTokenSource.IsCancellationRequested)
  1474. {
  1475. return;
  1476. }
  1477. _inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
  1478. var input = _netEvents.DequeueInput ();
  1479. if (input.HasValue)
  1480. {
  1481. _resultQueue.Add (input.Value);
  1482. }
  1483. }
  1484. }
  1485. }