NetDriver.cs 42 KB

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