CursesDriver.cs 25 KB

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