WindowsDriver.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. using System;
  2. using System.Runtime.InteropServices;
  3. using System.Threading.Tasks;
  4. using Mono.Terminal;
  5. namespace Terminal.Gui {
  6. internal class WindowsConsole {
  7. public const int STD_OUTPUT_HANDLE = -11;
  8. public const int STD_INPUT_HANDLE = -10;
  9. public const int STD_ERROR_HANDLE = -12;
  10. IntPtr inputHandle, outputHandle;
  11. public WindowsConsole ()
  12. {
  13. inputHandle = GetStdHandle (STD_INPUT_HANDLE);
  14. outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
  15. }
  16. [Flags]
  17. public enum ConsoleModes : uint
  18. {
  19. EnableMouseInput = 16,
  20. EnableQuickEditMode = 64,
  21. EnableExtendedFlags = 128,
  22. }
  23. [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
  24. public struct KeyEventRecord {
  25. [FieldOffset (0), MarshalAs (UnmanagedType.Bool)]
  26. public bool bKeyDown;
  27. [FieldOffset (4), MarshalAs (UnmanagedType.U2)]
  28. public ushort wRepeatCount;
  29. [FieldOffset (6), MarshalAs (UnmanagedType.U2)]
  30. public ushort wVirtualKeyCode;
  31. [FieldOffset (8), MarshalAs (UnmanagedType.U2)]
  32. public ushort wVirtualScanCode;
  33. [FieldOffset (10)]
  34. public char UnicodeChar;
  35. [FieldOffset (12), MarshalAs (UnmanagedType.U4)]
  36. public ControlKeyState dwControlKeyState;
  37. }
  38. [Flags]
  39. public enum ButtonState {
  40. Button1Pressed = 1,
  41. Button2Pressed = 4,
  42. Button3Pressed = 8,
  43. Button4Pressed = 16,
  44. RightmostButtonPressed = 2,
  45. }
  46. [Flags]
  47. public enum ControlKeyState {
  48. RightAltPressed = 1,
  49. LeftAltPressed = 2,
  50. RightControlPressed = 4,
  51. LeftControlPressed = 8,
  52. ShiftPressed = 16,
  53. NumlockOn = 32,
  54. ScrolllockOn = 64,
  55. CapslockOn = 128,
  56. EnhancedKey = 256
  57. }
  58. [Flags]
  59. public enum EventFlags {
  60. MouseMoved = 1,
  61. DoubleClick = 2,
  62. MouseWheeled = 4,
  63. MouseHorizontalWheeled = 8
  64. }
  65. [StructLayout (LayoutKind.Explicit)]
  66. public struct MouseEventRecord {
  67. [FieldOffset (0)]
  68. public Coordinate MousePosition;
  69. [FieldOffset (4)]
  70. public ButtonState ButtonState;
  71. [FieldOffset (8)]
  72. public ControlKeyState ControlKeyState;
  73. [FieldOffset (12)]
  74. public EventFlags EventFlags;
  75. public override string ToString ()
  76. {
  77. return $"[Mouse({MousePosition},{ButtonState},{ControlKeyState},{EventFlags}";
  78. }
  79. }
  80. [StructLayout (LayoutKind.Sequential)]
  81. public struct Coordinate {
  82. public short X;
  83. public short Y;
  84. public Coordinate (short X, short Y)
  85. {
  86. this.X = X;
  87. this.Y = Y;
  88. }
  89. public override string ToString () => $"({X},{Y})";
  90. };
  91. internal struct WindowBufferSizeRecord {
  92. public Coordinate size;
  93. public WindowBufferSizeRecord (short x, short y)
  94. {
  95. this.size = new Coordinate (x, y);
  96. }
  97. public override string ToString () => $"[WindowBufferSize{size}";
  98. }
  99. [StructLayout (LayoutKind.Sequential)]
  100. public struct MenuEventRecord {
  101. public uint dwCommandId;
  102. }
  103. [StructLayout (LayoutKind.Sequential)]
  104. public struct FocusEventRecord {
  105. public uint bSetFocus;
  106. }
  107. public enum EventType {
  108. Focus = 0x10,
  109. Key = 0x1,
  110. Menu = 0x8,
  111. Mouse = 2,
  112. WindowBufferSize = 4
  113. }
  114. [StructLayout (LayoutKind.Explicit)]
  115. public struct InputRecord {
  116. [FieldOffset (0)]
  117. public EventType EventType;
  118. [FieldOffset (4)]
  119. public KeyEventRecord KeyEvent;
  120. [FieldOffset (4)]
  121. public MouseEventRecord MouseEvent;
  122. [FieldOffset (4)]
  123. public WindowBufferSizeRecord WindowBufferSizeEvent;
  124. [FieldOffset (4)]
  125. public MenuEventRecord MenuEvent;
  126. [FieldOffset (4)]
  127. public FocusEventRecord FocusEvent;
  128. public override string ToString ()
  129. {
  130. switch (EventType) {
  131. case EventType.Focus:
  132. return FocusEvent.ToString ();
  133. case EventType.Key:
  134. return KeyEvent.ToString ();
  135. case EventType.Menu:
  136. return MenuEvent.ToString ();
  137. case EventType.Mouse:
  138. return MouseEvent.ToString ();
  139. case EventType.WindowBufferSize:
  140. return WindowBufferSizeEvent.ToString ();
  141. default:
  142. return "Unknown event type: " + EventType;
  143. }
  144. }
  145. };
  146. public void PollEvents(Action<InputRecord> inputEventHandler)
  147. {
  148. if (OriginalConsoleMode != 0)
  149. return;
  150. OriginalConsoleMode = ConsoleMode;
  151. ConsoleMode |= (uint)ConsoleModes.EnableMouseInput;
  152. ConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
  153. ConsoleMode |= (uint)ConsoleModes.EnableExtendedFlags;
  154. Task.Run(() =>
  155. {
  156. uint numberEventsRead = 0;
  157. uint length = 1;
  158. InputRecord[] records = new InputRecord[length];
  159. while (
  160. ContinueListeningForConsoleEvents &&
  161. ReadConsoleInput(inputHandle, records, length, out numberEventsRead) &&
  162. numberEventsRead > 0
  163. )
  164. {
  165. inputEventHandler(records[0]);
  166. }
  167. });
  168. }
  169. public void Cleanup()
  170. {
  171. ContinueListeningForConsoleEvents = false;
  172. ConsoleMode = OriginalConsoleMode;
  173. OriginalConsoleMode = 0;
  174. }
  175. private bool ContinueListeningForConsoleEvents = true;
  176. private uint OriginalConsoleMode = 0;
  177. public uint ConsoleMode {
  178. get {
  179. uint v;
  180. GetConsoleMode (inputHandle, out v);
  181. return v;
  182. }
  183. set {
  184. SetConsoleMode (inputHandle, value);
  185. }
  186. }
  187. [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
  188. public static extern bool ReadConsoleInput (
  189. IntPtr hConsoleInput,
  190. [Out] InputRecord [] lpBuffer,
  191. uint nLength,
  192. out uint lpNumberOfEventsRead);
  193. [DllImport ("kernel32.dll")]
  194. static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
  195. [DllImport ("kernel32.dll")]
  196. static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
  197. [DllImport ("kernel32.dll", SetLastError = true)]
  198. static extern IntPtr GetStdHandle (int nStdHandle);
  199. }
  200. internal class WindowsDriver : NetDriver {
  201. WindowsConsole WinConsole;
  202. public WindowsDriver ()
  203. {
  204. WinConsole = new WindowsConsole();
  205. }
  206. private MouseEvent ToDriverMouse(WindowsConsole.MouseEventRecord mouseEvent)
  207. {
  208. MouseFlags mouseFlag = MouseFlags.AllEvents;
  209. if (mouseEvent.EventFlags == 0)
  210. {
  211. switch (mouseEvent.ButtonState)
  212. {
  213. case WindowsConsole.ButtonState.Button1Pressed:
  214. mouseFlag = MouseFlags.Button1Clicked;
  215. break;
  216. case WindowsConsole.ButtonState.Button2Pressed:
  217. mouseFlag = MouseFlags.Button2Clicked;
  218. break;
  219. case WindowsConsole.ButtonState.Button3Pressed:
  220. mouseFlag = MouseFlags.Button3Clicked;
  221. break;
  222. }
  223. }
  224. else if(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved)
  225. {
  226. mouseFlag = MouseFlags.ReportMousePosition;
  227. }
  228. return new MouseEvent () {
  229. X = mouseEvent.MousePosition.X,
  230. Y = mouseEvent.MousePosition.Y,
  231. Flags = mouseFlag
  232. };
  233. }
  234. private ConsoleKeyInfo ToConsoleKeyInfo(WindowsConsole.KeyEventRecord keyEvent)
  235. {
  236. var state = keyEvent.dwControlKeyState;
  237. bool shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0;
  238. bool alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0;
  239. bool control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 0;
  240. return new ConsoleKeyInfo(keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
  241. }
  242. public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
  243. {
  244. WinConsole.PollEvents (inputEvent =>
  245. {
  246. switch(inputEvent.EventType)
  247. {
  248. case WindowsConsole.EventType.Key:
  249. if (inputEvent.KeyEvent.bKeyDown == false)
  250. return;
  251. var map = MapKey (ToConsoleKeyInfo (inputEvent.KeyEvent));
  252. if (map == (Key) 0xffffffff)
  253. return;
  254. keyHandler (new KeyEvent (map));
  255. break;
  256. case WindowsConsole.EventType.Mouse:
  257. mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
  258. break;
  259. }
  260. });
  261. }
  262. public override void End()
  263. {
  264. WinConsole.Cleanup();
  265. base.End();
  266. }
  267. }
  268. }