CursesDriver.cs 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. //
  2. // Driver.cs: Curses-based Driver
  3. //
  4. using System.Runtime.InteropServices;
  5. using Terminal.Gui.ConsoleDrivers;
  6. using Unix.Terminal;
  7. namespace Terminal.Gui;
  8. /// <summary>This is the Curses driver for the gui.cs/Terminal framework.</summary>
  9. internal class CursesDriver : ConsoleDriver
  10. {
  11. public Curses.Window _window;
  12. private CursorVisibility? _currentCursorVisibility;
  13. private CursorVisibility? _initialCursorVisibility;
  14. private MouseFlags _lastMouseFlags;
  15. private UnixMainLoop _mainLoopDriver;
  16. private object _processInputToken;
  17. public override int Cols
  18. {
  19. get => Curses.Cols;
  20. internal set
  21. {
  22. Curses.Cols = value;
  23. ClearContents ();
  24. }
  25. }
  26. public override int Rows
  27. {
  28. get => Curses.Lines;
  29. internal set
  30. {
  31. Curses.Lines = value;
  32. ClearContents ();
  33. }
  34. }
  35. public override bool SupportsTrueColor => false;
  36. /// <inheritdoc/>
  37. public override bool EnsureCursorVisibility () { return false; }
  38. /// <inheritdoc/>
  39. public override bool GetCursorVisibility (out CursorVisibility visibility)
  40. {
  41. visibility = CursorVisibility.Invisible;
  42. if (!_currentCursorVisibility.HasValue)
  43. {
  44. return false;
  45. }
  46. visibility = _currentCursorVisibility.Value;
  47. return true;
  48. }
  49. public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; }
  50. public static bool Is_WSL_Platform ()
  51. {
  52. // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell
  53. //if (new CursesClipboard ().IsSupported) {
  54. // // If xclip is installed on Linux under WSL, this will return true.
  55. // return false;
  56. //}
  57. (int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
  58. if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL"))
  59. {
  60. return true;
  61. }
  62. return false;
  63. }
  64. public override bool IsRuneSupported (Rune rune)
  65. {
  66. // See Issue #2615 - CursesDriver is broken with non-BMP characters
  67. return base.IsRuneSupported (rune) && rune.IsBmp;
  68. }
  69. public override void Move (int col, int row)
  70. {
  71. base.Move (col, row);
  72. if (RunningUnitTests)
  73. {
  74. return;
  75. }
  76. if (IsValidLocation (col, row))
  77. {
  78. Curses.move (row, col);
  79. }
  80. else
  81. {
  82. // Not a valid location (outside screen or clip region)
  83. // Move within the clip region, then AddRune will actually move to Col, Row
  84. Curses.move (Clip.Y, Clip.X);
  85. }
  86. }
  87. public override void Refresh ()
  88. {
  89. UpdateScreen ();
  90. UpdateCursor ();
  91. }
  92. public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
  93. {
  94. KeyCode key;
  95. if (consoleKey == ConsoleKey.Packet)
  96. {
  97. var mod = new ConsoleModifiers ();
  98. if (shift)
  99. {
  100. mod |= ConsoleModifiers.Shift;
  101. }
  102. if (alt)
  103. {
  104. mod |= ConsoleModifiers.Alt;
  105. }
  106. if (control)
  107. {
  108. mod |= ConsoleModifiers.Control;
  109. }
  110. var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
  111. cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
  112. key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo);
  113. }
  114. else
  115. {
  116. key = (KeyCode)keyChar;
  117. }
  118. OnKeyDown (new Key (key));
  119. OnKeyUp (new Key (key));
  120. //OnKeyPressed (new KeyEventArgsEventArgs (key));
  121. }
  122. /// <inheritdoc/>
  123. public override bool SetCursorVisibility (CursorVisibility visibility)
  124. {
  125. if (_initialCursorVisibility.HasValue == false)
  126. {
  127. return false;
  128. }
  129. if (!RunningUnitTests)
  130. {
  131. Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
  132. }
  133. if (visibility != CursorVisibility.Invisible)
  134. {
  135. Console.Out.Write (
  136. EscSeqUtils.CSI_SetCursorStyle (
  137. (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24)
  138. & 0xFF)
  139. )
  140. );
  141. }
  142. _currentCursorVisibility = visibility;
  143. return true;
  144. }
  145. public void StartReportingMouseMoves ()
  146. {
  147. if (!RunningUnitTests)
  148. {
  149. Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
  150. }
  151. }
  152. public void StopReportingMouseMoves ()
  153. {
  154. if (!RunningUnitTests)
  155. {
  156. Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
  157. }
  158. }
  159. public override void Suspend ()
  160. {
  161. StopReportingMouseMoves ();
  162. if (!RunningUnitTests)
  163. {
  164. Platform.Suspend ();
  165. Curses.Window.Standard.redrawwin ();
  166. Curses.refresh ();
  167. }
  168. StartReportingMouseMoves ();
  169. }
  170. public override void UpdateCursor ()
  171. {
  172. EnsureCursorVisibility ();
  173. if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
  174. {
  175. Curses.move (Row, Col);
  176. }
  177. }
  178. public override void UpdateScreen ()
  179. {
  180. for (var row = 0; row < Rows; row++)
  181. {
  182. if (!_dirtyLines [row])
  183. {
  184. continue;
  185. }
  186. _dirtyLines [row] = false;
  187. for (var col = 0; col < Cols; col++)
  188. {
  189. if (Contents [row, col].IsDirty == false)
  190. {
  191. continue;
  192. }
  193. if (RunningUnitTests)
  194. {
  195. // In unit tests, we don't want to actually write to the screen.
  196. continue;
  197. }
  198. Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
  199. Rune rune = Contents [row, col].Rune;
  200. if (rune.IsBmp)
  201. {
  202. // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
  203. if (rune.GetColumns () < 2)
  204. {
  205. Curses.mvaddch (row, col, rune.Value);
  206. }
  207. else /*if (col + 1 < Cols)*/
  208. {
  209. Curses.mvaddwstr (row, col, rune.ToString ());
  210. }
  211. }
  212. else
  213. {
  214. Curses.mvaddwstr (row, col, rune.ToString ());
  215. if (rune.GetColumns () > 1 && col + 1 < Cols)
  216. {
  217. // TODO: This is a hack to deal with non-BMP and wide characters.
  218. //col++;
  219. Curses.mvaddch (row, ++col, '*');
  220. }
  221. }
  222. }
  223. }
  224. if (!RunningUnitTests)
  225. {
  226. Curses.move (Row, Col);
  227. _window.wrefresh ();
  228. }
  229. }
  230. internal override void End ()
  231. {
  232. StopReportingMouseMoves ();
  233. SetCursorVisibility (CursorVisibility.Default);
  234. if (_mainLoopDriver is { })
  235. {
  236. _mainLoopDriver.RemoveWatch (_processInputToken);
  237. }
  238. if (RunningUnitTests)
  239. {
  240. return;
  241. }
  242. // throws away any typeahead that has been typed by
  243. // the user and has not yet been read by the program.
  244. Curses.flushinp ();
  245. Curses.endwin ();
  246. }
  247. internal override MainLoop Init ()
  248. {
  249. _mainLoopDriver = new UnixMainLoop (this);
  250. if (!RunningUnitTests)
  251. {
  252. _window = Curses.initscr ();
  253. Curses.set_escdelay (10);
  254. // Ensures that all procedures are performed at some previous closing.
  255. Curses.doupdate ();
  256. //
  257. // We are setting Invisible as default so we could ignore XTerm DECSUSR setting
  258. //
  259. switch (Curses.curs_set (0))
  260. {
  261. case 0:
  262. _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible;
  263. break;
  264. case 1:
  265. _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline;
  266. Curses.curs_set (1);
  267. break;
  268. case 2:
  269. _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box;
  270. Curses.curs_set (2);
  271. break;
  272. default:
  273. _currentCursorVisibility = _initialCursorVisibility = null;
  274. break;
  275. }
  276. if (!Curses.HasColors)
  277. {
  278. throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does.");
  279. }
  280. Curses.raw ();
  281. Curses.noecho ();
  282. Curses.Window.Standard.keypad (true);
  283. Curses.StartColor ();
  284. Curses.UseDefaultColors ();
  285. if (!RunningUnitTests)
  286. {
  287. Curses.timeout (0);
  288. }
  289. _processInputToken = _mainLoopDriver?.AddWatch (
  290. 0,
  291. UnixMainLoop.Condition.PollIn,
  292. x =>
  293. {
  294. ProcessInput ();
  295. return true;
  296. }
  297. );
  298. }
  299. CurrentAttribute = new Attribute (ColorName.White, ColorName.Black);
  300. if (Environment.OSVersion.Platform == PlatformID.Win32NT)
  301. {
  302. Clipboard = new FakeDriver.FakeClipboard ();
  303. }
  304. else
  305. {
  306. if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
  307. {
  308. Clipboard = new MacOSXClipboard ();
  309. }
  310. else
  311. {
  312. if (Is_WSL_Platform ())
  313. {
  314. Clipboard = new WSLClipboard ();
  315. }
  316. else
  317. {
  318. Clipboard = new CursesClipboard ();
  319. }
  320. }
  321. }
  322. ClearContents ();
  323. StartReportingMouseMoves ();
  324. if (!RunningUnitTests)
  325. {
  326. Curses.CheckWinChange ();
  327. Curses.refresh ();
  328. }
  329. return new MainLoop (_mainLoopDriver);
  330. }
  331. internal void ProcessInput ()
  332. {
  333. int wch;
  334. int code = Curses.get_wch (out wch);
  335. //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
  336. if (code == Curses.ERR)
  337. {
  338. return;
  339. }
  340. var k = KeyCode.Null;
  341. if (code == Curses.KEY_CODE_YES)
  342. {
  343. while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize)
  344. {
  345. ProcessWinChange ();
  346. code = Curses.get_wch (out wch);
  347. }
  348. if (wch == 0)
  349. {
  350. return;
  351. }
  352. if (wch == Curses.KeyMouse)
  353. {
  354. int wch2 = wch;
  355. while (wch2 == Curses.KeyMouse)
  356. {
  357. Key kea = null;
  358. ConsoleKeyInfo [] cki =
  359. {
  360. new ((char)KeyCode.Esc, 0, false, false, false),
  361. new ('[', 0, false, false, false),
  362. new ('<', 0, false, false, false)
  363. };
  364. code = 0;
  365. HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
  366. }
  367. return;
  368. }
  369. k = MapCursesKey (wch);
  370. if (wch >= 277 && wch <= 288)
  371. {
  372. // Shift+(F1 - F12)
  373. wch -= 12;
  374. k = KeyCode.ShiftMask | MapCursesKey (wch);
  375. }
  376. else if (wch >= 289 && wch <= 300)
  377. {
  378. // Ctrl+(F1 - F12)
  379. wch -= 24;
  380. k = KeyCode.CtrlMask | MapCursesKey (wch);
  381. }
  382. else if (wch >= 301 && wch <= 312)
  383. {
  384. // Ctrl+Shift+(F1 - F12)
  385. wch -= 36;
  386. k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
  387. }
  388. else if (wch >= 313 && wch <= 324)
  389. {
  390. // Alt+(F1 - F12)
  391. wch -= 48;
  392. k = KeyCode.AltMask | MapCursesKey (wch);
  393. }
  394. else if (wch >= 325 && wch <= 327)
  395. {
  396. // Shift+Alt+(F1 - F3)
  397. wch -= 60;
  398. k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
  399. }
  400. OnKeyDown (new Key (k));
  401. OnKeyUp (new Key (k));
  402. return;
  403. }
  404. // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
  405. if (wch == 27)
  406. {
  407. Curses.timeout (10);
  408. code = Curses.get_wch (out int wch2);
  409. if (code == Curses.KEY_CODE_YES)
  410. {
  411. k = KeyCode.AltMask | MapCursesKey (wch);
  412. }
  413. Key key = null;
  414. if (code == 0)
  415. {
  416. // The ESC-number handling, debatable.
  417. // Simulates the AltMask itself by pressing Alt + Space.
  418. if (wch2 == (int)KeyCode.Space)
  419. {
  420. k = KeyCode.AltMask;
  421. }
  422. else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A
  423. && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z)
  424. {
  425. k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space));
  426. }
  427. else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64)
  428. {
  429. k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64));
  430. }
  431. else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9)
  432. {
  433. k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
  434. }
  435. else if (wch2 == Curses.KeyCSI)
  436. {
  437. ConsoleKeyInfo [] cki =
  438. {
  439. new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false)
  440. };
  441. HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
  442. return;
  443. }
  444. else
  445. {
  446. // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
  447. if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0)
  448. {
  449. k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
  450. }
  451. if (wch2 == 0)
  452. {
  453. k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
  454. }
  455. else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
  456. {
  457. k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
  458. }
  459. else if (wch2 < 256)
  460. {
  461. k = (KeyCode)wch2; // | KeyCode.AltMask;
  462. }
  463. else
  464. {
  465. k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
  466. }
  467. }
  468. key = new Key (k);
  469. }
  470. else
  471. {
  472. key = new Key (KeyCode.Esc);
  473. }
  474. OnKeyDown (key);
  475. OnKeyUp (key);
  476. }
  477. else if (wch == Curses.KeyTab)
  478. {
  479. k = MapCursesKey (wch);
  480. OnKeyDown (new Key (k));
  481. OnKeyUp (new Key (k));
  482. }
  483. else
  484. {
  485. // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
  486. k = (KeyCode)wch;
  487. if (wch == 0)
  488. {
  489. k = KeyCode.CtrlMask | KeyCode.Space;
  490. }
  491. else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64)
  492. {
  493. if ((KeyCode)(wch + 64) != KeyCode.J)
  494. {
  495. k = KeyCode.CtrlMask | (KeyCode)(wch + 64);
  496. }
  497. }
  498. else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
  499. {
  500. k = (KeyCode)wch | KeyCode.ShiftMask;
  501. }
  502. if (wch == '\n' || wch == '\r')
  503. {
  504. k = KeyCode.Enter;
  505. }
  506. OnKeyDown (new Key (k));
  507. OnKeyUp (new Key (k));
  508. }
  509. }
  510. internal void ProcessWinChange ()
  511. {
  512. if (!RunningUnitTests && Curses.CheckWinChange ())
  513. {
  514. ClearContents ();
  515. OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
  516. }
  517. }
  518. private void HandleEscSeqResponse (
  519. ref int code,
  520. ref KeyCode k,
  521. ref int wch2,
  522. ref Key keyEventArgs,
  523. ref ConsoleKeyInfo [] cki
  524. )
  525. {
  526. ConsoleKey ck = 0;
  527. ConsoleModifiers mod = 0;
  528. while (code == 0)
  529. {
  530. code = Curses.get_wch (out wch2);
  531. var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
  532. if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse)
  533. {
  534. EscSeqUtils.DecodeEscSeq (
  535. null,
  536. ref consoleKeyInfo,
  537. ref ck,
  538. cki,
  539. ref mod,
  540. out _,
  541. out _,
  542. out _,
  543. out _,
  544. out bool isKeyMouse,
  545. out List<MouseFlags> mouseFlags,
  546. out Point pos,
  547. out _,
  548. ProcessMouseEvent
  549. );
  550. if (isKeyMouse)
  551. {
  552. foreach (MouseFlags mf in mouseFlags)
  553. {
  554. ProcessMouseEvent (mf, pos);
  555. }
  556. cki = null;
  557. if (wch2 == 27)
  558. {
  559. cki = EscSeqUtils.ResizeArray (
  560. new ConsoleKeyInfo (
  561. (char)KeyCode.Esc,
  562. 0,
  563. false,
  564. false,
  565. false
  566. ),
  567. cki
  568. );
  569. }
  570. }
  571. else
  572. {
  573. k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
  574. keyEventArgs = new Key (k);
  575. OnKeyDown (keyEventArgs);
  576. }
  577. }
  578. else
  579. {
  580. cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
  581. }
  582. }
  583. }
  584. private static KeyCode MapCursesKey (int cursesKey)
  585. {
  586. switch (cursesKey)
  587. {
  588. case Curses.KeyF1: return KeyCode.F1;
  589. case Curses.KeyF2: return KeyCode.F2;
  590. case Curses.KeyF3: return KeyCode.F3;
  591. case Curses.KeyF4: return KeyCode.F4;
  592. case Curses.KeyF5: return KeyCode.F5;
  593. case Curses.KeyF6: return KeyCode.F6;
  594. case Curses.KeyF7: return KeyCode.F7;
  595. case Curses.KeyF8: return KeyCode.F8;
  596. case Curses.KeyF9: return KeyCode.F9;
  597. case Curses.KeyF10: return KeyCode.F10;
  598. case Curses.KeyF11: return KeyCode.F11;
  599. case Curses.KeyF12: return KeyCode.F12;
  600. case Curses.KeyUp: return KeyCode.CursorUp;
  601. case Curses.KeyDown: return KeyCode.CursorDown;
  602. case Curses.KeyLeft: return KeyCode.CursorLeft;
  603. case Curses.KeyRight: return KeyCode.CursorRight;
  604. case Curses.KeyHome: return KeyCode.Home;
  605. case Curses.KeyEnd: return KeyCode.End;
  606. case Curses.KeyNPage: return KeyCode.PageDown;
  607. case Curses.KeyPPage: return KeyCode.PageUp;
  608. case Curses.KeyDeleteChar: return KeyCode.Delete;
  609. case Curses.KeyInsertChar: return KeyCode.Insert;
  610. case Curses.KeyTab: return KeyCode.Tab;
  611. case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
  612. case Curses.KeyBackspace: return KeyCode.Backspace;
  613. case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask;
  614. case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask;
  615. case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask;
  616. case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask;
  617. case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask;
  618. case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask;
  619. case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask;
  620. case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask;
  621. case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask;
  622. case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask;
  623. case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask;
  624. case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask;
  625. case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask;
  626. case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask;
  627. case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask;
  628. case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask;
  629. case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask;
  630. case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask;
  631. case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask;
  632. case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask;
  633. case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask;
  634. case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask;
  635. case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask;
  636. case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask;
  637. case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
  638. case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
  639. case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask;
  640. case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask;
  641. case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask;
  642. case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask;
  643. case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
  644. case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
  645. case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask;
  646. case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask;
  647. case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask;
  648. case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask;
  649. case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask;
  650. case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask;
  651. case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask;
  652. case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask;
  653. case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask;
  654. case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask;
  655. case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask;
  656. case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask;
  657. default: return KeyCode.Null;
  658. }
  659. }
  660. private void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
  661. {
  662. bool WasButtonReleased (MouseFlags flag)
  663. {
  664. return flag.HasFlag (MouseFlags.Button1Released)
  665. || flag.HasFlag (MouseFlags.Button2Released)
  666. || flag.HasFlag (MouseFlags.Button3Released)
  667. || flag.HasFlag (MouseFlags.Button4Released);
  668. }
  669. bool IsButtonNotPressed (MouseFlags flag)
  670. {
  671. return !flag.HasFlag (MouseFlags.Button1Pressed)
  672. && !flag.HasFlag (MouseFlags.Button2Pressed)
  673. && !flag.HasFlag (MouseFlags.Button3Pressed)
  674. && !flag.HasFlag (MouseFlags.Button4Pressed);
  675. }
  676. bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
  677. {
  678. return flag.HasFlag (MouseFlags.Button1Clicked)
  679. || flag.HasFlag (MouseFlags.Button2Clicked)
  680. || flag.HasFlag (MouseFlags.Button3Clicked)
  681. || flag.HasFlag (MouseFlags.Button4Clicked)
  682. || flag.HasFlag (MouseFlags.Button1DoubleClicked)
  683. || flag.HasFlag (MouseFlags.Button2DoubleClicked)
  684. || flag.HasFlag (MouseFlags.Button3DoubleClicked)
  685. || flag.HasFlag (MouseFlags.Button4DoubleClicked);
  686. }
  687. if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0))
  688. {
  689. return;
  690. }
  691. _lastMouseFlags = mouseFlag;
  692. var me = new MouseEvent { Flags = mouseFlag, X = pos.X, Y = pos.Y };
  693. OnMouseEvent (new MouseEventEventArgs (me));
  694. }
  695. #region Color Handling
  696. /// <summary>Creates an Attribute from the provided curses-based foreground and background color numbers</summary>
  697. /// <param name="foreground">Contains the curses color number for the foreground (color, plus any attributes)</param>
  698. /// <param name="background">Contains the curses color number for the background (color, plus any attributes)</param>
  699. /// <returns></returns>
  700. private static Attribute MakeColor (short foreground, short background)
  701. {
  702. var v = (short)(foreground | (background << 4));
  703. // TODO: for TrueColor - Use InitExtendedPair
  704. Curses.InitColorPair (v, foreground, background);
  705. return new Attribute (
  706. Curses.ColorPair (v),
  707. CursesColorNumberToColorName (foreground),
  708. CursesColorNumberToColorName (background)
  709. );
  710. }
  711. /// <inheritdoc/>
  712. /// <remarks>
  713. /// In the CursesDriver, colors are encoded as an int. The foreground color is stored in the most significant 4
  714. /// bits, and the background color is stored in the least significant 4 bits. The Terminal.GUi Color values are
  715. /// converted to curses color encoding before being encoded.
  716. /// </remarks>
  717. public override Attribute MakeColor (in Color foreground, in Color background)
  718. {
  719. if (!RunningUnitTests)
  720. {
  721. return MakeColor (
  722. ColorNameToCursesColorNumber (foreground.GetClosestNamedColor ()),
  723. ColorNameToCursesColorNumber (background.GetClosestNamedColor ())
  724. );
  725. }
  726. return new Attribute (
  727. 0,
  728. foreground,
  729. background
  730. );
  731. }
  732. private static short ColorNameToCursesColorNumber (ColorName color)
  733. {
  734. switch (color)
  735. {
  736. case ColorName.Black:
  737. return Curses.COLOR_BLACK;
  738. case ColorName.Blue:
  739. return Curses.COLOR_BLUE;
  740. case ColorName.Green:
  741. return Curses.COLOR_GREEN;
  742. case ColorName.Cyan:
  743. return Curses.COLOR_CYAN;
  744. case ColorName.Red:
  745. return Curses.COLOR_RED;
  746. case ColorName.Magenta:
  747. return Curses.COLOR_MAGENTA;
  748. case ColorName.Yellow:
  749. return Curses.COLOR_YELLOW;
  750. case ColorName.Gray:
  751. return Curses.COLOR_WHITE;
  752. case ColorName.DarkGray:
  753. return Curses.COLOR_GRAY;
  754. case ColorName.BrightBlue:
  755. return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
  756. case ColorName.BrightGreen:
  757. return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
  758. case ColorName.BrightCyan:
  759. return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
  760. case ColorName.BrightRed:
  761. return Curses.COLOR_RED | Curses.COLOR_GRAY;
  762. case ColorName.BrightMagenta:
  763. return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
  764. case ColorName.BrightYellow:
  765. return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
  766. case ColorName.White:
  767. return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
  768. }
  769. throw new ArgumentException ("Invalid color code");
  770. }
  771. private static ColorName CursesColorNumberToColorName (short color)
  772. {
  773. switch (color)
  774. {
  775. case Curses.COLOR_BLACK:
  776. return ColorName.Black;
  777. case Curses.COLOR_BLUE:
  778. return ColorName.Blue;
  779. case Curses.COLOR_GREEN:
  780. return ColorName.Green;
  781. case Curses.COLOR_CYAN:
  782. return ColorName.Cyan;
  783. case Curses.COLOR_RED:
  784. return ColorName.Red;
  785. case Curses.COLOR_MAGENTA:
  786. return ColorName.Magenta;
  787. case Curses.COLOR_YELLOW:
  788. return ColorName.Yellow;
  789. case Curses.COLOR_WHITE:
  790. return ColorName.Gray;
  791. case Curses.COLOR_GRAY:
  792. return ColorName.DarkGray;
  793. case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
  794. return ColorName.BrightBlue;
  795. case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
  796. return ColorName.BrightGreen;
  797. case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
  798. return ColorName.BrightCyan;
  799. case Curses.COLOR_RED | Curses.COLOR_GRAY:
  800. return ColorName.BrightRed;
  801. case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
  802. return ColorName.BrightMagenta;
  803. case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
  804. return ColorName.BrightYellow;
  805. case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
  806. return ColorName.White;
  807. }
  808. throw new ArgumentException ("Invalid curses color code");
  809. }
  810. #endregion
  811. }
  812. internal static class Platform
  813. {
  814. private static int _suspendSignal;
  815. /// <summary>Suspends the process by sending SIGTSTP to itself</summary>
  816. /// <returns>The suspend.</returns>
  817. public static bool Suspend ()
  818. {
  819. int signal = GetSuspendSignal ();
  820. if (signal == -1)
  821. {
  822. return false;
  823. }
  824. killpg (0, signal);
  825. return true;
  826. }
  827. private static int GetSuspendSignal ()
  828. {
  829. if (_suspendSignal != 0)
  830. {
  831. return _suspendSignal;
  832. }
  833. nint buf = Marshal.AllocHGlobal (8192);
  834. if (uname (buf) != 0)
  835. {
  836. Marshal.FreeHGlobal (buf);
  837. _suspendSignal = -1;
  838. return _suspendSignal;
  839. }
  840. try
  841. {
  842. switch (Marshal.PtrToStringAnsi (buf))
  843. {
  844. case "Darwin":
  845. case "DragonFly":
  846. case "FreeBSD":
  847. case "NetBSD":
  848. case "OpenBSD":
  849. _suspendSignal = 18;
  850. break;
  851. case "Linux":
  852. // TODO: should fetch the machine name and
  853. // if it is MIPS return 24
  854. _suspendSignal = 20;
  855. break;
  856. case "Solaris":
  857. _suspendSignal = 24;
  858. break;
  859. default:
  860. _suspendSignal = -1;
  861. break;
  862. }
  863. return _suspendSignal;
  864. }
  865. finally
  866. {
  867. Marshal.FreeHGlobal (buf);
  868. }
  869. }
  870. [DllImport ("libc")]
  871. private static extern int killpg (int pgrp, int pid);
  872. [DllImport ("libc")]
  873. private static extern int uname (nint buf);
  874. }