WindowsConsole.cs 11 KB

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