diff --git a/Examples/UICatalog/Properties/launchSettings.json b/Examples/UICatalog/Properties/launchSettings.json index fe464d100..a20ddaf93 100644 --- a/Examples/UICatalog/Properties/launchSettings.json +++ b/Examples/UICatalog/Properties/launchSettings.json @@ -48,6 +48,30 @@ "commandLineArgs": "dotnet UICatalog.dll --driver v2net", "distributionName": "" }, + "WSL-Gnome: UICatalog": { + "commandName": "Executable", + "executablePath": "wsl", + "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll; exec bash\"'", + "distributionName": "" + }, + "WSL-Gnome: UICatalog --driver NetDriver": { + "commandName": "Executable", + "executablePath": "wsl", + "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver NetDriver; exec bash\"'", + "distributionName": "" + }, + "WSL-Gnome: UICatalog --driver v2": { + "commandName": "Executable", + "executablePath": "wsl", + "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'", + "distributionName": "" + }, + "WSL-Gnome: UICatalog --driver v2net": { + "commandName": "Executable", + "executablePath": "wsl", + "commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'", + "distributionName": "" + }, "Benchmark All": { "commandName": "Project", "commandLineArgs": "--benchmark" diff --git a/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs b/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs index 078ef823a..5694e4be9 100644 --- a/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs +++ b/Examples/UICatalog/Scenarios/NumericUpDownDemo.cs @@ -252,7 +252,7 @@ internal class NumericUpDownEditor : View where T : notnull { X = Pos.Center (), Y = Pos.Bottom (_increment) + 1, - Increment = NumericUpDown.TryConvert (1, out T? increment) ? increment : default, + Increment = NumericUpDown.TryConvert (1, out T? increment) ? increment : default (T?), }; _numericUpDown.ValueChanged += NumericUpDownOnValueChanged; diff --git a/Examples/UICatalog/UICatalogTop.cs b/Examples/UICatalog/UICatalogTop.cs index 7253d695e..2993f6116 100644 --- a/Examples/UICatalog/UICatalogTop.cs +++ b/Examples/UICatalog/UICatalogTop.cs @@ -166,13 +166,23 @@ public class UICatalogTop : Toplevel CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked }; - _force16ColorsMenuItemCb.CheckedStateChanged += (sender, args) => - { - Application.Force16Colors = args.Value == CheckState.Checked; + _force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) => + { + if (Application.Force16Colors + && args.Result == CheckState.UnChecked + && !Application.Driver!.SupportsTrueColor) + { + args.Handled = true; + } + }; - _force16ColorsShortcutCb!.CheckedState = args.Value; - Application.LayoutAndDraw (); - }; + _force16ColorsMenuItemCb.CheckedStateChanged += (sender, args) => + { + Application.Force16Colors = args.Value == CheckState.Checked; + + _force16ColorsShortcutCb!.CheckedState = args.Value; + Application.LayoutAndDraw (); + }; menuItems.Add ( new MenuItemv2 @@ -608,11 +618,22 @@ public class UICatalogTop : Toplevel }; _force16ColorsShortcutCb.CheckedStateChanging += (sender, args) => - { - Application.Force16Colors = args.Result == CheckState.Checked; - _force16ColorsMenuItemCb!.CheckedState = args.Result; - Application.LayoutAndDraw (); - }; + { + if (Application.Force16Colors + && args.Result == CheckState.UnChecked + && !Application.Driver!.SupportsTrueColor) + { + // If the driver does not support TrueColor, we cannot disable 16 colors + args.Handled = true; + } + }; + + _force16ColorsShortcutCb.CheckedStateChanged += (sender, args) => + { + Application.Force16Colors = args.Value == CheckState.Checked; + _force16ColorsMenuItemCb!.CheckedState = args.Value; + Application.LayoutAndDraw (); + }; statusBar.Add ( _shQuit, 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/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs index cf85db80c..5d2252337 100644 --- a/Terminal.Gui/Drivers/ConsoleDriver.cs +++ b/Terminal.Gui/Drivers/ConsoleDriver.cs @@ -269,7 +269,7 @@ public abstract class ConsoleDriver : IConsoleDriver if (Contents [Row, Col - 1].Rune.GetColumns () > 1) { // Invalidate cell to left - Contents [Row, Col - 1].Rune = Rune.ReplacementChar; + Contents [Row, Col - 1].Rune = (Rune)'\0'; Contents [Row, Col - 1].IsDirty = true; } } @@ -308,7 +308,7 @@ public abstract class ConsoleDriver : IConsoleDriver { // Invalidate cell to right so that it doesn't get drawn // TODO: Figure out if it is better to show a replacement character or ' ' - Contents [Row, Col + 1].Rune = Rune.ReplacementChar; + Contents [Row, Col + 1].Rune = (Rune)'\0'; Contents [Row, Col + 1].IsDirty = true; } } diff --git a/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs index 087f3b17d..885478975 100644 --- a/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs @@ -123,12 +123,6 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests) { Platform.Suspend (); - - if (Force16Colors) - { - Curses.Window.Standard.redrawwin (); - Curses.refresh (); - } } StartReportingMouseMoves (); @@ -140,164 +134,98 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) { - if (Force16Colors) - { - Curses.move (Row, Col); - - Curses.raw (); - Curses.noecho (); - Curses.refresh (); - } - else - { - _mainLoopDriver?.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); - } + _mainLoopDriver?.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); } } public override bool UpdateScreen () { bool updated = false; - if (Force16Colors) + if (RunningUnitTests + || Console.WindowHeight < 1 + || Contents?.Length != Rows * Cols + || Rows != Console.WindowHeight) { - for (var row = 0; row < Rows; row++) - { - if (!_dirtyLines! [row]) - { - continue; - } - - _dirtyLines [row] = false; - - for (var col = 0; col < Cols; col++) - { - if (Contents! [row, col].IsDirty == false) - { - continue; - } - - if (RunningUnitTests) - { - // In unit tests, we don't want to actually write to the screen. - continue; - } - - Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor); - - Rune rune = Contents [row, col].Rune; - - if (rune.IsBmp) - { - // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell. - if (rune.GetColumns () < 2) - { - Curses.mvaddch (row, col, rune.Value); - } - else /*if (col + 1 < Cols)*/ - { - Curses.mvaddwstr (row, col, rune.ToString ()); - } - } - else - { - Curses.mvaddwstr (row, col, rune.ToString ()); - - if (rune.GetColumns () > 1 && col + 1 < Cols) - { - // TODO: This is a hack to deal with non-BMP and wide characters. - //col++; - Curses.mvaddch (row, ++col, '*'); - } - } - } - } - - if (!RunningUnitTests) - { - Curses.move (Row, Col); - _window?.wrefresh (); - } + return updated; } - else + + var top = 0; + var left = 0; + int rows = Rows; + int cols = Cols; + var output = new StringBuilder (); + Attribute? redrawAttr = null; + int lastCol = -1; + + CursorVisibility? savedVisibility = _currentCursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + for (int row = top; row < rows; row++) { - if (RunningUnitTests - || Console.WindowHeight < 1 - || Contents!.Length != Rows * Cols - || Rows != Console.WindowHeight) + if (Console.WindowHeight < 1) { return updated; } - var top = 0; - var left = 0; - int rows = Rows; - int cols = Cols; - var output = new StringBuilder (); - Attribute? redrawAttr = null; - int lastCol = -1; - - CursorVisibility? savedVisibility = _currentCursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - - for (int row = top; row < rows; row++) + if (!_dirtyLines! [row]) { - if (Console.WindowHeight < 1) + continue; + } + + if (!SetCursorPosition (0, row)) + { + return updated; + } + + updated = true; + _dirtyLines [row] = false; + output.Clear (); + + for (int col = left; col < cols; col++) + { + lastCol = -1; + var outputWidth = 0; + + for (; col < cols; col++) { - return updated; - } - - if (!_dirtyLines! [row]) - { - continue; - } - - if (!SetCursorPosition (0, row)) - { - return updated; - } - - _dirtyLines [row] = false; - output.Clear (); - - for (int col = left; col < cols; col++) - { - lastCol = -1; - var outputWidth = 0; - - for (; col < cols; col++) + if (!Contents [row, col].IsDirty) { - updated = true; - if (!Contents [row, col].IsDirty) + if (output.Length > 0) { - if (output.Length > 0) - { - WriteToConsole (output, ref lastCol, row, ref outputWidth); - } - else if (lastCol == -1) - { - lastCol = col; - } - - if (lastCol + 1 < cols) - { - lastCol++; - } - - continue; + WriteToConsole (output, ref lastCol, row, ref outputWidth); } - - if (lastCol == -1) + else if (lastCol == -1) { lastCol = col; } - Attribute attr = Contents [row, col].Attribute!.Value; - - // Performance: Only send the escape sequence if the attribute has changed. - if (attr != redrawAttr) + if (lastCol + 1 < cols) { - redrawAttr = attr; + lastCol++; + } + continue; + } + + if (lastCol == -1) + { + lastCol = col; + } + + Attribute attr = Contents [row, col].Attribute!.Value; + + // Performance: Only send the escape sequence if the attribute has changed. + if (attr != redrawAttr) + { + redrawAttr = attr; + + if (Force16Colors) + { + output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ())); + output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ())); + } + else + { output.Append ( EscSeqUtils.CSI_SetForegroundColorRGB ( attr.Foreground.R, @@ -314,60 +242,62 @@ internal class CursesDriver : ConsoleDriver ) ); } - - outputWidth++; - Rune rune = Contents [row, col].Rune; - output.Append (rune); - - if (Contents [row, col].CombiningMarks.Count > 0) - { - // AtlasEngine does not support NON-NORMALIZED combining marks in a way - // compatible with the driver architecture. Any CMs (except in the first col) - // are correctly combined with the base char, but are ALSO treated as 1 column - // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. - // - // For now, we just ignore the list of CMs. - //foreach (var combMark in Contents [row, col].CombiningMarks) { - // output.Append (combMark); - //} - // WriteToConsole (output, ref lastCol, row, ref outputWidth); - } - else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) - { - WriteToConsole (output, ref lastCol, row, ref outputWidth); - SetCursorPosition (col - 1, row); - } - - Contents [row, col].IsDirty = false; } - } - if (output.Length > 0) - { - SetCursorPosition (lastCol, row); - Console.Write (output); + outputWidth++; + Rune rune = Contents [row, col].Rune; + output.Append (rune); + + if (Contents [row, col].CombiningMarks.Count > 0) + { + // AtlasEngine does not support NON-NORMALIZED combining marks in a way + // compatible with the driver architecture. Any CMs (except in the first col) + // are correctly combined with the base char, but are ALSO treated as 1 column + // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. + // + // For now, we just ignore the list of CMs. + //foreach (var combMark in Contents [row, col].CombiningMarks) { + // output.Append (combMark); + //} + // WriteToConsole (output, ref lastCol, row, ref outputWidth); + } + else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) + { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + SetCursorPosition (col - 1, row); + } + + Contents [row, col].IsDirty = false; } } - // SIXELS - foreach (SixelToRender s in Application.Sixel) - { - SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y); - Console.Write (s.SixelData); - } - - SetCursorPosition (0, 0); - - _currentCursorVisibility = savedVisibility; - - void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) + if (output.Length > 0) { SetCursorPosition (lastCol, row); Console.Write (output); - output.Clear (); - lastCol += outputWidth; - outputWidth = 0; } + + foreach (var s in Application.Sixel) + { + if (!string.IsNullOrWhiteSpace (s.SixelData)) + { + SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y); + Console.Write (s.SixelData); + } + } + } + + SetCursorPosition (0, 0); + + _currentCursorVisibility = savedVisibility; + + void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) + { + SetCursorPosition (lastCol, row); + Console.Write (output); + output.Clear (); + lastCol += outputWidth; + outputWidth = 0; } return updated; @@ -396,29 +326,6 @@ internal class CursesDriver : ConsoleDriver ); } - /// - /// - /// In the CursesDriver, colors are encoded as an int. The foreground color is stored in the most significant 4 - /// bits, and the background color is stored in the least significant 4 bits. The Terminal.GUi Color values are - /// converted to curses color encoding before being encoded. - /// - public override Attribute MakeColor (in Color foreground, in Color background) - { - if (!RunningUnitTests && Force16Colors) - { - return MakeColor ( - ColorNameToCursesColorNumber (foreground.GetClosestNamedColor16 ()), - ColorNameToCursesColorNumber (background.GetClosestNamedColor16 ()) - ); - } - - return new ( - 0, - foreground, - background - ); - } - private static short ColorNameToCursesColorNumber (ColorName16 color) { switch (color) diff --git a/Terminal.Gui/Drivers/NetDriver/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver/NetDriver.cs index fa2ccadd9..d11e0d3f0 100644 --- a/Terminal.Gui/Drivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/Drivers/NetDriver/NetDriver.cs @@ -223,7 +223,7 @@ internal class NetDriver : ConsoleDriver // BUGBUG: Fix this nullable issue. /// - internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser; + internal override IAnsiResponseParser GetParser () => _mainLoopDriver!._netEvents!.Parser; internal NetMainLoop? _mainLoopDriver; /// @@ -356,11 +356,6 @@ internal class NetDriver : ConsoleDriver } public override void End () { - if (IsWinPlatform) - { - NetWinConsole?.Cleanup (); - } - StopReportingMouseMoves (); if (!RunningUnitTests) @@ -373,6 +368,11 @@ internal class NetDriver : ConsoleDriver //Set cursor key to cursor. Console.Out.Write (EscSeqUtils.CSI_ShowCursor); Console.Out.Close (); + + // Reset the console to its original state + // after sending the escape sequences to restore + // alternative buffer and cursor visibility. + NetWinConsole?.Cleanup (); } } @@ -746,47 +746,6 @@ internal class NetDriver : ConsoleDriver public virtual void ResizeScreen () { - // Not supported on Unix. - if (IsWinPlatform) - { - // Can raise an exception while is still resizing. - try - { -#pragma warning disable CA1416 - if (Console.WindowHeight > 0) - { - Console.CursorTop = 0; - Console.CursorLeft = 0; - Console.WindowTop = 0; - Console.WindowLeft = 0; - - if (Console.WindowHeight > Rows) - { - Console.SetWindowSize (Cols, Rows); - } - - Console.SetBufferSize (Cols, Rows); - } -#pragma warning restore CA1416 - } - // INTENT: Why are these eating the exceptions? - // Comments would be good here. - catch (IOException) - { - // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (Screen); - } - catch (ArgumentOutOfRangeException) - { - // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (Screen); - } - } - else - { - Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols)); - } - // CONCURRENCY: Unsynchronized access to Clip is not safe. Clip = new (Screen); } diff --git a/Terminal.Gui/Drivers/NetDriver/NetEvents.cs b/Terminal.Gui/Drivers/NetDriver/NetEvents.cs index 2dea3a509..2272a5e1b 100644 --- a/Terminal.Gui/Drivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/Drivers/NetDriver/NetEvents.cs @@ -80,7 +80,7 @@ internal class NetEvents : IDisposable return Console.ReadKey (intercept); } - while (!_netEventsDisposed.IsCancellationRequested) + while (!_netEventsDisposed!.IsCancellationRequested) { Task.Delay (100, _netEventsDisposed.Token).Wait (_netEventsDisposed.Token); @@ -113,11 +113,11 @@ internal class NetEvents : IDisposable private void ProcessInputQueue () { - while (!_netEventsDisposed.IsCancellationRequested) + while (_netEventsDisposed is { IsCancellationRequested: false }) { if (_inputQueue.Count == 0) { - while (!_netEventsDisposed.IsCancellationRequested) + while (_netEventsDisposed is { IsCancellationRequested: false }) { ConsoleKeyInfo consoleKeyInfo; @@ -147,7 +147,7 @@ internal class NetEvents : IDisposable { void RequestWindowSize () { - while (!_netEventsDisposed.IsCancellationRequested) + while (_netEventsDisposed is { IsCancellationRequested: false }) { // Wait for a while then check if screen has changed sizes Task.Delay (500, _netEventsDisposed.Token).Wait (_netEventsDisposed.Token); @@ -179,7 +179,7 @@ internal class NetEvents : IDisposable _netEventsDisposed.Token.ThrowIfCancellationRequested (); } - while (!_netEventsDisposed.IsCancellationRequested) + while (!_netEventsDisposed!.IsCancellationRequested) { try { @@ -434,10 +434,6 @@ internal class NetEvents : IDisposable new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv } ); } - else - { - return; - } break; @@ -563,15 +559,15 @@ internal class NetEvents : IDisposable public readonly override string ToString () { - return EventType switch - { - EventType.Key => ToString (ConsoleKeyInfo), - EventType.Mouse => MouseEvent.ToString (), + return (EventType switch + { + EventType.Key => ToString (ConsoleKeyInfo), + EventType.Mouse => MouseEvent.ToString (), - //EventType.WindowSize => WindowSize.ToString (), - //EventType.RequestResponse => RequestResponse.ToString (), - _ => "Unknown event type: " + EventType - }; + //EventType.WindowSize => WindowSize.ToString (), + //EventType.RequestResponse => RequestResponse.ToString (), + _ => "Unknown event type: " + EventType + })!; } /// Prints a ConsoleKeyInfoEx structure diff --git a/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs b/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs index 39dee104c..873fba47d 100644 --- a/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs +++ b/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs @@ -5,31 +5,31 @@ namespace Terminal.Gui.Drivers; internal class NetWinVTConsole { - private const uint DISABLE_NEWLINE_AUTO_RETURN = 8; - private const uint ENABLE_ECHO_INPUT = 4; - private const uint ENABLE_EXTENDED_FLAGS = 128; - private const uint ENABLE_INSERT_MODE = 32; - private const uint ENABLE_LINE_INPUT = 2; - private const uint ENABLE_LVB_GRID_WORLDWIDE = 10; - private const uint ENABLE_MOUSE_INPUT = 16; - // Input modes. private const uint ENABLE_PROCESSED_INPUT = 1; + private const uint ENABLE_LINE_INPUT = 2; + private const uint ENABLE_ECHO_INPUT = 4; + private const uint ENABLE_WINDOW_INPUT = 8; + private const uint ENABLE_MOUSE_INPUT = 16; + private const uint ENABLE_INSERT_MODE = 32; + private const uint ENABLE_QUICK_EDIT_MODE = 64; + private const uint ENABLE_EXTENDED_FLAGS = 128; + private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; // Output modes. private const uint ENABLE_PROCESSED_OUTPUT = 1; - private const uint ENABLE_QUICK_EDIT_MODE = 64; - private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512; - private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; - private const uint ENABLE_WINDOW_INPUT = 8; private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2; + private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; + private const uint DISABLE_NEWLINE_AUTO_RETURN = 8; + private const uint ENABLE_LVB_GRID_WORLDWIDE = 10; + + // Standard handles. private const int STD_ERROR_HANDLE = -12; private const int STD_INPUT_HANDLE = -10; private const int STD_OUTPUT_HANDLE = -11; - private readonly nint _errorHandle; + // Handles and original console modes. private readonly nint _inputHandle; - private readonly uint _originalErrorConsoleMode; private readonly uint _originalInputConsoleMode; private readonly uint _originalOutputConsoleMode; private readonly nint _outputHandle; @@ -45,7 +45,7 @@ internal class NetWinVTConsole _originalInputConsoleMode = mode; - if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) + if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) == 0) { mode |= ENABLE_VIRTUAL_TERMINAL_INPUT; @@ -64,34 +64,15 @@ internal class NetWinVTConsole _originalOutputConsoleMode = mode; - if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) + if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) { - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode (_outputHandle, mode)) { throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}."); } } - - _errorHandle = GetStdHandle (STD_ERROR_HANDLE); - - if (!GetConsoleMode (_errorHandle, out mode)) - { - throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}."); - } - - _originalErrorConsoleMode = mode; - - if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN) - { - mode |= DISABLE_NEWLINE_AUTO_RETURN; - - if (!SetConsoleMode (_errorHandle, mode)) - { - throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}."); - } - } } public void Cleanup () @@ -110,11 +91,6 @@ internal class NetWinVTConsole { throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}."); } - - if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode)) - { - throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}."); - } } [DllImport ("kernel32.dll")] diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 2e42ae3fc..e08ddb32d 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -234,7 +234,7 @@ internal partial class WindowsOutput : OutputBase, IConsoleOutput public override void Write (IOutputBuffer outputBuffer) { _force16Colors = Application.Driver!.Force16Colors; - _everythingStringBuilder = new StringBuilder (); + _everythingStringBuilder.Clear (); // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. _consoleBuffer = 0; @@ -485,7 +485,7 @@ internal partial class WindowsOutput : OutputBase, IConsoleOutput private bool _isDisposed; private bool _force16Colors; private nint _consoleBuffer; - private StringBuilder _everythingStringBuilder; + private StringBuilder _everythingStringBuilder = new (); /// public void Dispose () diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs index ba3dee599..7038c144c 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs @@ -2,6 +2,8 @@ using System.Collections.Concurrent; using System.ComponentModel; using System.Runtime.InteropServices; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable IDE1006// Naming rule violation: Prefix '_' is not expected namespace Terminal.Gui.Drivers; @@ -35,10 +37,69 @@ public partial class WindowsConsole newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput; ConsoleMode = newConsoleMode; - _inputReadyCancellationTokenSource = new (); + IsVirtualTerminal = GetConsoleMode (_outputHandle, out uint mode) && (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0; + + if (!IsVirtualTerminal) + { + CreateConsoleScreenBuffer (); + Size bufferSize = GetConsoleBufferWindow (out _); + SmallRect window = new () + { + Top = 0, + Left = 0, + Bottom = (short)bufferSize.Height, + Right = (short)bufferSize.Width + }; + + ReadFromConsoleOutput (bufferSize, new ((short)bufferSize.Width, (short)bufferSize.Height), ref window); + + 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 ()}."); + } + } + + SetInitialCursorVisibility (); + + _inputReadyCancellationTokenSource = new (); Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token); } + private void CreateConsoleScreenBuffer () + { + _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 ()); + } + } + public InputRecord? DequeueInput () { while (_inputReadyCancellationTokenSource is { }) @@ -146,35 +207,138 @@ public partial class WindowsConsole private CharInfo []? _originalStdOutChars; + private struct Run + { + public ushort attr; + public string text; + + public Run (ushort attr, string text) + { + this.attr = attr; + this.text = text; + } + } + public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors) { //Debug.WriteLine ("WriteToConsole"); - if (!IsWindowsTerminal && _screenBuffer == nint.Zero) - { - ReadFromConsoleOutput (size, bufferSize, ref window); - } - - SetInitialCursorVisibility (); - + Attribute? prev = null; var result = false; if (force16Colors) { + _stringBuilder.Clear (); + var i = 0; - CharInfo [] ci = new CharInfo [charInfoBuffer.Length]; + List runs = []; + Run? current = null; + SetCursorPosition (new Coord (0, 0)); foreach (ExtendedCharInfo info in charInfoBuffer) { - ci [i++] = new CharInfo + if (IsVirtualTerminal) { - Char = new CharUnion { UnicodeChar = info.Char }, - Attributes = - (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4)) - }; + Attribute attr = info.Attribute; + AnsiColorCode fgColor = info.Attribute.Foreground.GetAnsiColorCode (); + AnsiColorCode bgColor = info.Attribute.Background.GetAnsiColorCode (); + + if (attr != prev) + { + prev = attr; + _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColor (fgColor)); + _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColor (bgColor)); + + EscSeqUtils.CSI_AppendTextStyleChange (_stringBuilder, _redrawTextStyle, attr.Style); + _redrawTextStyle = attr.Style; + } + + if (info.Char [0] != '\x1b') + { + if (!info.Empty) + { + _stringBuilder.Append (info.Char); + } + } + else + { + _stringBuilder.Append (' '); + } + } + else + { + if (info.Empty) + { + i++; + continue; + } + + if (!info.Empty) + { + var attr = (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () + | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4)); + + // Start new run if needed + if (current == null || attr != current.Value.attr) + { + if (current != null) + { + runs.Add (new (current.Value.attr, _stringBuilder.ToString ())); + } + + _stringBuilder.Clear (); + current = new Run (attr, ""); + } + + _stringBuilder!.Append (info.Char); + } + + i++; + + if (i > 0 && i <= charInfoBuffer.Length && i % bufferSize.X == 0) + { + if (i < charInfoBuffer.Length) + { + _stringBuilder.AppendLine (); + } + + runs.Add (new (current!.Value.attr, _stringBuilder.ToString ())); + _stringBuilder.Clear (); + } + } } - result = WriteConsoleOutput (IsWindowsTerminal ? _outputHandle : _screenBuffer, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window); + if (IsVirtualTerminal) + { + _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_HideCursor); + + var s = _stringBuilder.ToString (); + + // TODO: requires extensive testing if we go down this route + // If console output has changed + if (s != _lastWrite) + { + // supply console with the new content + result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero); + } + + _lastWrite = s; + + foreach (var sixel in Application.Sixel) + { + SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y)); + WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + } + } + else + { + foreach (var run in runs) + { + SetConsoleTextAttribute (IsVirtualTerminal ? _outputHandle : _screenBuffer, run.attr); + result = WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, run.text, (uint)run.text.Length, out _, nint.Zero); + } + } } else { @@ -183,8 +347,6 @@ public partial class WindowsConsole _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); EscSeqUtils.CSI_AppendCursorPosition (_stringBuilder, 0, 0); - Attribute? prev = null; - foreach (ExtendedCharInfo info in charInfoBuffer) { Attribute attr = info.Attribute; @@ -198,7 +360,7 @@ public partial class WindowsConsole _redrawTextStyle = attr.Style; } - if (info.Char != '\x1b') + if (info.Char [0] != '\x1b') { if (!info.Empty) { @@ -229,7 +391,7 @@ public 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); } } @@ -259,29 +421,6 @@ public partial class WindowsConsole 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); - } - } - - if (!SetConsoleActiveScreenBuffer (_screenBuffer)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } - _originalStdOutChars = new CharInfo [size.Height * size.Width]; if (!ReadConsoleOutput (_screenBuffer, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window)) @@ -292,7 +431,7 @@ public 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 +444,14 @@ public 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 +519,7 @@ public 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 +569,7 @@ public 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 +579,7 @@ public 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 +618,19 @@ public 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 ()); } @@ -509,11 +648,18 @@ public partial class WindowsConsole return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); } + internal Size GetLargestConsoleWindowSize () + { + Coord maxWinSize = GetLargestConsoleWindowSize (IsVirtualTerminal ? _outputHandle : _screenBuffer); + + return new (maxWinSize.X, maxWinSize.Y); + } + 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 +667,7 @@ public 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 +677,7 @@ public 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 +702,7 @@ public partial class WindowsConsole return sz; } - internal bool IsWindowsTerminal { get; set; } + internal bool IsVirtualTerminal { get; init; } private uint ConsoleMode { @@ -573,6 +719,7 @@ public partial class WindowsConsole public enum ConsoleModes : uint { EnableProcessedInput = 1, + EnableVirtualTerminalProcessing = 4, EnableMouseInput = 16, EnableQuickEditMode = 64, EnableExtendedFlags = 128 @@ -791,11 +938,11 @@ public partial class WindowsConsole public struct ExtendedCharInfo { - public char Char { get; set; } + public char [] Char { get; set; } public Attribute Attribute { get; set; } public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences - public ExtendedCharInfo (char character, Attribute attribute) + public ExtendedCharInfo (char [] character, Attribute attribute) { Char = character; Attribute = attribute; @@ -946,7 +1093,13 @@ public partial class WindowsConsole ); [DllImport ("kernel32.dll", SetLastError = true)] - static extern bool FlushFileBuffers (nint hFile); + private static extern bool SetConsoleTextAttribute ( + nint hConsoleOutput, + ushort wAttributes + ); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool FlushFileBuffers (nint hFile); [DllImport ("kernel32.dll")] private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition); diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index 59941924f..19f3e4dcb 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; } @@ -337,7 +335,7 @@ internal class WindowsDriver : ConsoleDriver if (Contents [row, col].IsDirty == false) { _outputBuffer [position].Empty = true; - _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; + _outputBuffer [position].Char = [(char)Contents [row, col].Rune.Value]; continue; } @@ -346,12 +344,12 @@ internal class WindowsDriver : ConsoleDriver if (Contents [row, col].Rune.IsBmp) { - _outputBuffer [position].Char = (char)Contents [row, col].Rune.Value; + _outputBuffer [position].Char = [(char)Contents [row, col].Rune.Value]; } else { - //_outputBuffer [position].Empty = true; - _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; + _outputBuffer [position].Char = [(char)Contents [row, col].Rune.ToString () [0], + (char)Contents [row, col].Rune.ToString () [1]]; if (Contents [row, col].Rune.GetColumns () > 1 && col + 1 < Cols) { @@ -359,7 +357,7 @@ internal class WindowsDriver : ConsoleDriver col++; position = row * Cols + col; _outputBuffer [position].Empty = false; - _outputBuffer [position].Char = ' '; + _outputBuffer [position].Char = ['\0']; } } } @@ -396,7 +394,7 @@ internal class WindowsDriver : ConsoleDriver { #if HACK_CHECK_WINCHANGED - _mainLoopDriver.WinChanged -= ChangeWin; + _mainLoopDriver.WinChanged -= ChangeWin!; #endif } @@ -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); @@ -422,9 +420,9 @@ internal class WindowsDriver : ConsoleDriver { if (WinConsole is { }) { - // 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 _); + // The results from GetConsoleBufferWindow are correct when called from Init. + // Our thread in WindowsMainLoop.CheckWin will get the resize event. See #if HACK_CHECK_WINCHANGED + Size winSize = WinConsole.GetConsoleBufferWindow (out _); Cols = winSize.Width; Rows = winSize.Height; OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); @@ -432,7 +430,7 @@ internal class WindowsDriver : ConsoleDriver WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); - if (_isWindowsTerminal) + if (_isVirtualTerminal) { Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); } @@ -463,7 +461,7 @@ internal class WindowsDriver : ConsoleDriver ClearContents (); #if HACK_CHECK_WINCHANGED - _mainLoopDriver.WinChanged = ChangeWin; + _mainLoopDriver.WinChanged = ChangeWin!; #endif if (!RunningUnitTests) @@ -604,13 +602,6 @@ internal class WindowsDriver : ConsoleDriver return; } - int w = e.Size.Value.Width; - - if (w == Cols - 3 && e.Size.Value.Height < Rows) - { - w += 3; - } - Left = 0; Top = 0; Cols = e.Size.Value.Width; @@ -618,9 +609,9 @@ internal class WindowsDriver : ConsoleDriver if (!RunningUnitTests) { - Size newSize = WinConsole.SetConsoleWindow ( - (short)Math.Max (w, 16), - (short)Math.Max (e.Size.Value.Height, 0)); + Size newSize = WinConsole!.SetConsoleWindow ( + (short)Math.Max (e.Size.Value.Width, 16), + (short)Math.Max (e.Size.Value.Height, 0)); Cols = newSize.Width; Rows = newSize.Height; diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs index 73308ae85..1dc098145 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs @@ -220,6 +220,7 @@ internal class WindowsMainLoop : IMainLoopDriver private readonly ManualResetEventSlim _winChange = new (false); private bool _winChanged; private Size _windowSize; + private Size? _lastWindowSizeBeforeMaximized = null; private void CheckWinChange () { while (_mainLoop is { }) @@ -232,7 +233,22 @@ internal class WindowsMainLoop : IMainLoopDriver while (_mainLoop is { }) { Task.Delay (500).Wait (); - _windowSize = _winConsole.GetConsoleBufferWindow (out _); + Size largestWindowSize = _winConsole!.GetLargestConsoleWindowSize (); + _windowSize = _winConsole!.GetConsoleBufferWindow (out _); + + if (_lastWindowSizeBeforeMaximized is null && _windowSize == largestWindowSize) + { + _lastWindowSizeBeforeMaximized = new (_consoleDriver.Cols, _consoleDriver.Rows); + } + else if (_lastWindowSizeBeforeMaximized is { } && _windowSize != largestWindowSize) + { + if (_windowSize != _lastWindowSizeBeforeMaximized) + { + _windowSize = _lastWindowSizeBeforeMaximized.Value; + } + + _lastWindowSizeBeforeMaximized = null; + } if (_windowSize != Size.Empty && (_windowSize.Width != _consoleDriver.Cols diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 369de9e8a..6fa45e276 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -111,7 +111,7 @@ public class ComboBox : View, IDesignable } /// - protected override bool OnSettingScheme (ValueChangingEventArgs args) + protected override bool OnSettingScheme (ValueChangingEventArgs args) { _listview.SetScheme(args.NewValue); return base.OnSettingScheme (args); diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs index deaacbb0b..344399b82 100644 --- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs @@ -115,7 +115,7 @@ public class FileDialog : Dialog, IDesignable _btnUp.Text = GetUpButtonText (); _btnUp.Accepting += (s, e) => { - _history.Up (); + _history?.Up (); e.Handled = true; }; @@ -123,7 +123,7 @@ public class FileDialog : Dialog, IDesignable _btnBack.Text = GetBackButtonText (); _btnBack.Accepting += (s, e) => { - _history.Back (); + _history?.Back (); e.Handled = true; }; @@ -131,7 +131,7 @@ public class FileDialog : Dialog, IDesignable _btnForward.Text = GetForwardButtonText (); _btnForward.Accepting += (s, e) => { - _history.Forward (); + _history?.Forward (); e.Handled = true; }; @@ -605,7 +605,7 @@ public class FileDialog : Dialog, IDesignable bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, - string pathText = null + string? pathText = null ) { // no change of state @@ -1109,7 +1109,7 @@ public class FileDialog : Dialog, IDesignable bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, - string pathText = null + string? pathText = null ) { if (State is SearchState search) diff --git a/Terminal.Gui/Views/NumericUpDown.cs b/Terminal.Gui/Views/NumericUpDown.cs index cfa5bbc6c..b0066c363 100644 --- a/Terminal.Gui/Views/NumericUpDown.cs +++ b/Terminal.Gui/Views/NumericUpDown.cs @@ -264,28 +264,28 @@ public class NumericUpDown : View where T : notnull protected override bool OnDrawingText () { return true; } /// - /// Attempts to convert the specified to type . + /// Attempts to convert the specified to type . /// - /// The type to which the value should be converted. + /// The type to which the value should be converted. /// The value to convert. /// /// When this method returns, contains the converted value if the conversion succeeded, - /// or the default value of if the conversion failed. + /// or the default value of if the conversion failed. /// /// /// true if the conversion was successful; otherwise, false. /// - public static bool TryConvert (object value, out T? result) + public static bool TryConvert (object value, out TValue? result) { try { - result = (T)Convert.ChangeType (value, typeof (T)); + result = (TValue)Convert.ChangeType (value, typeof (TValue)); return true; } catch { - result = default (T); + result = default (TValue); return false; } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index 5ad3ecd62..2cf55d85e 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Drawing; using TerminalGuiFluentTesting; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace Terminal.Gui.Drivers; @@ -15,26 +16,26 @@ public class FakeApplicationFactory { var cts = new CancellationTokenSource (); var fakeInput = new FakeNetInput (cts.Token); - FakeOutput _output = new (); - _output.Size = new (25, 25); + FakeOutput output = new (); + output.Size = new (25, 25); IApplication origApp = ApplicationImpl.Instance; var sizeMonitor = new FakeSizeMonitor (); - var v2 = new ApplicationV2 (new FakeNetComponentFactory (fakeInput, _output, sizeMonitor)); + var v2 = new ApplicationV2 (new FakeNetComponentFactory (fakeInput, output, sizeMonitor)); ApplicationImpl.ChangeInstance (v2); v2.Init (null,"v2net"); - var d = (ConsoleDriverFacade)Application.Driver; + var d = (ConsoleDriverFacade)Application.Driver!; sizeMonitor.SizeChanging += (_, e) => { if (e.Size != null) { var s = e.Size.Value; - _output.Size = s; + output.Size = s; d.OutputBuffer.SetWindowSize (s.Width, s.Height); } }; @@ -95,7 +96,7 @@ class FakeDriverV2 : ConsoleDriverFacade, IFakeDriverV2 { public ConcurrentQueue InputBuffer { get; } public FakeSizeMonitor SizeMonitor { get; } - public OutputBuffer OutputBuffer { get; } + public new OutputBuffer OutputBuffer { get; } public IConsoleOutput ConsoleOutput { get; } diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index f4aad2a54..31608bae4 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -2,6 +2,7 @@ using System.Drawing; using System.Text; using Microsoft.Extensions.Logging; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace TerminalGuiFluentTesting; @@ -386,16 +387,16 @@ public class GuiTestContext : IDisposable { case V2TestDriver.V2Win: - _winInput.InputBuffer.Enqueue ( - new () - { - EventType = WindowsConsole.EventType.Mouse, - MouseEvent = new () - { - ButtonState = btn, - MousePosition = new ((short)screenX, (short)screenY) - } - }); + _winInput.InputBuffer!.Enqueue ( + new () + { + EventType = WindowsConsole.EventType.Mouse, + MouseEvent = new () + { + ButtonState = btn, + MousePosition = new ((short)screenX, (short)screenY) + } + }); _winInput.InputBuffer.Enqueue ( new () @@ -410,7 +411,6 @@ public class GuiTestContext : IDisposable return WaitUntil (() => _winInput.InputBuffer.IsEmpty); - break; case V2TestDriver.V2Net: int netButton = btn switch @@ -471,8 +471,6 @@ public class GuiTestContext : IDisposable } return WaitIteration (); - - ; } /// @@ -699,12 +697,12 @@ public class GuiTestContext : IDisposable down.bKeyDown = true; up.bKeyDown = false; - _winInput.InputBuffer.Enqueue ( - new () - { - EventType = WindowsConsole.EventType.Key, - KeyEvent = down - }); + _winInput.InputBuffer!.Enqueue ( + new () + { + EventType = WindowsConsole.EventType.Key, + KeyEvent = down + }); _winInput.InputBuffer.Enqueue ( new () @@ -718,7 +716,7 @@ public class GuiTestContext : IDisposable private void SendNetKey (ConsoleKeyInfo consoleKeyInfo, bool wait = true) { - _netInput.InputBuffer.Enqueue (consoleKeyInfo); + _netInput.InputBuffer!.Enqueue (consoleKeyInfo); if (wait) { @@ -732,20 +730,20 @@ public class GuiTestContext : IDisposable /// private void SendWindowsKey (ConsoleKeyMapping.VK specialKey) { - _winInput.InputBuffer.Enqueue ( - new () - { - EventType = WindowsConsole.EventType.Key, - KeyEvent = new () - { - bKeyDown = true, - wRepeatCount = 0, - wVirtualKeyCode = specialKey, - wVirtualScanCode = 0, - UnicodeChar = '\0', - dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed - } - }); + _winInput.InputBuffer!.Enqueue ( + new () + { + EventType = WindowsConsole.EventType.Key, + KeyEvent = new () + { + bKeyDown = true, + wRepeatCount = 0, + wVirtualKeyCode = specialKey, + wVirtualScanCode = 0, + UnicodeChar = '\0', + dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed + } + }); _winInput.InputBuffer.Enqueue ( new () diff --git a/Tests/TerminalGuiFluentTesting/TextWriterLogger.cs b/Tests/TerminalGuiFluentTesting/TextWriterLogger.cs index 7f81a1c70..39c39266b 100644 --- a/Tests/TerminalGuiFluentTesting/TextWriterLogger.cs +++ b/Tests/TerminalGuiFluentTesting/TextWriterLogger.cs @@ -4,7 +4,7 @@ namespace TerminalGuiFluentTesting; internal class TextWriterLogger (TextWriter writer) : ILogger { - public IDisposable? BeginScope (TState state) { return null; } + public IDisposable? BeginScope (TState state) where TState : notnull { return null; } public bool IsEnabled (LogLevel logLevel) { return true; }