Переглянути джерело

Fixes #4200. ExtendedCharInfo needs be enhanced to properly deal with all codepoints (#4202)

* Fixes #4196. Application.Begin doesn't refresh the screen at start

* Fixes #4198. Application.Invoke isn't wakeup the driver if idle

* Reformatting to run CI again

* Revert "Reformatting to run CI again"

This reverts commit ef639c1e6489022a3df840147f2261239f9c2a5d.

* Trying fix an issue where sometimes subview variable is null running unit tests

* Replace ExtendedCharInfo.Char with char array

* Replace IsWindowsTerminal with IsVirtualTerminal

* Add a lastSize parameter to process resize automatically

* Handling surrogate pairs in input

* Implement SetConsoleTextAttribute

* Prevent select true color is not supported

* Fix null exception

* Revert GetWindowSize and add SetWindowSize

* Fix unit tests

* Revert all v2 changes except the one related with the ExtendedCharInfo

* Revert newlines and FakeOutput

* Prevents null reference

* Add gnome-terminal to launch settings

* Fixes issue on restore window size after maximize causing width shrinking

* Add ; exec bash to stay in terminal

* Fixes issue on restore window size after maximize causing width shrinking

* Tidying up input and output console modes

* Fixes uninitialized screen buffer.

* Revert "Fixes issue on restore window size after maximize causing width shrinking"

This reverts commit e5edad79f67155b5675d10f9e4c77f935b10082e.

* Reset console after sending escape sequences

* Remove unnecessary code only for buggy VSDebugConsole

* Fix more annoying exceptions

* Ensure flush the input buffer before reset the console

* Remove unnecessary ENABLE_VIRTUAL_TERMINAL_INPUT

* Remove unnecessary error handles

* Fix CI warnings

* Fix more CI warnings

* Fix more CI warnings

* Fixes #2796. CursesDriver doesn't render wide codepoints correctly

---------

Co-authored-by: Tig <[email protected]>
BDisp 15 годин тому
батько
коміт
ad8ebc9890

+ 24 - 0
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"

+ 1 - 1
Examples/UICatalog/Scenarios/NumericUpDownDemo.cs

@@ -252,7 +252,7 @@ internal class NumericUpDownEditor<T> : View where T : notnull
             {
                 X = Pos.Center (),
                 Y = Pos.Bottom (_increment) + 1,
-                Increment = NumericUpDown<int>.TryConvert (1, out T? increment) ? increment : default,
+                Increment = NumericUpDown<int>.TryConvert (1, out T? increment) ? increment : default (T?),
             };
 
             _numericUpDown.ValueChanged += NumericUpDownOnValueChanged;

+ 31 - 10
Examples/UICatalog/UICatalogTop.cs

@@ -166,13 +166,23 @@ public class UICatalogTop : Toplevel
                 CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked
             };
 
+            _force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) =>
+                                                             {
+                                                                 if (Application.Force16Colors
+                                                                     && args.Result == CheckState.UnChecked
+                                                                     && !Application.Driver!.SupportsTrueColor)
+                                                                 {
+                                                                     args.Handled = true;
+                                                                 }
+                                                             };
+
             _force16ColorsMenuItemCb.CheckedStateChanged += (sender, args) =>
-            {
-                Application.Force16Colors = args.Value == CheckState.Checked;
+                                                            {
+                                                                Application.Force16Colors = args.Value == CheckState.Checked;
 
-                _force16ColorsShortcutCb!.CheckedState = args.Value;
-                Application.LayoutAndDraw ();
-            };
+                                                                _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,

+ 2 - 2
Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs

@@ -20,7 +20,7 @@ public class SixelSupportDetector
     public void Detect (Action<SixelSupportResult> 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"));
 

+ 2 - 2
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;
                             }
                         }

+ 105 - 198
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);
+            return updated;
+        }
 
-                    Rune rune = Contents [row, col].Rune;
+        var top = 0;
+        var left = 0;
+        int rows = Rows;
+        int cols = Cols;
+        var output = new StringBuilder ();
+        Attribute? redrawAttr = null;
+        int lastCol = -1;
 
-                    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 ());
+        CursorVisibility? savedVisibility = _currentCursorVisibility;
+        SetCursorVisibility (CursorVisibility.Invisible);
 
-                        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, '*');
-                        }
-                    }
-                }
+        for (int row = top; row < rows; row++)
+        {
+            if (Console.WindowHeight < 1)
+            {
+                return updated;
             }
 
-            if (!RunningUnitTests)
+            if (!_dirtyLines! [row])
             {
-                Curses.move (Row, Col);
-                _window?.wrefresh ();
+                continue;
             }
-        }
-        else
-        {
-            if (RunningUnitTests
-                || Console.WindowHeight < 1
-                || Contents!.Length != Rows * Cols
-                || Rows != Console.WindowHeight)
+
+            if (!SetCursorPosition (0, row))
             {
                 return updated;
             }
 
-            var top = 0;
-            var left = 0;
-            int rows = Rows;
-            int cols = Cols;
-            var output = new StringBuilder ();
-            Attribute? redrawAttr = null;
-            int lastCol = -1;
+            updated = true;
+            _dirtyLines [row] = false;
+            output.Clear ();
 
-            CursorVisibility? savedVisibility = _currentCursorVisibility;
-            SetCursorVisibility (CursorVisibility.Invisible);
-
-            for (int row = top; row < rows; row++)
+            for (int col = left; col < cols; col++)
             {
-                if (Console.WindowHeight < 1)
-                {
-                    return updated;
-                }
+                lastCol = -1;
+                var outputWidth = 0;
 
-                if (!_dirtyLines! [row])
+                for (; col < cols; col++)
                 {
-                    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,62 +242,64 @@ 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);
-                        }
+                    outputWidth++;
+                    Rune rune = Contents [row, col].Rune;
+                    output.Append (rune);
 
-                        Contents [row, col].IsDirty = false;
+                    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);
                     }
-                }
 
-                if (output.Length > 0)
-                {
-                    SetCursorPosition (lastCol, row);
-                    Console.Write (output);
+                    Contents [row, col].IsDirty = false;
                 }
             }
 
-            // SIXELS
-            foreach (SixelToRender s in Application.Sixel)
+            if (output.Length > 0)
             {
-                SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y);
-                Console.Write (s.SixelData);
+                SetCursorPosition (lastCol, row);
+                Console.Write (output);
             }
 
-            SetCursorPosition (0, 0);
-
-            _currentCursorVisibility = savedVisibility;
-
-            void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+            foreach (var s in Application.Sixel)
             {
-                SetCursorPosition (lastCol, row);
-                Console.Write (output);
-                output.Clear ();
-                lastCol += outputWidth;
-                outputWidth = 0;
+                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
                    );
     }
 
-    /// <inheritdoc/>
-    /// <remarks>
-    ///     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.
-    /// </remarks>
-    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)

+ 6 - 47
Terminal.Gui/Drivers/NetDriver/NetDriver.cs

@@ -223,7 +223,7 @@ internal class NetDriver : ConsoleDriver
 
     // BUGBUG: Fix this nullable issue.
     /// <inheritdoc />
-    internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
+    internal override IAnsiResponseParser GetParser () => _mainLoopDriver!._netEvents!.Parser;
     internal NetMainLoop? _mainLoopDriver;
 
     /// <inheritdoc />
@@ -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);
     }

+ 13 - 17
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
+                    })!;
         }
 
         /// <summary>Prints a ConsoleKeyInfoEx structure</summary>

+ 17 - 41
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")]

+ 2 - 2
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 ();
 
     /// <inheritdoc/>
     public void Dispose ()

+ 210 - 57
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;
 
-    public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
+    private struct Run
     {
-        //Debug.WriteLine ("WriteToConsole");
+        public ushort attr;
+        public string text;
 
-        if (!IsWindowsTerminal && _screenBuffer == nint.Zero)
+        public Run (ushort attr, string text)
         {
-            ReadFromConsoleOutput (size, bufferSize, ref window);
+            this.attr = attr;
+            this.text = text;
         }
+    }
 
-        SetInitialCursorVisibility ();
+    public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
+    {
+        //Debug.WriteLine ("WriteToConsole");
 
+        Attribute? prev = null;
         var result = false;
 
         if (force16Colors)
         {
+            _stringBuilder.Clear ();
+
             var i = 0;
-            CharInfo [] ci = new CharInfo [charInfoBuffer.Length];
+            List<Run> runs = [];
+            Run? current = null;
+            SetCursorPosition (new Coord (0, 0));
 
             foreach (ExtendedCharInfo info in charInfoBuffer)
             {
-                ci [i++] = new CharInfo
+                if (IsVirtualTerminal)
+                {
+                    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
                 {
-                    Char = new CharUnion { UnicodeChar = info.Char },
-                    Attributes =
-                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
-                };
+                    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);

+ 19 - 28
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;

+ 17 - 1
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

+ 1 - 1
Terminal.Gui/Views/ComboBox.cs

@@ -111,7 +111,7 @@ public class ComboBox : View, IDesignable
     }
 
     /// <inheritdoc />
-    protected override bool OnSettingScheme (ValueChangingEventArgs<Scheme?> args)
+    protected override bool OnSettingScheme (ValueChangingEventArgs<Scheme> args)
     {
         _listview.SetScheme(args.NewValue);
         return base.OnSettingScheme (args);

+ 5 - 5
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)

+ 6 - 6
Terminal.Gui/Views/NumericUpDown.cs

@@ -264,28 +264,28 @@ public class NumericUpDown<T> : View where T : notnull
     protected override bool OnDrawingText () { return true; }
 
     /// <summary>
-    ///     Attempts to convert the specified <paramref name="value"/> to type <typeparamref name="T"/>.
+    ///     Attempts to convert the specified <paramref name="value"/> to type <typeparamref name="TValue"/>.
     /// </summary>
-    /// <typeparam name="T">The type to which the value should be converted.</typeparam>
+    /// <typeparam name="TValue">The type to which the value should be converted.</typeparam>
     /// <param name="value">The value to convert.</param>
     /// <param name="result">
     ///     When this method returns, contains the converted value if the conversion succeeded,
-    ///     or the default value of <typeparamref name="T"/> if the conversion failed.
+    ///     or the default value of <typeparamref name="TValue"/> if the conversion failed.
     /// </param>
     /// <returns>
     ///     <c>true</c> if the conversion was successful; otherwise, <c>false</c>.
     /// </returns>
-    public static bool TryConvert<T> (object value, out T? result)
+    public static bool TryConvert<TValue> (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;
         }

+ 7 - 6
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<ConsoleKeyInfo>)Application.Driver;
+        var d = (ConsoleDriverFacade<ConsoleKeyInfo>)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<ConsoleKeyInfo>, IFakeDriverV2
 {
     public ConcurrentQueue<ConsoleKeyInfo> InputBuffer { get; }
     public FakeSizeMonitor SizeMonitor { get; }
-    public OutputBuffer OutputBuffer { get; }
+    public new OutputBuffer OutputBuffer { get; }
 
     public IConsoleOutput ConsoleOutput { get; }
 

+ 32 - 34
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 ();
-
-        ;
     }
 
     /// <summary>
@@ -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
     /// <param name="specialKey"></param>
     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 ()

+ 1 - 1
Tests/TerminalGuiFluentTesting/TextWriterLogger.cs

@@ -4,7 +4,7 @@ namespace TerminalGuiFluentTesting;
 
 internal class TextWriterLogger (TextWriter writer) : ILogger
 {
-    public IDisposable? BeginScope<TState> (TState state) { return null; }
+    public IDisposable? BeginScope<TState> (TState state) where TState : notnull { return null; }
 
     public bool IsEnabled (LogLevel logLevel) { return true; }