From eec477a89f32fb93cdf8ab8e94a249195218e044 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 2 Dec 2020 17:46:20 +0000 Subject: [PATCH] Fixes #1028. WindowsDriver does not handle Window docking properly with the -usc argument. --- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 247 ++++++++++++++----- UICatalog/UICatalog.cs | 22 ++ 2 files changed, 203 insertions(+), 66 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index abc70c0a1..5d66b6a81 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -59,34 +59,13 @@ namespace Terminal.Gui { public bool WriteToConsole (CharInfo [] charInfoBuffer, Coord coords, SmallRect window) { if (ScreenBuffer == IntPtr.Zero) { - ScreenBuffer = CreateConsoleScreenBuffer ( - DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - ShareMode.FileShareRead | ShareMode.FileShareWrite, - IntPtr.Zero, - 1, - IntPtr.Zero - ); - if (ScreenBuffer == INVALID_HANDLE_VALUE) { - var err = Marshal.GetLastWin32Error (); - - if (err != 0) - throw new System.ComponentModel.Win32Exception (err); - } - - if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) { - var err = Marshal.GetLastWin32Error (); - throw new System.ComponentModel.Win32Exception (err); - } - - OriginalStdOutChars = new CharInfo [Console.WindowHeight * Console.WindowWidth]; - - ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window); + window = ReadFromConsoleOutput (new Size (Console.WindowWidth, Console.WindowHeight), coords, window); } return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window); } - public void ReadFromConsoleOutput (Size size, Coord coords) + public SmallRect ReadFromConsoleOutput (Size size, Coord coords, SmallRect window) { ScreenBuffer = CreateConsoleScreenBuffer ( DesiredAccess.GenericRead | DesiredAccess.GenericWrite, @@ -98,18 +77,22 @@ namespace Terminal.Gui { if (ScreenBuffer == INVALID_HANDLE_VALUE) { var err = Marshal.GetLastWin32Error (); - if (err != 0) + if (err != 0 && HeightAsBuffer) { throw new System.ComponentModel.Win32Exception (err); + } } if (!SetConsoleActiveScreenBuffer (ScreenBuffer)) { var err = Marshal.GetLastWin32Error (); - throw new System.ComponentModel.Win32Exception (err); + if (HeightAsBuffer) { + throw new System.ComponentModel.Win32Exception (err); + } } OriginalStdOutChars = new CharInfo [size.Height * size.Width]; - SmallRect window = new SmallRect (); ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window); + + return window; } public bool SetCursorPosition (Coord position) @@ -146,6 +129,8 @@ namespace Terminal.Gui { } } + public bool HeightAsBuffer { get; set; } + [Flags] public enum ConsoleModes : uint { EnableProcessedInput = 1, @@ -483,6 +468,11 @@ namespace Terminal.Gui { [DllImport ("kernel32.dll", ExactSpelling = true)] static extern IntPtr GetConsoleWindow (); + internal IntPtr GetConsole () + { + return GetConsoleWindow (); + } + [DllImport ("user32.dll")] [return: MarshalAs (UnmanagedType.Bool)] static extern bool GetWindowPlacement (IntPtr hWnd, ref WindowPlacement lpwndpl); @@ -498,7 +488,9 @@ namespace Terminal.Gui { public System.Drawing.Point ptMinPosition; public System.Drawing.Point ptMaxPosition; public System.Drawing.Rectangle rcNormalPosition; +#if _MAC public System.Drawing.Rectangle rcDevice; +#endif } // flags @@ -508,7 +500,7 @@ namespace Terminal.Gui { // showCmd public const int HIDE = 0; - public const int NORMAL = 1; + public const int SHOW_NORMAL = 1; public const int SHOW_MINIMIZED = 2; public const int SHOW_MAXIMIZED = 3; public const int SHOW_NOACTIVATE = 4; @@ -520,28 +512,62 @@ namespace Terminal.Gui { public const int SHOW_DEFAULT = 10; public const int FORCE_MINIMIZE = 11; - internal WindowPlacement GetWindow () + internal bool GetWindow (IntPtr handle, ref WindowPlacement placement) { - IntPtr thisConsole = GetConsoleWindow (); - WindowPlacement placement = new WindowPlacement { + placement = new WindowPlacement { length = Marshal.SizeOf (typeof (WindowPlacement)) }; - GetWindowPlacement (thisConsole, ref placement); - - return placement; + return GetWindowPlacement (handle, ref placement); } - internal void SetWindow (int showCmd) + internal bool SetWindow (IntPtr handle, ref WindowPlacement placement) { - IntPtr thisConsole = GetConsoleWindow (); - WindowPlacement placement = new WindowPlacement { - length = Marshal.SizeOf (typeof (WindowPlacement)), - showCmd = showCmd - }; - SetWindowPlacement (thisConsole, ref placement); + return SetWindowPlacement (handle, ref placement); + } + + [DllImport ("user32.dll", SetLastError = true)] + static extern bool GetWindowRect (IntPtr hwnd, out System.Drawing.Rectangle lpRect); + + internal bool GetRect (IntPtr handle, out System.Drawing.Rectangle lpRect) + { + return GetWindowRect (handle, out lpRect); } #if false + // size of a device name string + private const int CCHDEVICENAME = 32; + + [StructLayout (LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct MonitorInfoEx { + public uint cbSize; + public System.Drawing.Rectangle rcMonitor; + public System.Drawing.Rectangle rcWork; + public int dwFlags; + [MarshalAs (UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string szDevice; + } + + [DllImport ("user32.dll", CharSet = CharSet.Auto)] + static extern bool GetMonitorInfo (IntPtr hMonitor, ref MonitorInfoEx lpmi); + + internal bool GetMonitor(IntPtr hMonitor, ref MonitorInfoEx minfo) + { + minfo.cbSize = (uint)Marshal.SizeOf (minfo); + return GetMonitorInfo (hMonitor, ref minfo); + } + + [DllImport ("user32.dll")] + static extern IntPtr MonitorFromWindow (IntPtr hwnd, uint dwFlags); + + public const int MONITOR_DEFAULTTONULL = 0; + public const int MONITOR_DEFAULTTOPRIMARY = 1; + public const int MONITOR_DEFAULTTONEAREST = 2; + + internal IntPtr GetMonitorWindow (IntPtr hwnd, uint dwFlag) + { + return MonitorFromWindow (hwnd, dwFlag); + } + [DllImport ("kernel32.dll", SetLastError = true)] static extern bool GetConsoleScreenBufferInfo (IntPtr hConsoleOutput, out ConsoleScreenBufferInfo ConsoleScreenBufferInfo); @@ -580,10 +606,12 @@ namespace Terminal.Gui { public WindowsDriver () { - winConsole = new WindowsConsole (); + winConsole = new WindowsConsole () { + HeightAsBuffer = this.HeightAsBuffer + }; } - bool winChanging; + MainLoop mainLoop; public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { @@ -591,6 +619,7 @@ namespace Terminal.Gui { this.keyDownHandler = keyDownHandler; this.keyUpHandler = keyUpHandler; this.mouseHandler = mouseHandler; + this.mainLoop = mainLoop; var mLoop = mainLoop.Driver as WindowsMainLoop; @@ -599,6 +628,9 @@ namespace Terminal.Gui { mLoop.WinChanged = (e) => ChangeWin (e); } + bool winChanging; + bool wasChangeWin; + void ChangeWin (Size size) { if (!HeightAsBuffer) { @@ -606,21 +638,22 @@ namespace Terminal.Gui { top = 0; cols = size.Width; rows = size.Height; + ResizeScreen (); + UpdateOffScreen (); var bufferCoords = new WindowsConsole.Coord () { X = (short)cols, Y = (short)rows }; - winConsole.ReadFromConsoleOutput (size, bufferCoords); - ResizeScreen (); - UpdateOffScreen (); + winConsole.ReadFromConsoleOutput (size, bufferCoords, damageRegion); if (!winChanging) { TerminalResized.Invoke (); - } else { - System.Diagnostics.Debugger.Break (); } + wasChangeWin = true; } } + bool isFromRestore; + void ProcessInput (WindowsConsole.InputRecord inputEvent) { switch (inputEvent.EventType) { @@ -718,6 +751,18 @@ namespace Terminal.Gui { ResizeScreen (); UpdateOffScreen (); TerminalResized?.Invoke (); + } else if (!HeightAsBuffer && !wasChangeWin && !(mainLoop.Driver as WindowsMainLoop).Maximized + && !isFromRestore) { + ChangeWin (new Size (inputEvent.WindowBufferSizeEvent.size.X, + inputEvent.WindowBufferSizeEvent.size.Y)); + } else if (!HeightAsBuffer && wasChangeWin && (mainLoop.Driver as WindowsMainLoop).Restored) { + (mainLoop.Driver as WindowsMainLoop).Restored = false; + isFromRestore = true; + } else if (!HeightAsBuffer && wasChangeWin && !(mainLoop.Driver as WindowsMainLoop).Maximized + && !(mainLoop.Driver as WindowsMainLoop).Restored && !isFromRestore) { + wasChangeWin = false; + } else if (isFromRestore) { + isFromRestore = false; } break; @@ -1358,7 +1403,6 @@ namespace Terminal.Gui { ConsoleDriver consoleDriver; WindowsConsole winConsole; bool winChanged; - WindowsConsole.WindowPlacement windowPlacement; Size windowSize; CancellationTokenSource tokenSource = new CancellationTokenSource (); @@ -1375,6 +1419,9 @@ namespace Terminal.Gui { /// public Action WinChanged; + public bool Maximized; + public bool Restored; + public WindowsMainLoop (ConsoleDriver consoleDriver = null) { if (consoleDriver == null) { @@ -1414,27 +1461,93 @@ namespace Terminal.Gui { } } + const int Width_Divider = 8; + const int Height_Divider = 18; + bool docked; + void WaitWinChange () { - while (true) { - if (!consoleDriver.HeightAsBuffer) { - windowPlacement = winConsole.GetWindow (); - if (windowPlacement.rcNormalPosition.Size.Height > -1) { - windowSize = new Size (Math.Max (((windowPlacement.rcNormalPosition.Size.Width - - windowPlacement.rcNormalPosition.X) / 8) - 2, 0), - Math.Max (((windowPlacement.rcNormalPosition.Size.Height - - windowPlacement.rcNormalPosition.Y) / 16) - 7, 0)); - if (windowPlacement.showCmd != WindowsConsole.SHOW_MAXIMIZED - && (windowSize.Width != consoleDriver.Cols || windowSize.Height != consoleDriver.Rows)) { - return; - } else if (windowPlacement.showCmd == WindowsConsole.SHOW_MAXIMIZED - && (Console.LargestWindowWidth != consoleDriver.Cols || Console.LargestWindowHeight != consoleDriver.Rows)) { - windowSize = new Size (Console.LargestWindowWidth, Console.LargestWindowHeight); - return; - } + var handle = winConsole.GetConsole (); + + while (!consoleDriver.HeightAsBuffer) { + WindowsConsole.WindowPlacement windowPlacement = new WindowsConsole.WindowPlacement (); + winConsole.GetWindow (handle, ref windowPlacement); + + if (windowPlacement.rcNormalPosition.Size.Height > -1) { + windowSize = SetWindowSize (windowPlacement.rcNormalPosition); + + if (windowPlacement.showCmd != WindowsConsole.SHOW_MAXIMIZED && !Maximized && !Restored && !docked + && (windowSize.Width != consoleDriver.Cols || windowSize.Height != consoleDriver.Rows)) { + docked = false; + return; + } else if (windowPlacement.showCmd == WindowsConsole.SHOW_MAXIMIZED && !Maximized + && (Console.LargestWindowWidth != consoleDriver.Cols || Console.LargestWindowHeight != consoleDriver.Rows)) { + windowSize = new Size (Console.LargestWindowWidth, Console.LargestWindowHeight); + Maximized = true; + docked = false; + return; + } else if (windowPlacement.showCmd != WindowsConsole.SHOW_MAXIMIZED && Maximized) { + windowPlacement = new WindowsConsole.WindowPlacement () { + showCmd = WindowsConsole.RESTORE + }; + winConsole.SetWindow (handle, ref windowPlacement); + Restored = true; + Maximized = false; + docked = false; + return; + } else if (!Maximized && IsDockedToMonitor (handle, windowPlacement)) { + return; } } } + + Size SetWindowSize (System.Drawing.Rectangle rect) + { + return new Size (Math.Max (((rect.Width - rect.X) / Width_Divider) - 2, 0), + Math.Max (((rect.Height - rect.Y) / Height_Divider) - 2, 0)); + } + + bool IsDockedToMonitor (IntPtr hWnd, WindowsConsole.WindowPlacement placement) + { + System.Drawing.Rectangle rc; + winConsole.GetRect (hWnd, out rc); + + var changed = placement.showCmd == WindowsConsole.SHOW_NORMAL + && (rc.Left != placement.rcNormalPosition.Left || + rc.Top != placement.rcNormalPosition.Top || + rc.Right != placement.rcNormalPosition.Right || + rc.Bottom != placement.rcNormalPosition.Bottom); + + if (changed) { + var pSize = new Size (placement.rcNormalPosition.Size.Width - placement.rcNormalPosition.X, + placement.rcNormalPosition.Size.Height - placement.rcNormalPosition.Y); + var rSize = new Size (rc.Width - rc.X, + rc.Height - rc.Y); + windowSize = SetWindowSize (rc); + + if ((rc.X < 0) || (rc.Y == 0) || (rc.Y == 0 && rc.X < 0) + || (rc.Y == 0 && rc.Right / Width_Divider >= Console.LargestWindowWidth) + || (rc.X < 0 && rc.Bottom / Height_Divider >= Console.LargestWindowHeight) + || (rc.X / Width_Divider >= Console.LargestWindowWidth / 2 - 1 && rc.Bottom / Height_Divider >= Console.LargestWindowHeight)) { + if (!docked || consoleDriver.Cols != windowSize.Width + || consoleDriver.Rows != windowSize.Height) { + docked = true; + } else { + changed = false; + } + } else { + if (!docked && (pSize == rSize || rSize.Width / Width_Divider >= Console.LargestWindowWidth + || rSize.Height / Height_Divider >= Console.LargestWindowHeight)) { + changed = false; + } + docked = false; + } + } else { + docked = false; + } + + return changed; + } } void IMainLoopDriver.Wakeup () @@ -1451,7 +1564,9 @@ namespace Terminal.Gui { //result = null; waitForProbe.Set (); - winChange.Set (); + if (!consoleDriver.HeightAsBuffer) { + winChange.Set (); + } try { if (!tokenSource.IsCancellationRequested) { @@ -1464,7 +1579,7 @@ namespace Terminal.Gui { } if (!tokenSource.IsCancellationRequested) { - return result != null || CheckTimers (wait, out waitTimeout) || winChanged; + return result != null || CheckTimers (wait, out _) || winChanged; } tokenSource.Dispose (); diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs index 983168346..6643e5dd6 100644 --- a/UICatalog/UICatalog.cs +++ b/UICatalog/UICatalog.cs @@ -167,6 +167,7 @@ namespace UICatalog { }), new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()), new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()), + new MenuBarItem ("_Size Style", CreateSizeStyle()), new MenuBarItem ("_Help", new MenuItem [] { new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://migueldeicaza.github.io/gui.cs/articles/overview.html"), null, null, Key.F1), new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/migueldeicaza/gui.cs"), null, null, Key.F2), @@ -274,6 +275,27 @@ namespace UICatalog { return _runningScenario; } + static MenuItem [] CreateSizeStyle () + { + List menuItems = new List (); + for (int i = 0; i < 2; i++) { + var item = new MenuItem (); + item.Title = i == 0 ? "_Normal" : "_As Buffer"; + item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0]; + item.CheckType |= MenuItemCheckStyle.Radio; + item.Checked = i == 0 && !Application.HeightAsBuffer ? true : false; + item.Action += () => { + item.Checked = !item.Checked; + Application.HeightAsBuffer = item.Title == "_Normal" ? false : true; + foreach (var menuItem in menuItems) { + menuItem.Checked = menuItem.Title.Equals (item.Title); + } + }; + menuItems.Add (item); + } + return menuItems.ToArray (); + } + static MenuItem [] CreateDiagnosticMenuItems () { const string OFF = "Diagnostics: _Off";