NetDriver.cs 43 KB

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