From 7097c0b7e330ac485a4d5d53258d003ae335022b Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 22 Jul 2025 16:24:12 +0100 Subject: [PATCH] Replace IsWindowsTerminal with IsVirtualTerminal --- .../Drawing/Sixel/SixelSupportDetector.cs | 4 +- .../Drivers/V2/ConsoleDriverFacade.cs | 11 ++- .../Drivers/V2/MainLoopCoordinator.cs | 19 ++++- Terminal.Gui/Drivers/V2/WindowsInput.cs | 6 ++ Terminal.Gui/Drivers/V2/WindowsOutput.cs | 78 ++++++++++++++----- .../Drivers/WindowsDriver/WindowsConsole.cs | 37 +++++---- .../Drivers/WindowsDriver/WindowsDriver.cs | 14 ++-- 7 files changed, 118 insertions(+), 51 deletions(-) diff --git a/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs b/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs index 2ee0ee8fa..1d9c78cab 100644 --- a/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs +++ b/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs @@ -20,7 +20,7 @@ public class SixelSupportDetector public void Detect (Action resultCallback) { var result = new SixelSupportResult (); - result.SupportsTransparency = IsWindowsTerminal () || IsXtermWithTransparency (); + result.SupportsTransparency = IsVirtualTerminal () || IsXtermWithTransparency (); IsSixelSupportedByDar (result, resultCallback); } @@ -142,7 +142,7 @@ public class SixelSupportDetector private static bool ResponseIndicatesSupport (string response) { return response.Split (';').Contains ("4"); } - private static bool IsWindowsTerminal () + private static bool IsVirtualTerminal () { return !string.IsNullOrWhiteSpace (Environment.GetEnvironmentVariable ("WT_SESSION")); diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index c89c63965..2441cfc35 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -28,6 +28,15 @@ internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade _outputBuffer = outputBuffer; _ansiRequestScheduler = ansiRequestScheduler; + if (InputProcessor is WindowsInputProcessor) + { + SupportsTrueColor = new WindowsInput ().IsVirtualTerminal (); + } + else if (InputProcessor is NetInputProcessor) + { + SupportsTrueColor = Application.Driver.SupportsTrueColor; + } + InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e); InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e); InputProcessor.MouseEvent += (s, e) => @@ -145,7 +154,7 @@ internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade // TODO: Probably not everyone right? /// Gets whether the supports TrueColor output. - public bool SupportsTrueColor => true; + public bool SupportsTrueColor { get; init; } = true; // TODO: Currently ignored /// diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs index a2cc34c49..46697437a 100644 --- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs @@ -25,7 +25,6 @@ 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); @@ -61,7 +60,6 @@ internal class MainLoopCoordinator : IMainLoopCoordinator _inputProcessor = inputProcessor; _outputFactory = outputFactory; _loop = loop; - _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; } /// @@ -162,7 +160,15 @@ internal class MainLoopCoordinator : IMainLoopCoordinator _loop.AnsiRequestScheduler, _loop.WindowSizeMonitor); - if (!_isWindowsTerminal) + if (_facade.SupportsTrueColor) + { + if (!ConsoleDriver.RunningUnitTests) + { + // Enable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + } + } + else { Application.Force16Colors = _facade.Force16Colors = true; } @@ -187,6 +193,13 @@ internal class MainLoopCoordinator : IMainLoopCoordinator _stopCalled = true; _tokenSource.Cancel (); + + if (!ConsoleDriver.RunningUnitTests && _facade.SupportsTrueColor) + { + // Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + } + _output.Dispose (); // Wait for input infinite loop to exit diff --git a/Terminal.Gui/Drivers/V2/WindowsInput.cs b/Terminal.Gui/Drivers/V2/WindowsInput.cs index 11d01cb60..48460e037 100644 --- a/Terminal.Gui/Drivers/V2/WindowsInput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsInput.cs @@ -56,6 +56,12 @@ internal class WindowsInput : ConsoleInput, IWindows SetConsoleMode (_inputHandle, newConsoleMode); } + internal bool IsVirtualTerminal () + { + nint outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + return GetConsoleMode (outputHandle, out uint mode) && (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0; + } + protected override bool Peek () { const int bufferSize = 1; // We only need to check if there's at least one event diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 8a155cd19..43aa54ec7 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -59,7 +59,17 @@ internal partial class WindowsOutput : IConsoleOutput [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo); - private readonly nint _screenBuffer; + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern nint GetStdHandle (int nStdHandle); + + [DllImport ("kernel32.dll")] + private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); + + [DllImport ("kernel32.dll")] + private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode); + + private nint _screenBuffer; + private nint _outputHandle; // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). private TextStyle _redrawTextStyle = TextStyle.None; @@ -73,33 +83,61 @@ internal partial class WindowsOutput : IConsoleOutput return; } - _screenBuffer = CreateConsoleScreenBuffer ( - DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - ShareMode.FileShareRead | ShareMode.FileShareWrite, - nint.Zero, - 1, - nint.Zero - ); + _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); - if (_screenBuffer == INVALID_HANDLE_VALUE) + if (!GetConsoleMode (_outputHandle, out uint mode)) { - int err = Marshal.GetLastWin32Error (); - - if (err != 0) - { - throw new Win32Exception (err); - } + throw new ApplicationException ($"Failed to get _outputHandle console mode, error code: {Marshal.GetLastWin32Error ()}."); } - if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + IsVirtualTerminal = (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0; + + if (!IsVirtualTerminal) { - throw new Win32Exception (Marshal.GetLastWin32Error ()); + _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); + } + } + + if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + if (!GetConsoleMode (_screenBuffer, out mode)) + { + throw new ApplicationException ($"Failed to get screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}."); + } + + const uint ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002; + + mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; // Disable wrap + + if (!SetConsoleMode (_screenBuffer, mode)) + { + throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}."); + } } } + internal bool IsVirtualTerminal { get; init; } + public void Write (ReadOnlySpan str) { - if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero)) + if (!WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero)) { throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer."); } @@ -270,7 +308,7 @@ internal partial class WindowsOutput : IConsoleOutput stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length); // Supply console with the new content. - result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero); + result = WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero); } finally { @@ -280,7 +318,7 @@ internal partial class WindowsOutput : IConsoleOutput foreach (SixelToRender sixel in Application.Sixel) { SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y); - WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } } diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs index 130058889..d66d25e72 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs @@ -35,7 +35,9 @@ internal partial class WindowsConsole newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; ConsoleMode = newConsoleMode; - _inputReadyCancellationTokenSource = new (); + IsVirtualTerminal = GetConsoleMode (_outputHandle, out uint mode) && (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0; + + _inputReadyCancellationTokenSource = new (); Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); } @@ -150,7 +152,7 @@ internal partial class WindowsConsole { //Debug.WriteLine ("WriteToConsole"); - if (!IsWindowsTerminal && _screenBuffer == nint.Zero) + if (!IsVirtualTerminal && _screenBuffer == nint.Zero) { ReadFromConsoleOutput (size, bufferSize, ref window); } @@ -229,7 +231,7 @@ internal partial class WindowsConsole foreach (var sixel in Application.Sixel) { SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y)); - WriteConsole (IsWindowsTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } } @@ -292,7 +294,7 @@ internal partial class WindowsConsole public bool SetCursorPosition (Coord position) { - return SetConsoleCursorPosition (IsWindowsTerminal ? _outputHandle : _screenBuffer, position); + return SetConsoleCursorPosition (IsVirtualTerminal ? _outputHandle : _screenBuffer, position); } public void SetInitialCursorVisibility () @@ -305,14 +307,14 @@ internal partial class WindowsConsole public bool GetCursorVisibility (out CursorVisibility visibility) { - if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero) + if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero) { visibility = CursorVisibility.Invisible; return false; } - if (!GetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info)) + if (!GetConsoleCursorInfo (IsVirtualTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info)) { int err = Marshal.GetLastWin32Error (); @@ -380,7 +382,7 @@ internal partial class WindowsConsole bVisible = ((uint)visibility & 0xFF00) != 0 }; - if (!SetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref info)) + if (!SetConsoleCursorInfo (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref info)) { return false; } @@ -430,7 +432,7 @@ internal partial class WindowsConsole internal Size GetConsoleBufferWindow (out Point position) { - if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero) + if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero) { position = Point.Empty; @@ -440,7 +442,7 @@ internal partial class WindowsConsole var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); position = Point.Empty; @@ -479,19 +481,19 @@ internal partial class WindowsConsole var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { throw new Win32Exception (Marshal.GetLastWin32Error ()); } - Coord maxWinSize = GetLargestConsoleWindowSize (IsWindowsTerminal ? _outputHandle : _screenBuffer); + Coord maxWinSize = GetLargestConsoleWindowSize (IsVirtualTerminal ? _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)) + if (!SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { throw new Win32Exception (Marshal.GetLastWin32Error ()); } @@ -511,9 +513,9 @@ internal partial class WindowsConsole private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi) { - if ((IsWindowsTerminal + if ((IsVirtualTerminal ? _outputHandle - : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { throw new Win32Exception (Marshal.GetLastWin32Error ()); } @@ -521,7 +523,7 @@ internal partial class WindowsConsole internal Size SetConsoleOutputWindow (out Point position) { - if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero) + if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero) { position = Point.Empty; @@ -531,7 +533,7 @@ internal partial class WindowsConsole var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi)) + if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { throw new Win32Exception (Marshal.GetLastWin32Error ()); } @@ -556,7 +558,7 @@ internal partial class WindowsConsole return sz; } - internal bool IsWindowsTerminal { get; set; } + internal bool IsVirtualTerminal { get; init; } private uint ConsoleMode { @@ -573,6 +575,7 @@ internal partial class WindowsConsole public enum ConsoleModes : uint { EnableProcessedInput = 1, + EnableVirtualTerminalProcessing = 4, EnableMouseInput = 16, EnableQuickEditMode = 64, EnableExtendedFlags = 128 diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index 0f7e5616d..7a265e8dc 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -24,7 +24,7 @@ namespace Terminal.Gui.Drivers; internal class WindowsDriver : ConsoleDriver { - private readonly bool _isWindowsTerminal; + private readonly bool _isVirtualTerminal; private WindowsConsole.SmallRect _damageRegion; private bool _isButtonDoubleClicked; @@ -57,18 +57,16 @@ internal class WindowsDriver : ConsoleDriver // force 16color mode (.e.g ConEmu which really doesn't work well at all). if (!RunningUnitTests) { - WinConsole!.IsWindowsTerminal = _isWindowsTerminal = - Environment.GetEnvironmentVariable ("WT_SESSION") is { } - || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; + _isVirtualTerminal = WinConsole!.IsVirtualTerminal; } - if (!_isWindowsTerminal) + if (!_isVirtualTerminal) { Force16Colors = true; } } - public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isWindowsTerminal); + public override bool SupportsTrueColor => RunningUnitTests || (Environment.OSVersion.Version.Build >= 14931 && _isVirtualTerminal); public WindowsConsole? WinConsole { get; private set; } @@ -405,7 +403,7 @@ internal class WindowsDriver : ConsoleDriver WinConsole?.Cleanup (); WinConsole = null; - if (!RunningUnitTests && _isWindowsTerminal) + if (!RunningUnitTests && _isVirtualTerminal) { // Disable alternative screen buffer. Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); @@ -432,7 +430,7 @@ internal class WindowsDriver : ConsoleDriver WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); - if (_isWindowsTerminal) + if (_isVirtualTerminal) { Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); }