WindowsConsole.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. using System.Runtime.InteropServices;
  2. // ReSharper disable InconsistentNaming
  3. namespace Terminal.Gui.Drivers;
  4. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
  5. /// <summary>
  6. /// Definitions for Windows Console API structures and constants.
  7. /// </summary>
  8. public class WindowsConsole
  9. {
  10. [Flags]
  11. public enum ButtonState
  12. {
  13. NoButtonPressed = 0,
  14. Button1Pressed = 1,
  15. Button2Pressed = 4,
  16. Button3Pressed = 8,
  17. Button4Pressed = 16,
  18. RightmostButtonPressed = 2
  19. }
  20. /// <summary>
  21. /// Windows Console mode flags.
  22. /// </summary>
  23. [Flags]
  24. public enum ConsoleModes : uint
  25. {
  26. EnableProcessedInput = 1,
  27. EnableVirtualTerminalProcessing = 4,
  28. EnableMouseInput = 16,
  29. EnableQuickEditMode = 64,
  30. EnableExtendedFlags = 128
  31. }
  32. [Flags]
  33. public enum ControlKeyState
  34. {
  35. NoControlKeyPressed = 0,
  36. RightAltPressed = 1,
  37. LeftAltPressed = 2,
  38. RightControlPressed = 4,
  39. LeftControlPressed = 8,
  40. ShiftPressed = 16,
  41. NumlockOn = 32,
  42. ScrolllockOn = 64,
  43. CapslockOn = 128,
  44. EnhancedKey = 256
  45. }
  46. [Flags]
  47. public enum EventFlags
  48. {
  49. NoEvent = 0,
  50. MouseMoved = 1,
  51. DoubleClick = 2,
  52. MouseWheeled = 4,
  53. MouseHorizontalWheeled = 8
  54. }
  55. public enum EventType : ushort
  56. {
  57. Focus = 0x10,
  58. Key = 0x1,
  59. Menu = 0x8,
  60. Mouse = 2,
  61. WindowBufferSize = 4
  62. }
  63. /// <summary>
  64. /// Standard input handle constant.
  65. /// </summary>
  66. public const int STD_INPUT_HANDLE = -10;
  67. public static ConsoleKeyInfoEx ToConsoleKeyInfoEx (KeyEventRecord keyEvent)
  68. {
  69. ControlKeyState state = keyEvent.dwControlKeyState;
  70. bool shift = (state & ControlKeyState.ShiftPressed) != 0;
  71. bool alt = (state & (ControlKeyState.LeftAltPressed | ControlKeyState.RightAltPressed)) != 0;
  72. bool control = (state & (ControlKeyState.LeftControlPressed | ControlKeyState.RightControlPressed)) != 0;
  73. bool capslock = (state & ControlKeyState.CapslockOn) != 0;
  74. bool numlock = (state & ControlKeyState.NumlockOn) != 0;
  75. bool scrolllock = (state & ControlKeyState.ScrolllockOn) != 0;
  76. var cki = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
  77. return new (cki, capslock, numlock, scrolllock);
  78. }
  79. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  80. public struct CharInfo
  81. {
  82. [FieldOffset (0)]
  83. public CharUnion Char;
  84. [FieldOffset (2)]
  85. public ushort Attributes;
  86. }
  87. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  88. public struct CharUnion
  89. {
  90. [FieldOffset (0)]
  91. public char UnicodeChar;
  92. [FieldOffset (0)]
  93. public byte AsciiChar;
  94. }
  95. [StructLayout (LayoutKind.Explicit, Size = 4)]
  96. public struct COLORREF
  97. {
  98. public COLORREF (byte r, byte g, byte b)
  99. {
  100. Value = 0;
  101. R = r;
  102. G = g;
  103. B = b;
  104. }
  105. public COLORREF (uint value)
  106. {
  107. R = 0;
  108. G = 0;
  109. B = 0;
  110. Value = value & 0x00FFFFFF;
  111. }
  112. [FieldOffset (0)]
  113. public byte R;
  114. [FieldOffset (1)]
  115. public byte G;
  116. [FieldOffset (2)]
  117. public byte B;
  118. [FieldOffset (0)]
  119. public uint Value;
  120. }
  121. // See: https://github.com/gui-cs/Terminal.Gui/issues/357
  122. [StructLayout (LayoutKind.Sequential)]
  123. public struct CONSOLE_SCREEN_BUFFER_INFOEX
  124. {
  125. public uint cbSize;
  126. public Coord dwSize;
  127. public Coord dwCursorPosition;
  128. public ushort wAttributes;
  129. public SmallRect srWindow;
  130. public Coord dwMaximumWindowSize;
  131. public ushort wPopupAttributes;
  132. public bool bFullscreenSupported;
  133. [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
  134. public COLORREF [] ColorTable;
  135. }
  136. [StructLayout (LayoutKind.Sequential)]
  137. public struct ConsoleCursorInfo
  138. {
  139. /// <summary>
  140. /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100.
  141. /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal
  142. /// line at the bottom of the cell.
  143. /// </summary>
  144. public uint dwSize;
  145. public bool bVisible;
  146. }
  147. [StructLayout (LayoutKind.Sequential)]
  148. public struct ConsoleKeyInfoEx
  149. {
  150. public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock)
  151. {
  152. ConsoleKeyInfo = consoleKeyInfo;
  153. CapsLock = capslock;
  154. NumLock = numlock;
  155. ScrollLock = scrolllock;
  156. }
  157. public ConsoleKeyInfo ConsoleKeyInfo;
  158. public bool CapsLock;
  159. public bool NumLock;
  160. public bool ScrollLock;
  161. /// <summary>
  162. /// Prints a ConsoleKeyInfoEx structure
  163. /// </summary>
  164. /// <param name="ex"></param>
  165. /// <returns></returns>
  166. public readonly string ToString (ConsoleKeyInfoEx ex)
  167. {
  168. var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar);
  169. var sb = new StringBuilder ();
  170. sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})");
  171. sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
  172. sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
  173. sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
  174. sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) ");
  175. sb.Append (ex.CapsLock ? "caps," : string.Empty);
  176. sb.Append (ex.NumLock ? "num," : string.Empty);
  177. sb.Append (ex.ScrollLock ? "scroll," : string.Empty);
  178. string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
  179. return $"[ConsoleKeyInfoEx({s})]";
  180. }
  181. }
  182. [StructLayout (LayoutKind.Sequential)]
  183. public struct Coord
  184. {
  185. public Coord (short x, short y)
  186. {
  187. X = x;
  188. Y = y;
  189. }
  190. public short X;
  191. public short Y;
  192. public readonly override string ToString () { return $"({X},{Y})"; }
  193. }
  194. [StructLayout (LayoutKind.Sequential)]
  195. public struct FocusEventRecord
  196. {
  197. public uint bSetFocus;
  198. }
  199. [StructLayout (LayoutKind.Explicit)]
  200. public struct InputRecord
  201. {
  202. [FieldOffset (0)]
  203. public EventType EventType;
  204. [FieldOffset (4)]
  205. public KeyEventRecord KeyEvent;
  206. [FieldOffset (4)]
  207. public MouseEventRecord MouseEvent;
  208. [FieldOffset (4)]
  209. public WindowBufferSizeRecord WindowBufferSizeEvent;
  210. [FieldOffset (4)]
  211. public MenuEventRecord MenuEvent;
  212. [FieldOffset (4)]
  213. public FocusEventRecord FocusEvent;
  214. public readonly override string ToString ()
  215. {
  216. return (EventType switch
  217. {
  218. EventType.Focus => FocusEvent.ToString (),
  219. EventType.Key => KeyEvent.ToString (),
  220. EventType.Menu => MenuEvent.ToString (),
  221. EventType.Mouse => MouseEvent.ToString (),
  222. EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
  223. _ => "Unknown event type: " + EventType
  224. })!;
  225. }
  226. }
  227. /// <summary>
  228. /// Key event record structure.
  229. /// </summary>
  230. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  231. public struct KeyEventRecord
  232. {
  233. [FieldOffset (0)]
  234. [MarshalAs (UnmanagedType.Bool)]
  235. public bool bKeyDown;
  236. [FieldOffset (4)]
  237. [MarshalAs (UnmanagedType.U2)]
  238. public ushort wRepeatCount;
  239. [FieldOffset (6)]
  240. [MarshalAs (UnmanagedType.U2)]
  241. public VK wVirtualKeyCode;
  242. [FieldOffset (8)]
  243. [MarshalAs (UnmanagedType.U2)]
  244. public ushort wVirtualScanCode;
  245. [FieldOffset (10)]
  246. public char UnicodeChar;
  247. [FieldOffset (12)]
  248. [MarshalAs (UnmanagedType.U4)]
  249. public ControlKeyState dwControlKeyState;
  250. public readonly override string ToString ()
  251. {
  252. return
  253. $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]";
  254. }
  255. }
  256. [StructLayout (LayoutKind.Sequential)]
  257. public struct MenuEventRecord
  258. {
  259. public uint dwCommandId;
  260. }
  261. [StructLayout (LayoutKind.Explicit)]
  262. public struct MouseEventRecord
  263. {
  264. [FieldOffset (0)]
  265. public Coord MousePosition;
  266. [FieldOffset (4)]
  267. public ButtonState ButtonState;
  268. [FieldOffset (8)]
  269. public ControlKeyState ControlKeyState;
  270. [FieldOffset (12)]
  271. public EventFlags EventFlags;
  272. public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; }
  273. }
  274. [StructLayout (LayoutKind.Sequential)]
  275. public struct SmallRect
  276. {
  277. public SmallRect (short left, short top, short right, short bottom)
  278. {
  279. Left = left;
  280. Top = top;
  281. Right = right;
  282. Bottom = bottom;
  283. }
  284. public short Left;
  285. public short Top;
  286. public short Right;
  287. public short Bottom;
  288. public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; }
  289. public readonly override string ToString () { return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; }
  290. public static void Update (ref SmallRect rect, short col, short row)
  291. {
  292. if (rect.Left == -1)
  293. {
  294. rect.Left = rect.Right = col;
  295. rect.Bottom = rect.Top = row;
  296. return;
  297. }
  298. if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom)
  299. {
  300. return;
  301. }
  302. if (col < rect.Left)
  303. {
  304. rect.Left = col;
  305. }
  306. if (col > rect.Right)
  307. {
  308. rect.Right = col;
  309. }
  310. if (row < rect.Top)
  311. {
  312. rect.Top = row;
  313. }
  314. if (row > rect.Bottom)
  315. {
  316. rect.Bottom = row;
  317. }
  318. }
  319. }
  320. public struct WindowBufferSizeRecord (short x, short y)
  321. {
  322. public Coord _size = new (x, y);
  323. public readonly override string ToString () { return $"[WindowBufferSize{_size}"; }
  324. }
  325. }