EscSeqUtils.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Management;
  6. using System.Runtime.InteropServices;
  7. using System.Threading.Tasks;
  8. namespace Terminal.Gui {
  9. /// <summary>
  10. /// Provides a platform-independent API for managing ANSI escape sequence codes.
  11. /// </summary>
  12. public static class EscSeqUtils {
  13. /// <summary>
  14. /// Represents the escape key.
  15. /// </summary>
  16. public static readonly char KeyEsc = (char)Key.Esc;
  17. /// <summary>
  18. /// Represents the CSI (Control Sequence Introducer).
  19. /// </summary>
  20. public static readonly string KeyCSI = $"{KeyEsc}[";
  21. /// <summary>
  22. /// Represents the CSI for enable any mouse event tracking.
  23. /// </summary>
  24. public static readonly string CSI_EnableAnyEventMouse = KeyCSI + "?1003h";
  25. /// <summary>
  26. /// Represents the CSI for enable SGR (Select Graphic Rendition).
  27. /// </summary>
  28. public static readonly string CSI_EnableSgrExtModeMouse = KeyCSI + "?1006h";
  29. /// <summary>
  30. /// Represents the CSI for enable URXVT (Unicode Extended Virtual Terminal).
  31. /// </summary>
  32. public static readonly string CSI_EnableUrxvtExtModeMouse = KeyCSI + "?1015h";
  33. /// <summary>
  34. /// Represents the CSI for disable any mouse event tracking.
  35. /// </summary>
  36. public static readonly string CSI_DisableAnyEventMouse = KeyCSI + "?1003l";
  37. /// <summary>
  38. /// Represents the CSI for disable SGR (Select Graphic Rendition).
  39. /// </summary>
  40. public static readonly string CSI_DisableSgrExtModeMouse = KeyCSI + "?1006l";
  41. /// <summary>
  42. /// Represents the CSI for disable URXVT (Unicode Extended Virtual Terminal).
  43. /// </summary>
  44. public static readonly string CSI_DisableUrxvtExtModeMouse = KeyCSI + "?1015l";
  45. /// <summary>
  46. /// Control sequence for enable mouse events.
  47. /// </summary>
  48. public static string EnableMouseEvents { get; set; } =
  49. CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
  50. /// <summary>
  51. /// Control sequence for disable mouse events.
  52. /// </summary>
  53. public static string DisableMouseEvents { get; set; } =
  54. CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
  55. /// <summary>
  56. /// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
  57. /// </summary>
  58. /// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
  59. /// <returns>The <see cref="ConsoleKeyInfo"/> modified.</returns>
  60. public static ConsoleKeyInfo GetConsoleInputKey (ConsoleKeyInfo consoleKeyInfo)
  61. {
  62. ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
  63. ConsoleKey key;
  64. var keyChar = consoleKeyInfo.KeyChar;
  65. switch ((uint)keyChar) {
  66. case 0:
  67. if (consoleKeyInfo.Key == (ConsoleKey)64) { // Ctrl+Space in Windows.
  68. newConsoleKeyInfo = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar,
  69. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  70. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  71. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  72. }
  73. break;
  74. case uint n when (n >= '\u0001' && n <= '\u001a'):
  75. if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') {
  76. key = ConsoleKey.Enter;
  77. newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar,
  78. key,
  79. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  80. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  81. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  82. } else if (consoleKeyInfo.Key == 0) {
  83. key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
  84. newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
  85. key,
  86. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  87. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  88. true);
  89. }
  90. break;
  91. case 127:
  92. newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, ConsoleKey.Backspace,
  93. (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
  94. (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
  95. (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
  96. break;
  97. default:
  98. newConsoleKeyInfo = consoleKeyInfo;
  99. break;
  100. }
  101. return newConsoleKeyInfo;
  102. }
  103. /// <summary>
  104. /// A helper to resize the <see cref="ConsoleKeyInfo"/> as needed.
  105. /// </summary>
  106. /// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
  107. /// <param name="cki">The <see cref="ConsoleKeyInfo"/> array to resize.</param>
  108. /// <returns>The <see cref="ConsoleKeyInfo"/> resized.</returns>
  109. public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki)
  110. {
  111. Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1);
  112. cki [cki.Length - 1] = consoleKeyInfo;
  113. return cki;
  114. }
  115. /// <summary>
  116. /// Decodes a escape sequence to been processed in the appropriate manner.
  117. /// </summary>
  118. /// <param name="escSeqReqProc">The <see cref="EscSeqReqProc"/> which may contain a request.</param>
  119. /// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may changes.</param>
  120. /// <param name="key">The <see cref="ConsoleKey"/> which may changes.</param>
  121. /// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
  122. /// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
  123. /// <param name="c1Control">The control returned by the <see cref="GetC1ControlChar(char)"/> method.</param>
  124. /// <param name="code">The code returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
  125. /// <param name="values">The values returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
  126. /// <param name="terminating">The terminating returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
  127. /// <param name="isKeyMouse">Indicates if the escape sequence is a mouse key.</param>
  128. /// <param name="buttonState">The <see cref="MouseFlags"/> button state.</param>
  129. /// <param name="pos">The <see cref="MouseFlags"/> position.</param>
  130. /// <param name="isReq">Indicates if the escape sequence is a response to a request.</param>
  131. /// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
  132. public static void DecodeEscSeq (EscSeqReqProc escSeqReqProc, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string [] values, out string terminating, out bool isKeyMouse, out List<MouseFlags> buttonState, out Point pos, out bool isReq, Action<MouseFlags, Point> continuousButtonPressedHandler)
  133. {
  134. char [] kChars = GetKeyCharArray (cki);
  135. (c1Control, code, values, terminating) = GetEscapeResult (kChars);
  136. isKeyMouse = false;
  137. buttonState = new List<MouseFlags> () { 0 };
  138. pos = default;
  139. isReq = false;
  140. switch (c1Control) {
  141. case "ESC":
  142. if (values == null && string.IsNullOrEmpty (terminating)) {
  143. key = ConsoleKey.Escape;
  144. newConsoleKeyInfo = new ConsoleKeyInfo (cki [0].KeyChar, key,
  145. (mod & ConsoleModifiers.Shift) != 0,
  146. (mod & ConsoleModifiers.Alt) != 0,
  147. (mod & ConsoleModifiers.Control) != 0);
  148. } else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) {
  149. key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1);
  150. newConsoleKeyInfo = new ConsoleKeyInfo (cki [1].KeyChar,
  151. key,
  152. false,
  153. true,
  154. true);
  155. } else {
  156. if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) {
  157. key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0];
  158. } else {
  159. key = (ConsoleKey)cki [1].KeyChar;
  160. }
  161. newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
  162. (ConsoleKey)Math.Min ((uint)key, 255),
  163. false,
  164. true,
  165. false);
  166. }
  167. break;
  168. case "SS3":
  169. key = GetConsoleKey (terminating [0], values [0], ref mod);
  170. newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
  171. key,
  172. (mod & ConsoleModifiers.Shift) != 0,
  173. (mod & ConsoleModifiers.Alt) != 0,
  174. (mod & ConsoleModifiers.Control) != 0);
  175. break;
  176. case "CSI":
  177. if (!string.IsNullOrEmpty (code) && code == "<") {
  178. GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler);
  179. isKeyMouse = true;
  180. return;
  181. } else if (escSeqReqProc != null && escSeqReqProc.Requested (terminating)) {
  182. isReq = true;
  183. escSeqReqProc.Remove (terminating);
  184. return;
  185. }
  186. key = GetConsoleKey (terminating [0], values [0], ref mod);
  187. if (key != 0 && values.Length > 1) {
  188. mod |= GetConsoleModifiers (values [1]);
  189. }
  190. newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
  191. key,
  192. (mod & ConsoleModifiers.Shift) != 0,
  193. (mod & ConsoleModifiers.Alt) != 0,
  194. (mod & ConsoleModifiers.Control) != 0);
  195. break;
  196. }
  197. }
  198. /// <summary>
  199. /// Gets all the needed information about a escape sequence.
  200. /// </summary>
  201. /// <param name="kChar">The array with all chars.</param>
  202. /// <returns>
  203. /// The c1Control returned by <see cref="GetC1ControlChar(char)"/>, code, values and terminating.
  204. /// </returns>
  205. public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar)
  206. {
  207. if (kChar == null || kChar.Length == 0) {
  208. return (null, null, null, null);
  209. }
  210. if (kChar [0] != '\x1b') {
  211. throw new InvalidOperationException ("Invalid escape character!");
  212. }
  213. if (kChar.Length == 1) {
  214. return ("ESC", null, null, null);
  215. }
  216. if (kChar.Length == 2) {
  217. return ("ESC", null, null, kChar [1].ToString ());
  218. }
  219. string c1Control = GetC1ControlChar (kChar [1]);
  220. string code = null;
  221. int nSep = kChar.Count (x => x == ';') + 1;
  222. string [] values = new string [nSep];
  223. int valueIdx = 0;
  224. string terminating = "";
  225. for (int i = 2; i < kChar.Length; i++) {
  226. var c = kChar [i];
  227. if (char.IsDigit (c)) {
  228. values [valueIdx] += c.ToString ();
  229. } else if (c == ';') {
  230. valueIdx++;
  231. } else if (valueIdx == nSep - 1 || i == kChar.Length - 1) {
  232. terminating += c.ToString ();
  233. } else {
  234. code += c.ToString ();
  235. }
  236. }
  237. return (c1Control, code, values, terminating);
  238. }
  239. /// <summary>
  240. /// Gets the c1Control used in the called escape sequence.
  241. /// </summary>
  242. /// <param name="c">The char used.</param>
  243. /// <returns>The c1Control.</returns>
  244. public static string GetC1ControlChar (char c)
  245. {
  246. // These control characters are used in the vtXXX emulation.
  247. switch (c) {
  248. case 'D':
  249. return "IND"; // Index
  250. case 'E':
  251. return "NEL"; // Next Line
  252. case 'H':
  253. return "HTS"; // Tab Set
  254. case 'M':
  255. return "RI"; // Reverse Index
  256. case 'N':
  257. return "SS2"; // Single Shift Select of G2 Character Set: affects next character only
  258. case 'O':
  259. return "SS3"; // Single Shift Select of G3 Character Set: affects next character only
  260. case 'P':
  261. return "DCS"; // Device Control String
  262. case 'V':
  263. return "SPA"; // Start of Guarded Area
  264. case 'W':
  265. return "EPA"; // End of Guarded Area
  266. case 'X':
  267. return "SOS"; // Start of String
  268. case 'Z':
  269. return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA)
  270. case '[':
  271. return "CSI"; // Control Sequence Introducer
  272. case '\\':
  273. return "ST"; // String Terminator
  274. case ']':
  275. return "OSC"; // Operating System Command
  276. case '^':
  277. return "PM"; // Privacy Message
  278. case '_':
  279. return "APC"; // Application Program Command
  280. default:
  281. return ""; // Not supported
  282. }
  283. }
  284. /// <summary>
  285. /// Gets the <see cref="ConsoleModifiers"/> from the value.
  286. /// </summary>
  287. /// <param name="value">The value.</param>
  288. /// <returns>The <see cref="ConsoleModifiers"/> or zero.</returns>
  289. public static ConsoleModifiers GetConsoleModifiers (string value)
  290. {
  291. switch (value) {
  292. case "2":
  293. return ConsoleModifiers.Shift;
  294. case "3":
  295. return ConsoleModifiers.Alt;
  296. case "4":
  297. return ConsoleModifiers.Shift | ConsoleModifiers.Alt;
  298. case "5":
  299. return ConsoleModifiers.Control;
  300. case "6":
  301. return ConsoleModifiers.Shift | ConsoleModifiers.Control;
  302. case "7":
  303. return ConsoleModifiers.Alt | ConsoleModifiers.Control;
  304. case "8":
  305. return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control;
  306. default:
  307. return 0;
  308. }
  309. }
  310. /// <summary>
  311. /// Gets the <see cref="ConsoleKey"/> depending on terminating and value.
  312. /// </summary>
  313. /// <param name="terminating">The terminating.</param>
  314. /// <param name="value">The value.</param>
  315. /// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
  316. /// <returns>The <see cref="ConsoleKey"/> and probably the <see cref="ConsoleModifiers"/>.</returns>
  317. public static ConsoleKey GetConsoleKey (char terminating, string value, ref ConsoleModifiers mod)
  318. {
  319. ConsoleKey key;
  320. switch (terminating) {
  321. case 'A':
  322. key = ConsoleKey.UpArrow;
  323. break;
  324. case 'B':
  325. key = ConsoleKey.DownArrow;
  326. break;
  327. case 'C':
  328. key = ConsoleKey.RightArrow;
  329. break;
  330. case 'D':
  331. key = ConsoleKey.LeftArrow;
  332. break;
  333. case 'F':
  334. key = ConsoleKey.End;
  335. break;
  336. case 'H':
  337. key = ConsoleKey.Home;
  338. break;
  339. case 'P':
  340. key = ConsoleKey.F1;
  341. break;
  342. case 'Q':
  343. key = ConsoleKey.F2;
  344. break;
  345. case 'R':
  346. key = ConsoleKey.F3;
  347. break;
  348. case 'S':
  349. key = ConsoleKey.F4;
  350. break;
  351. case 'Z':
  352. key = ConsoleKey.Tab;
  353. mod |= ConsoleModifiers.Shift;
  354. break;
  355. case '~':
  356. switch (value) {
  357. case "2":
  358. key = ConsoleKey.Insert;
  359. break;
  360. case "3":
  361. key = ConsoleKey.Delete;
  362. break;
  363. case "5":
  364. key = ConsoleKey.PageUp;
  365. break;
  366. case "6":
  367. key = ConsoleKey.PageDown;
  368. break;
  369. case "15":
  370. key = ConsoleKey.F5;
  371. break;
  372. case "17":
  373. key = ConsoleKey.F6;
  374. break;
  375. case "18":
  376. key = ConsoleKey.F7;
  377. break;
  378. case "19":
  379. key = ConsoleKey.F8;
  380. break;
  381. case "20":
  382. key = ConsoleKey.F9;
  383. break;
  384. case "21":
  385. key = ConsoleKey.F10;
  386. break;
  387. case "23":
  388. key = ConsoleKey.F11;
  389. break;
  390. case "24":
  391. key = ConsoleKey.F12;
  392. break;
  393. default:
  394. key = 0;
  395. break;
  396. }
  397. break;
  398. default:
  399. key = 0;
  400. break;
  401. }
  402. return key;
  403. }
  404. /// <summary>
  405. /// A helper to get only the <see cref="ConsoleKeyInfo.KeyChar"/> from the <see cref="ConsoleKeyInfo"/> array.
  406. /// </summary>
  407. /// <param name="cki"></param>
  408. /// <returns>The char array of the escape sequence.</returns>
  409. public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
  410. {
  411. if (cki == null) {
  412. return null;
  413. }
  414. char [] kChar = new char [] { };
  415. var length = 0;
  416. foreach (var kc in cki) {
  417. length++;
  418. Array.Resize (ref kChar, length);
  419. kChar [length - 1] = kc.KeyChar;
  420. }
  421. return kChar;
  422. }
  423. private static MouseFlags? lastMouseButtonPressed;
  424. //private static MouseFlags? lastMouseButtonReleased;
  425. private static bool isButtonPressed;
  426. //private static bool isButtonReleased;
  427. private static bool isButtonClicked;
  428. private static bool isButtonDoubleClicked;
  429. private static bool isButtonTripleClicked;
  430. private static Point point;
  431. /// <summary>
  432. /// Gets the <see cref="MouseFlags"/> mouse button flags and the position.
  433. /// </summary>
  434. /// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
  435. /// <param name="mouseFlags">The mouse button flags.</param>
  436. /// <param name="pos">The mouse position.</param>
  437. /// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
  438. public static void GetMouse (ConsoleKeyInfo [] cki, out List<MouseFlags> mouseFlags, out Point pos, Action<MouseFlags, Point> continuousButtonPressedHandler)
  439. {
  440. MouseFlags buttonState = 0;
  441. pos = new Point ();
  442. int buttonCode = 0;
  443. bool foundButtonCode = false;
  444. int foundPoint = 0;
  445. string value = "";
  446. var kChar = GetKeyCharArray (cki);
  447. //System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
  448. for (int i = 0; i < kChar.Length; i++) {
  449. var c = kChar [i];
  450. if (c == '<') {
  451. foundButtonCode = true;
  452. } else if (foundButtonCode && c != ';') {
  453. value += c.ToString ();
  454. } else if (c == ';') {
  455. if (foundButtonCode) {
  456. foundButtonCode = false;
  457. buttonCode = int.Parse (value);
  458. }
  459. if (foundPoint == 1) {
  460. pos.X = int.Parse (value) - 1;
  461. }
  462. value = "";
  463. foundPoint++;
  464. } else if (foundPoint > 0 && c != 'm' && c != 'M') {
  465. value += c.ToString ();
  466. } else if (c == 'm' || c == 'M') {
  467. //pos.Y = int.Parse (value) + Console.WindowTop - 1;
  468. pos.Y = int.Parse (value) - 1;
  469. switch (buttonCode) {
  470. case 0:
  471. case 8:
  472. case 16:
  473. case 24:
  474. case 32:
  475. case 36:
  476. case 40:
  477. case 48:
  478. case 56:
  479. buttonState = c == 'M' ? MouseFlags.Button1Pressed
  480. : MouseFlags.Button1Released;
  481. break;
  482. case 1:
  483. case 9:
  484. case 17:
  485. case 25:
  486. case 33:
  487. case 37:
  488. case 41:
  489. case 45:
  490. case 49:
  491. case 53:
  492. case 57:
  493. case 61:
  494. buttonState = c == 'M' ? MouseFlags.Button2Pressed
  495. : MouseFlags.Button2Released;
  496. break;
  497. case 2:
  498. case 10:
  499. case 14:
  500. case 18:
  501. case 22:
  502. case 26:
  503. case 30:
  504. case 34:
  505. case 42:
  506. case 46:
  507. case 50:
  508. case 54:
  509. case 58:
  510. case 62:
  511. buttonState = c == 'M' ? MouseFlags.Button3Pressed
  512. : MouseFlags.Button3Released;
  513. break;
  514. case 35:
  515. //// Needed for Windows OS
  516. //if (isButtonPressed && c == 'm'
  517. // && (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed
  518. // || lastMouseEvent.ButtonState == MouseFlags.Button2Pressed
  519. // || lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) {
  520. // switch (lastMouseEvent.ButtonState) {
  521. // case MouseFlags.Button1Pressed:
  522. // buttonState = MouseFlags.Button1Released;
  523. // break;
  524. // case MouseFlags.Button2Pressed:
  525. // buttonState = MouseFlags.Button2Released;
  526. // break;
  527. // case MouseFlags.Button3Pressed:
  528. // buttonState = MouseFlags.Button3Released;
  529. // break;
  530. // }
  531. //} else {
  532. // buttonState = MouseFlags.ReportMousePosition;
  533. //}
  534. //break;
  535. case 39:
  536. case 43:
  537. case 47:
  538. case 51:
  539. case 55:
  540. case 59:
  541. case 63:
  542. buttonState = MouseFlags.ReportMousePosition;
  543. break;
  544. case 64:
  545. buttonState = MouseFlags.WheeledUp;
  546. break;
  547. case 65:
  548. buttonState = MouseFlags.WheeledDown;
  549. break;
  550. case 68:
  551. case 72:
  552. case 80:
  553. buttonState = MouseFlags.WheeledLeft; // Shift/Ctrl+WheeledUp
  554. break;
  555. case 69:
  556. case 73:
  557. case 81:
  558. buttonState = MouseFlags.WheeledRight; // Shift/Ctrl+WheeledDown
  559. break;
  560. }
  561. // Modifiers.
  562. switch (buttonCode) {
  563. case 8:
  564. case 9:
  565. case 10:
  566. case 43:
  567. buttonState |= MouseFlags.ButtonAlt;
  568. break;
  569. case 14:
  570. case 47:
  571. buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
  572. break;
  573. case 16:
  574. case 17:
  575. case 18:
  576. case 51:
  577. buttonState |= MouseFlags.ButtonCtrl;
  578. break;
  579. case 22:
  580. case 55:
  581. buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
  582. break;
  583. case 24:
  584. case 25:
  585. case 26:
  586. case 59:
  587. buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
  588. break;
  589. case 30:
  590. case 63:
  591. buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
  592. break;
  593. case 32:
  594. case 33:
  595. case 34:
  596. buttonState |= MouseFlags.ReportMousePosition;
  597. break;
  598. case 36:
  599. case 37:
  600. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift;
  601. break;
  602. case 39:
  603. case 68:
  604. case 69:
  605. buttonState |= MouseFlags.ButtonShift;
  606. break;
  607. case 40:
  608. case 41:
  609. case 42:
  610. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt;
  611. break;
  612. case 45:
  613. case 46:
  614. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
  615. break;
  616. case 48:
  617. case 49:
  618. case 50:
  619. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl;
  620. break;
  621. case 53:
  622. case 54:
  623. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
  624. break;
  625. case 56:
  626. case 57:
  627. case 58:
  628. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
  629. break;
  630. case 61:
  631. case 62:
  632. buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
  633. break;
  634. }
  635. }
  636. }
  637. mouseFlags = new List<MouseFlags> () { MouseFlags.AllEvents };
  638. if (lastMouseButtonPressed != null && !isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition)
  639. && !buttonState.HasFlag (MouseFlags.Button1Released)
  640. && !buttonState.HasFlag (MouseFlags.Button2Released)
  641. && !buttonState.HasFlag (MouseFlags.Button3Released)
  642. && !buttonState.HasFlag (MouseFlags.Button4Released)) {
  643. lastMouseButtonPressed = null;
  644. isButtonPressed = false;
  645. }
  646. if (!isButtonClicked && !isButtonDoubleClicked && ((buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
  647. buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) && lastMouseButtonPressed == null) ||
  648. isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)) {
  649. mouseFlags [0] = buttonState;
  650. lastMouseButtonPressed = buttonState;
  651. isButtonPressed = true;
  652. if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) {
  653. point = new Point () {
  654. X = pos.X,
  655. Y = pos.Y
  656. };
  657. Application.MainLoop.AddIdle (() => {
  658. Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler));
  659. return false;
  660. });
  661. } else if (mouseFlags [0] == MouseFlags.ReportMousePosition) {
  662. isButtonPressed = false;
  663. }
  664. } else if (isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
  665. buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
  666. mouseFlags [0] = GetButtonTripleClicked (buttonState);
  667. isButtonDoubleClicked = false;
  668. isButtonTripleClicked = true;
  669. } else if (isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
  670. buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
  671. mouseFlags [0] = GetButtonDoubleClicked (buttonState);
  672. isButtonClicked = false;
  673. isButtonDoubleClicked = true;
  674. Application.MainLoop.AddIdle (() => {
  675. Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
  676. return false;
  677. });
  678. }
  679. //else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) {
  680. // mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased);
  681. // lastMouseButtonReleased = null;
  682. // isButtonReleased = false;
  683. // isButtonClicked = true;
  684. // Application.MainLoop.AddIdle (() => {
  685. // Task.Run (async () => await ProcessButtonClickedAsync ());
  686. // return false;
  687. // });
  688. //}
  689. else if (!isButtonClicked && !isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released ||
  690. buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) {
  691. mouseFlags [0] = buttonState;
  692. isButtonPressed = false;
  693. if (isButtonTripleClicked) {
  694. isButtonTripleClicked = false;
  695. } else if (pos.X == point.X && pos.Y == point.Y) {
  696. mouseFlags.Add (GetButtonClicked (buttonState));
  697. isButtonClicked = true;
  698. Application.MainLoop.AddIdle (() => {
  699. Task.Run (async () => await ProcessButtonClickedAsync ());
  700. return false;
  701. });
  702. }
  703. point = pos;
  704. //if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) {
  705. // lastMouseButtonReleased = buttonState;
  706. // isButtonPressed = false;
  707. // isButtonReleased = true;
  708. //} else {
  709. // lastMouseButtonPressed = null;
  710. // isButtonPressed = false;
  711. //}
  712. } else if (buttonState == MouseFlags.WheeledUp) {
  713. mouseFlags [0] = MouseFlags.WheeledUp;
  714. } else if (buttonState == MouseFlags.WheeledDown) {
  715. mouseFlags [0] = MouseFlags.WheeledDown;
  716. } else if (buttonState == MouseFlags.WheeledLeft) {
  717. mouseFlags [0] = MouseFlags.WheeledLeft;
  718. } else if (buttonState == MouseFlags.WheeledRight) {
  719. mouseFlags [0] = MouseFlags.WheeledRight;
  720. } else if (buttonState == MouseFlags.ReportMousePosition) {
  721. mouseFlags [0] = MouseFlags.ReportMousePosition;
  722. } else {
  723. mouseFlags [0] = buttonState;
  724. //foreach (var flag in buttonState.GetUniqueFlags()) {
  725. // mouseFlag [0] |= flag;
  726. //}
  727. }
  728. mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]);
  729. //buttonState = mouseFlags;
  730. //System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}");
  731. //foreach (var mf in mouseFlags) {
  732. // System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}");
  733. //}
  734. }
  735. private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action<MouseFlags, Point> continuousButtonPressedHandler)
  736. {
  737. while (isButtonPressed) {
  738. await Task.Delay (100);
  739. //var me = new MouseEvent () {
  740. // X = point.X,
  741. // Y = point.Y,
  742. // Flags = mouseFlag
  743. //};
  744. var view = Application.WantContinuousButtonPressedView;
  745. if (view == null)
  746. break;
  747. if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
  748. Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point));
  749. }
  750. }
  751. }
  752. private static async Task ProcessButtonClickedAsync ()
  753. {
  754. await Task.Delay (300);
  755. isButtonClicked = false;
  756. }
  757. private static async Task ProcessButtonDoubleClickedAsync ()
  758. {
  759. await Task.Delay (300);
  760. isButtonDoubleClicked = false;
  761. }
  762. private static MouseFlags GetButtonClicked (MouseFlags mouseFlag)
  763. {
  764. MouseFlags mf = default;
  765. switch (mouseFlag) {
  766. case MouseFlags.Button1Released:
  767. mf = MouseFlags.Button1Clicked;
  768. break;
  769. case MouseFlags.Button2Released:
  770. mf = MouseFlags.Button2Clicked;
  771. break;
  772. case MouseFlags.Button3Released:
  773. mf = MouseFlags.Button3Clicked;
  774. break;
  775. }
  776. return mf;
  777. }
  778. private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag)
  779. {
  780. MouseFlags mf = default;
  781. switch (mouseFlag) {
  782. case MouseFlags.Button1Pressed:
  783. mf = MouseFlags.Button1DoubleClicked;
  784. break;
  785. case MouseFlags.Button2Pressed:
  786. mf = MouseFlags.Button2DoubleClicked;
  787. break;
  788. case MouseFlags.Button3Pressed:
  789. mf = MouseFlags.Button3DoubleClicked;
  790. break;
  791. }
  792. return mf;
  793. }
  794. private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag)
  795. {
  796. MouseFlags mf = default;
  797. switch (mouseFlag) {
  798. case MouseFlags.Button1Pressed:
  799. mf = MouseFlags.Button1TripleClicked;
  800. break;
  801. case MouseFlags.Button2Pressed:
  802. mf = MouseFlags.Button2TripleClicked;
  803. break;
  804. case MouseFlags.Button3Pressed:
  805. mf = MouseFlags.Button3TripleClicked;
  806. break;
  807. }
  808. return mf;
  809. }
  810. private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag)
  811. {
  812. if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)
  813. mouseFlag |= MouseFlags.ButtonCtrl;
  814. if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0)
  815. mouseFlag |= MouseFlags.ButtonShift;
  816. if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0)
  817. mouseFlag |= MouseFlags.ButtonAlt;
  818. return mouseFlag;
  819. }
  820. /// <summary>
  821. /// Get the terminal that holds the console driver.
  822. /// </summary>
  823. /// <param name="process">The process.</param>
  824. /// <returns>If supported the executable console process, null otherwise.</returns>
  825. public static Process GetParentProcess (Process process)
  826. {
  827. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
  828. return null;
  829. }
  830. string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id;
  831. using (ManagementObjectSearcher mos = new ManagementObjectSearcher (query)) {
  832. foreach (ManagementObject mo in mos.Get ()) {
  833. if (mo ["ParentProcessId"] != null) {
  834. try {
  835. var id = Convert.ToInt32 (mo ["ParentProcessId"]);
  836. return Process.GetProcessById (id);
  837. } catch {
  838. }
  839. }
  840. }
  841. }
  842. return null;
  843. }
  844. }
  845. }