From ef9fb593e503d9bd482444b9f8107537e7590b48 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 29 Sep 2021 22:17:22 +0100 Subject: [PATCH] Fixes WindowsDriver HeightAsBuffer set to false. (#1466) --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 382 ++++++++++++------- Terminal.Gui/Core/Application.cs | 3 +- 2 files changed, 239 insertions(+), 146 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 022bf1e73..e9250b91a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -43,7 +43,7 @@ namespace Terminal.Gui { internal IntPtr InputHandle, OutputHandle; IntPtr ScreenBuffer; - uint originalConsoleMode; + readonly uint originalConsoleMode; CursorVisibility? initialCursorVisibility = null; CursorVisibility? currentCursorVisibility = null; CursorVisibility? pendingCursorVisibility = null; @@ -62,10 +62,10 @@ namespace Terminal.Gui { public CharInfo [] OriginalStdOutChars; - public bool WriteToConsole (CharInfo [] charInfoBuffer, Coord coords, SmallRect window) + public bool WriteToConsole (Size size, CharInfo [] charInfoBuffer, Coord coords, SmallRect window) { if (ScreenBuffer == IntPtr.Zero) { - ReadFromConsoleOutput (new Size (Console.WindowWidth, Console.WindowHeight), coords, ref window); + ReadFromConsoleOutput (size, coords, ref window); } return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window); @@ -92,13 +92,14 @@ namespace Terminal.Gui { } if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) { - var err = Marshal.GetLastWin32Error (); - throw new System.ComponentModel.Win32Exception (err); + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); } OriginalStdOutChars = new CharInfo [size.Height * size.Width]; - ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window); + if (!ReadConsoleOutput (ScreenBuffer, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } } public bool SetCursorPosition (Coord position) @@ -183,6 +184,8 @@ namespace Terminal.Gui { SetCursorVisibility (initialCursorVisibility.Value); } + SetConsoleOutputWindow (out _); + ConsoleMode = originalConsoleMode; //ContinueListeningForConsoleEvents = false; if (!SetConsoleActiveScreenBuffer (OutputHandle)) { @@ -196,22 +199,103 @@ namespace Terminal.Gui { ScreenBuffer = IntPtr.Zero; } + internal Size GetConsoleBufferWindow (out Point position) + { + if (ScreenBuffer == IntPtr.Zero) { + position = Point.Empty; + return Size.Empty; + } + + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); + + return sz; + } + + internal Size GetConsoleOutputWindow (out Point position) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); + + return sz; + } + + internal Size SetConsoleWindow (short cols, short rows) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + csbi.dwSize = new Coord (cols, Math.Max (rows, (short)1)); + csbi.srWindow = new SmallRect (0, 0, cols, rows); + csbi.dwMaximumWindowSize = new Coord (cols, rows); + if (!SetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var winRect = new SmallRect (0, 0, (short)(cols - 1), (short)Math.Max (rows - 1, 0)); + if (!SetConsoleWindowInfo (ScreenBuffer, true, ref winRect)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + SetConsoleOutputWindow (csbi); + return new Size (winRect.Right + 1, rows - 1 < 0 ? 0 : winRect.Bottom + 1); + } + + void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) + { + if (ScreenBuffer != IntPtr.Zero && !SetConsoleScreenBufferInfoEx (OutputHandle, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + } + + internal Size SetConsoleOutputWindow (out Point position) + { + if (ScreenBuffer == IntPtr.Zero) { + position = Point.Empty; + return Size.Empty; + } + + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + if (!GetConsoleScreenBufferInfoEx (ScreenBuffer, ref csbi)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + var sz = new Size (csbi.srWindow.Right - csbi.srWindow.Left + 1, + Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); + position = new Point (csbi.srWindow.Left, csbi.srWindow.Top); + SetConsoleOutputWindow (csbi); + var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0)); + if (!SetConsoleWindowInfo (OutputHandle, true, ref winRect)) { + throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + } + + return sz; + } + //bool ContinueListeningForConsoleEvents = true; public uint ConsoleMode { get { - uint v; - GetConsoleMode (InputHandle, out v); + GetConsoleMode (InputHandle, out uint v); return v; } - set { SetConsoleMode (InputHandle, value); } } - public bool HeightAsBuffer { get; set; } - [Flags] public enum ConsoleModes : uint { EnableProcessedInput = 1, @@ -269,7 +353,7 @@ namespace Terminal.Gui { [StructLayout (LayoutKind.Explicit)] public struct MouseEventRecord { [FieldOffset (0)] - public Coordinate MousePosition; + public Coord MousePosition; [FieldOffset (4)] public ButtonState ButtonState; [FieldOffset (8)] @@ -283,26 +367,12 @@ namespace Terminal.Gui { } } - [StructLayout (LayoutKind.Sequential)] - public struct Coordinate { - public short X; - public short Y; - - public Coordinate (short X, short Y) - { - this.X = X; - this.Y = Y; - } - - public override string ToString () => $"({X},{Y})"; - }; - public struct WindowBufferSizeRecord { - public Coordinate size; + public Coord size; public WindowBufferSizeRecord (short x, short y) { - this.size = new Coordinate (x, y); + this.size = new Coord (x, y); } public override string ToString () => $"[WindowBufferSize{size}"; @@ -413,6 +483,14 @@ namespace Terminal.Gui { public short Right; public short Bottom; + public SmallRect (short left, short top, short right, short bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + } + public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; @@ -517,13 +595,12 @@ namespace Terminal.Gui { DesiredAccess dwDesiredAccess, ShareMode dwShareMode, IntPtr secutiryAttributes, - UInt32 flags, + uint flags, IntPtr screenBufferData ); internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr (-1); - [DllImport ("kernel32.dll", SetLastError = true)] static extern bool SetConsoleActiveScreenBuffer (IntPtr Handle); @@ -531,8 +608,7 @@ namespace Terminal.Gui { static extern bool GetNumberOfConsoleInputEvents (IntPtr handle, out uint lpcNumberOfEvents); public uint InputEventCount { get { - uint v; - GetNumberOfConsoleInputEvents (InputHandle, out v); + GetNumberOfConsoleInputEvents (InputHandle, out uint v); return v; } } @@ -573,68 +649,80 @@ namespace Terminal.Gui { ShowWindow (thisConsole, state); } #endif -#if false // See: https://github.com/migueldeicaza/gui.cs/issues/357 - [StructLayout (LayoutKind.Sequential)] - public struct SMALL_RECT { - public short Left; - public short Top; - public short Right; - public short Bottom; + // See: https://github.com/migueldeicaza/gui.cs/issues/357 - public SMALL_RECT (short Left, short Top, short Right, short Bottom) - { - this.Left = Left; - this.Top = Top; - this.Right = Right; - this.Bottom = Bottom; - } + [StructLayout (LayoutKind.Sequential)] + public struct CONSOLE_SCREEN_BUFFER_INFOEX { + public uint cbSize; + public Coord dwSize; + public Coord dwCursorPosition; + public ushort wAttributes; + public SmallRect srWindow; + public Coord dwMaximumWindowSize; + public ushort wPopupAttributes; + public bool bFullscreenSupported; + + [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)] + public COLORREF [] ColorTable; } - [StructLayout (LayoutKind.Sequential)] - public struct CONSOLE_SCREEN_BUFFER_INFO { - public int dwSize; - public int dwCursorPosition; - public short wAttributes; - public SMALL_RECT srWindow; - public int dwMaximumWindowSize; + [StructLayout (LayoutKind.Explicit, Size = 4)] + public struct COLORREF { + public COLORREF (byte r, byte g, byte b) + { + Value = 0; + R = r; + G = g; + B = b; + } + + public COLORREF (uint value) + { + R = 0; + G = 0; + B = 0; + Value = value & 0x00FFFFFF; + } + + [FieldOffset (0)] + public byte R; + [FieldOffset (1)] + public byte G; + [FieldOffset (2)] + public byte B; + + [FieldOffset (0)] + public uint Value; } [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool GetConsoleScreenBufferInfo (IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo); + static extern bool GetConsoleScreenBufferInfoEx (IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi); - // Theoretically GetConsoleScreenBuffer height should give the console Windoww size - // It does not work, however, and always returns the size the window was initially created at - internal Size GetWindowSize () - { - var consoleScreenBufferInfo = new CONSOLE_SCREEN_BUFFER_INFO (); - //consoleScreenBufferInfo.dwSize = Marshal.SizeOf (typeof (CONSOLE_SCREEN_BUFFER_INFO)); - GetConsoleScreenBufferInfo (OutputHandle, out consoleScreenBufferInfo); - return new Size (consoleScreenBufferInfo.srWindow.Right - consoleScreenBufferInfo.srWindow.Left, - consoleScreenBufferInfo.srWindow.Bottom - consoleScreenBufferInfo.srWindow.Top); - } -#endif + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleScreenBufferInfoEx (IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo); + + [DllImport ("kernel32.dll", SetLastError = true)] + static extern bool SetConsoleWindowInfo ( + IntPtr hConsoleOutput, + bool bAbsolute, + [In] ref SmallRect lpConsoleWindow); } internal class WindowsDriver : ConsoleDriver { static bool sync = false; WindowsConsole.CharInfo [] OutputBuffer; - int cols, rows, top; - WindowsConsole winConsole; + int cols, rows, left, top; WindowsConsole.SmallRect damageRegion; IClipboard clipboard; public override int Cols => cols; public override int Rows => rows; - public override int Left => 0; + public override int Left => left; public override int Top => top; public override bool HeightAsBuffer { get; set; } - public override IClipboard Clipboard => clipboard; - public WindowsConsole WinConsole { - get => winConsole; - private set => winConsole = value; - } + public WindowsConsole WinConsole { get; private set; } Action keyHandler; Action keyDownHandler; @@ -643,7 +731,7 @@ namespace Terminal.Gui { public WindowsDriver () { - winConsole = new WindowsConsole (); + WinConsole = new WindowsConsole (); clipboard = new WindowsClipboard (); } @@ -669,16 +757,18 @@ namespace Terminal.Gui { { winChanging = true; if (!HeightAsBuffer) { + var w = e.Width; + if (w == cols - 3 && e.Height < rows) { + w += 3; + } + var newSize = WinConsole.SetConsoleWindow ( + (short)Math.Max (w, 16), (short)e.Height); + left = 0; top = 0; - cols = e.Width; - rows = e.Height; + cols = newSize.Width; + rows = newSize.Height; ResizeScreen (); UpdateOffScreen (); - var bufferCoords = new WindowsConsole.Coord () { - X = (short)Clip.Width, - Y = (short)Clip.Height - }; - winConsole.ReadFromConsoleOutput (e, bufferCoords, ref damageRegion); if (!winChanging) { TerminalResized.Invoke (); } @@ -783,11 +873,17 @@ namespace Terminal.Gui { break; case WindowsConsole.EventType.WindowBufferSize: - cols = inputEvent.WindowBufferSizeEvent.size.X; - rows = inputEvent.WindowBufferSizeEvent.size.Y; - ResizeScreen (); - UpdateOffScreen (); - TerminalResized?.Invoke (); + if (HeightAsBuffer) { + var winSize = WinConsole.GetConsoleBufferWindow (out Point pos); + left = pos.X; + top = pos.Y; + cols = inputEvent.WindowBufferSizeEvent.size.X; + rows = inputEvent.WindowBufferSizeEvent.size.Y; + //System.Diagnostics.Debug.WriteLine ($"{HeightAsBuffer},{cols},{rows}"); + ResizeScreen (); + UpdateOffScreen (); + TerminalResized?.Invoke (); + } break; case WindowsConsole.EventType.Focus: @@ -1276,11 +1372,10 @@ namespace Terminal.Gui { { TerminalResized = terminalResized; - cols = Console.WindowWidth; - rows = Console.WindowHeight; -#if false - winConsole.ShowWindow (WindowsConsole.RESTORE); -#endif + var winSize = WinConsole.GetConsoleOutputWindow (out Point pos); + cols = winSize.Width; + rows = winSize.Height; + WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); ResizeScreen (); @@ -1333,7 +1428,7 @@ namespace Terminal.Gui { Bottom = (short)Rows, Right = (short)Cols }; - winConsole.ForceRefreshCursorVisibility (); + WinConsole.ForceRefreshCursorVisibility (); } void UpdateOffScreen () @@ -1415,7 +1510,7 @@ namespace Terminal.Gui { { UpdateScreen (); - winConsole.SetInitialCursorVisibility (); + WinConsole.SetInitialCursorVisibility (); #if false var bufferCoords = new WindowsConsole.Coord (){ X = (short)Clip.Width, @@ -1430,7 +1525,7 @@ namespace Terminal.Gui { }; UpdateCursor(); - winConsole.WriteToConsole (OutputBuffer, bufferCoords, window); + WinConsole.WriteToConsole (OutputBuffer, bufferCoords, window); #endif } @@ -1452,8 +1547,8 @@ namespace Terminal.Gui { //}; UpdateCursor (); - winConsole.WriteToConsole (OutputBuffer, bufferCoords, damageRegion); - // System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n"); + WinConsole.WriteToConsole (new Size (Cols, Rows), OutputBuffer, bufferCoords, damageRegion); + // System.Diagnostics.Debugger.Log (0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n"); WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); } @@ -1463,12 +1558,13 @@ namespace Terminal.Gui { X = (short)ccol, Y = (short)crow }; - winConsole.SetCursorPosition (position); + WinConsole.SetCursorPosition (position); } public override void End () { - winConsole.Cleanup (); + WinConsole.Cleanup (); + WinConsole = null; } public override Attribute GetAttribute () @@ -1479,28 +1575,30 @@ namespace Terminal.Gui { /// public override bool GetCursorVisibility (out CursorVisibility visibility) { - return winConsole.GetCursorVisibility (out visibility); + return WinConsole.GetCursorVisibility (out visibility); } /// public override bool SetCursorVisibility (CursorVisibility visibility) { - return winConsole.SetCursorVisibility (visibility); + return WinConsole.SetCursorVisibility (visibility); } /// public override bool EnsureCursorVisibility () { - return winConsole.EnsureCursorVisibility (); + return WinConsole.EnsureCursorVisibility (); } public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) { - WindowsConsole.InputRecord input = new WindowsConsole.InputRecord (); - input.EventType = WindowsConsole.EventType.Key; + WindowsConsole.InputRecord input = new WindowsConsole.InputRecord { + EventType = WindowsConsole.EventType.Key + }; - WindowsConsole.KeyEventRecord keyEvent = new WindowsConsole.KeyEventRecord (); - keyEvent.bKeyDown = true; + WindowsConsole.KeyEventRecord keyEvent = new WindowsConsole.KeyEventRecord { + bKeyDown = true + }; WindowsConsole.ControlKeyState controlKey = new WindowsConsole.ControlKeyState (); if (shift) { controlKey |= WindowsConsole.ControlKeyState.ShiftPressed; @@ -1607,12 +1705,12 @@ namespace Terminal.Gui { internal class WindowsMainLoop : IMainLoopDriver { ManualResetEventSlim eventReady = new ManualResetEventSlim (false); ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); - //ManualResetEventSlim winChange = new ManualResetEventSlim (false); + ManualResetEventSlim winChange = new ManualResetEventSlim (false); MainLoop mainLoop; ConsoleDriver consoleDriver; WindowsConsole winConsole; - //bool winChanged; - //Size windowSize; + bool winChanged; + Size windowSize; CancellationTokenSource tokenSource = new CancellationTokenSource (); // The records that we keep fetching @@ -1630,10 +1728,7 @@ namespace Terminal.Gui { public WindowsMainLoop (ConsoleDriver consoleDriver = null) { - if (consoleDriver == null) { - throw new ArgumentNullException ("Console driver instance must be provided."); - } - this.consoleDriver = consoleDriver; + this.consoleDriver = consoleDriver ?? throw new ArgumentNullException ("Console driver instance must be provided."); winConsole = ((WindowsDriver)consoleDriver).WinConsole; } @@ -1641,8 +1736,7 @@ namespace Terminal.Gui { { this.mainLoop = mainLoop; Task.Run (WindowsInputHandler); - // Not working yet. - //Task.Run (CheckWinChange); + Task.Run (CheckWinChange); } void WindowsInputHandler () @@ -1657,29 +1751,31 @@ namespace Terminal.Gui { } } - //void CheckWinChange () - //{ - // while (true) { - // winChange.Wait (); - // winChange.Reset (); - // WaitWinChange (); - // winChanged = true; - // eventReady.Set (); - // } - //} + void CheckWinChange () + { + while (true) { + winChange.Wait (); + winChange.Reset (); + WaitWinChange (); + winChanged = true; + eventReady.Set (); + } + } - //void WaitWinChange () - //{ - // while (true) { - // if (!consoleDriver.HeightAsBuffer) { - // windowSize = new Size (Console.WindowWidth, Console.WindowHeight); - // if (windowSize.Height < consoleDriver.Rows) { - // // I still haven't been able to find a way to capture the shrinking in height. - // //return; - // } - // } - // } - //} + void WaitWinChange () + { + while (true) { + Thread.Sleep (100); + if (!consoleDriver.HeightAsBuffer) { + windowSize = winConsole.GetConsoleBufferWindow (out _); + //System.Diagnostics.Debug.WriteLine ($"{consoleDriver.HeightAsBuffer},{windowSize.Width},{windowSize.Height}"); + if (windowSize != Size.Empty && windowSize.Width != consoleDriver.Cols + || windowSize.Height != consoleDriver.Rows) { + return; + } + } + } + } void IMainLoopDriver.Wakeup () { @@ -1695,8 +1791,7 @@ namespace Terminal.Gui { result = null; waitForProbe.Set (); - // Nor working yet. - //winChange.Set (); + winChange.Set (); try { if (!tokenSource.IsCancellationRequested) { @@ -1709,8 +1804,7 @@ namespace Terminal.Gui { } if (!tokenSource.IsCancellationRequested) { - //return result != null || CheckTimers (wait, out _) || winChanged; - return result != null || CheckTimers (wait, out _); + return result != null || CheckTimers (wait, out _) || winChanged; } tokenSource.Dispose (); @@ -1748,10 +1842,10 @@ namespace Terminal.Gui { result = null; ProcessInput?.Invoke (inputEvent); } - //if (winChanged) { - // winChanged = false; - // WinChanged?.Invoke (windowSize); - //} + if (winChanged) { + winChanged = false; + WinChanged?.Invoke (windowSize); + } } } diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index cfc270db2..3d518e68f 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -118,11 +118,9 @@ namespace Terminal.Gui { if (Driver == null) { throw new ArgumentNullException ("The driver must be initialized first."); } - if (Driver.HeightAsBuffer != value) { Driver.HeightAsBuffer = value; } } - } /// /// Used only by to forcing always moving the cursor position when writing to the screen. @@ -545,6 +543,7 @@ namespace Terminal.Gui { if (OutsideFrame (new Point (nme.X, nme.Y), mouseGrabView.Frame)) { lastMouseOwnerView?.OnMouseLeave (me); } + // System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}"); if (mouseGrabView != null) { mouseGrabView.OnMouseEvent (nme); return;