CursesDriver.cs 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. //
  2. // Driver.cs: Curses-based Driver
  3. //
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Linq;
  8. using System.Runtime.InteropServices;
  9. using System.Threading.Tasks;
  10. using NStack;
  11. using Unix.Terminal;
  12. namespace Terminal.Gui {
  13. /// <summary>
  14. /// This is the Curses driver for the gui.cs/Terminal framework.
  15. /// </summary>
  16. internal class CursesDriver : ConsoleDriver {
  17. public override int Cols => Curses.Cols;
  18. public override int Rows => Curses.Lines;
  19. public override int Left => 0;
  20. public override int Top => 0;
  21. public override bool EnableConsoleScrolling { get; set; }
  22. public override IClipboard Clipboard { get => clipboard; }
  23. CursorVisibility? initialCursorVisibility = null;
  24. CursorVisibility? currentCursorVisibility = null;
  25. IClipboard clipboard;
  26. int [,,] contents;
  27. public override int [,,] Contents => contents;
  28. // Current row, and current col, tracked by Move/AddRune only
  29. int ccol, crow;
  30. bool needMove;
  31. public override void Move (int col, int row)
  32. {
  33. ccol = col;
  34. crow = row;
  35. if (Clip.Contains (col, row)) {
  36. Curses.move (row, col);
  37. needMove = false;
  38. } else {
  39. Curses.move (Clip.Y, Clip.X);
  40. needMove = true;
  41. }
  42. }
  43. static bool sync = false;
  44. public override void AddRune (Rune rune)
  45. {
  46. rune = MakePrintable (rune);
  47. var runeWidth = Rune.ColumnWidth (rune);
  48. var validClip = IsValidContent (ccol, crow, Clip);
  49. if (validClip) {
  50. if (needMove) {
  51. Curses.move (crow, ccol);
  52. needMove = false;
  53. }
  54. if (runeWidth == 0 && ccol > 0) {
  55. var r = contents [crow, ccol - 1, 0];
  56. var s = new string (new char [] { (char)r, (char)rune });
  57. string sn;
  58. if (!s.IsNormalized ()) {
  59. sn = s.Normalize ();
  60. } else {
  61. sn = s;
  62. }
  63. var c = sn [0];
  64. Curses.mvaddch (crow, ccol - 1, (int)(uint)c);
  65. contents [crow, ccol - 1, 0] = c;
  66. contents [crow, ccol - 1, 1] = CurrentAttribute;
  67. contents [crow, ccol - 1, 2] = 1;
  68. } else {
  69. if (runeWidth < 2 && ccol > 0
  70. && Rune.ColumnWidth ((char)contents [crow, ccol - 1, 0]) > 1) {
  71. var curAtttib = CurrentAttribute;
  72. Curses.attrset (contents [crow, ccol - 1, 1]);
  73. Curses.mvaddch (crow, ccol - 1, (int)(uint)' ');
  74. contents [crow, ccol - 1, 0] = (int)(uint)' ';
  75. Curses.move (crow, ccol);
  76. Curses.attrset (curAtttib);
  77. } else if (runeWidth < 2 && ccol <= Clip.Right - 1
  78. && Rune.ColumnWidth ((char)contents [crow, ccol, 0]) > 1) {
  79. var curAtttib = CurrentAttribute;
  80. Curses.attrset (contents [crow, ccol + 1, 1]);
  81. Curses.mvaddch (crow, ccol + 1, (int)(uint)' ');
  82. contents [crow, ccol + 1, 0] = (int)(uint)' ';
  83. Curses.move (crow, ccol);
  84. Curses.attrset (curAtttib);
  85. }
  86. if (runeWidth > 1 && ccol == Clip.Right - 1) {
  87. Curses.addch ((int)(uint)' ');
  88. contents [crow, ccol, 0] = (int)(uint)' ';
  89. } else {
  90. Curses.addch ((int)(uint)rune);
  91. contents [crow, ccol, 0] = (int)(uint)rune;
  92. }
  93. contents [crow, ccol, 1] = CurrentAttribute;
  94. contents [crow, ccol, 2] = 1;
  95. }
  96. } else {
  97. needMove = true;
  98. }
  99. if (runeWidth < 0 || runeWidth > 0) {
  100. ccol++;
  101. }
  102. if (runeWidth > 1) {
  103. if (validClip && ccol < Clip.Right) {
  104. contents [crow, ccol, 1] = CurrentAttribute;
  105. contents [crow, ccol, 2] = 0;
  106. }
  107. ccol++;
  108. }
  109. if (sync) {
  110. UpdateScreen ();
  111. }
  112. }
  113. public override void AddStr (ustring str)
  114. {
  115. // TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
  116. foreach (var rune in str)
  117. AddRune (rune);
  118. }
  119. public override void Refresh ()
  120. {
  121. Curses.raw ();
  122. Curses.noecho ();
  123. Curses.refresh ();
  124. ProcessWinChange ();
  125. }
  126. private void ProcessWinChange ()
  127. {
  128. if (Curses.CheckWinChange ()) {
  129. ResizeScreen ();
  130. UpdateOffScreen ();
  131. TerminalResized?.Invoke ();
  132. }
  133. }
  134. public override void UpdateCursor () => Refresh ();
  135. public override void End ()
  136. {
  137. StopReportingMouseMoves ();
  138. SetCursorVisibility (CursorVisibility.Default);
  139. Curses.endwin ();
  140. }
  141. public override void UpdateScreen () => window.redrawwin ();
  142. public override void SetAttribute (Attribute c)
  143. {
  144. base.SetAttribute (c);
  145. Curses.attrset (CurrentAttribute);
  146. }
  147. public Curses.Window window;
  148. //static short last_color_pair = 16;
  149. /// <summary>
  150. /// Creates a curses color from the provided foreground and background colors
  151. /// </summary>
  152. /// <param name="foreground">Contains the curses attributes for the foreground (color, plus any attributes)</param>
  153. /// <param name="background">Contains the curses attributes for the background (color, plus any attributes)</param>
  154. /// <returns></returns>
  155. public static Attribute MakeColor (short foreground, short background)
  156. {
  157. var v = (short)((int)foreground | background << 4);
  158. //Curses.InitColorPair (++last_color_pair, foreground, background);
  159. Curses.InitColorPair (v, foreground, background);
  160. return new Attribute (
  161. //value: Curses.ColorPair (last_color_pair),
  162. value: Curses.ColorPair (v),
  163. //foreground: (Color)foreground,
  164. foreground: MapCursesColor (foreground),
  165. //background: (Color)background);
  166. background: MapCursesColor (background));
  167. }
  168. public override Attribute MakeColor (Color fore, Color back)
  169. {
  170. return MakeColor ((short)MapColor (fore), (short)MapColor (back));
  171. }
  172. int [,] colorPairs = new int [16, 16];
  173. public override void SetColors (ConsoleColor foreground, ConsoleColor background)
  174. {
  175. // BUGBUG: This code is never called ?? See Issue #2300
  176. int f = (short)foreground;
  177. int b = (short)background;
  178. var v = colorPairs [f, b];
  179. if ((v & 0x10000) == 0) {
  180. b &= 0x7;
  181. bool bold = (f & 0x8) != 0;
  182. f &= 0x7;
  183. v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
  184. colorPairs [(int)foreground, (int)background] = v | 0x1000;
  185. }
  186. SetAttribute (v & 0xffff);
  187. }
  188. Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
  189. public override void SetColors (short foreColorId, short backgroundColorId)
  190. {
  191. // BUGBUG: This code is never called ?? See Issue #2300
  192. int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId;
  193. if (!rawPairs.TryGetValue (key, out var v)) {
  194. v = MakeColor (foreColorId, backgroundColorId);
  195. rawPairs [key] = v;
  196. }
  197. SetAttribute (v);
  198. }
  199. static Key MapCursesKey (int cursesKey)
  200. {
  201. switch (cursesKey) {
  202. case Curses.KeyF1: return Key.F1;
  203. case Curses.KeyF2: return Key.F2;
  204. case Curses.KeyF3: return Key.F3;
  205. case Curses.KeyF4: return Key.F4;
  206. case Curses.KeyF5: return Key.F5;
  207. case Curses.KeyF6: return Key.F6;
  208. case Curses.KeyF7: return Key.F7;
  209. case Curses.KeyF8: return Key.F8;
  210. case Curses.KeyF9: return Key.F9;
  211. case Curses.KeyF10: return Key.F10;
  212. case Curses.KeyF11: return Key.F11;
  213. case Curses.KeyF12: return Key.F12;
  214. case Curses.KeyUp: return Key.CursorUp;
  215. case Curses.KeyDown: return Key.CursorDown;
  216. case Curses.KeyLeft: return Key.CursorLeft;
  217. case Curses.KeyRight: return Key.CursorRight;
  218. case Curses.KeyHome: return Key.Home;
  219. case Curses.KeyEnd: return Key.End;
  220. case Curses.KeyNPage: return Key.PageDown;
  221. case Curses.KeyPPage: return Key.PageUp;
  222. case Curses.KeyDeleteChar: return Key.DeleteChar;
  223. case Curses.KeyInsertChar: return Key.InsertChar;
  224. case Curses.KeyTab: return Key.Tab;
  225. case Curses.KeyBackTab: return Key.BackTab;
  226. case Curses.KeyBackspace: return Key.Backspace;
  227. case Curses.ShiftKeyUp: return Key.CursorUp | Key.ShiftMask;
  228. case Curses.ShiftKeyDown: return Key.CursorDown | Key.ShiftMask;
  229. case Curses.ShiftKeyLeft: return Key.CursorLeft | Key.ShiftMask;
  230. case Curses.ShiftKeyRight: return Key.CursorRight | Key.ShiftMask;
  231. case Curses.ShiftKeyHome: return Key.Home | Key.ShiftMask;
  232. case Curses.ShiftKeyEnd: return Key.End | Key.ShiftMask;
  233. case Curses.ShiftKeyNPage: return Key.PageDown | Key.ShiftMask;
  234. case Curses.ShiftKeyPPage: return Key.PageUp | Key.ShiftMask;
  235. case Curses.AltKeyUp: return Key.CursorUp | Key.AltMask;
  236. case Curses.AltKeyDown: return Key.CursorDown | Key.AltMask;
  237. case Curses.AltKeyLeft: return Key.CursorLeft | Key.AltMask;
  238. case Curses.AltKeyRight: return Key.CursorRight | Key.AltMask;
  239. case Curses.AltKeyHome: return Key.Home | Key.AltMask;
  240. case Curses.AltKeyEnd: return Key.End | Key.AltMask;
  241. case Curses.AltKeyNPage: return Key.PageDown | Key.AltMask;
  242. case Curses.AltKeyPPage: return Key.PageUp | Key.AltMask;
  243. case Curses.CtrlKeyUp: return Key.CursorUp | Key.CtrlMask;
  244. case Curses.CtrlKeyDown: return Key.CursorDown | Key.CtrlMask;
  245. case Curses.CtrlKeyLeft: return Key.CursorLeft | Key.CtrlMask;
  246. case Curses.CtrlKeyRight: return Key.CursorRight | Key.CtrlMask;
  247. case Curses.CtrlKeyHome: return Key.Home | Key.CtrlMask;
  248. case Curses.CtrlKeyEnd: return Key.End | Key.CtrlMask;
  249. case Curses.CtrlKeyNPage: return Key.PageDown | Key.CtrlMask;
  250. case Curses.CtrlKeyPPage: return Key.PageUp | Key.CtrlMask;
  251. case Curses.ShiftCtrlKeyUp: return Key.CursorUp | Key.ShiftMask | Key.CtrlMask;
  252. case Curses.ShiftCtrlKeyDown: return Key.CursorDown | Key.ShiftMask | Key.CtrlMask;
  253. case Curses.ShiftCtrlKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.CtrlMask;
  254. case Curses.ShiftCtrlKeyRight: return Key.CursorRight | Key.ShiftMask | Key.CtrlMask;
  255. case Curses.ShiftCtrlKeyHome: return Key.Home | Key.ShiftMask | Key.CtrlMask;
  256. case Curses.ShiftCtrlKeyEnd: return Key.End | Key.ShiftMask | Key.CtrlMask;
  257. case Curses.ShiftCtrlKeyNPage: return Key.PageDown | Key.ShiftMask | Key.CtrlMask;
  258. case Curses.ShiftCtrlKeyPPage: return Key.PageUp | Key.ShiftMask | Key.CtrlMask;
  259. case Curses.ShiftAltKeyUp: return Key.CursorUp | Key.ShiftMask | Key.AltMask;
  260. case Curses.ShiftAltKeyDown: return Key.CursorDown | Key.ShiftMask | Key.AltMask;
  261. case Curses.ShiftAltKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.AltMask;
  262. case Curses.ShiftAltKeyRight: return Key.CursorRight | Key.ShiftMask | Key.AltMask;
  263. case Curses.ShiftAltKeyNPage: return Key.PageDown | Key.ShiftMask | Key.AltMask;
  264. case Curses.ShiftAltKeyPPage: return Key.PageUp | Key.ShiftMask | Key.AltMask;
  265. case Curses.ShiftAltKeyHome: return Key.Home | Key.ShiftMask | Key.AltMask;
  266. case Curses.ShiftAltKeyEnd: return Key.End | Key.ShiftMask | Key.AltMask;
  267. case Curses.AltCtrlKeyNPage: return Key.PageDown | Key.AltMask | Key.CtrlMask;
  268. case Curses.AltCtrlKeyPPage: return Key.PageUp | Key.AltMask | Key.CtrlMask;
  269. case Curses.AltCtrlKeyHome: return Key.Home | Key.AltMask | Key.CtrlMask;
  270. case Curses.AltCtrlKeyEnd: return Key.End | Key.AltMask | Key.CtrlMask;
  271. default: return Key.Unknown;
  272. }
  273. }
  274. KeyModifiers keyModifiers;
  275. KeyModifiers MapKeyModifiers (Key key)
  276. {
  277. if (keyModifiers == null)
  278. keyModifiers = new KeyModifiers ();
  279. if (!keyModifiers.Shift && (key & Key.ShiftMask) != 0)
  280. keyModifiers.Shift = true;
  281. if (!keyModifiers.Alt && (key & Key.AltMask) != 0)
  282. keyModifiers.Alt = true;
  283. if (!keyModifiers.Ctrl && (key & Key.CtrlMask) != 0)
  284. keyModifiers.Ctrl = true;
  285. return keyModifiers;
  286. }
  287. void ProcessInput ()
  288. {
  289. int wch;
  290. var code = Curses.get_wch (out wch);
  291. //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
  292. if (code == Curses.ERR)
  293. return;
  294. keyModifiers = new KeyModifiers ();
  295. Key k = Key.Null;
  296. if (code == Curses.KEY_CODE_YES) {
  297. if (wch == Curses.KeyResize) {
  298. ProcessWinChange ();
  299. }
  300. if (wch == Curses.KeyMouse) {
  301. int wch2 = wch;
  302. while (wch2 == Curses.KeyMouse) {
  303. KeyEvent key = null;
  304. ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
  305. new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
  306. new ConsoleKeyInfo ('[', 0, false, false, false),
  307. new ConsoleKeyInfo ('<', 0, false, false, false)
  308. };
  309. code = 0;
  310. GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki);
  311. }
  312. return;
  313. }
  314. k = MapCursesKey (wch);
  315. if (wch >= 277 && wch <= 288) { // Shift+(F1 - F12)
  316. wch -= 12;
  317. k = Key.ShiftMask | MapCursesKey (wch);
  318. } else if (wch >= 289 && wch <= 300) { // Ctrl+(F1 - F12)
  319. wch -= 24;
  320. k = Key.CtrlMask | MapCursesKey (wch);
  321. } else if (wch >= 301 && wch <= 312) { // Ctrl+Shift+(F1 - F12)
  322. wch -= 36;
  323. k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch);
  324. } else if (wch >= 313 && wch <= 324) { // Alt+(F1 - F12)
  325. wch -= 48;
  326. k = Key.AltMask | MapCursesKey (wch);
  327. } else if (wch >= 325 && wch <= 327) { // Shift+Alt+(F1 - F3)
  328. wch -= 60;
  329. k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch);
  330. }
  331. keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
  332. keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
  333. keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
  334. return;
  335. }
  336. // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
  337. if (wch == 27) {
  338. Curses.timeout (10);
  339. code = Curses.get_wch (out int wch2);
  340. if (code == Curses.KEY_CODE_YES) {
  341. k = Key.AltMask | MapCursesKey (wch);
  342. }
  343. if (code == 0) {
  344. KeyEvent key = null;
  345. // The ESC-number handling, debatable.
  346. // Simulates the AltMask itself by pressing Alt + Space.
  347. if (wch2 == (int)Key.Space) {
  348. k = Key.AltMask;
  349. } else if (wch2 - (int)Key.Space >= (uint)Key.A && wch2 - (int)Key.Space <= (uint)Key.Z) {
  350. k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space));
  351. } else if (wch2 >= (uint)Key.A - 64 && wch2 <= (uint)Key.Z - 64) {
  352. k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
  353. } else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
  354. k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0));
  355. } else if (wch2 == Curses.KeyCSI) {
  356. ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
  357. new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
  358. new ConsoleKeyInfo ('[', 0, false, false, false)
  359. };
  360. GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki);
  361. return;
  362. } else {
  363. // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
  364. if (((Key)wch2 & Key.CtrlMask) != 0) {
  365. keyModifiers.Ctrl = true;
  366. }
  367. if (wch2 == 0) {
  368. k = Key.CtrlMask | Key.AltMask | Key.Space;
  369. } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
  370. keyModifiers.Shift = true;
  371. keyModifiers.Alt = true;
  372. } else if (wch2 < 256) {
  373. k = (Key)wch2;
  374. keyModifiers.Alt = true;
  375. } else {
  376. k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2);
  377. }
  378. }
  379. key = new KeyEvent (k, MapKeyModifiers (k));
  380. keyDownHandler (key);
  381. keyHandler (key);
  382. } else {
  383. k = Key.Esc;
  384. keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
  385. }
  386. } else if (wch == Curses.KeyTab) {
  387. k = MapCursesKey (wch);
  388. keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
  389. keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
  390. } else {
  391. // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
  392. k = (Key)wch;
  393. if (wch == 0) {
  394. k = Key.CtrlMask | Key.Space;
  395. } else if (wch >= (uint)Key.A - 64 && wch <= (uint)Key.Z - 64) {
  396. if ((Key)(wch + 64) != Key.J) {
  397. k = Key.CtrlMask | (Key)(wch + 64);
  398. }
  399. } else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
  400. keyModifiers.Shift = true;
  401. }
  402. keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
  403. keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
  404. keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
  405. }
  406. // Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above
  407. // will not impact KeyUp.
  408. // This is causing ESC firing even if another keystroke was handled.
  409. //if (wch == Curses.KeyTab) {
  410. // keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers));
  411. //} else {
  412. // keyUpHandler (new KeyEvent ((Key)wch, keyModifiers));
  413. //}
  414. }
  415. void GetEscSeq (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki)
  416. {
  417. ConsoleKey ck = 0;
  418. ConsoleModifiers mod = 0;
  419. while (code == 0) {
  420. code = Curses.get_wch (out wch2);
  421. var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
  422. if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) {
  423. 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 _, ProcessContinuousButtonPressed);
  424. if (isKeyMouse) {
  425. foreach (var mf in mouseFlags) {
  426. ProcessMouseEvent (mf, pos);
  427. }
  428. cki = null;
  429. if (wch2 == 27) {
  430. cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
  431. false, false, false), cki);
  432. }
  433. } else {
  434. k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
  435. k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
  436. key = new KeyEvent (k, MapKeyModifiers (k));
  437. keyDownHandler (key);
  438. keyHandler (key);
  439. }
  440. } else {
  441. cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
  442. }
  443. }
  444. }
  445. void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
  446. {
  447. var me = new MouseEvent () {
  448. Flags = mouseFlag,
  449. X = pos.X,
  450. Y = pos.Y
  451. };
  452. mouseHandler (me);
  453. }
  454. void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos)
  455. {
  456. ProcessMouseEvent (mouseFlag, pos);
  457. }
  458. Action<KeyEvent> keyHandler;
  459. Action<KeyEvent> keyDownHandler;
  460. Action<KeyEvent> keyUpHandler;
  461. Action<MouseEvent> mouseHandler;
  462. public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
  463. {
  464. // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
  465. Curses.timeout (0);
  466. this.keyHandler = keyHandler;
  467. this.keyDownHandler = keyDownHandler;
  468. this.keyUpHandler = keyUpHandler;
  469. this.mouseHandler = mouseHandler;
  470. var mLoop = mainLoop.Driver as UnixMainLoop;
  471. mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
  472. ProcessInput ();
  473. return true;
  474. });
  475. mLoop.WinChanged += () => {
  476. ProcessWinChange ();
  477. };
  478. }
  479. public override void Init (Action terminalResized)
  480. {
  481. if (window != null)
  482. return;
  483. try {
  484. window = Curses.initscr ();
  485. Curses.set_escdelay (10);
  486. } catch (Exception e) {
  487. throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}");
  488. }
  489. // Ensures that all procedures are performed at some previous closing.
  490. Curses.doupdate ();
  491. //
  492. // We are setting Invisible as default so we could ignore XTerm DECSUSR setting
  493. //
  494. switch (Curses.curs_set (0)) {
  495. case 0:
  496. currentCursorVisibility = initialCursorVisibility = CursorVisibility.Invisible;
  497. break;
  498. case 1:
  499. currentCursorVisibility = initialCursorVisibility = CursorVisibility.Underline;
  500. Curses.curs_set (1);
  501. break;
  502. case 2:
  503. currentCursorVisibility = initialCursorVisibility = CursorVisibility.Box;
  504. Curses.curs_set (2);
  505. break;
  506. default:
  507. currentCursorVisibility = initialCursorVisibility = null;
  508. break;
  509. }
  510. if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
  511. clipboard = new MacOSXClipboard ();
  512. } else {
  513. if (Is_WSL_Platform ()) {
  514. clipboard = new WSLClipboard ();
  515. } else {
  516. clipboard = new CursesClipboard ();
  517. }
  518. }
  519. Curses.raw ();
  520. Curses.noecho ();
  521. Curses.Window.Standard.keypad (true);
  522. TerminalResized = terminalResized;
  523. StartReportingMouseMoves ();
  524. CurrentAttribute = MakeColor (Color.White, Color.Black);
  525. if (Curses.HasColors) {
  526. Curses.StartColor ();
  527. Curses.UseDefaultColors ();
  528. InitalizeColorSchemes ();
  529. } else {
  530. InitalizeColorSchemes (false);
  531. // BUGBUG: This is a hack to make the colors work on the Mac?
  532. // The new Theme support overwrites these colors, so this is not needed?
  533. Colors.TopLevel.Normal = Curses.COLOR_GREEN;
  534. Colors.TopLevel.Focus = Curses.COLOR_WHITE;
  535. Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;
  536. Colors.TopLevel.HotFocus = Curses.COLOR_YELLOW;
  537. Colors.TopLevel.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
  538. Colors.Base.Normal = Curses.A_NORMAL;
  539. Colors.Base.Focus = Curses.A_REVERSE;
  540. Colors.Base.HotNormal = Curses.A_BOLD;
  541. Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
  542. Colors.Base.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
  543. Colors.Menu.Normal = Curses.A_REVERSE;
  544. Colors.Menu.Focus = Curses.A_NORMAL;
  545. Colors.Menu.HotNormal = Curses.A_BOLD;
  546. Colors.Menu.HotFocus = Curses.A_NORMAL;
  547. Colors.Menu.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
  548. Colors.Dialog.Normal = Curses.A_REVERSE;
  549. Colors.Dialog.Focus = Curses.A_NORMAL;
  550. Colors.Dialog.HotNormal = Curses.A_BOLD;
  551. Colors.Dialog.HotFocus = Curses.A_NORMAL;
  552. Colors.Dialog.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
  553. Colors.Error.Normal = Curses.A_BOLD;
  554. Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
  555. Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
  556. Colors.Error.HotFocus = Curses.A_REVERSE;
  557. Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
  558. }
  559. ResizeScreen ();
  560. UpdateOffScreen ();
  561. }
  562. public override void ResizeScreen ()
  563. {
  564. Clip = new Rect (0, 0, Cols, Rows);
  565. Curses.refresh ();
  566. }
  567. public override void UpdateOffScreen ()
  568. {
  569. contents = new int [Rows, Cols, 3];
  570. for (int row = 0; row < Rows; row++) {
  571. for (int col = 0; col < Cols; col++) {
  572. //Curses.move (row, col);
  573. //Curses.attrset (Colors.TopLevel.Normal);
  574. //Curses.addch ((int)(uint)' ');
  575. contents [row, col, 0] = ' ';
  576. contents [row, col, 1] = Colors.TopLevel.Normal;
  577. contents [row, col, 2] = 0;
  578. }
  579. }
  580. }
  581. public static bool Is_WSL_Platform ()
  582. {
  583. // xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell
  584. //if (new CursesClipboard ().IsSupported) {
  585. // // If xclip is installed on Linux under WSL, this will return true.
  586. // return false;
  587. //}
  588. var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
  589. if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
  590. return true;
  591. }
  592. return false;
  593. }
  594. static int MapColor (Color color)
  595. {
  596. switch (color) {
  597. case Color.Black:
  598. return Curses.COLOR_BLACK;
  599. case Color.Blue:
  600. return Curses.COLOR_BLUE;
  601. case Color.Green:
  602. return Curses.COLOR_GREEN;
  603. case Color.Cyan:
  604. return Curses.COLOR_CYAN;
  605. case Color.Red:
  606. return Curses.COLOR_RED;
  607. case Color.Magenta:
  608. return Curses.COLOR_MAGENTA;
  609. case Color.Brown:
  610. return Curses.COLOR_YELLOW;
  611. case Color.Gray:
  612. return Curses.COLOR_WHITE;
  613. case Color.DarkGray:
  614. //return Curses.COLOR_BLACK | Curses.A_BOLD;
  615. return Curses.COLOR_GRAY;
  616. case Color.BrightBlue:
  617. return Curses.COLOR_BLUE | Curses.A_BOLD | Curses.COLOR_GRAY;
  618. case Color.BrightGreen:
  619. return Curses.COLOR_GREEN | Curses.A_BOLD | Curses.COLOR_GRAY;
  620. case Color.BrightCyan:
  621. return Curses.COLOR_CYAN | Curses.A_BOLD | Curses.COLOR_GRAY;
  622. case Color.BrightRed:
  623. return Curses.COLOR_RED | Curses.A_BOLD | Curses.COLOR_GRAY;
  624. case Color.BrightMagenta:
  625. return Curses.COLOR_MAGENTA | Curses.A_BOLD | Curses.COLOR_GRAY;
  626. case Color.BrightYellow:
  627. return Curses.COLOR_YELLOW | Curses.A_BOLD | Curses.COLOR_GRAY;
  628. case Color.White:
  629. return Curses.COLOR_WHITE | Curses.A_BOLD | Curses.COLOR_GRAY;
  630. }
  631. throw new ArgumentException ("Invalid color code");
  632. }
  633. static Color MapCursesColor (int color)
  634. {
  635. switch (color) {
  636. case Curses.COLOR_BLACK:
  637. return Color.Black;
  638. case Curses.COLOR_BLUE:
  639. return Color.Blue;
  640. case Curses.COLOR_GREEN:
  641. return Color.Green;
  642. case Curses.COLOR_CYAN:
  643. return Color.Cyan;
  644. case Curses.COLOR_RED:
  645. return Color.Red;
  646. case Curses.COLOR_MAGENTA:
  647. return Color.Magenta;
  648. case Curses.COLOR_YELLOW:
  649. return Color.Brown;
  650. case Curses.COLOR_WHITE:
  651. return Color.Gray;
  652. case Curses.COLOR_GRAY:
  653. return Color.DarkGray;
  654. case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
  655. return Color.BrightBlue;
  656. case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
  657. return Color.BrightGreen;
  658. case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
  659. return Color.BrightCyan;
  660. case Curses.COLOR_RED | Curses.COLOR_GRAY:
  661. return Color.BrightRed;
  662. case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
  663. return Color.BrightMagenta;
  664. case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
  665. return Color.BrightYellow;
  666. case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
  667. return Color.White;
  668. }
  669. throw new ArgumentException ("Invalid curses color code");
  670. }
  671. public override Attribute MakeAttribute (Color fore, Color back)
  672. {
  673. var f = MapColor (fore);
  674. //return MakeColor ((short)(f & 0xffff), (short)MapColor (back)) | ((f & Curses.A_BOLD) != 0 ? Curses.A_BOLD : 0);
  675. return MakeColor ((short)(f & 0xffff), (short)MapColor (back));
  676. }
  677. public override void Suspend ()
  678. {
  679. StopReportingMouseMoves ();
  680. Platform.Suspend ();
  681. Curses.Window.Standard.redrawwin ();
  682. Curses.refresh ();
  683. StartReportingMouseMoves ();
  684. }
  685. public override void StartReportingMouseMoves ()
  686. {
  687. Console.Out.Write (EscSeqUtils.EnableMouseEvents);
  688. }
  689. public override void StopReportingMouseMoves ()
  690. {
  691. Console.Out.Write (EscSeqUtils.DisableMouseEvents);
  692. }
  693. //int lastMouseInterval;
  694. //bool mouseGrabbed;
  695. public override void UncookMouse ()
  696. {
  697. //if (mouseGrabbed)
  698. // return;
  699. //lastMouseInterval = Curses.mouseinterval (0);
  700. //mouseGrabbed = true;
  701. }
  702. public override void CookMouse ()
  703. {
  704. //mouseGrabbed = false;
  705. //Curses.mouseinterval (lastMouseInterval);
  706. }
  707. /// <inheritdoc/>
  708. public override bool GetCursorVisibility (out CursorVisibility visibility)
  709. {
  710. visibility = CursorVisibility.Invisible;
  711. if (!currentCursorVisibility.HasValue)
  712. return false;
  713. visibility = currentCursorVisibility.Value;
  714. return true;
  715. }
  716. /// <inheritdoc/>
  717. public override bool SetCursorVisibility (CursorVisibility visibility)
  718. {
  719. if (initialCursorVisibility.HasValue == false)
  720. return false;
  721. Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
  722. if (visibility != CursorVisibility.Invisible) {
  723. Console.Out.Write ("\x1b[{0} q", ((int)visibility >> 24) & 0xFF);
  724. }
  725. currentCursorVisibility = visibility;
  726. return true;
  727. }
  728. /// <inheritdoc/>
  729. public override bool EnsureCursorVisibility ()
  730. {
  731. return false;
  732. }
  733. public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
  734. {
  735. Key key;
  736. if (consoleKey == ConsoleKey.Packet) {
  737. ConsoleModifiers mod = new ConsoleModifiers ();
  738. if (shift) {
  739. mod |= ConsoleModifiers.Shift;
  740. }
  741. if (alt) {
  742. mod |= ConsoleModifiers.Alt;
  743. }
  744. if (control) {
  745. mod |= ConsoleModifiers.Control;
  746. }
  747. var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _);
  748. key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable);
  749. if (mappable) {
  750. key = (Key)kchar;
  751. }
  752. } else {
  753. key = (Key)keyChar;
  754. }
  755. KeyModifiers km = new KeyModifiers ();
  756. if (shift) {
  757. if (keyChar == 0) {
  758. key |= Key.ShiftMask;
  759. }
  760. km.Shift = shift;
  761. }
  762. if (alt) {
  763. key |= Key.AltMask;
  764. km.Alt = alt;
  765. }
  766. if (control) {
  767. key |= Key.CtrlMask;
  768. km.Ctrl = control;
  769. }
  770. keyDownHandler (new KeyEvent (key, km));
  771. keyHandler (new KeyEvent (key, km));
  772. keyUpHandler (new KeyEvent (key, km));
  773. }
  774. public override bool GetColors (int value, out Color foreground, out Color background)
  775. {
  776. bool hasColor = false;
  777. foreground = default;
  778. background = default;
  779. int back = -1;
  780. IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
  781. .OfType<ConsoleColor> ()
  782. .Select (s => (int)s);
  783. if (values.Contains ((value >> 12) & 0xffff)) {
  784. hasColor = true;
  785. back = (value >> 12) & 0xffff;
  786. background = MapCursesColor (back);
  787. }
  788. if (values.Contains ((value - (back << 12)) >> 8)) {
  789. hasColor = true;
  790. foreground = MapCursesColor ((value - (back << 12)) >> 8);
  791. }
  792. return hasColor;
  793. }
  794. }
  795. internal static class Platform {
  796. [DllImport ("libc")]
  797. static extern int uname (IntPtr buf);
  798. [DllImport ("libc")]
  799. static extern int killpg (int pgrp, int pid);
  800. static int suspendSignal;
  801. static int GetSuspendSignal ()
  802. {
  803. if (suspendSignal != 0)
  804. return suspendSignal;
  805. IntPtr buf = Marshal.AllocHGlobal (8192);
  806. if (uname (buf) != 0) {
  807. Marshal.FreeHGlobal (buf);
  808. suspendSignal = -1;
  809. return suspendSignal;
  810. }
  811. try {
  812. switch (Marshal.PtrToStringAnsi (buf)) {
  813. case "Darwin":
  814. case "DragonFly":
  815. case "FreeBSD":
  816. case "NetBSD":
  817. case "OpenBSD":
  818. suspendSignal = 18;
  819. break;
  820. case "Linux":
  821. // TODO: should fetch the machine name and
  822. // if it is MIPS return 24
  823. suspendSignal = 20;
  824. break;
  825. case "Solaris":
  826. suspendSignal = 24;
  827. break;
  828. default:
  829. suspendSignal = -1;
  830. break;
  831. }
  832. return suspendSignal;
  833. } finally {
  834. Marshal.FreeHGlobal (buf);
  835. }
  836. }
  837. /// <summary>
  838. /// Suspends the process by sending SIGTSTP to itself
  839. /// </summary>
  840. /// <returns>The suspend.</returns>
  841. static public bool Suspend ()
  842. {
  843. int signal = GetSuspendSignal ();
  844. if (signal == -1)
  845. return false;
  846. killpg (0, signal);
  847. return true;
  848. }
  849. }
  850. /// <summary>
  851. /// A clipboard implementation for Linux.
  852. /// This implementation uses the xclip command to access the clipboard.
  853. /// </summary>
  854. /// <remarks>
  855. /// If xclip is not installed, this implementation will not work.
  856. /// </remarks>
  857. class CursesClipboard : ClipboardBase {
  858. public CursesClipboard ()
  859. {
  860. IsSupported = CheckSupport ();
  861. }
  862. string xclipPath = string.Empty;
  863. public override bool IsSupported { get; }
  864. bool CheckSupport ()
  865. {
  866. #pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
  867. try {
  868. var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
  869. if (exitCode == 0 && result.FileExists ()) {
  870. xclipPath = result;
  871. return true;
  872. }
  873. } catch (Exception) {
  874. // Permissions issue.
  875. }
  876. #pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
  877. return false;
  878. }
  879. protected override string GetClipboardDataImpl ()
  880. {
  881. var tempFileName = System.IO.Path.GetTempFileName ();
  882. var xclipargs = "-selection clipboard -o";
  883. try {
  884. var (exitCode, result) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
  885. if (exitCode == 0) {
  886. if (Application.Driver is CursesDriver) {
  887. Curses.raw ();
  888. Curses.noecho ();
  889. }
  890. return System.IO.File.ReadAllText (tempFileName);
  891. }
  892. } catch (Exception e) {
  893. throw new NotSupportedException ($"\"{xclipPath} {xclipargs}\" failed.", e);
  894. } finally {
  895. System.IO.File.Delete (tempFileName);
  896. }
  897. return string.Empty;
  898. }
  899. protected override void SetClipboardDataImpl (string text)
  900. {
  901. var xclipargs = "-selection clipboard -i";
  902. try {
  903. var (exitCode, _) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs}", text, waitForOutput: false);
  904. if (exitCode == 0 && Application.Driver is CursesDriver) {
  905. Curses.raw ();
  906. Curses.noecho ();
  907. }
  908. } catch (Exception e) {
  909. throw new NotSupportedException ($"\"{xclipPath} {xclipargs} < {text}\" failed", e);
  910. }
  911. }
  912. }
  913. /// <summary>
  914. /// A clipboard implementation for MacOSX.
  915. /// This implementation uses the Mac clipboard API (via P/Invoke) to copy/paste.
  916. /// The existance of the Mac pbcopy and pbpaste commands
  917. /// is used to determine if copy/paste is supported.
  918. /// </summary>
  919. class MacOSXClipboard : ClipboardBase {
  920. IntPtr nsString = objc_getClass ("NSString");
  921. IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
  922. IntPtr utfTextType;
  923. IntPtr generalPasteboard;
  924. IntPtr initWithUtf8Register = sel_registerName ("initWithUTF8String:");
  925. IntPtr allocRegister = sel_registerName ("alloc");
  926. IntPtr setStringRegister = sel_registerName ("setString:forType:");
  927. IntPtr stringForTypeRegister = sel_registerName ("stringForType:");
  928. IntPtr utf8Register = sel_registerName ("UTF8String");
  929. IntPtr nsStringPboardType;
  930. IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard");
  931. IntPtr clearContentsRegister = sel_registerName ("clearContents");
  932. public MacOSXClipboard ()
  933. {
  934. utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text");
  935. nsStringPboardType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "NSStringPboardType");
  936. generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister);
  937. IsSupported = CheckSupport ();
  938. }
  939. public override bool IsSupported { get; }
  940. bool CheckSupport ()
  941. {
  942. var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true);
  943. if (exitCode != 0 || !result.FileExists ()) {
  944. return false;
  945. }
  946. (exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true);
  947. return exitCode == 0 && result.FileExists ();
  948. }
  949. protected override string GetClipboardDataImpl ()
  950. {
  951. var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType);
  952. var charArray = objc_msgSend (ptr, utf8Register);
  953. return Marshal.PtrToStringAnsi (charArray);
  954. }
  955. protected override void SetClipboardDataImpl (string text)
  956. {
  957. IntPtr str = default;
  958. try {
  959. str = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, text);
  960. objc_msgSend (generalPasteboard, clearContentsRegister);
  961. objc_msgSend (generalPasteboard, setStringRegister, str, utfTextType);
  962. } finally {
  963. if (str != default) {
  964. objc_msgSend (str, sel_registerName ("release"));
  965. }
  966. }
  967. }
  968. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  969. static extern IntPtr objc_getClass (string className);
  970. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  971. static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector);
  972. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  973. static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, string arg1);
  974. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  975. static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1);
  976. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  977. static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
  978. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  979. static extern IntPtr sel_registerName (string selectorName);
  980. }
  981. /// <summary>
  982. /// A clipboard implementation for Linux, when running under WSL.
  983. /// This implementation uses the Windows clipboard to store the data, and uses Windows'
  984. /// powershell.exe (launched via WSL interop services) to set/get the Windows
  985. /// clipboard.
  986. /// </summary>
  987. class WSLClipboard : ClipboardBase {
  988. bool isSupported = false;
  989. public WSLClipboard ()
  990. {
  991. isSupported = CheckSupport ();
  992. }
  993. public override bool IsSupported {
  994. get {
  995. return isSupported = CheckSupport ();
  996. }
  997. }
  998. private static string powershellPath = string.Empty;
  999. bool CheckSupport ()
  1000. {
  1001. if (string.IsNullOrEmpty (powershellPath)) {
  1002. // Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
  1003. var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true);
  1004. if (exitCode > 0) {
  1005. (exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true);
  1006. }
  1007. if (exitCode == 0) {
  1008. powershellPath = result;
  1009. }
  1010. }
  1011. return !string.IsNullOrEmpty (powershellPath);
  1012. }
  1013. protected override string GetClipboardDataImpl ()
  1014. {
  1015. if (!IsSupported) {
  1016. return string.Empty;
  1017. }
  1018. var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, "-noprofile -command \"Get-Clipboard\"");
  1019. if (exitCode == 0) {
  1020. if (Application.Driver is CursesDriver) {
  1021. Curses.raw ();
  1022. Curses.noecho ();
  1023. }
  1024. if (output.EndsWith ("\r\n")) {
  1025. output = output.Substring (0, output.Length - 2);
  1026. }
  1027. return output;
  1028. }
  1029. return string.Empty;
  1030. }
  1031. protected override void SetClipboardDataImpl (string text)
  1032. {
  1033. if (!IsSupported) {
  1034. return;
  1035. }
  1036. var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\"");
  1037. if (exitCode == 0) {
  1038. if (Application.Driver is CursesDriver) {
  1039. Curses.raw ();
  1040. Curses.noecho ();
  1041. }
  1042. }
  1043. }
  1044. }
  1045. }