Quellcode durchsuchen

Merge branch 'gui-cs:v2_develop' into v2_develop

Tig vor 1 Woche
Ursprung
Commit
1fb983636c

+ 7 - 6
Terminal.Gui/App/Application.Initialization.cs

@@ -212,14 +212,15 @@ public static partial class Application // Initialization (Init/Shutdown)
         // use reflection to get the list of drivers
         List<Type?> driverTypes = new ();
 
-        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
+        // Only inspect the IConsoleDriver assembly
+        var asm = typeof (IConsoleDriver).Assembly;
+
+        foreach (Type? type in asm.GetTypes ())
         {
-            foreach (Type? type in asm.GetTypes ())
+            if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
+                type is { IsAbstract: false, IsClass: true })
             {
-                if (typeof (IConsoleDriver).IsAssignableFrom (type) && !type.IsAbstract && type.IsClass)
-                {
-                    driverTypes.Add (type);
-                }
+                driverTypes.Add (type);
             }
         }
 

+ 8 - 0
Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs

@@ -96,6 +96,11 @@ internal class NetWinVTConsole
 
     public void Cleanup ()
     {
+        if (!FlushConsoleInputBuffer (_inputHandle))
+        {
+            throw new ApplicationException ($"Failed to flush input buffer, error code: {GetLastError ()}.");
+        }
+
         if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
         {
             throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
@@ -123,4 +128,7 @@ internal class NetWinVTConsole
 
     [DllImport ("kernel32.dll")]
     private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
 }

+ 0 - 6
Terminal.Gui/Drivers/V2/IOutputBuffer.cs

@@ -9,12 +9,6 @@ namespace Terminal.Gui.Drivers;
 /// </summary>
 public interface IOutputBuffer
 {
-    /// <summary>
-    ///     As performance is a concern, we keep track of the dirty lines and only refresh those.
-    ///     This is in addition to the dirty flag on each cell.
-    /// </summary>
-    public bool [] DirtyLines { get; }
-
     /// <summary>
     ///     The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
     /// </summary>

+ 39 - 0
Terminal.Gui/Drivers/V2/InputProcessor.cs

@@ -162,4 +162,43 @@ public abstract class InputProcessor<T> : IInputProcessor
     /// </summary>
     /// <param name="input"></param>
     protected abstract void ProcessAfterParsing (T input);
+
+    internal char _highSurrogate = '\0';
+
+    internal bool IsValidInput (Key key, out Key result)
+    {
+        result = key;
+
+        if (char.IsHighSurrogate ((char)key))
+        {
+            _highSurrogate = (char)key;
+
+            return false;
+        }
+
+        if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key))
+        {
+            result = (KeyCode)new Rune (_highSurrogate, (char)key).Value;
+            _highSurrogate = '\0';
+
+            return true;
+        }
+
+        if (char.IsSurrogate ((char)key))
+        {
+            return false;
+        }
+
+        if (_highSurrogate > 0)
+        {
+            _highSurrogate = '\0';
+        }
+
+        if (key.KeyCode == 0)
+        {
+            return false;
+        }
+
+        return true;
+    }
 }

+ 0 - 7
Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs

@@ -25,7 +25,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
     private ConsoleDriverFacade<T> _facade;
     private Task _inputTask;
     private readonly ITimedEvents _timedEvents;
-    private readonly bool _isWindowsTerminal;
 
     private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
 
@@ -61,7 +60,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
         _inputProcessor = inputProcessor;
         _outputFactory = outputFactory;
         _loop = loop;
-        _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
     }
 
     /// <summary>
@@ -162,11 +160,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
                            _loop.AnsiRequestScheduler,
                            _loop.WindowSizeMonitor);
 
-            if (!_isWindowsTerminal)
-            {
-                Application.Force16Colors = _facade.Force16Colors = true;
-            }
-
             Application.Driver = _facade;
 
             _startupSemaphore.Release ();

+ 14 - 2
Terminal.Gui/Drivers/V2/NetInput.cs

@@ -40,6 +40,12 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
             }
         }
 
+        //Enable alternative screen buffer.
+        Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+        //Set cursor key to application.
+        Console.Out.Write (EscSeqUtils.CSI_HideCursor);
+
         Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
         Console.TreatControlCAsInput = true;
     }
@@ -68,8 +74,14 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
     public override void Dispose ()
     {
         base.Dispose ();
-        _adjustConsole?.Cleanup ();
-
         Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+        //Disable alternative screen buffer.
+        Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+        //Set cursor key to cursor.
+        Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+        _adjustConsole?.Cleanup ();
     }
 }

+ 7 - 2
Terminal.Gui/Drivers/V2/NetInputProcessor.cs

@@ -41,8 +41,13 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
         var key = KeyConverter.ToKey (input);
-        OnKeyDown (key);
-        OnKeyUp (key);
+
+        // If the key is not valid, we don't want to raise any events.
+        if (IsValidInput (key, out key))
+        {
+            OnKeyDown (key);
+            OnKeyUp (key);
+        }
     }
 
     /* For building test cases */

+ 29 - 197
Terminal.Gui/Drivers/V2/NetOutput.cs

@@ -6,15 +6,10 @@ namespace Terminal.Gui.Drivers;
 ///     Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
 ///     methods e.g. <see cref="System.Console"/>
 /// </summary>
-public class NetOutput : IConsoleOutput
+public class NetOutput : OutputBase, IConsoleOutput
 {
     private readonly bool _isWinPlatform;
 
-    private CursorVisibility? _cachedCursorVisibility;
-
-    // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
-    private TextStyle _redrawTextStyle = TextStyle.None;
-
     /// <summary>
     ///     Creates a new instance of the <see cref="NetOutput"/> class.
     /// </summary>
@@ -30,176 +25,10 @@ public class NetOutput : IConsoleOutput
         {
             _isWinPlatform = true;
         }
-
-        //Enable alternative screen buffer.
-        Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
-        //Set cursor key to application.
-        Console.Out.Write (EscSeqUtils.CSI_HideCursor);
     }
 
     /// <inheritdoc/>
-    public void Write (ReadOnlySpan<char> text)
-    {
-        Console.Out.Write (text);
-    }
-
-    /// <inheritdoc/>
-    public void Write (IOutputBuffer buffer)
-    {
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
-
-        if (Console.WindowHeight < 1
-            || buffer.Contents.Length != buffer.Rows * buffer.Cols
-            || buffer.Rows != Console.WindowHeight)
-        {
-            //     return;
-        }
-
-        var top = 0;
-        var left = 0;
-        int rows = buffer.Rows;
-        int cols = buffer.Cols;
-        var output = new StringBuilder ();
-        Attribute? redrawAttr = null;
-        int lastCol = -1;
-
-        CursorVisibility? savedVisibility = _cachedCursorVisibility;
-        SetCursorVisibility (CursorVisibility.Invisible);
-
-        const int maxCharsPerRune = 2;
-        Span<char> runeBuffer = stackalloc char[maxCharsPerRune];
-
-        for (int row = top; row < rows; row++)
-        {
-            if (Console.WindowHeight < 1)
-            {
-                return;
-            }
-
-            if (!buffer.DirtyLines [row])
-            {
-                continue;
-            }
-
-            if (!SetCursorPositionImpl (0, row))
-            {
-                return;
-            }
-
-            buffer.DirtyLines [row] = false;
-            output.Clear ();
-
-            for (int col = left; col < cols; col++)
-            {
-                lastCol = -1;
-                var outputWidth = 0;
-
-                for (; col < cols; col++)
-                {
-                    if (!buffer.Contents [row, col].IsDirty)
-                    {
-                        if (output.Length > 0)
-                        {
-                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        }
-                        else if (lastCol == -1)
-                        {
-                            lastCol = col;
-                        }
-
-                        if (lastCol + 1 < cols)
-                        {
-                            lastCol++;
-                        }
-
-                        continue;
-                    }
-
-                    if (lastCol == -1)
-                    {
-                        lastCol = col;
-                    }
-
-                    Attribute attr = buffer.Contents [row, col].Attribute.Value;
-
-                    // Performance: Only send the escape sequence if the attribute has changed.
-                    if (attr != redrawAttr)
-                    {
-                        redrawAttr = attr;
-
-                        EscSeqUtils.CSI_AppendForegroundColorRGB (
-                            output,
-                            attr.Foreground.R,
-                            attr.Foreground.G,
-                            attr.Foreground.B
-                        );
-
-                        EscSeqUtils.CSI_AppendBackgroundColorRGB (
-                            output,
-                            attr.Background.R,
-                            attr.Background.G,
-                            attr.Background.B
-                        );
-
-                        EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style);
-
-                        _redrawTextStyle = attr.Style;
-                    }
-
-                    outputWidth++;
-
-                    // Avoid Rune.ToString() by appending the rune chars.
-                    Rune rune = buffer.Contents [row, col].Rune;
-                    int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
-                    ReadOnlySpan<char> runeChars = runeBuffer[..runeCharsWritten];
-                    output.Append (runeChars);
-
-                    if (buffer.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);
-                        SetCursorPositionImpl (col - 1, row);
-                    }
-
-                    buffer.Contents [row, col].IsDirty = false;
-                }
-            }
-
-            if (output.Length > 0)
-            {
-                SetCursorPositionImpl (lastCol, row);
-                Console.Out.Write (output);
-            }
-        }
-
-        foreach (SixelToRender s in Application.Sixel)
-        {
-            if (!string.IsNullOrWhiteSpace (s.SixelData))
-            {
-                SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
-                Console.Out.Write (s.SixelData);
-            }
-        }
-
-        SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
-        _cachedCursorVisibility = savedVisibility;
-    }
+    public void Write (ReadOnlySpan<char> text) { Console.Out.Write (text); }
 
     /// <inheritdoc/>
     public Size GetWindowSize ()
@@ -213,23 +42,37 @@ public class NetOutput : IConsoleOutput
         return new (Console.WindowWidth, Console.WindowHeight);
     }
 
-    private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+    /// <inheritdoc/>
+    public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
+
+    private Point? _lastCursorPosition;
+
+    /// <inheritdoc/>
+    protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
-        SetCursorPositionImpl (lastCol, row);
-        Console.Out.Write (output);
-        output.Clear ();
-        lastCol += outputWidth;
-        outputWidth = 0;
+        EscSeqUtils.CSI_AppendForegroundColorRGB (
+                                                  output,
+                                                  attr.Foreground.R,
+                                                  attr.Foreground.G,
+                                                  attr.Foreground.B
+                                                 );
+
+        EscSeqUtils.CSI_AppendBackgroundColorRGB (
+                                                  output,
+                                                  attr.Background.R,
+                                                  attr.Background.G,
+                                                  attr.Background.B
+                                                 );
+
+        EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
     }
 
     /// <inheritdoc/>
-    public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
+    protected override void Write (StringBuilder output) { Console.Out.Write (output); }
 
-    private Point _lastCursorPosition;
-
-    private bool SetCursorPositionImpl (int col, int row)
+    protected override bool SetCursorPositionImpl (int col, int row)
     {
-        if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+        if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
         {
             return true;
         }
@@ -259,21 +102,10 @@ public class NetOutput : IConsoleOutput
     }
 
     /// <inheritdoc/>
-    public void Dispose ()
-    {
-        Console.ResetColor ();
-
-        //Disable alternative screen buffer.
-        Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
-        //Set cursor key to cursor.
-        Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-
-        Console.Out.Close ();
-    }
+    public void Dispose () { }
 
     /// <inheritdoc/>
-    public void SetCursorVisibility (CursorVisibility visibility)
+    public override void SetCursorVisibility (CursorVisibility visibility)
     {
         Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
     }

+ 163 - 0
Terminal.Gui/Drivers/V2/OutputBase.cs

@@ -0,0 +1,163 @@
+namespace Terminal.Gui.Drivers;
+
+public abstract class OutputBase
+{
+    private CursorVisibility? _cachedCursorVisibility;
+
+    // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
+    private TextStyle _redrawTextStyle = TextStyle.None;
+
+    /// <inheritdoc/>
+    public virtual void Write (IOutputBuffer buffer)
+    {
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
+        if (Console.WindowHeight < 1
+            || buffer.Contents.Length != buffer.Rows * buffer.Cols
+            || buffer.Rows != Console.WindowHeight)
+        {
+            //     return;
+        }
+
+        var top = 0;
+        var left = 0;
+        int rows = buffer.Rows;
+        int cols = buffer.Cols;
+        var output = new StringBuilder ();
+        Attribute? redrawAttr = null;
+        int lastCol = -1;
+
+        CursorVisibility? savedVisibility = _cachedCursorVisibility;
+        SetCursorVisibility (CursorVisibility.Invisible);
+
+        const int maxCharsPerRune = 2;
+        Span<char> runeBuffer = stackalloc char [maxCharsPerRune];
+
+        for (int row = top; row < rows; row++)
+        {
+            if (Console.WindowHeight < 1)
+            {
+                return;
+            }
+
+            if (!SetCursorPositionImpl (0, row))
+            {
+                return;
+            }
+
+            output.Clear ();
+
+            for (int col = left; col < cols; col++)
+            {
+                lastCol = -1;
+                var outputWidth = 0;
+
+                for (; col < cols; col++)
+                {
+                    if (!buffer.Contents [row, col].IsDirty)
+                    {
+                        if (output.Length > 0)
+                        {
+                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        }
+                        else if (lastCol == -1)
+                        {
+                            lastCol = col;
+                        }
+
+                        if (lastCol + 1 < cols)
+                        {
+                            lastCol++;
+                        }
+
+                        continue;
+                    }
+
+                    if (lastCol == -1)
+                    {
+                        lastCol = col;
+                    }
+
+                    Attribute attr = buffer.Contents [row, col].Attribute.Value;
+
+                    // Performance: Only send the escape sequence if the attribute has changed.
+                    if (attr != redrawAttr)
+                    {
+                        redrawAttr = attr;
+
+                        AppendOrWriteAttribute (output, attr, _redrawTextStyle);
+
+                        _redrawTextStyle = attr.Style;
+                    }
+
+                    outputWidth++;
+
+                    // Avoid Rune.ToString() by appending the rune chars.
+                    Rune rune = buffer.Contents [row, col].Rune;
+                    int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
+                    ReadOnlySpan<char> runeChars = runeBuffer [..runeCharsWritten];
+                    output.Append (runeChars);
+
+                    if (buffer.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);
+                        SetCursorPositionImpl (col - 1, row);
+                    }
+
+                    buffer.Contents [row, col].IsDirty = false;
+                }
+            }
+
+            if (output.Length > 0)
+            {
+                SetCursorPositionImpl (lastCol, row);
+                Write (output);
+            }
+        }
+
+        foreach (SixelToRender s in Application.Sixel)
+        {
+            if (!string.IsNullOrWhiteSpace (s.SixelData))
+            {
+                SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
+                Console.Out.Write (s.SixelData);
+            }
+        }
+
+        SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
+        _cachedCursorVisibility = savedVisibility;
+    }
+
+    protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
+
+    private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+    {
+        SetCursorPositionImpl (lastCol, row);
+        Write (output);
+        output.Clear ();
+        lastCol += outputWidth;
+        outputWidth = 0;
+    }
+
+    protected abstract void Write (StringBuilder output);
+
+    protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
+
+    public abstract void SetCursorVisibility (CursorVisibility visibility);
+}

+ 16 - 8
Terminal.Gui/Drivers/V2/WindowsInput.cs

@@ -4,7 +4,7 @@ using static Terminal.Gui.Drivers.WindowsConsole;
 
 namespace Terminal.Gui.Drivers;
 
-internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindowsInput
+internal class WindowsInput : ConsoleInput<InputRecord>, IWindowsInput
 {
     private readonly nint _inputHandle;
 
@@ -35,6 +35,9 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
 
     private readonly uint _originalConsoleMode;
 
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
+
     public WindowsInput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
@@ -50,16 +53,16 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
         _originalConsoleMode = v;
 
         uint newConsoleMode = _originalConsoleMode;
-        newConsoleMode |= (uint)(WindowsConsole.ConsoleModes.EnableMouseInput | WindowsConsole.ConsoleModes.EnableExtendedFlags);
-        newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableQuickEditMode;
-        newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableProcessedInput;
+        newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
+        newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
+        newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
         SetConsoleMode (_inputHandle, newConsoleMode);
     }
 
     protected override bool Peek ()
     {
         const int bufferSize = 1; // We only need to check if there's at least one event
-        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
+        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
 
         try
         {
@@ -89,10 +92,10 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
         }
     }
 
-    protected override IEnumerable<WindowsConsole.InputRecord> Read ()
+    protected override IEnumerable<InputRecord> Read ()
     {
         const int bufferSize = 1;
-        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
+        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
 
         try
         {
@@ -104,7 +107,7 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
 
             return numberEventsRead == 0
                        ? []
-                       : new [] { Marshal.PtrToStructure<WindowsConsole.InputRecord> (pRecord) };
+                       : new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
         }
         catch (Exception)
         {
@@ -123,6 +126,11 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
             return;
         }
 
+        if (!FlushConsoleInputBuffer (_inputHandle))
+        {
+            throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
+        }
+
         SetConsoleMode (_inputHandle, _originalConsoleMode);
     }
 }

+ 25 - 5
Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs

@@ -71,7 +71,8 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
     {
         var key = KeyConverter.ToKey (input);
 
-        if (key != (Key)0)
+        // If the key is not valid, we don't want to raise any events.
+        if (IsValidInput (key, out key))
         {
             OnKeyDown (key!);
             OnKeyUp (key!);
@@ -82,10 +83,29 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
     {
         var mouseFlags = MouseFlags.None;
 
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button1Pressed, MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
-
+        mouseFlags = UpdateMouseFlags (
+                                       mouseFlags,
+                                       e.ButtonState,
+                                       WindowsConsole.ButtonState.Button1Pressed,
+                                       MouseFlags.Button1Pressed,
+                                       MouseFlags.Button1Released,
+                                       0);
+
+        mouseFlags = UpdateMouseFlags (
+                                       mouseFlags,
+                                       e.ButtonState,
+                                       WindowsConsole.ButtonState.Button2Pressed,
+                                       MouseFlags.Button2Pressed,
+                                       MouseFlags.Button2Released,
+                                       1);
+
+        mouseFlags = UpdateMouseFlags (
+                                       mouseFlags,
+                                       e.ButtonState,
+                                       WindowsConsole.ButtonState.Button4Pressed,
+                                       MouseFlags.Button4Pressed,
+                                       MouseFlags.Button4Released,
+                                       3);
 
         // Deal with button 3 separately because it is considered same as 'rightmost button'
         if (e.ButtonState.HasFlag (WindowsConsole.ButtonState.Button3Pressed) || e.ButtonState.HasFlag (WindowsConsole.ButtonState.RightmostButtonPressed))

+ 302 - 171
Terminal.Gui/Drivers/V2/WindowsOutput.cs

@@ -1,13 +1,12 @@
 #nullable enable
-using System.Buffers;
 using System.ComponentModel;
 using System.Runtime.InteropServices;
+using System.Text;
 using Microsoft.Extensions.Logging;
-using static Terminal.Gui.Drivers.WindowsConsole;
 
 namespace Terminal.Gui.Drivers;
 
-internal partial class WindowsOutput : IConsoleOutput
+internal partial class WindowsOutput : OutputBase, IConsoleOutput
 {
     [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
     [return: MarshalAs (UnmanagedType.Bool)]
@@ -19,11 +18,15 @@ internal partial class WindowsOutput : IConsoleOutput
         nint lpReserved
     );
 
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool CloseHandle (nint handle);
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    private static partial nint GetStdHandle (int nStdHandle);
 
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern nint CreateConsoleScreenBuffer (
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool CloseHandle (nint handle);
+
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    private static partial nint CreateConsoleScreenBuffer (
         DesiredAccess dwDesiredAccess,
         ShareMode dwShareMode,
         nint secutiryAttributes,
@@ -32,6 +35,7 @@ internal partial class WindowsOutput : IConsoleOutput
     );
 
     [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
     private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi);
 
     [Flags]
@@ -50,19 +54,52 @@ internal partial class WindowsOutput : IConsoleOutput
 
     internal static nint INVALID_HANDLE_VALUE = new (-1);
 
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleActiveScreenBuffer (nint handle);
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool SetConsoleActiveScreenBuffer (nint handle);
 
-    [DllImport ("kernel32.dll")]
-    private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
+    [LibraryImport ("kernel32.dll")]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
 
     [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
     private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo);
 
-    private readonly nint _screenBuffer;
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    public static partial bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes);
+
+    [LibraryImport ("kernel32.dll")]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
+
+    [LibraryImport ("kernel32.dll")]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static partial bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+    [LibraryImport ("kernel32.dll", SetLastError = true)]
+    private static partial WindowsConsole.Coord GetLargestConsoleWindowSize (
+        nint hConsoleOutput
+    );
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    [return: MarshalAs (UnmanagedType.Bool)]
+    private static extern bool SetConsoleWindowInfo (
+        nint hConsoleOutput,
+        bool bAbsolute,
+        [In] ref WindowsConsole.SmallRect lpConsoleWindow
+    );
 
-    // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
-    private TextStyle _redrawTextStyle = TextStyle.None;
+    private const int STD_OUTPUT_HANDLE = -11;
+    private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
+    private readonly nint _outputHandle;
+    private nint _screenBuffer;
+    private readonly bool _isVirtualTerminal;
 
     public WindowsOutput ()
     {
@@ -73,13 +110,48 @@ internal partial class WindowsOutput : IConsoleOutput
             return;
         }
 
+        // Get the standard output handle which is the current screen buffer.
+        _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+        GetConsoleMode (_outputHandle, out uint mode);
+        _isVirtualTerminal = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
+
+        if (_isVirtualTerminal)
+        {
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+        }
+        else
+        {
+            CreateScreenBuffer ();
+
+            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 ()}.");
+            }
+
+            // Force 16 colors if not in virtual terminal mode.
+            Application.Force16Colors = true;
+        }
+    }
+
+    private void CreateScreenBuffer ()
+    {
         _screenBuffer = CreateConsoleScreenBuffer (
-                                                   DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
-                                                   ShareMode.FileShareRead | ShareMode.FileShareWrite,
-                                                   nint.Zero,
-                                                   1,
-                                                   nint.Zero
-                                                  );
+                                   DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
+                                   ShareMode.FileShareRead | ShareMode.FileShareWrite,
+                                   nint.Zero,
+                                   1,
+                                   nint.Zero
+                                  );
 
         if (_screenBuffer == INVALID_HANDLE_VALUE)
         {
@@ -99,212 +171,229 @@ internal partial class WindowsOutput : IConsoleOutput
 
     public void Write (ReadOnlySpan<char> str)
     {
-        if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
+        if (!WriteConsole (_isVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
         }
     }
 
-    public void Write (IOutputBuffer buffer)
+    public Size ResizeBuffer (Size size)
     {
-        WindowsConsole.ExtendedCharInfo [] outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols];
+        Size newSize = SetConsoleWindow (
+                                 (short)Math.Max (size.Width, 0),
+                                 (short)Math.Max (size.Height, 0));
 
-        // TODO: probably do need this right?
-        /*
-        if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
+        return newSize;
+    }
+
+    internal Size SetConsoleWindow (short cols, short rows)
+    {
+        var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
+        csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+        if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
-            return;
-        }*/
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
 
-        var bufferCoords = new WindowsConsole.Coord
+        WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+        short newCols = Math.Min (cols, maxWinSize.X);
+        short newRows = Math.Min (rows, maxWinSize.Y);
+        csbi.dwSize = new (newCols, Math.Max (newRows, (short)1));
+        csbi.srWindow = new (0, 0, newCols, newRows);
+        csbi.dwMaximumWindowSize = new (newCols, newRows);
+
+        if (!SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
-            X = (short)buffer.Cols, //Clip.Width,
-            Y = (short)buffer.Rows //Clip.Height
-        };
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+
+        var winRect = new WindowsConsole.SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
 
-        for (var row = 0; row < buffer.Rows; row++)
+        if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
         {
-            if (!buffer.DirtyLines [row])
-            {
-                continue;
-            }
+            //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+            return new (cols, rows);
+        }
 
-            buffer.DirtyLines [row] = false;
+        SetConsoleOutputWindow (csbi);
 
-            for (var col = 0; col < buffer.Cols; col++)
-            {
-                int position = row * buffer.Cols + col;
-                outputBuffer [position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault ();
+        return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
+    }
 
-                if (buffer.Contents [row, col].IsDirty == false)
-                {
-                    outputBuffer [position].Empty = true;
-                    outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+    private void SetConsoleOutputWindow (WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi)
+    {
+        if ((_isVirtualTerminal
+                 ? _outputHandle
+                 : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        {
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+    }
 
-                    continue;
-                }
+    public override void Write (IOutputBuffer outputBuffer)
+    {
+        _force16Colors = Application.Driver!.Force16Colors;
+        _everythingStringBuilder = new StringBuilder ();
+
+        // 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;
+        if (_force16Colors)
+        {
+            if (_isVirtualTerminal)
+            {
+                _consoleBuffer = _outputHandle;
+            }
+            else
+            {
+                _consoleBuffer = _screenBuffer;
+            }
+        }
+        else
+        {
+            _consoleBuffer = _outputHandle;
+        }
 
-                outputBuffer [position].Empty = false;
+        base.Write (outputBuffer);
 
-                if (buffer.Contents [row, col].Rune.IsBmp)
-                {
-                    outputBuffer [position].Char = (char)buffer.Contents [row, col].Rune.Value;
-                }
-                else
+        try
+        {
+            if (_force16Colors && !_isVirtualTerminal)
+            {
+                SetConsoleActiveScreenBuffer (_consoleBuffer);
+            }
+            else
+            {
+                var span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
+
+                var result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+                if (!result)
                 {
-                    //outputBuffer [position].Empty = true;
-                    outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+                    int err = Marshal.GetLastWin32Error ();
 
-                    if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols)
+                    if (err != 0)
                     {
-                        // TODO: This is a hack to deal with non-BMP and wide characters.
-                        col++;
-                        position = row * buffer.Cols + col;
-                        outputBuffer [position].Empty = false;
-                        outputBuffer [position].Char = ' ';
+                        throw new Win32Exception (err);
                     }
                 }
             }
         }
-
-        var damageRegion = new WindowsConsole.SmallRect
+        catch (Exception e)
         {
-            Top = 0,
-            Left = 0,
-            Bottom = (short)buffer.Rows,
-            Right = (short)buffer.Cols
-        };
+            Logging.Logger.LogError ($"Error: {e.Message} in {nameof (WindowsOutput)}");
 
-        //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
-        if (!ConsoleDriver.RunningUnitTests
-            && !WriteToConsole (
-                                new (buffer.Cols, buffer.Rows),
-                                outputBuffer,
-                                bufferCoords,
-                                damageRegion,
-                                Application.Driver!.Force16Colors))
-        {
-            int err = Marshal.GetLastWin32Error ();
-
-            if (err != 0)
+            if (!ConsoleDriver.RunningUnitTests)
             {
-                throw new Win32Exception (err);
+                throw;
             }
         }
-
-        WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
     }
-
-    public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors)
+    /// <inheritdoc />
+    protected override void Write (StringBuilder output)
     {
+        if (output.Length == 0)
+        {
+            return;
+        }
 
-        //Debug.WriteLine ("WriteToConsole");
+        var str = output.ToString ();
 
-        //if (_screenBuffer == nint.Zero)
-        //{
-        //    ReadFromConsoleOutput (size, bufferSize, ref window);
-        //}
+        if (_force16Colors && !_isVirtualTerminal)
+        {
+            var a = str.ToCharArray ();
+            WriteConsole (_screenBuffer,a ,(uint)a.Length, out _, nint.Zero);
+        }
+        else
+        {
+            _everythingStringBuilder.Append (str);
+        }
+    }
 
-        var result = false;
+    /// <inheritdoc />
+    protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
+    {
+        var force16Colors = Application.Force16Colors;
 
         if (force16Colors)
         {
-            var i = 0;
-            WindowsConsole.CharInfo [] ci = new WindowsConsole.CharInfo [charInfoBuffer.Length];
-
-            foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
+            if (_isVirtualTerminal)
             {
-                ci [i++] = new ()
-                {
-                    Char = new () { UnicodeChar = info.Char },
-                    Attributes =
-                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
-                };
+                output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
+                output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
+                EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+            }
+            else
+            {
+                var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
+                SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
             }
-
-            result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new () { X = window.Left, Y = window.Top }, ref window);
         }
         else
         {
-            StringBuilder stringBuilder = new();
-
-            stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
-            EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0);
+            EscSeqUtils.CSI_AppendForegroundColorRGB (output, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
+            EscSeqUtils.CSI_AppendBackgroundColorRGB (output, attr.Background.R, attr.Background.G, attr.Background.B);
+            EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+        }
+    }
 
-            Attribute? prev = null;
 
-            foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
-            {
-                Attribute attr = info.Attribute;
+    private Size? _lastSize;
+    private Size? _lastWindowSizeBeforeMaximized;
+    private bool _lockResize;
 
-                if (attr != prev)
-                {
-                    prev = attr;
-                    EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
-                    EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
-                    EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style);
-                    _redrawTextStyle = attr.Style;
-                }
-
-                if (info.Char != '\x1b')
-                {
-                    if (!info.Empty)
-                    {
-                        stringBuilder.Append (info.Char);
-                    }
-                }
-                else
-                {
-                    stringBuilder.Append (' ');
-                }
-            }
-
-            stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
-            stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
+    public Size GetWindowSize ()
+    {
+        if (_lockResize)
+        {
+            return _lastSize!.Value;
+        }
 
-            // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed.
-            char [] rentedWriteArray = ArrayPool<char>.Shared.Rent (minimumLength: stringBuilder.Length);
-            try
-            {
-                Span<char> writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length);
-                stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
+        var newSize = GetWindowSize (out _);
+        Size largestWindowSize = GetLargestConsoleWindowSize ();
 
-                // Supply console with the new content.
-                result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
-            }
-            finally
+        if (_lastWindowSizeBeforeMaximized is null && newSize == largestWindowSize)
+        {
+            _lastWindowSizeBeforeMaximized = _lastSize;
+        }
+        else if (_lastWindowSizeBeforeMaximized is { } && newSize != largestWindowSize)
+        {
+            if (newSize != _lastWindowSizeBeforeMaximized)
             {
-                ArrayPool<char>.Shared.Return (rentedWriteArray);
+                newSize = _lastWindowSizeBeforeMaximized.Value;
             }
 
-            foreach (SixelToRender sixel in Application.Sixel)
-            {
-                SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
-                WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
-            }
+            _lastWindowSizeBeforeMaximized = null;
         }
 
-        if (!result)
+        if (_lastSize == null || _lastSize != newSize)
         {
-            int err = Marshal.GetLastWin32Error ();
-
-            if (err != 0)
+            // User is resizing the screen, they can only ever resize the active
+            // buffer since. We now however have issue because background offscreen
+            // buffer will be wrong size, recreate it to ensure it doesn't result in
+            // differing active and back buffer sizes (which causes flickering of window size)
+            Size? bufSize = null;
+            while (bufSize != newSize)
             {
-                throw new Win32Exception (err);
+                _lockResize = true;
+                bufSize = ResizeBuffer (newSize);
             }
+
+            _lockResize = false;
+            _lastSize = newSize;
         }
 
-        return result;
+        return newSize;
     }
 
-    public Size GetWindowSize ()
+    public Size GetWindowSize (out WindowsConsole.Coord cursorPosition)
     {
         var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
         csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-        if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+        if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
             //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+            cursorPosition = default;
             return Size.Empty;
         }
 
@@ -312,18 +401,45 @@ internal partial class WindowsOutput : IConsoleOutput
                        csbi.srWindow.Right - csbi.srWindow.Left + 1,
                        csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
 
+        cursorPosition = csbi.dwCursorPosition;
         return sz;
     }
 
+    private Size GetLargestConsoleWindowSize ()
+    {
+        WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+
+        return new (maxWinSize.X, maxWinSize.Y);
+    }
+
+    /// <inheritdoc />
+    protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
+    {
+        if (_force16Colors && !_isVirtualTerminal)
+        {
+            SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY));
+        }
+        else
+        {
+            // CSI codes are 1 indexed
+            _everythingStringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
+            EscSeqUtils.CSI_AppendCursorPosition (_everythingStringBuilder, screenPositionY + 1, screenPositionX + 1);
+        }
+
+        _lastCursorPosition = new (screenPositionX, screenPositionY);
+
+        return true;
+    }
+
     /// <inheritdoc/>
-    public void SetCursorVisibility (CursorVisibility visibility)
+    public override void SetCursorVisibility (CursorVisibility visibility)
     {
         if (ConsoleDriver.RunningUnitTests)
         {
             return;
         }
 
-        if (Application.Driver!.Force16Colors)
+        if (!_isVirtualTerminal)
         {
             var info = new WindowsConsole.ConsoleCursorInfo
             {
@@ -342,22 +458,34 @@ internal partial class WindowsOutput : IConsoleOutput
         }
     }
 
-    private Point _lastCursorPosition;
+    private Point? _lastCursorPosition;
 
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row)
     {
-        if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+        if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
         {
             return;
         }
 
         _lastCursorPosition = new (col, row);
 
-        SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+        if (_isVirtualTerminal)
+        {
+            var sb = new StringBuilder ();
+            EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1);
+            Write (sb.ToString ());
+        }
+        else
+        {
+            SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+        }
     }
 
     private bool _isDisposed;
+    private bool _force16Colors;
+    private nint _consoleBuffer;
+    private StringBuilder _everythingStringBuilder;
 
     /// <inheritdoc/>
     public void Dispose ()
@@ -367,16 +495,19 @@ internal partial class WindowsOutput : IConsoleOutput
             return;
         }
 
-        if (_screenBuffer != nint.Zero)
+        if (_isVirtualTerminal)
+        {
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+        }
+        else
         {
-            try
+            if (_screenBuffer != nint.Zero)
             {
                 CloseHandle (_screenBuffer);
             }
-            catch (Exception e)
-            {
-                Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method");
-            }
+
+            _screenBuffer = nint.Zero;
         }
 
         _isDisposed = true;

+ 7 - 5
Terminal.Gui/Views/Menu/MenuBarv2.cs

@@ -397,10 +397,10 @@ public class MenuBarv2 : Menuv2, IDesignable
 
         // If the active Application Popover is part of this MenuBar, hide it.
         if (Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu
-            && popoverMenu?.Root?.SuperMenuItem?.SuperView == this)
+            && popoverMenu.Root?.SuperMenuItem?.SuperView == this)
         {
             // Logging.Debug ($"{Title} - Calling Application.Popover?.Hide ({popoverMenu.Title})");
-            Application.Popover?.Hide (popoverMenu);
+            Application.Popover.Hide (popoverMenu);
         }
 
         if (menuBarItem is null)
@@ -416,6 +416,7 @@ public class MenuBarv2 : Menuv2, IDesignable
         if (menuBarItem.PopoverMenu?.Root is { })
         {
             menuBarItem.PopoverMenu.Root.SuperMenuItem = menuBarItem;
+            menuBarItem.PopoverMenu.Root.SchemeName = SchemeName;
         }
 
         // Logging.Debug ($"{Title} - \"{menuBarItem.PopoverMenu?.Title}\".MakeVisible");
@@ -423,14 +424,15 @@ public class MenuBarv2 : Menuv2, IDesignable
 
         menuBarItem.Accepting += OnMenuItemAccepted;
 
-        menuBarItem.PopoverMenu!.Root.SchemeName = SchemeName;
-
         return;
 
         void OnMenuItemAccepted (object? sender, EventArgs args)
         {
             // Logging.Debug ($"{Title} - OnMenuItemAccepted");
-            menuBarItem.PopoverMenu!.VisibleChanged -= OnMenuItemAccepted;
+            if (menuBarItem.PopoverMenu is { })
+            {
+                menuBarItem.PopoverMenu.VisibleChanged -= OnMenuItemAccepted;
+            }
 
             if (Active && menuBarItem.PopoverMenu is { Visible: false })
             {