NetDriver.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. //
  2. // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using NStack;
  13. namespace Terminal.Gui {
  14. internal class NetDriver : ConsoleDriver {
  15. int cols, rows, top;
  16. public override int Cols => cols;
  17. public override int Rows => rows;
  18. public override int Top => top;
  19. // The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
  20. int [,,] contents;
  21. bool [] dirtyLine;
  22. public NetDriver ()
  23. {
  24. ResizeScreen ();
  25. UpdateOffscreen ();
  26. }
  27. void UpdateOffscreen ()
  28. {
  29. int cols = Cols;
  30. int rows = Rows;
  31. contents = new int [rows, cols, 3];
  32. dirtyLine = new bool [rows];
  33. for (int row = 0; row < rows; row++) {
  34. for (int c = 0; c < cols; c++) {
  35. contents [row, c, 0] = ' ';
  36. contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
  37. contents [row, c, 2] = 0;
  38. dirtyLine [row] = true;
  39. }
  40. }
  41. }
  42. static bool sync = false;
  43. bool needMove;
  44. // Current row, and current col, tracked by Move/AddCh only
  45. int ccol, crow;
  46. public override void Move (int col, int row)
  47. {
  48. ccol = col;
  49. crow = row;
  50. if (Clip.Contains (col, row)) {
  51. if (cols == Console.WindowWidth && rows == Console.WindowHeight) {
  52. Console.SetCursorPosition (col, row);
  53. needMove = false;
  54. }
  55. } else {
  56. if (cols == Console.WindowWidth && rows == Console.WindowHeight) {
  57. if (Console.WindowHeight > 0) {
  58. Console.SetCursorPosition (Clip.X, Clip.Y);
  59. }
  60. needMove = true;
  61. }
  62. }
  63. }
  64. public override void AddRune (Rune rune)
  65. {
  66. rune = MakePrintable (rune);
  67. if (Clip.Contains (ccol, crow)) {
  68. if (needMove) {
  69. if (cols == Console.WindowWidth && rows == Console.WindowHeight) {
  70. Console.SetCursorPosition (ccol, crow);
  71. }
  72. needMove = false;
  73. }
  74. contents [crow, ccol, 0] = (int)(uint)rune;
  75. contents [crow, ccol, 1] = currentAttribute;
  76. contents [crow, ccol, 2] = 1;
  77. dirtyLine [crow] = true;
  78. } else
  79. needMove = true;
  80. ccol++;
  81. //if (ccol == Cols) {
  82. // ccol = 0;
  83. // if (crow + 1 < Rows)
  84. // crow++;
  85. //}
  86. if (sync)
  87. UpdateScreen ();
  88. }
  89. public override void AddStr (ustring str)
  90. {
  91. foreach (var rune in str)
  92. AddRune (rune);
  93. }
  94. public override void End ()
  95. {
  96. Console.ResetColor ();
  97. Clear ();
  98. }
  99. void Clear ()
  100. {
  101. if (Rows > 0) {
  102. Console.Clear ();
  103. }
  104. }
  105. static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
  106. {
  107. // Encode the colors into the int value.
  108. return new Attribute () { value = ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff) };
  109. }
  110. public override void Init (Action terminalResized)
  111. {
  112. TerminalResized = terminalResized;
  113. Console.TreatControlCAsInput = true;
  114. Colors.TopLevel = new ColorScheme ();
  115. Colors.Base = new ColorScheme ();
  116. Colors.Dialog = new ColorScheme ();
  117. Colors.Menu = new ColorScheme ();
  118. Colors.Error = new ColorScheme ();
  119. Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
  120. Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
  121. Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
  122. Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
  123. Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue);
  124. Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
  125. Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
  126. Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
  127. // Focused,
  128. // Selected, Hot: Yellow on Black
  129. // Selected, text: white on black
  130. // Unselected, hot: yellow on cyan
  131. // unselected, text: same as unfocused
  132. Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
  133. Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
  134. Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
  135. Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
  136. Colors.Menu.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Cyan);
  137. Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
  138. Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
  139. Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
  140. Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan);
  141. Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red);
  142. Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
  143. Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
  144. Colors.Error.HotFocus = Colors.Error.HotNormal;
  145. Clear ();
  146. }
  147. void ResizeScreen ()
  148. {
  149. cols = Console.WindowWidth;
  150. rows = Console.WindowHeight;
  151. Clip = new Rect (0, 0, Cols, Rows);
  152. top = Console.WindowTop;
  153. }
  154. public override Attribute MakeAttribute (Color fore, Color back)
  155. {
  156. return MakeColor ((ConsoleColor)fore, (ConsoleColor)back);
  157. }
  158. int redrawColor = -1;
  159. void SetColor (int color)
  160. {
  161. redrawColor = color;
  162. IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
  163. .OfType<ConsoleColor> ()
  164. .Select (s => (int)s);
  165. if (values.Contains (color & 0xffff)) {
  166. Console.BackgroundColor = (ConsoleColor)(color & 0xffff);
  167. }
  168. if (values.Contains ((color >> 16) & 0xffff)) {
  169. Console.ForegroundColor = (ConsoleColor)((color >> 16) & 0xffff);
  170. }
  171. }
  172. public override void UpdateScreen ()
  173. {
  174. if (Rows == 0) {
  175. return;
  176. }
  177. int rows = Rows;
  178. int cols = Cols;
  179. for (int row = top; row < rows; row++) {
  180. if (!dirtyLine [row])
  181. continue;
  182. dirtyLine [row] = false;
  183. for (int col = 0; col < cols; col++) {
  184. if (contents [row, col, 2] != 1)
  185. continue;
  186. if (Console.WindowHeight > 0) {
  187. Console.SetCursorPosition (col, row);
  188. }
  189. for (; col < cols && contents [row, col, 2] == 1; col++) {
  190. var color = contents [row, col, 1];
  191. if (color != redrawColor)
  192. SetColor (color);
  193. Console.Write ((char)contents [row, col, 0]);
  194. contents [row, col, 2] = 0;
  195. }
  196. }
  197. }
  198. UpdateCursor ();
  199. }
  200. public override void Refresh ()
  201. {
  202. if (Console.WindowWidth != Cols || Console.WindowHeight != Rows || Console.WindowTop != Top) {
  203. ResizeScreen ();
  204. UpdateOffscreen ();
  205. TerminalResized.Invoke ();
  206. }
  207. UpdateScreen ();
  208. }
  209. public override void UpdateCursor ()
  210. {
  211. // Prevents the exception of size changing during resizing.
  212. try {
  213. if (ccol > 0 && ccol < Console.WindowWidth && crow > 0 && crow < Console.WindowHeight) {
  214. Console.SetCursorPosition (ccol, crow);
  215. }
  216. } catch (ArgumentOutOfRangeException) { }
  217. }
  218. public override void StartReportingMouseMoves ()
  219. {
  220. }
  221. public override void StopReportingMouseMoves ()
  222. {
  223. }
  224. public override void Suspend ()
  225. {
  226. }
  227. int currentAttribute;
  228. public override void SetAttribute (Attribute c)
  229. {
  230. currentAttribute = c.value;
  231. }
  232. Key MapKey (ConsoleKeyInfo keyInfo)
  233. {
  234. MapKeyModifiers (keyInfo);
  235. switch (keyInfo.Key) {
  236. case ConsoleKey.Escape:
  237. return Key.Esc;
  238. case ConsoleKey.Tab:
  239. return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
  240. case ConsoleKey.Home:
  241. return Key.Home;
  242. case ConsoleKey.End:
  243. return Key.End;
  244. case ConsoleKey.LeftArrow:
  245. return Key.CursorLeft;
  246. case ConsoleKey.RightArrow:
  247. return Key.CursorRight;
  248. case ConsoleKey.UpArrow:
  249. return Key.CursorUp;
  250. case ConsoleKey.DownArrow:
  251. return Key.CursorDown;
  252. case ConsoleKey.PageUp:
  253. return Key.PageUp;
  254. case ConsoleKey.PageDown:
  255. return Key.PageDown;
  256. case ConsoleKey.Enter:
  257. return Key.Enter;
  258. case ConsoleKey.Spacebar:
  259. return Key.Space;
  260. case ConsoleKey.Backspace:
  261. return Key.Backspace;
  262. case ConsoleKey.Delete:
  263. return Key.Delete;
  264. case ConsoleKey.Oem1:
  265. case ConsoleKey.Oem2:
  266. case ConsoleKey.Oem3:
  267. case ConsoleKey.Oem4:
  268. case ConsoleKey.Oem5:
  269. case ConsoleKey.Oem6:
  270. case ConsoleKey.Oem7:
  271. case ConsoleKey.Oem8:
  272. case ConsoleKey.Oem102:
  273. case ConsoleKey.OemPeriod:
  274. case ConsoleKey.OemComma:
  275. case ConsoleKey.OemPlus:
  276. case ConsoleKey.OemMinus:
  277. return (Key)((uint)keyInfo.KeyChar);
  278. }
  279. var key = keyInfo.Key;
  280. if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
  281. var delta = key - ConsoleKey.A;
  282. if (keyInfo.Modifiers == ConsoleModifiers.Control) {
  283. return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
  284. }
  285. if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
  286. return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
  287. }
  288. if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
  289. if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
  290. return (Key)((uint)Key.A + delta);
  291. }
  292. }
  293. return (Key)((uint)keyInfo.KeyChar);
  294. }
  295. if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
  296. var delta = key - ConsoleKey.D0;
  297. if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
  298. return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
  299. }
  300. if (keyInfo.Modifiers == ConsoleModifiers.Control) {
  301. return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
  302. }
  303. if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
  304. if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
  305. return (Key)((uint)Key.D0 + delta);
  306. }
  307. }
  308. return (Key)((uint)keyInfo.KeyChar);
  309. }
  310. if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
  311. var delta = key - ConsoleKey.F1;
  312. if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
  313. return (Key)((uint)Key.F1 + delta);
  314. }
  315. return (Key)((uint)Key.F1 + delta);
  316. }
  317. if (keyInfo.KeyChar != 0) {
  318. return (Key)((uint)keyInfo.KeyChar);
  319. }
  320. return (Key)(0xffffffff);
  321. }
  322. KeyModifiers keyModifiers;
  323. void MapKeyModifiers (ConsoleKeyInfo keyInfo)
  324. {
  325. if (keyModifiers == null)
  326. keyModifiers = new KeyModifiers ();
  327. if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
  328. keyModifiers.Shift = true;
  329. if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
  330. keyModifiers.Ctrl = true;
  331. if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0)
  332. keyModifiers.Alt = true;
  333. }
  334. public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
  335. {
  336. // Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
  337. (mainLoop.Driver as NetMainLoop).KeyPressed = delegate (ConsoleKeyInfo consoleKey) {
  338. var map = MapKey (consoleKey);
  339. if (map == (Key)0xffffffff) {
  340. return;
  341. }
  342. keyHandler (new KeyEvent (map, keyModifiers));
  343. keyUpHandler (new KeyEvent (map, keyModifiers));
  344. keyModifiers = null;
  345. };
  346. }
  347. public override void SetColors (ConsoleColor foreground, ConsoleColor background)
  348. {
  349. throw new NotImplementedException ();
  350. }
  351. public override void SetColors (short foregroundColorId, short backgroundColorId)
  352. {
  353. throw new NotImplementedException ();
  354. }
  355. public override void CookMouse ()
  356. {
  357. }
  358. public override void UncookMouse ()
  359. {
  360. }
  361. //
  362. // These are for the .NET driver, but running natively on Windows, wont run
  363. // on the Mono emulation
  364. //
  365. }
  366. /// <summary>
  367. /// Mainloop intended to be used with the .NET System.Console API, and can
  368. /// be used on Windows and Unix, it is cross platform but lacks things like
  369. /// file descriptor monitoring.
  370. /// </summary>
  371. /// <remarks>
  372. /// This implementation is used for NetDriver.
  373. /// </remarks>
  374. public class NetMainLoop : IMainLoopDriver {
  375. ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
  376. ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
  377. ManualResetEventSlim winChange = new ManualResetEventSlim (false);
  378. ConsoleKeyInfo? keyResult = null;
  379. MainLoop mainLoop;
  380. ConsoleDriver consoleDriver;
  381. bool winChanged;
  382. CancellationTokenSource tokenSource = new CancellationTokenSource ();
  383. /// <summary>
  384. /// Invoked when a Key is pressed.
  385. /// </summary>
  386. public Action<ConsoleKeyInfo> KeyPressed;
  387. /// <summary>
  388. /// Initializes the class with the console driver.
  389. /// </summary>
  390. /// <remarks>
  391. /// Passing a consoleDriver is provided to capture windows resizing.
  392. /// </remarks>
  393. /// <param name="consoleDriver">The console driver used by this Net main loop.</param>
  394. public NetMainLoop (ConsoleDriver consoleDriver = null)
  395. {
  396. if (consoleDriver == null) {
  397. throw new ArgumentNullException ("console driver instance must be provided.");
  398. }
  399. this.consoleDriver = consoleDriver;
  400. }
  401. void KeyReader ()
  402. {
  403. while (true) {
  404. waitForProbe.Wait ();
  405. waitForProbe.Reset ();
  406. keyResult = Console.ReadKey (true);
  407. keyReady.Set ();
  408. }
  409. }
  410. void CheckWinChange ()
  411. {
  412. while (true) {
  413. winChange.Wait ();
  414. winChange.Reset ();
  415. WaitWinChange ();
  416. winChanged = true;
  417. keyReady.Set ();
  418. }
  419. }
  420. void WaitWinChange ()
  421. {
  422. while (true) {
  423. if (Console.WindowWidth != consoleDriver.Cols || Console.WindowHeight != consoleDriver.Rows
  424. || Console.WindowTop != consoleDriver.Top) { // Top only working on Windows.
  425. return;
  426. }
  427. }
  428. }
  429. void IMainLoopDriver.Setup (MainLoop mainLoop)
  430. {
  431. this.mainLoop = mainLoop;
  432. Task.Run (KeyReader);
  433. Task.Run (CheckWinChange);
  434. }
  435. void IMainLoopDriver.Wakeup ()
  436. {
  437. keyReady.Set ();
  438. }
  439. bool IMainLoopDriver.EventsPending (bool wait)
  440. {
  441. long now = DateTime.UtcNow.Ticks;
  442. waitForProbe.Set ();
  443. winChange.Set ();
  444. if (CheckTimers (wait, out var waitTimeout)) {
  445. return true;
  446. }
  447. try {
  448. if (!tokenSource.IsCancellationRequested) {
  449. keyReady.Wait (waitTimeout, tokenSource.Token);
  450. }
  451. } catch (OperationCanceledException) {
  452. return true;
  453. } finally {
  454. keyReady.Reset ();
  455. }
  456. if (!tokenSource.IsCancellationRequested) {
  457. return keyResult.HasValue || CheckTimers (wait, out _) || winChanged;
  458. }
  459. tokenSource.Dispose ();
  460. tokenSource = new CancellationTokenSource ();
  461. return true;
  462. }
  463. bool CheckTimers (bool wait, out int waitTimeout)
  464. {
  465. long now = DateTime.UtcNow.Ticks;
  466. if (mainLoop.timeouts.Count > 0) {
  467. waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
  468. if (waitTimeout < 0)
  469. return true;
  470. } else {
  471. waitTimeout = -1;
  472. }
  473. if (!wait)
  474. waitTimeout = 0;
  475. int ic;
  476. lock (mainLoop.idleHandlers) {
  477. ic = mainLoop.idleHandlers.Count;
  478. }
  479. return ic > 0;
  480. }
  481. void IMainLoopDriver.MainIteration ()
  482. {
  483. if (keyResult.HasValue) {
  484. var kr = keyResult;
  485. keyResult = null;
  486. KeyPressed?.Invoke (kr.Value);
  487. }
  488. if (winChanged) {
  489. winChanged = false;
  490. consoleDriver.Refresh ();
  491. }
  492. }
  493. }
  494. }