From 8eea18b8e8fa28530b3571b7bd7894915bee99f6 Mon Sep 17 00:00:00 2001 From: Nick Van Dyck Date: Thu, 10 May 2018 01:49:58 +0100 Subject: [PATCH] First implementation of mouse events on windows --- Terminal.Gui/Core.cs | 10 +- Terminal.Gui/Drivers/NetDriver.cs | 2 +- Terminal.Gui/Drivers/WindowsDriver.cs | 161 +++++++++++++++++++++++--- Terminal.Gui/MonoCurses/mainloop.cs | 9 +- 4 files changed, 157 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 4f69396fb..6f81484dc 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -1590,14 +1590,12 @@ namespace Terminal.Gui { if (Top != null) return; - if (!UseSystemConsole) { - var p = Environment.OSVersion.Platform; - if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) - UseSystemConsole = true; - } - //UseSystemConsole = true; + var p = Environment.OSVersion.Platform; + if (UseSystemConsole) Driver = new NetDriver (); + else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) + Driver = new WindowsDriver(); else Driver = new CursesDriver (); Driver.Init (TerminalResized); diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs index 62e0ef53f..1e7e165c6 100644 --- a/Terminal.Gui/Drivers/NetDriver.cs +++ b/Terminal.Gui/Drivers/NetDriver.cs @@ -238,7 +238,7 @@ namespace Terminal.Gui { currentAttribute = c.value; } - Key MapKey (ConsoleKeyInfo keyInfo) + public Key MapKey (ConsoleKeyInfo keyInfo) { switch (keyInfo.Key) { case ConsoleKey.Escape: diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 17d206d27..06e3da646 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -1,5 +1,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Mono.Terminal; namespace Terminal.Gui { @@ -8,9 +10,6 @@ namespace Terminal.Gui { public const int STD_INPUT_HANDLE = -10; public const int STD_ERROR_HANDLE = -12; - [DllImport ("kernel32.dll", SetLastError = true)] - static extern IntPtr GetStdHandle (int nStdHandle); - IntPtr inputHandle, outputHandle; public WindowsConsole () @@ -19,6 +18,15 @@ namespace Terminal.Gui { outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); } + [Flags] + public enum ConsoleModes : uint + { + EnableMouseInput = 16, + EnableQuickEditMode = 64, + EnableExtendedFlags = 128, + } + + [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)] public struct KeyEventRecord { [FieldOffset (0), MarshalAs (UnmanagedType.Bool)] @@ -160,6 +168,57 @@ namespace Terminal.Gui { } }; + public void PollEvents(Action inputEventHandler) + { + if (OriginalConsoleMode != 0) + return; + + OriginalConsoleMode = ConsoleMode; + + ConsoleMode |= (uint)ConsoleModes.EnableMouseInput; + ConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode; + ConsoleMode |= (uint)ConsoleModes.EnableExtendedFlags; + + Task.Run(() => + { + uint numberEventsRead = 0; + uint length = 1; + InputRecord[] records = new InputRecord[length]; + + while ( + ContinueListeningForConsoleEvents && + ReadConsoleInput(inputHandle, records, length, out numberEventsRead) && + numberEventsRead > 0 + ) + { + inputEventHandler(records[0]); + } + }); + } + + public void Cleanup() + { + ContinueListeningForConsoleEvents = false; + ConsoleMode = OriginalConsoleMode; + OriginalConsoleMode = 0; + } + + private bool ContinueListeningForConsoleEvents = true; + + private uint OriginalConsoleMode = 0; + + public uint ConsoleMode { + get { + uint v; + GetConsoleMode (inputHandle, out v); + return v; + } + + set { + SetConsoleMode (inputHandle, value); + } + } + [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)] public static extern bool ReadConsoleInput ( IntPtr hConsoleInput, @@ -173,21 +232,93 @@ namespace Terminal.Gui { [DllImport ("kernel32.dll")] static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode); - public uint ConsoleMode { - get { - uint v; - GetConsoleMode (inputHandle, out v); - return v; - } + [DllImport ("kernel32.dll", SetLastError = true)] + static extern IntPtr GetStdHandle (int nStdHandle); + + } + + internal class WindowsDriver : NetDriver { + + WindowsConsole WinConsole; - set { - SetConsoleMode (inputHandle, value); - } - } - } - public class WindowsDriver { public WindowsDriver () { + WinConsole = new WindowsConsole(); } + + private MouseEvent ToDriverMouse(WindowsConsole.MouseEventRecord mouseEvent) + { + MouseFlags mouseFlag = MouseFlags.AllEvents; + + if (mouseEvent.EventFlags == 0) + { + switch (mouseEvent.ButtonState) + { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1Clicked; + break; + + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2Clicked; + break; + + case WindowsConsole.ButtonState.Button3Pressed: + mouseFlag = MouseFlags.Button3Clicked; + break; + } + } + else if(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) + { + mouseFlag = MouseFlags.ReportMousePosition; + } + + + return new MouseEvent () { + X = mouseEvent.MousePosition.X, + Y = mouseEvent.MousePosition.Y, + Flags = mouseFlag + }; + } + + private ConsoleKeyInfo ToConsoleKeyInfo(WindowsConsole.KeyEventRecord keyEvent) + { + var state = keyEvent.dwControlKeyState; + + bool shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0; + bool alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0; + bool control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 0; + + return new ConsoleKeyInfo(keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control); + } + + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler) + { + WinConsole.PollEvents (inputEvent => + { + switch(inputEvent.EventType) + { + case WindowsConsole.EventType.Key: + if (inputEvent.KeyEvent.bKeyDown == false) + return; + var map = MapKey (ToConsoleKeyInfo (inputEvent.KeyEvent)); + if (map == (Key) 0xffffffff) + return; + keyHandler (new KeyEvent (map)); + break; + + case WindowsConsole.EventType.Mouse: + mouseHandler (ToDriverMouse (inputEvent.MouseEvent)); + break; + + } + }); + } + + public override void End() + { + WinConsole.Cleanup(); + base.End(); + } + } } diff --git a/Terminal.Gui/MonoCurses/mainloop.cs b/Terminal.Gui/MonoCurses/mainloop.cs index 7af17db46..b5f52aaf4 100644 --- a/Terminal.Gui/MonoCurses/mainloop.cs +++ b/Terminal.Gui/MonoCurses/mainloop.cs @@ -128,10 +128,13 @@ namespace Mono.Terminal { read (wakeupPipes [0], ignore, (IntPtr)1); return true; }); - } else { - Thread readThread = new Thread (WindowsKeyReader); - readThread.Start (); } + // Using this results in buggy behavior when we are hooking into ReadConsoleInput api ourselves + // Because if left in both custom code and this one tries to read from the consoleInput + // else { + // Thread readThread = new Thread (WindowsKeyReader); + // readThread.Start (); + // } } void Wakeup ()