From c870c26bc969a3770728450c831ea50a658eb36a Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Apr 2025 22:47:27 +0100 Subject: [PATCH 1/4] Fix WindowsDriver to work with non-WindowsTerminal --- .../WindowsDriver/WindowsConsole.cs | 244 +++++++++--------- .../WindowsDriver/WindowsDriver.cs | 11 +- .../WindowsDriver/WindowsMainLoop.cs | 2 + 3 files changed, 132 insertions(+), 125 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index f2fa41e25b..0a6d12e967 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -17,7 +17,7 @@ internal partial class WindowsConsole private readonly nint _inputHandle; private nint _outputHandle; - //private nint _screenBuffer; + private nint _screenBuffer; private readonly uint _originalConsoleMode; private CursorVisibility? _initialCursorVisibility; private CursorVisibility? _currentCursorVisibility; @@ -147,10 +147,12 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord { //Debug.WriteLine ("WriteToConsole"); - //if (_screenBuffer == nint.Zero) - //{ - // ReadFromConsoleOutput (size, bufferSize, ref window); - //} + if (!IsWindowsTerminal && _screenBuffer == nint.Zero) + { + ReadFromConsoleOutput (size, bufferSize, ref window); + } + + SetInitialCursorVisibility (); var result = false; @@ -169,7 +171,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord }; } - result = WriteConsoleOutput (_outputHandle, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); + result = WriteConsoleOutput (IsWindowsTerminal ? _outputHandle : _screenBuffer, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); } else { @@ -222,7 +224,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord foreach (var sixel in Application.Sixel) { SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y)); - WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + WriteConsole (IsWindowsTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } } @@ -252,34 +254,32 @@ internal bool WriteANSI (string ansi) public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) { - //_screenBuffer = CreateConsoleScreenBuffer ( - // DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - // ShareMode.FileShareRead | ShareMode.FileShareWrite, - // nint.Zero, - // 1, - // nint.Zero - // ); - - //if (_screenBuffer == INVALID_HANDLE_VALUE) - //{ - // int err = Marshal.GetLastWin32Error (); - - // if (err != 0) - // { - // throw new Win32Exception (err); - // } - //} + _screenBuffer = CreateConsoleScreenBuffer ( + DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + ShareMode.FileShareRead | ShareMode.FileShareWrite, + nint.Zero, + 1, + nint.Zero + ); - SetInitialCursorVisibility (); + if (_screenBuffer == INVALID_HANDLE_VALUE) + { + int err = Marshal.GetLastWin32Error (); + + if (err != 0) + { + throw new Win32Exception (err); + } + } - //if (!SetConsoleActiveScreenBuffer (_screenBuffer)) - //{ - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - //} + if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } _originalStdOutChars = new CharInfo [size.Height * size.Width]; - if (!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 Win32Exception (Marshal.GetLastWin32Error ()); } @@ -287,7 +287,7 @@ public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window public bool SetCursorPosition (Coord position) { - return SetConsoleCursorPosition (_outputHandle, position); + return SetConsoleCursorPosition (IsWindowsTerminal ? _outputHandle : _screenBuffer, position); } public void SetInitialCursorVisibility () @@ -300,14 +300,14 @@ public void SetInitialCursorVisibility () public bool GetCursorVisibility (out CursorVisibility visibility) { - if (_outputHandle == nint.Zero) + if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero) { visibility = CursorVisibility.Invisible; return false; } - if (!GetConsoleCursorInfo (_outputHandle, out ConsoleCursorInfo info)) + if (!GetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info)) { int err = Marshal.GetLastWin32Error (); @@ -375,7 +375,7 @@ public bool SetCursorVisibility (CursorVisibility visibility) bVisible = ((uint)visibility & 0xFF00) != 0 }; - if (!SetConsoleCursorInfo (_outputHandle, ref info)) + if (!SetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref info)) { return false; } @@ -411,12 +411,12 @@ public void Cleanup () Console.WriteLine ("Error: {0}", err); } - //if (_screenBuffer != nint.Zero) - //{ - // CloseHandle (_screenBuffer); - //} + if (_screenBuffer != nint.Zero) + { + CloseHandle (_screenBuffer); + } - //_screenBuffer = nint.Zero; + _screenBuffer = nint.Zero; _inputReadyCancellationTokenSource?.Cancel (); _inputReadyCancellationTokenSource?.Dispose (); @@ -425,7 +425,7 @@ public void Cleanup () internal Size GetConsoleBufferWindow (out Point position) { - if (_outputHandle == nint.Zero) + if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero) { position = Point.Empty; @@ -435,7 +435,7 @@ internal Size GetConsoleBufferWindow (out Point position) var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) { //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); position = Point.Empty; @@ -469,85 +469,89 @@ internal Size GetConsoleOutputWindow (out Point position) 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 Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer); - // short newCols = Math.Min (cols, maxWinSize.X); - // short newRows = Math.Min (rows, maxWinSize.Y); - // csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); - // csbi.srWindow = new SmallRect (0, 0, newCols, newRows); - // csbi.dwMaximumWindowSize = new Coord (newCols, newRows); - - // if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); - - // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) - // { - // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - // return new (cols, rows); - // } - - // SetConsoleOutputWindow (csbi); - - // return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); - //} - - //private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) - //{ - // if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - //} - - //internal Size SetConsoleOutputWindow (out Point position) - //{ - // if (_screenBuffer == nint.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 Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // Size sz = new ( - // csbi.srWindow.Right - csbi.srWindow.Left + 1, - // Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); - // position = new (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 (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) - // { - // throw new Win32Exception (Marshal.GetLastWin32Error ()); - // } - - // return sz; - //} + internal Size SetConsoleWindow (short cols, short rows) + { + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + Coord maxWinSize = GetLargestConsoleWindowSize (IsWindowsTerminal ? _outputHandle : _screenBuffer); + short newCols = Math.Min (cols, maxWinSize.X); + short newRows = Math.Min (rows, maxWinSize.Y); + csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1)); + csbi.srWindow = new SmallRect (0, 0, newCols, newRows); + csbi.dwMaximumWindowSize = new Coord (newCols, newRows); + + if (!SetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); + + if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + { + //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + return new (cols, rows); + } + + SetConsoleOutputWindow (csbi); + + return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); + } + + private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) + { + if ((IsWindowsTerminal + ? _outputHandle + : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + } + + internal Size SetConsoleOutputWindow (out Point position) + { + if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero) + { + position = Point.Empty; + + return Size.Empty; + } + + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + Size sz = new ( + csbi.srWindow.Right - csbi.srWindow.Left + 1, + Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0)); + position = new (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 (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + return sz; + } + + internal bool IsWindowsTerminal { get; set; } private uint ConsoleMode { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 1fc1eb8424..107aad1cb8 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -14,7 +14,7 @@ // the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is // still incorrect so we still need this hack. -//#define HACK_CHECK_WINCHANGED +#define HACK_CHECK_WINCHANGED using System.ComponentModel; using System.Diagnostics; @@ -57,8 +57,9 @@ public WindowsDriver () // TODO: if some other Windows-based terminal supports true color, update this logic to not // force 16color mode (.e.g ConEmu which really doesn't work well at all). - _isWindowsTerminal = _isWindowsTerminal = - Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; + WinConsole!.IsWindowsTerminal = _isWindowsTerminal = _isWindowsTerminal = + Environment.GetEnvironmentVariable ("WT_SESSION") is { } + || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; if (!_isWindowsTerminal) { @@ -422,7 +423,7 @@ public override MainLoop Init () { // BUGBUG: The results from GetConsoleOutputWindow are incorrect when called from Init. // Our thread in WindowsMainLoop.CheckWin will get the correct results. See #if HACK_CHECK_WINCHANGED - Size winSize = WinConsole.GetConsoleOutputWindow (out Point _); + Size winSize = WinConsole.GetConsoleOutputWindow (out _); Cols = winSize.Width; Rows = winSize.Height; OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); @@ -466,7 +467,7 @@ public override MainLoop Init () if (!RunningUnitTests) { - WinConsole?.SetInitialCursorVisibility (); + WinConsole?.SetInitialCursorVisibility (); } return new MainLoop (_mainLoopDriver); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index 13fcafbd9a..547f5dcd2a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -1,5 +1,7 @@ #nullable enable +#define HACK_CHECK_WINCHANGED + using System.Collections.Concurrent; namespace Terminal.Gui; From bbe1d6fd5a7c9b0f3aa1daf3858c0a7c69b0a763 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 25 Apr 2025 23:35:46 +0100 Subject: [PATCH 2/4] Fix unit test failure --- .../ConsoleDrivers/WindowsDriver/WindowsDriver.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 107aad1cb8..b45f7d4ebe 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -57,9 +57,12 @@ public WindowsDriver () // TODO: if some other Windows-based terminal supports true color, update this logic to not // force 16color mode (.e.g ConEmu which really doesn't work well at all). - WinConsole!.IsWindowsTerminal = _isWindowsTerminal = _isWindowsTerminal = - Environment.GetEnvironmentVariable ("WT_SESSION") is { } - || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; + if (!RunningUnitTests) + { + WinConsole!.IsWindowsTerminal = _isWindowsTerminal = + Environment.GetEnvironmentVariable ("WT_SESSION") is { } + || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; + } if (!_isWindowsTerminal) { From 32bf299e49f66a294a1b9c3cc78e909573ed11db Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Apr 2025 15:43:36 +0100 Subject: [PATCH 3/4] Fix v2win to work with non-WindowsTerminal --- .../ConsoleDrivers/V2/MainLoopCoordinator.cs | 8 ++++++ .../ConsoleDrivers/V2/WindowsOutput.cs | 26 +++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs index 2effde3b73..506384b834 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs @@ -25,6 +25,7 @@ internal class MainLoopCoordinator : IMainLoopCoordinator private ConsoleDriverFacade _facade; private Task _inputTask; private readonly ITimedEvents _timedEvents; + private readonly bool _isWindowsTerminal; private readonly SemaphoreSlim _startupSemaphore = new (0, 1); @@ -60,6 +61,7 @@ IMainLoop loop _inputProcessor = inputProcessor; _outputFactory = outputFactory; _loop = loop; + _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; } /// @@ -159,6 +161,12 @@ private void BuildFacadeIfPossible () _output, _loop.AnsiRequestScheduler, _loop.WindowSizeMonitor); + + if (!_isWindowsTerminal) + { + _facade.Force16Colors = true; + } + Application.Driver = _facade; _startupSemaphore.Release (); diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs index ba111c130f..81142fb83c 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs @@ -56,6 +56,9 @@ private enum DesiredAccess : uint [DllImport ("kernel32.dll")] private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition); + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo); + private readonly nint _screenBuffer; public WindowsOutput () @@ -170,7 +173,7 @@ public void Write (IOutputBuffer buffer) outputBuffer, bufferCoords, damageRegion, - false)) + Application.Driver!.Force16Colors)) { int err = Marshal.GetLastWin32Error (); @@ -304,10 +307,23 @@ public Size GetWindowSize () /// public void SetCursorVisibility (CursorVisibility visibility) { - string cursorVisibilitySequence = visibility != CursorVisibility.Invisible - ? EscSeqUtils.CSI_ShowCursor - : EscSeqUtils.CSI_HideCursor; - Write (cursorVisibilitySequence); + if (Application.Driver!.Force16Colors) + { + var info = new ConsoleCursorInfo + { + dwSize = (uint)visibility & 0x00FF, + bVisible = ((uint)visibility & 0xFF00) != 0 + }; + + SetConsoleCursorInfo (_screenBuffer, ref info); + } + else + { + string cursorVisibilitySequence = visibility != CursorVisibility.Invisible + ? EscSeqUtils.CSI_ShowCursor + : EscSeqUtils.CSI_HideCursor; + Write (cursorVisibilitySequence); + } } private Point _lastCursorPosition; From 50119ca6f9e36d9ce6abcaa8356a86a743a45fda Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 26 Apr 2025 19:58:38 +0100 Subject: [PATCH 4/4] Force16Colors isn't being setting in v2win driver on changing. --- Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs | 6 +++++- Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs index 700caf72cb..2cebd145d7 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs @@ -141,7 +141,11 @@ public int Top /// , indicating that the cannot support TrueColor. /// /// - public bool Force16Colors { get; set; } + public bool Force16Colors + { + get => Application.Force16Colors || !SupportsTrueColor; + set => Application.Force16Colors = value || !SupportsTrueColor; + } /// /// The that will be used for the next or diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs index 506384b834..9594bb08bc 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs @@ -164,7 +164,7 @@ private void BuildFacadeIfPossible () if (!_isWindowsTerminal) { - _facade.Force16Colors = true; + Application.Force16Colors = _facade.Force16Colors = true; } Application.Driver = _facade;