Browse Source

Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop

Tig 3 months ago
parent
commit
3fb1c413b1
100 changed files with 2164 additions and 1448 deletions
  1. 2 2
      Examples/NativeAot/Publish_linux-x64_Debug.sh
  2. 2 2
      Examples/NativeAot/Publish_linux-x64_Release.sh
  3. 2 2
      Examples/NativeAot/Publish_osx-x64_Debug.sh
  4. 2 2
      Examples/NativeAot/Publish_osx-x64_Release.sh
  5. 24 0
      Examples/UICatalog/Properties/launchSettings.json
  6. 1 1
      Examples/UICatalog/Scenarios/NumericUpDownDemo.cs
  7. 9 11
      Examples/UICatalog/Scenarios/SendKeys.cs
  8. 31 10
      Examples/UICatalog/UICatalogTop.cs
  9. 5 1
      Terminal.Gui/App/Application.Run.cs
  10. 14 0
      Terminal.Gui/App/Application.cs
  11. 9 139
      Terminal.Gui/App/Clipboard/Clipboard.cs
  12. 79 0
      Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs
  13. 4 2
      Terminal.Gui/App/RunState.cs
  14. 27 5
      Terminal.Gui/Configuration/DeepCloner.cs
  15. 2 2
      Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
  16. 2 2
      Terminal.Gui/Drivers/ConsoleDriver.cs
  17. 116 286
      Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs
  18. 83 0
      Terminal.Gui/Drivers/CursesDriver/Platform.cs
  19. 1 2
      Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs
  20. 17 21
      Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs
  21. 6 47
      Terminal.Gui/Drivers/NetDriver/NetDriver.cs
  22. 13 17
      Terminal.Gui/Drivers/NetDriver/NetEvents.cs
  23. 17 41
      Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs
  24. 47 35
      Terminal.Gui/Drivers/V2/ApplicationV2.cs
  25. 26 0
      Terminal.Gui/Drivers/V2/ComponentFactory.cs
  26. 62 13
      Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs
  27. 50 0
      Terminal.Gui/Drivers/V2/IComponentFactory.cs
  28. 14 2
      Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs
  29. 11 0
      Terminal.Gui/Drivers/V2/IInputProcessor.cs
  30. 8 1
      Terminal.Gui/Drivers/V2/IMainLoop.cs
  31. 1 1
      Terminal.Gui/Drivers/V2/IOutputBuffer.cs
  32. 4 1
      Terminal.Gui/Drivers/V2/IWindowsInput.cs
  33. 18 1
      Terminal.Gui/Drivers/V2/InputProcessor.cs
  34. 16 4
      Terminal.Gui/Drivers/V2/MainLoop.cs
  35. 11 24
      Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs
  36. 29 0
      Terminal.Gui/Drivers/V2/NetComponentFactory.cs
  37. 17 5
      Terminal.Gui/Drivers/V2/NetOutput.cs
  38. 34 2
      Terminal.Gui/Drivers/V2/OutputBase.cs
  39. 2 0
      Terminal.Gui/Drivers/V2/OutputBuffer.cs
  40. 3 0
      Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs
  41. 29 0
      Terminal.Gui/Drivers/V2/WindowsComponentFactory.cs
  42. 3 3
      Terminal.Gui/Drivers/V2/WindowsOutput.cs
  43. 211 58
      Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs
  44. 19 28
      Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs
  45. 17 1
      Terminal.Gui/Drivers/WindowsDriver/WindowsMainLoop.cs
  46. 7 0
      Terminal.Gui/ViewBase/Adornment/ShadowView.cs
  47. 5 2
      Terminal.Gui/ViewBase/View.cs
  48. 1 1
      Terminal.Gui/ViewBase/ViewCollectionHelpers.cs
  49. 7 4
      Terminal.Gui/Views/CharMap/CharMap.cs
  50. 1 1
      Terminal.Gui/Views/ComboBox.cs
  51. 0 6
      Terminal.Gui/Views/Dialog.cs
  52. 5 5
      Terminal.Gui/Views/FileDialogs/FileDialog.cs
  53. 6 6
      Terminal.Gui/Views/NumericUpDown.cs
  54. 48 50
      Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs
  55. 74 62
      Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs
  56. 41 40
      Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs
  57. 31 26
      Tests/IntegrationTests/FluentTests/PopverMenuTests.cs
  58. 1 1
      Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs
  59. 45 0
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationFactory.cs
  60. 16 0
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs
  61. 20 0
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeDriverFactory.cs
  62. 58 0
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeDriverV2.cs
  63. 0 0
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeNetInput.cs
  64. 20 0
      Tests/TerminalGuiFluentTesting/FakeDriver/FakeSizeMonitor.cs
  65. 8 0
      Tests/TerminalGuiFluentTesting/FakeDriver/IFakeDriverV2.cs
  66. 1 1
      Tests/TerminalGuiFluentTesting/FakeOutput.cs
  67. 290 162
      Tests/TerminalGuiFluentTesting/GuiTestContext.cs
  68. 1 1
      Tests/TerminalGuiFluentTesting/TextWriterLogger.cs
  69. 31 0
      Tests/TerminalGuiFluentTesting/ThreadSafeStringWriter.cs
  70. 7 7
      Tests/TerminalGuiFluentTesting/With.cs
  71. 2 2
      Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs
  72. 53 82
      Tests/UnitTests/Application/ApplicationTests.cs
  73. 41 4
      Tests/UnitTests/AutoInitShutdownAttribute.cs
  74. 1 1
      Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
  75. 32 14
      Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
  76. 6 6
      Tests/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs
  77. 11 1
      Tests/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs
  78. 62 67
      Tests/UnitTests/Dialogs/DialogTests.cs
  79. 16 25
      Tests/UnitTests/Dialogs/MessageBoxTests.cs
  80. 5 8
      Tests/UnitTests/Dialogs/WizardTests.cs
  81. 1 1
      Tests/UnitTests/Drawing/RulerTests.cs
  82. 7 6
      Tests/UnitTests/Drawing/ThicknessTests.cs
  83. 17 8
      Tests/UnitTests/FileServices/FileDialogTests.cs
  84. 8 1
      Tests/UnitTests/SetupFakeDriverAttribute.cs
  85. 11 10
      Tests/UnitTests/Text/AutocompleteTests.cs
  86. 2 2
      Tests/UnitTests/Text/TextFormatterTests.cs
  87. 1 0
      Tests/UnitTests/UnitTests.csproj
  88. 15 15
      Tests/UnitTests/View/Adornment/BorderTests.cs
  89. 2 2
      Tests/UnitTests/View/Adornment/MarginTests.cs
  90. 1 1
      Tests/UnitTests/View/Adornment/PaddingTests.cs
  91. 2 2
      Tests/UnitTests/View/Adornment/ShadowStyleTests.cs
  92. 2 2
      Tests/UnitTests/View/Draw/ClearViewportTests.cs
  93. 1 1
      Tests/UnitTests/View/Draw/ClipTests.cs
  94. 2 2
      Tests/UnitTests/View/Draw/DrawEventTests.cs
  95. 27 26
      Tests/UnitTests/View/Draw/DrawTests.cs
  96. 2 0
      Tests/UnitTests/View/Layout/Pos.AnchorEndTests.cs
  97. 4 4
      Tests/UnitTests/View/Layout/Pos.CenterTests.cs
  98. 3 2
      Tests/UnitTests/View/Layout/SetLayoutTests.cs
  99. 1 1
      Tests/UnitTests/View/Mouse/MouseTests.cs
  100. 2 3
      Tests/UnitTests/View/Navigation/CanFocusTests.cs

+ 2 - 2
Examples/NativeAot/Publish_linux-x64_Debug.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
 
-dotnet clean
-dotnet build
+dotnet clean -c Debug
+dotnet build -c Debug
 dotnet publish -c Debug -r linux-x64 --self-contained

+ 2 - 2
Examples/NativeAot/Publish_linux-x64_Release.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
 
-dotnet clean
-dotnet build
+dotnet clean -c Release
+dotnet build -c Release
 dotnet publish -c Release -r linux-x64 --self-contained

+ 2 - 2
Examples/NativeAot/Publish_osx-x64_Debug.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
 
-dotnet clean
-dotnet build
+dotnet clean -c Debug
+dotnet build -c Debug
 dotnet publish -c Debug -r osx-x64 --self-contained

+ 2 - 2
Examples/NativeAot/Publish_osx-x64_Release.sh

@@ -1,5 +1,5 @@
 #!/bin/bash
 
-dotnet clean
-dotnet build
+dotnet clean -c Release
+dotnet build -c Release
 dotnet publish -c Release -r osx-x64 --self-contained

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

+ 9 - 11
Examples/UICatalog/Scenarios/SendKeys.cs

@@ -1,4 +1,4 @@
-using System;
+using System.Text;
 
 namespace UICatalog.Scenarios;
 
@@ -39,7 +39,7 @@ public class SendKeys : Scenario
 
         txtResult.KeyDown += (s, e) =>
                              {
-                                 rKeys += (char)e.KeyCode;
+                                 rKeys += e.ToString ();
 
                                  if (!IsShift && e.IsShift)
                                  {
@@ -81,17 +81,15 @@ public class SendKeys : Scenario
 
             foreach (char r in txtInput.Text)
             {
-                ConsoleKey ck = char.IsLetter (r)
-                                    ? (ConsoleKey)char.ToUpper (r)
-                                    : (ConsoleKey)r;
+                ConsoleKeyInfo consoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (new (r, ConsoleKey.None, false, false, false));
 
                 Application.Driver?.SendKeys (
-                                             r,
-                                             ck,
-                                             ckbShift.CheckedState == CheckState.Checked,
-                                             ckbAlt.CheckedState == CheckState.Checked,
-                                             ckbControl.CheckedState == CheckState.Checked
-                                            );
+                                              r,
+                                              consoleKeyInfo.Key,
+                                              ckbShift.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+                                              ckbAlt.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+                                              ckbControl.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0
+                                             );
             }
 
             lblShippedKeys.Text = rKeys;

+ 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,

+ 5 - 1
Terminal.Gui/App/Application.Run.cs

@@ -460,7 +460,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
     /// <summary>This event is raised on each iteration of the main loop.</summary>
     /// <remarks>See also <see cref="Timeout"/></remarks>
     public static event EventHandler<IterationEventArgs>? Iteration;
-
+    
     /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
     /// <value>The main loop.</value>
     internal static MainLoop? MainLoop { get; set; }
@@ -618,4 +618,8 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         LayoutAndDraw (true);
     }
+    internal static void RaiseIteration ()
+    {
+        Iteration?.Invoke (null, new ());
+    }
 }

+ 14 - 0
Terminal.Gui/App/Application.cs

@@ -51,6 +51,20 @@ public static partial class Application
     /// </summary>
     public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
 
+    /// <summary>
+    /// Maximum number of iterations of the main loop (and hence draws)
+    /// to allow to occur per second. Defaults to <see cref="DefaultMaximumIterationsPerSecond"/>> which is a 40ms sleep
+    /// after iteration (factoring in how long iteration took to run).
+    /// <remarks>Note that not every iteration draws (see <see cref="View.NeedsDraw"/>).
+    /// Only affects v2 drivers.</remarks>
+    /// </summary>
+    public static ushort MaximumIterationsPerSecond = DefaultMaximumIterationsPerSecond;
+
+    /// <summary>
+    /// Default value for <see cref="MaximumIterationsPerSecond"/>
+    /// </summary>
+    public const ushort DefaultMaximumIterationsPerSecond = 25;
+
     /// <summary>
     ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.
     /// </summary>

+ 9 - 139
Terminal.Gui/App/Clipboard/Clipboard.cs

@@ -1,5 +1,4 @@
-using System.Diagnostics;
-
+#nullable enable
 namespace Terminal.Gui.App;
 
 /// <summary>Provides cut, copy, and paste support for the OS clipboard.</summary>
@@ -20,10 +19,10 @@ namespace Terminal.Gui.App;
 /// </remarks>
 public static class Clipboard
 {
-    private static string _contents = string.Empty;
+    private static string? _contents = string.Empty;
 
     /// <summary>Gets (copies from) or sets (pastes to) the contents of the OS clipboard.</summary>
-    public static string Contents
+    public static string? Contents
     {
         get
         {
@@ -31,13 +30,8 @@ public static class Clipboard
             {
                 if (IsSupported)
                 {
-                    string clipData = Application.Driver?.Clipboard.GetClipboardData ();
-
-                    if (clipData is null)
-                    {
-                        // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
-                        clipData = string.Empty;
-                    }
+                    // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
+                    string? clipData = Application.Driver?.Clipboard?.GetClipboardData () ?? string.Empty;
 
                     _contents = clipData;
                 }
@@ -55,12 +49,9 @@ public static class Clipboard
             {
                 if (IsSupported)
                 {
-                    if (value is null)
-                    {
-                        value = string.Empty;
-                    }
+                    value ??= string.Empty;
 
-                    Application.Driver?.Clipboard.SetClipboardData (value);
+                    Application.Driver?.Clipboard?.SetClipboardData (value);
                 }
 
                 _contents = value;
@@ -74,126 +65,5 @@ public static class Clipboard
 
     /// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard.</summary>
     /// <remarks></remarks>
-    public static bool IsSupported => Application.Driver?.Clipboard.IsSupported ?? false;
-
-    /// <summary>Copies the _contents of the OS clipboard to <paramref name="result"/> if possible.</summary>
-    /// <param name="result">The _contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
-    /// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
-    public static bool TryGetClipboardData (out string result)
-    {
-        if (IsSupported && Application.Driver!.Clipboard.TryGetClipboardData (out result))
-        {
-            _contents = result;
-
-            return true;
-        }
-
-        result = string.Empty;
-
-        return false;
-    }
-
-    /// <summary>Pastes the <paramref name="text"/> to the OS clipboard if possible.</summary>
-    /// <param name="text">The text to paste to the OS clipboard.</param>
-    /// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
-    public static bool TrySetClipboardData (string text)
-    {
-        if (IsSupported && Application.Driver!.Clipboard.TrySetClipboardData (text))
-        {
-            _contents = text;
-
-            return true;
-        }
-
-        return false;
-    }
-}
-
-/// <summary>
-///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
-///     CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
-/// </summary>
-internal static class ClipboardProcessRunner
-{
-    public static (int exitCode, string result) Bash (
-        string commandLine,
-        string inputText = "",
-        bool waitForOutput = false
-    )
-    {
-        var arguments = $"-c \"{commandLine}\"";
-        (int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
-
-        return (exitCode, result.TrimEnd ());
-    }
-
-    public static bool DoubleWaitForExit (this Process process)
-    {
-        bool result = process.WaitForExit (500);
-
-        if (result)
-        {
-            process.WaitForExit ();
-        }
-
-        return result;
-    }
-
-    public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); }
-
-    public static (int exitCode, string result) Process (
-        string cmd,
-        string arguments,
-        string input = null,
-        bool waitForOutput = true
-    )
-    {
-            var output = string.Empty;
-
-        using (var process = new Process
-               {
-                   StartInfo = new()
-                   {
-                       FileName = cmd,
-                       Arguments = arguments,
-                       RedirectStandardOutput = true,
-                       RedirectStandardError = true,
-                       RedirectStandardInput = true,
-                       UseShellExecute = false,
-                       CreateNoWindow = true
-                   }
-               })
-        {
-            TaskCompletionSource<bool> eventHandled = new ();
-            process.Start ();
-
-            if (!string.IsNullOrEmpty (input))
-            {
-                process.StandardInput.Write (input);
-                process.StandardInput.Close ();
-            }
-
-            if (!process.WaitForExit (5000))
-            {
-                var timeoutError =
-                    $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
-
-                throw new TimeoutException (timeoutError);
-            }
-
-            if (waitForOutput && process.StandardOutput.Peek () != -1)
-            {
-                output = process.StandardOutput.ReadToEnd ();
-            }
-
-            if (process.ExitCode > 0)
-            {
-                output = $@"Process failed to run. Command line: {cmd} {arguments}.
-										Output: {output}
-										Error: {process.StandardError.ReadToEnd ()}";
-            }
-
-            return (process.ExitCode, output);
-        }
-    }
-}
+    public static bool IsSupported => Application.Driver?.Clipboard?.IsSupported ?? false;
+}

+ 79 - 0
Terminal.Gui/App/Clipboard/ClipboardProcessRunner.cs

@@ -0,0 +1,79 @@
+#nullable enable
+using System.Diagnostics;
+
+namespace Terminal.Gui.App;
+
+/// <summary>
+///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
+///     CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
+/// </summary>
+internal static class ClipboardProcessRunner
+{
+    public static (int exitCode, string result) Bash (
+        string commandLine,
+        string inputText = "",
+        bool waitForOutput = false
+    )
+    {
+        var arguments = $"-c \"{commandLine}\"";
+        (int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
+
+        return (exitCode, result.TrimEnd ());
+    }
+
+    public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); }
+
+    public static (int exitCode, string result) Process (
+        string cmd,
+        string arguments,
+        string? input = null,
+        bool waitForOutput = true
+    )
+    {
+        var output = string.Empty;
+
+        using var process = new Process ();
+
+        process.StartInfo = new()
+        {
+            FileName = cmd,
+            Arguments = arguments,
+            RedirectStandardOutput = true,
+            RedirectStandardError = true,
+            RedirectStandardInput = true,
+            UseShellExecute = false,
+            CreateNoWindow = true
+        };
+
+        TaskCompletionSource<bool> eventHandled = new ();
+        process.Start ();
+
+        if (!string.IsNullOrEmpty (input))
+        {
+            process.StandardInput.Write (input);
+            process.StandardInput.Close ();
+        }
+
+        if (!process.WaitForExit (5000))
+        {
+            var timeoutError =
+                $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
+
+            throw new TimeoutException (timeoutError);
+        }
+
+        if (waitForOutput && process.StandardOutput.Peek () != -1)
+        {
+            output = process.StandardOutput.ReadToEnd ();
+        }
+
+        if (process.ExitCode > 0)
+        {
+            output = $@"Process failed to run. Command line: {cmd} {arguments}.
+										Output: {output}
+										Error: {process.StandardError.ReadToEnd ()}";
+        }
+
+        return (process.ExitCode, output);
+    }
+}

+ 4 - 2
Terminal.Gui/App/RunState.cs

@@ -44,15 +44,16 @@ public class RunState : IDisposable
     }
 
 #if DEBUG_IDISPOSABLE
+#pragma warning disable CS0419 // Ambiguous reference in cref attribute
     /// <summary>
-    ///     Gets whether <see cref="Dispose"/> was called on this RunState or not.
+    ///     Gets whether <see cref="RunState.Dispose"/> was called on this RunState or not.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
     public bool WasDisposed { get; private set; }
 
     /// <summary>
-    ///     Gets the number of times <see cref="Dispose"/> was called on this object.
+    ///     Gets the number of times <see cref="RunState.Dispose"/> was called on this object.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
@@ -71,5 +72,6 @@ public class RunState : IDisposable
     {
         Instances.Add (this);
     }
+#pragma warning restore CS0419 // Ambiguous reference in cref attribute
 #endif
 }

+ 27 - 5
Terminal.Gui/Configuration/DeepCloner.cs

@@ -278,17 +278,37 @@ public static class DeepCloner
 
         // Determine dictionary type and comparer
         Type [] genericArgs = type.GetGenericArguments ();
-        Type dictType = genericArgs.Length == 2
-            ? typeof (Dictionary<,>).MakeGenericType (genericArgs)
-            : typeof (Dictionary<object, object>);
+        Type dictType;
+
+        if (genericArgs.Length == 2)
+        {
+            if (type.GetGenericTypeDefinition () == typeof (Dictionary<,>))
+            {
+                dictType = typeof (Dictionary<,>).MakeGenericType (genericArgs);
+            }
+            else if (type.GetGenericTypeDefinition () == typeof (ConcurrentDictionary<,>))
+            {
+                dictType = typeof (ConcurrentDictionary<,>).MakeGenericType (genericArgs);
+            }
+            else
+            {
+                throw new InvalidOperationException (
+                                                     $"Unsupported dictionary type: {type}. Only Dictionary<,> and ConcurrentDictionary<,> are supported.");
+            }
+        }
+        else
+        {
+            dictType = typeof (Dictionary<object, object>);
+        }
+
         object? comparer = type.GetProperty ("Comparer")?.GetValue (source);
 
         // Create a temporary dictionary to hold cloned key-value pairs
         IDictionary tempDict = CreateDictionaryInstance (dictType, comparer);
         visited.TryAdd (source, tempDict);
 
-
         object? lastKey = null;
+
         try
         {
             // Clone all key-value pairs
@@ -311,7 +331,9 @@ public static class DeepCloner
         catch (InvalidOperationException ex)
         {
             // Handle cases where the dictionary is modified during enumeration
-            throw new InvalidOperationException ($"Error cloning dictionary ({source}) (last key was \"{lastKey}\"). Ensure the source dictionary is not modified during cloning.", ex);
+            throw new InvalidOperationException (
+                                                 $"Error cloning dictionary ({source}) (last key was \"{lastKey}\"). Ensure the source dictionary is not modified during cloning.",
+                                                 ex);
         }
 
         // If the original dictionary type has a parameterless constructor, create a new instance

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

+ 116 - 286
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;
-                    }
+            return updated;
+        }
 
-                    Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
+        var top = 0;
+        var left = 0;
+        int rows = Rows;
+        int cols = Cols;
+        var output = new StringBuilder ();
+        Attribute? redrawAttr = null;
+        int lastCol = -1;
 
-                    Rune rune = Contents [row, col].Rune;
+        CursorVisibility? savedVisibility = _currentCursorVisibility;
+        SetCursorVisibility (CursorVisibility.Invisible);
 
-                    if (rune.IsBmp)
-                    {
-                        // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
-                        if (rune.GetColumns () < 2)
-                        {
-                            Curses.mvaddch (row, col, rune.Value);
-                        }
-                        else /*if (col + 1 < Cols)*/
-                        {
-                            Curses.mvaddwstr (row, col, rune.ToString ());
-                        }
-                    }
-                    else
-                    {
-                        Curses.mvaddwstr (row, col, rune.ToString ());
-
-                        if (rune.GetColumns () > 1 && col + 1 < Cols)
-                        {
-                            // TODO: This is a hack to deal with non-BMP and wide characters.
-                            //col++;
-                            Curses.mvaddch (row, ++col, '*');
-                        }
-                    }
-                }
+        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)
@@ -536,6 +443,8 @@ internal class CursesDriver : ConsoleDriver
         return true;
     }
 
+    private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
+
     /// <inheritdoc/>
     public override bool SetCursorVisibility (CursorVisibility visibility)
     {
@@ -547,17 +456,19 @@ internal class CursesDriver : ConsoleDriver
         if (!RunningUnitTests)
         {
             Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
+            Curses.leaveok (_window!.Handle, !Force16Colors);
         }
 
         if (visibility != CursorVisibility.Invisible)
         {
-            _mainLoopDriver?.WriteRaw (
-                                       EscSeqUtils.CSI_SetCursorStyle (
-                                                                       (EscSeqUtils.DECSCUSR_Style)
-                                                                       (((int)visibility >> 24)
-                                                                        & 0xFF)
-                                                                      )
-                                      );
+            if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
+            {
+                _currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
+
+                _mainLoopDriver?.WriteRaw (
+                                           EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle)
+                                          );
+            }
         }
 
         _currentCursorVisibility = visibility;
@@ -1171,84 +1082,3 @@ internal class CursesDriver : ConsoleDriver
     /// <inheritdoc/>
     public override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); }
 }
-
-// TODO: One type per file - move to another file
-internal static class Platform
-{
-    private static int _suspendSignal;
-
-    /// <summary>Suspends the process by sending SIGTSTP to itself</summary>
-    /// <returns>True if the suspension was successful.</returns>
-    public static bool Suspend ()
-    {
-        int signal = GetSuspendSignal ();
-
-        if (signal == -1)
-        {
-            return false;
-        }
-
-        killpg (0, signal);
-
-        return true;
-    }
-
-    private static int GetSuspendSignal ()
-    {
-        if (_suspendSignal != 0)
-        {
-            return _suspendSignal;
-        }
-
-        nint buf = Marshal.AllocHGlobal (8192);
-
-        if (uname (buf) != 0)
-        {
-            Marshal.FreeHGlobal (buf);
-            _suspendSignal = -1;
-
-            return _suspendSignal;
-        }
-
-        try
-        {
-            switch (Marshal.PtrToStringAnsi (buf))
-            {
-                case "Darwin":
-                case "DragonFly":
-                case "FreeBSD":
-                case "NetBSD":
-                case "OpenBSD":
-                    _suspendSignal = 18;
-
-                    break;
-                case "Linux":
-                    // TODO: should fetch the machine name and
-                    // if it is MIPS return 24
-                    _suspendSignal = 20;
-
-                    break;
-                case "Solaris":
-                    _suspendSignal = 24;
-
-                    break;
-                default:
-                    _suspendSignal = -1;
-
-                    break;
-            }
-
-            return _suspendSignal;
-        }
-        finally
-        {
-            Marshal.FreeHGlobal (buf);
-        }
-    }
-
-    [DllImport ("libc")]
-    private static extern int killpg (int pgrp, int pid);
-
-    [DllImport ("libc")]
-    private static extern int uname (nint buf);
-}

+ 83 - 0
Terminal.Gui/Drivers/CursesDriver/Platform.cs

@@ -0,0 +1,83 @@
+using System.Runtime.InteropServices;
+
+namespace Terminal.Gui.Drivers;
+
+internal static class Platform
+{
+    private static int _suspendSignal;
+
+    /// <summary>Suspends the process by sending SIGTSTP to itself</summary>
+    /// <returns>True if the suspension was successful.</returns>
+    public static bool Suspend ()
+    {
+        int signal = GetSuspendSignal ();
+
+        if (signal == -1)
+        {
+            return false;
+        }
+
+        killpg (0, signal);
+
+        return true;
+    }
+
+    private static int GetSuspendSignal ()
+    {
+        if (_suspendSignal != 0)
+        {
+            return _suspendSignal;
+        }
+
+        nint buf = Marshal.AllocHGlobal (8192);
+
+        if (uname (buf) != 0)
+        {
+            Marshal.FreeHGlobal (buf);
+            _suspendSignal = -1;
+
+            return _suspendSignal;
+        }
+
+        try
+        {
+            switch (Marshal.PtrToStringAnsi (buf))
+            {
+                case "Darwin":
+                case "DragonFly":
+                case "FreeBSD":
+                case "NetBSD":
+                case "OpenBSD":
+                    _suspendSignal = 18;
+
+                    break;
+                case "Linux":
+                    // TODO: should fetch the machine name and
+                    // if it is MIPS return 24
+                    _suspendSignal = 20;
+
+                    break;
+                case "Solaris":
+                    _suspendSignal = 24;
+
+                    break;
+                default:
+                    _suspendSignal = -1;
+
+                    break;
+            }
+
+            return _suspendSignal;
+        }
+        finally
+        {
+            Marshal.FreeHGlobal (buf);
+        }
+    }
+
+    [DllImport ("libc")]
+    private static extern int killpg (int pgrp, int pid);
+
+    [DllImport ("libc")]
+    private static extern int uname (nint buf);
+}

+ 1 - 2
Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs

@@ -249,8 +249,7 @@ internal class UnixMainLoop : IMainLoopDriver
 
     private class Watch
     {
-        // BUGBUG: Fix this nullable issue.
-        public Func<MainLoop, bool> Callback;
+        public Func<MainLoop, bool>? Callback;
         public Condition Condition;
         public int File;
     }

+ 17 - 21
Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs

@@ -1,4 +1,5 @@
-//
+#nullable enable
+//
 // FakeDriver.cs: A fake IConsoleDriver for unit tests. 
 //
 
@@ -36,7 +37,7 @@ public class FakeDriver : ConsoleDriver
         public bool UseFakeClipboard { get; internal set; }
     }
 
-    public static Behaviors FakeBehaviors = new ();
+    public static Behaviors FakeBehaviors { get; } = new ();
     public override bool SupportsTrueColor => false;
 
     /// <inheritdoc />
@@ -47,8 +48,8 @@ public class FakeDriver : ConsoleDriver
 
     public FakeDriver ()
     {
-        Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
-        Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
+        base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
+        base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
 
         if (FakeBehaviors.UseFakeClipboard)
         {
@@ -87,7 +88,7 @@ public class FakeDriver : ConsoleDriver
         FakeConsole.Clear ();
     }
 
-    private FakeMainLoop _mainLoopDriver;
+    private FakeMainLoop? _mainLoopDriver;
 
     public override MainLoop Init ()
     {
@@ -124,7 +125,7 @@ public class FakeDriver : ConsoleDriver
 
         for (int row = top; row < rows; row++)
         {
-            if (!_dirtyLines [row])
+            if (!_dirtyLines! [row])
             {
                 continue;
             }
@@ -144,7 +145,7 @@ public class FakeDriver : ConsoleDriver
 
                 for (; col < cols; col++)
                 {
-                    if (!Contents [row, col].IsDirty)
+                    if (!Contents! [row, col].IsDirty)
                     {
                         if (output.Length > 0)
                         {
@@ -168,7 +169,7 @@ public class FakeDriver : ConsoleDriver
                         lastCol = col;
                     }
 
-                    Attribute attr = Contents [row, col].Attribute.Value;
+                    Attribute attr = Contents [row, col].Attribute!.Value;
 
                     // Performance: Only send the escape sequence if the attribute has changed.
                     if (attr != redrawAttr)
@@ -209,18 +210,18 @@ public class FakeDriver : ConsoleDriver
 
         //SetCursorVisibility (savedVisibility);
 
-        void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+        void WriteToConsole (StringBuilder outputSb, ref int lastColumn, int row, ref int outputWidth)
         {
             FakeConsole.CursorTop = row;
-            FakeConsole.CursorLeft = lastCol;
+            FakeConsole.CursorLeft = lastColumn;
 
-            foreach (char c in output.ToString ())
+            foreach (char c in outputSb.ToString ())
             {
                 FakeConsole.Write (c);
             }
 
-            output.Clear ();
-            lastCol += outputWidth;
+            outputSb.Clear ();
+            lastColumn += outputWidth;
             outputWidth = 0;
         }
 
@@ -506,7 +507,7 @@ public class FakeDriver : ConsoleDriver
 
     public class FakeClipboard : ClipboardBase
     {
-        public Exception FakeException;
+        public Exception? FakeException { get; set; }
 
         private readonly bool _isSupportedAlwaysFalse;
         private string _contents = string.Empty;
@@ -536,19 +537,14 @@ public class FakeDriver : ConsoleDriver
             return _contents;
         }
 
-        protected override void SetClipboardDataImpl (string text)
+        protected override void SetClipboardDataImpl (string? text)
         {
-            if (text is null)
-            {
-                throw new ArgumentNullException (nameof (text));
-            }
-
             if (FakeException is { })
             {
                 throw FakeException;
             }
 
-            _contents = text;
+            _contents = text ?? throw new ArgumentNullException (nameof (text));
         }
     }
 

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

+ 47 - 35
Terminal.Gui/Drivers/V2/ApplicationV2.cs

@@ -1,5 +1,6 @@
 #nullable enable
 using System.Collections.Concurrent;
+using System.ComponentModel;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Microsoft.Extensions.Logging;
@@ -12,10 +13,7 @@ namespace Terminal.Gui.Drivers;
 /// </summary>
 public class ApplicationV2 : ApplicationImpl
 {
-    private readonly Func<INetInput> _netInputFactory;
-    private readonly Func<IConsoleOutput> _netOutputFactory;
-    private readonly Func<IWindowsInput> _winInputFactory;
-    private readonly Func<IConsoleOutput> _winOutputFactory;
+    private readonly IComponentFactory? _componentFactory;
     private IMainLoopCoordinator? _coordinator;
     private string? _driverName;
 
@@ -24,29 +22,20 @@ public class ApplicationV2 : ApplicationImpl
     /// <inheritdoc/>
     public override ITimedEvents TimedEvents => _timedEvents;
 
+    internal IMainLoopCoordinator? Coordinator => _coordinator;
+
     /// <summary>
     ///     Creates anew instance of the Application backend. The provided
     ///     factory methods will be used on Init calls to get things booted.
     /// </summary>
-    public ApplicationV2 () : this (
-                                    () => new NetInput (),
-                                    () => new NetOutput (),
-                                    () => new WindowsInput (),
-                                    () => new WindowsOutput ()
-                                   )
-    { }
-
-    internal ApplicationV2 (
-        Func<INetInput> netInputFactory,
-        Func<IConsoleOutput> netOutputFactory,
-        Func<IWindowsInput> winInputFactory,
-        Func<IConsoleOutput> winOutputFactory
-    )
+    public ApplicationV2 ()
+    {
+        IsLegacy = false;
+    }
+
+    internal ApplicationV2 (IComponentFactory componentFactory)
     {
-        _netInputFactory = netInputFactory;
-        _netOutputFactory = netOutputFactory;
-        _winInputFactory = winInputFactory;
-        _winOutputFactory = winOutputFactory;
+        _componentFactory = componentFactory;
         IsLegacy = false;
     }
 
@@ -92,8 +81,8 @@ public class ApplicationV2 : ApplicationImpl
     {
         PlatformID p = Environment.OSVersion.Platform;
 
-        bool definetlyWin = driverName?.Contains ("win") ?? false;
-        bool definetlyNet = driverName?.Contains ("net") ?? false;
+        bool definetlyWin = (driverName?.Contains ("win") ?? false )|| _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
+        bool definetlyNet = (driverName?.Contains ("net") ?? false ) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
 
         if (definetlyWin)
         {
@@ -125,13 +114,21 @@ public class ApplicationV2 : ApplicationImpl
         ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ();
         MainLoop<WindowsConsole.InputRecord> loop = new ();
 
-        return new MainLoopCoordinator<WindowsConsole.InputRecord> (
-                                                                    _timedEvents,
-                                                                    _winInputFactory,
+        IComponentFactory<WindowsConsole.InputRecord> cf;
+
+        if (_componentFactory != null)
+        {
+            cf = (IComponentFactory<WindowsConsole.InputRecord>)_componentFactory;
+        }
+        else
+        {
+            cf = new WindowsComponentFactory ();
+        }
+
+        return new MainLoopCoordinator<WindowsConsole.InputRecord> (_timedEvents,
                                                                     inputBuffer,
-                                                                    new WindowsInputProcessor (inputBuffer),
-                                                                    _winOutputFactory,
-                                                                    loop);
+                                                                    loop,
+                                                                    cf);
     }
 
     private IMainLoopCoordinator CreateNetSubcomponents ()
@@ -139,13 +136,22 @@ public class ApplicationV2 : ApplicationImpl
         ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ();
         MainLoop<ConsoleKeyInfo> loop = new ();
 
+        IComponentFactory<ConsoleKeyInfo> cf;
+
+        if (_componentFactory != null)
+        {
+            cf = (IComponentFactory<ConsoleKeyInfo>)_componentFactory;
+        }
+        else
+        {
+            cf = new NetComponentFactory ();
+        }
+
         return new MainLoopCoordinator<ConsoleKeyInfo> (
                                                         _timedEvents,
-                                                        _netInputFactory,
                                                         inputBuffer,
-                                                        new NetInputProcessor (inputBuffer),
-                                                        _netOutputFactory,
-                                                        loop);
+                                                        loop,
+                                                        cf);
     }
 
     /// <inheritdoc/>
@@ -171,6 +177,12 @@ public class ApplicationV2 : ApplicationImpl
             throw new NotInitializedException (nameof (Run));
         }
 
+        if (Application.Driver == null)
+        {
+            // See Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws
+            throw new  InvalidOperationException ("Driver was inexplicably null when trying to Run view");
+        }
+
         Application.Top = view;
 
         RunState rs = Application.Begin (view);
@@ -258,4 +270,4 @@ public class ApplicationV2 : ApplicationImpl
         Application.Top?.SetNeedsDraw();
         Application.Top?.SetNeedsLayout ();
     }
-}
+}

+ 26 - 0
Terminal.Gui/Drivers/V2/ComponentFactory.cs

@@ -0,0 +1,26 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+/// Abstract base class implementation of <see cref="IComponentFactory{T}"/>
+/// </summary>
+/// <typeparam name="T"></typeparam>
+public abstract class ComponentFactory<T> : IComponentFactory<T>
+{
+    /// <inheritdoc />
+    public abstract IConsoleInput<T> CreateInput ();
+
+    /// <inheritdoc />
+    public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue<T> inputBuffer);
+
+    /// <inheritdoc />
+    public virtual IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer)
+    {
+        return new WindowSizeMonitor (consoleOutput, outputBuffer);
+    }
+
+    /// <inheritdoc />
+    public abstract IConsoleOutput CreateOutput ();
+}

+ 62 - 13
Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs

@@ -1,5 +1,5 @@
-using System.Runtime.InteropServices;
-using Microsoft.Extensions.Logging;
+#nullable enable
+using System.Runtime.InteropServices;
 
 namespace Terminal.Gui.Drivers;
 
@@ -11,9 +11,13 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     private CursorVisibility _lastCursor = CursorVisibility.Default;
 
     /// <summary>The event fired when the terminal is resized.</summary>
-    public event EventHandler<SizeChangedEventArgs> SizeChanged;
+    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
 
     public IInputProcessor InputProcessor { get; }
+    public IOutputBuffer OutputBuffer => _outputBuffer;
+
+    public IWindowSizeMonitor WindowSizeMonitor { get; }
+
 
     public ConsoleDriverFacade (
         IInputProcessor inputProcessor,
@@ -36,13 +40,21 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
                                          MouseEvent?.Invoke (s, e);
                                      };
 
-        windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this, e);
+        WindowSizeMonitor = windowSizeMonitor;
+        windowSizeMonitor.SizeChanging += (_,e) => SizeChanged?.Invoke (this, e);
 
         CreateClipboard ();
     }
 
     private void CreateClipboard ()
     {
+        if (FakeDriver.FakeBehaviors.UseFakeClipboard)
+        {
+            Clipboard = new FakeDriver.FakeClipboard ();
+
+            return;
+        }
+
         PlatformID p = Environment.OSVersion.Platform;
 
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
@@ -68,7 +80,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     {
         get
         {
-            if (ConsoleDriver.RunningUnitTests)
+            if (ConsoleDriver.RunningUnitTests && _output is WindowsOutput or NetOutput)
             {
                 // In unit tests, we don't have a real output, so we return an empty rectangle.
                 return Rectangle.Empty;
@@ -83,7 +95,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     ///     to.
     /// </summary>
     /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
-    public Region Clip
+    public Region? Clip
     {
         get => _outputBuffer.Clip;
         set => _outputBuffer.Clip = value;
@@ -109,7 +121,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     ///     The contents of the application output. The driver outputs this buffer to the terminal.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
-    public Cell [,] Contents
+    public Cell [,]? Contents
     {
         get => _outputBuffer.Contents;
         set => _outputBuffer.Contents = value;
@@ -224,7 +236,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// <summary>
     ///     Raised each time <see cref="ConsoleDriver.ClearContents"/> is called. For benchmarking.
     /// </summary>
-    public event EventHandler<EventArgs> ClearedContents;
+    public event EventHandler<EventArgs>? ClearedContents;
 
     /// <summary>
     ///     Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/>
@@ -321,7 +333,36 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     }
 
     /// <inheritdoc/>
-    public void Suspend () { }
+    public void Suspend ()
+    {
+        if (Environment.OSVersion.Platform != PlatformID.Unix)
+        {
+            return;
+        }
+
+        Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+        if (!ConsoleDriver.RunningUnitTests)
+        {
+            Console.ResetColor ();
+            Console.Clear ();
+
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+            //Set cursor key to cursor.
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+            Platform.Suspend ();
+
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+            Application.LayoutAndDraw ();
+        }
+
+        Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+    }
 
     /// <summary>
     ///     Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and
@@ -363,7 +404,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     }
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
-    public event EventHandler<Key> KeyDown;
+    public event EventHandler<Key>? KeyDown;
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>
@@ -371,10 +412,10 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     ///     processing is
     ///     complete.
     /// </remarks>
-    public event EventHandler<Key> KeyUp;
+    public event EventHandler<Key>? KeyUp;
 
     /// <summary>Event fired when a mouse event occurs.</summary>
-    public event EventHandler<MouseEventArgs> MouseEvent;
+    public event EventHandler<MouseEventArgs>? MouseEvent;
 
     /// <summary>Simulates a key press.</summary>
     /// <param name="keyChar">The key character.</param>
@@ -384,7 +425,15 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
     /// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
     public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl)
     {
-        // TODO: implement
+        ConsoleKeyInfo consoleKeyInfo = new (keyChar, key, shift, alt, ctrl);
+
+        Key k = EscSeqUtils.MapKey (consoleKeyInfo);
+
+        if (InputProcessor.IsValidInput (k, out k))
+        {
+            InputProcessor.OnKeyDown (k);
+            InputProcessor.OnKeyUp (k);
+        }
     }
 
     /// <summary>

+ 50 - 0
Terminal.Gui/Drivers/V2/IComponentFactory.cs

@@ -0,0 +1,50 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+/// Base untyped interface for <see cref="IComponentFactory{T}"/> for methods that are not templated on low level
+/// console input type.
+/// </summary>
+public interface IComponentFactory
+{
+    /// <summary>
+    /// Create the <see cref="IConsoleOutput"/> class for the current driver implementation i.e. the class responsible for
+    /// rendering <see cref="IOutputBuffer"/> into the console.
+    /// </summary>
+    /// <returns></returns>
+    IConsoleOutput CreateOutput ();
+}
+
+/// <summary>
+/// Creates driver specific subcomponent classes (<see cref="IConsoleInput{T}"/>, <see cref="IInputProcessor"/> etc) for a
+/// <see cref="IMainLoopCoordinator"/>.
+/// </summary>
+/// <typeparam name="T"></typeparam>
+public interface IComponentFactory<T> : IComponentFactory
+{
+    /// <summary>
+    /// Create <see cref="IConsoleInput{T}"/> class for the current driver implementation i.e. the class responsible for reading
+    /// user input from the console.
+    /// </summary>
+    /// <returns></returns>
+    IConsoleInput<T> CreateInput ();
+
+    /// <summary>
+    /// Creates the <see cref="InputProcessor{T}"/> class for the current driver implementation i.e. the class responsible for
+    /// translating raw console input into Terminal.Gui common event <see cref="Key"/> and <see cref="MouseEventArgs"/>.
+    /// </summary>
+    /// <param name="inputBuffer"></param>
+    /// <returns></returns>
+    IInputProcessor CreateInputProcessor (ConcurrentQueue<T> inputBuffer);
+
+    /// <summary>
+    /// Creates <see cref="IWindowSizeMonitor"/> class for the current driver implementation i.e. the class responsible for
+    /// reporting the current size of the terminal window.
+    /// </summary>
+    /// <param name="consoleOutput"></param>
+    /// <param name="outputBuffer"></param>
+    /// <returns></returns>
+    IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer);
+}

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

@@ -1,4 +1,5 @@
-namespace Terminal.Gui.Drivers;
+#nullable enable
+namespace Terminal.Gui.Drivers;
 
 /// <summary>
 ///     Interface for v2 driver abstraction layer
@@ -10,5 +11,16 @@ public interface IConsoleDriverFacade
     ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
     ///     and detecting and processing ansi escape sequences.
     /// </summary>
-    public IInputProcessor InputProcessor { get; }
+    IInputProcessor InputProcessor { get; }
+
+    /// <summary>
+    ///     Describes the desired screen state. Data source for <see cref="IConsoleOutput"/>.
+    /// </summary>
+    IOutputBuffer OutputBuffer { get; }
+
+    /// <summary>
+    ///     Interface for classes responsible for reporting the current
+    ///     size of the terminal window.
+    /// </summary>
+    IWindowSizeMonitor WindowSizeMonitor { get; }
 }

+ 11 - 0
Terminal.Gui/Drivers/V2/IInputProcessor.cs

@@ -58,4 +58,15 @@ public interface IInputProcessor
     /// </summary>
     /// <returns></returns>
     public IAnsiResponseParser GetParser ();
+
+    /// <summary>
+    ///     Handles surrogate pairs in the input stream.
+    /// </summary>
+    /// <param name="key">The key from input.</param>
+    /// <param name="result">Get the surrogate pair or the key.</param>
+    /// <returns>
+    ///     <see langword="true"/> if the result is a valid surrogate pair or a valid key, otherwise
+    ///     <see langword="false"/>.
+    /// </returns>
+    bool IsValidInput (Key key, out Key result);
 }

+ 8 - 1
Terminal.Gui/Drivers/V2/IMainLoop.cs

@@ -48,7 +48,14 @@ public interface IMainLoop<T> : IDisposable
     /// <param name="inputBuffer"></param>
     /// <param name="inputProcessor"></param>
     /// <param name="consoleOutput"></param>
-    void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
+    /// <param name="componentFactory"></param>
+    void Initialize (
+        ITimedEvents timedEvents,
+        ConcurrentQueue<T> inputBuffer,
+        IInputProcessor inputProcessor,
+        IConsoleOutput consoleOutput,
+        IComponentFactory<T> componentFactory
+    );
 
     /// <summary>
     ///     Perform a single iteration of the main loop then blocks for a fixed length

+ 1 - 1
Terminal.Gui/Drivers/V2/IOutputBuffer.cs

@@ -12,7 +12,7 @@ public interface IOutputBuffer
     /// <summary>
     ///     The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
     /// </summary>
-    Cell [,] Contents { get; set; }
+    Cell [,]? Contents { get; set; }
 
     /// <summary>
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject

+ 4 - 1
Terminal.Gui/Drivers/V2/IWindowsInput.cs

@@ -1,4 +1,7 @@
 namespace Terminal.Gui.Drivers;
 
-internal interface IWindowsInput : IConsoleInput<WindowsConsole.InputRecord>
+/// <summary>
+/// Interface for windows only input which uses low level win32 apis (v2win)
+/// </summary>
+public interface IWindowsInput : IConsoleInput<WindowsConsole.InputRecord>
 { }

+ 18 - 1
Terminal.Gui/Drivers/V2/InputProcessor.cs

@@ -165,7 +165,8 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     internal char _highSurrogate = '\0';
 
-    internal bool IsValidInput (Key key, out Key result)
+    /// <inheritdoc />
+    public bool IsValidInput (Key key, out Key result)
     {
         result = key;
 
@@ -179,6 +180,22 @@ public abstract class InputProcessor<T> : IInputProcessor
         if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key))
         {
             result = (KeyCode)new Rune (_highSurrogate, (char)key).Value;
+
+            if (key.IsAlt)
+            {
+                result = result.WithAlt;
+            }
+
+            if (key.IsCtrl)
+            {
+                result = result.WithCtrl;
+            }
+
+            if (key.IsShift)
+            {
+                result = result.WithShift;
+            }
+
             _highSurrogate = '\0';
 
             return true;

+ 16 - 4
Terminal.Gui/Drivers/V2/MainLoop.cs

@@ -83,7 +83,14 @@ public class MainLoop<T> : IMainLoop<T>
     /// <param name="inputBuffer"></param>
     /// <param name="inputProcessor"></param>
     /// <param name="consoleOutput"></param>
-    public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
+    /// <param name="componentFactory"></param>
+    public void Initialize (
+        ITimedEvents timedEvents,
+        ConcurrentQueue<T> inputBuffer,
+        IInputProcessor inputProcessor,
+        IConsoleOutput consoleOutput,
+        IComponentFactory<T> componentFactory
+    )
     {
         InputBuffer = inputBuffer;
         Out = consoleOutput;
@@ -92,18 +99,22 @@ public class MainLoop<T> : IMainLoop<T>
         TimedEvents = timedEvents;
         AnsiRequestScheduler = new (InputProcessor.GetParser ());
 
-        WindowSizeMonitor = new WindowSizeMonitor (Out, OutputBuffer);
+        WindowSizeMonitor = componentFactory.CreateWindowSizeMonitor (Out, OutputBuffer);
     }
 
     /// <inheritdoc/>
     public void Iteration ()
     {
+
+        Application.RaiseIteration ();
+
         DateTime dt = Now ();
+        int timeAllowed = 1000 / Math.Max(1,(int)Application.MaximumIterationsPerSecond);
 
         IterationImpl ();
 
         TimeSpan took = Now () - dt;
-        TimeSpan sleepFor = TimeSpan.FromMilliseconds (50) - took;
+        TimeSpan sleepFor = TimeSpan.FromMilliseconds (timeAllowed) - took;
 
         Logging.TotalIterationMetric.Record (took.Milliseconds);
 
@@ -123,7 +134,8 @@ public class MainLoop<T> : IMainLoop<T>
         if (Application.Top != null)
         {
             bool needsDrawOrLayout = AnySubViewsNeedDrawn (Application.Popover?.GetActivePopover () as View)
-                                     || AnySubViewsNeedDrawn (Application.Top);
+                                     || AnySubViewsNeedDrawn (Application.Top)
+                                     || (Application.MouseGrabHandler.MouseGrabView != null && AnySubViewsNeedDrawn (Application.MouseGrabHandler.MouseGrabView));
 
             bool sizeChanged = WindowSizeMonitor.Poll ();
 

+ 11 - 24
Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs

@@ -13,12 +13,11 @@ namespace Terminal.Gui.Drivers;
 /// <typeparam name="T"></typeparam>
 internal class MainLoopCoordinator<T> : IMainLoopCoordinator
 {
-    private readonly Func<IConsoleInput<T>> _inputFactory;
     private readonly ConcurrentQueue<T> _inputBuffer;
     private readonly IInputProcessor _inputProcessor;
     private readonly IMainLoop<T> _loop;
+    private readonly IComponentFactory<T> _componentFactory;
     private readonly CancellationTokenSource _tokenSource = new ();
-    private readonly Func<IConsoleOutput> _outputFactory;
     private IConsoleInput<T> _input;
     private IConsoleOutput _output;
     private readonly object _oLockInitialization = new ();
@@ -32,34 +31,22 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
     ///     Creates a new coordinator
     /// </summary>
     /// <param name="timedEvents"></param>
-    /// <param name="inputFactory">
-    ///     Function to create a new input. This must call <see langword="new"/>
-    ///     explicitly and cannot return an existing instance. This requirement arises because Windows
-    ///     console screen buffer APIs are thread-specific for certain operations.
-    /// </param>
     /// <param name="inputBuffer"></param>
-    /// <param name="inputProcessor"></param>
-    /// <param name="outputFactory">
-    ///     Function to create a new output. This must call <see langword="new"/>
-    ///     explicitly and cannot return an existing instance. This requirement arises because Windows
-    ///     console screen buffer APIs are thread-specific for certain operations.
-    /// </param>
     /// <param name="loop"></param>
+    /// <param name="componentFactory">Factory for creating driver components
+    /// (<see cref="IConsoleOutput"/>, <see cref="IConsoleInput{T}"/> etc)</param>
     public MainLoopCoordinator (
         ITimedEvents timedEvents,
-        Func<IConsoleInput<T>> inputFactory,
         ConcurrentQueue<T> inputBuffer,
-        IInputProcessor inputProcessor,
-        Func<IConsoleOutput> outputFactory,
-        IMainLoop<T> loop
+        IMainLoop<T> loop,
+        IComponentFactory<T> componentFactory
     )
     {
         _timedEvents = timedEvents;
-        _inputFactory = inputFactory;
         _inputBuffer = inputBuffer;
-        _inputProcessor = inputProcessor;
-        _outputFactory = outputFactory;
+        _inputProcessor = componentFactory.CreateInputProcessor (_inputBuffer);
         _loop = loop;
+        _componentFactory = componentFactory;
     }
 
     /// <summary>
@@ -89,7 +76,7 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
                 throw _inputTask.Exception;
             }
 
-            throw new ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
+            Logging.Logger.LogCritical("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
         }
 
         Logging.Logger.LogInformation ("Main Loop Coordinator booting complete");
@@ -102,7 +89,7 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
             lock (_oLockInitialization)
             {
                 // Instance must be constructed on the thread in which it is used.
-                _input = _inputFactory.Invoke ();
+                _input = _componentFactory.CreateInput ();
                 _input.Initialize (_inputBuffer);
 
                 BuildFacadeIfPossible ();
@@ -142,8 +129,8 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
         lock (_oLockInitialization)
         {
             // Instance must be constructed on the thread in which it is used.
-            _output = _outputFactory.Invoke ();
-            _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output);
+            _output = _componentFactory.CreateOutput ();
+            _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output,_componentFactory);
 
             BuildFacadeIfPossible ();
         }

+ 29 - 0
Terminal.Gui/Drivers/V2/NetComponentFactory.cs

@@ -0,0 +1,29 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+/// <see cref="IComponentFactory{T}"/> implementation for native csharp console I/O i.e. v2net.
+/// This factory creates instances of internal classes <see cref="NetInput"/>, <see cref="NetOutput"/> etc.
+/// </summary>
+public class NetComponentFactory : ComponentFactory<ConsoleKeyInfo>
+{
+    /// <inheritdoc/>
+    public override IConsoleInput<ConsoleKeyInfo> CreateInput ()
+    {
+        return new NetInput ();
+    }
+
+    /// <inheritdoc />
+    public override IConsoleOutput CreateOutput ()
+    {
+        return new NetOutput ();
+    }
+
+    /// <inheritdoc />
+    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
+    {
+        return new NetInputProcessor (inputBuffer);
+    }
+}

+ 17 - 5
Terminal.Gui/Drivers/V2/NetOutput.cs

@@ -28,7 +28,11 @@ public class NetOutput : OutputBase, IConsoleOutput
     }
 
     /// <inheritdoc/>
-    public void Write (ReadOnlySpan<char> text) { Console.Out.Write (text); }
+    public void Write (ReadOnlySpan<char> text)
+    {
+        Console.Out.Write (text);
+    }
+
 
     /// <inheritdoc/>
     public Size GetWindowSize ()
@@ -67,9 +71,14 @@ public class NetOutput : OutputBase, IConsoleOutput
         EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
     }
 
-    /// <inheritdoc/>
-    protected override void Write (StringBuilder output) { Console.Out.Write (output); }
 
+    /// <inheritdoc />
+    protected override void Write (StringBuilder output)
+    {
+        Console.Out.Write (output);
+    }
+
+    /// <inheritdoc />
     protected override bool SetCursorPositionImpl (int col, int row)
     {
         if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
@@ -102,9 +111,12 @@ public class NetOutput : OutputBase, IConsoleOutput
     }
 
     /// <inheritdoc/>
-    public void Dispose () { }
+    public void Dispose ()
+    {
+    }
 
-    /// <inheritdoc/>
+
+    /// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
     public override void SetCursorVisibility (CursorVisibility visibility)
     {
         Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);

+ 34 - 2
Terminal.Gui/Drivers/V2/OutputBase.cs

@@ -1,5 +1,14 @@
-namespace Terminal.Gui.Drivers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
 
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+/// Abstract base class to assist with implementing <see cref="IConsoleOutput"/>.
+/// </summary>
 public abstract class OutputBase
 {
     private CursorVisibility? _cachedCursorVisibility;
@@ -7,7 +16,7 @@ public abstract class OutputBase
     // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
     private TextStyle _redrawTextStyle = TextStyle.None;
 
-    /// <inheritdoc/>
+    /// <inheritdoc cref="IConsoleOutput.Write(IOutputBuffer)"/>
     public virtual void Write (IOutputBuffer buffer)
     {
         if (ConsoleDriver.RunningUnitTests)
@@ -144,6 +153,14 @@ public abstract class OutputBase
         _cachedCursorVisibility = savedVisibility;
     }
 
+    /// <summary>
+    /// Changes the color and text style of the console to the given <paramref name="attr"/> and <paramref name="redrawTextStyle"/>.
+    /// If command can be buffered in line with other output (e.g. CSI sequence) then it should be appended to <paramref name="output"/>
+    /// otherwise the relevant output state should be flushed directly (e.g. by calling relevant win 32 API method)
+    /// </summary>
+    /// <param name="output"></param>
+    /// <param name="attr"></param>
+    /// <param name="redrawTextStyle"></param>
     protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
 
     private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
@@ -155,9 +172,24 @@ public abstract class OutputBase
         outputWidth = 0;
     }
 
+    /// <summary>
+    /// Output the contents of the <paramref name="output"/> to the console.
+    /// </summary>
+    /// <param name="output"></param>
     protected abstract void Write (StringBuilder output);
 
+    /// <summary>
+    /// When overriden in derived class, positions the terminal output cursor to the specified point on the screen.
+    /// </summary>
+    /// <param name="screenPositionX">Column to move cursor to</param>
+    /// <param name="screenPositionY">Row to move cursor to</param>
+    /// <returns></returns>
     protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
 
+    /// <summary>
+    /// Changes the visibility of the cursor in the terminal to the specified <paramref name="visibility"/> e.g.
+    /// the flashing indicator, invisible, box indicator etc.
+    /// </summary>
+    /// <param name="visibility"></param>
     public abstract void SetCursorVisibility (CursorVisibility visibility);
 }

+ 2 - 0
Terminal.Gui/Drivers/V2/OutputBuffer.cs

@@ -141,6 +141,8 @@ public class OutputBuffer : IOutputBuffer
             return;
         }
 
+        Clip ??= new Region (Screen);
+
         Rectangle clipRect = Clip!.GetBounds ();
 
         if (validLocation)

+ 3 - 0
Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs

@@ -20,6 +20,9 @@ public class ToplevelTransitionManager : IToplevelTransitionManager
         {
             top.OnReady ();
             _readiedTopLevels.Add (top);
+
+            // Views can be closed and opened and run again multiple times, see End_Does_Not_Dispose
+            top.Closed += (s, e) => _readiedTopLevels.Remove (top);
         }
     }
 

+ 29 - 0
Terminal.Gui/Drivers/V2/WindowsComponentFactory.cs

@@ -0,0 +1,29 @@
+#nullable enable
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+/// <see cref="IComponentFactory{T}"/> implementation for win32 windows only I/O i.e. v2win.
+/// This factory creates instances of internal classes <see cref="WindowsInput"/>, <see cref="WindowsOutput"/> etc.
+/// </summary>
+public class WindowsComponentFactory : ComponentFactory<WindowsConsole.InputRecord>
+{
+    /// <inheritdoc />
+    public override IConsoleInput<WindowsConsole.InputRecord> CreateInput ()
+    {
+        return new WindowsInput ();
+    }
+
+    /// <inheritdoc />
+    public override IInputProcessor CreateInputProcessor (ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer)
+    {
+        return new WindowsInputProcessor (inputBuffer);
+    }
+
+    /// <inheritdoc />
+    public override IConsoleOutput CreateOutput ()
+    {
+        return new WindowsOutput ();
+    }
+}

+ 3 - 3
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;
@@ -431,7 +431,7 @@ internal partial class WindowsOutput : OutputBase, IConsoleOutput
         return true;
     }
 
-    /// <inheritdoc/>
+    /// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
     public override void SetCursorVisibility (CursorVisibility visibility)
     {
         if (ConsoleDriver.RunningUnitTests)
@@ -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 ()

+ 211 - 58
Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs

@@ -2,10 +2,12 @@
 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;
 
-internal partial class WindowsConsole
+public partial class WindowsConsole
 {
     private CancellationTokenSource? _inputReadyCancellationTokenSource;
     private readonly BlockingCollection<InputRecord> _inputQueue = new (new ConcurrentQueue<InputRecord> ());
@@ -35,10 +37,69 @@ internal 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 @@ internal 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 @@ internal 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 @@ internal partial class WindowsConsole
                     _redrawTextStyle = attr.Style;
                 }
 
-                if (info.Char != '\x1b')
+                if (info.Char [0] != '\x1b')
                 {
                     if (!info.Empty)
                     {
@@ -229,7 +391,7 @@ internal partial class WindowsConsole
             foreach (var sixel in Application.Sixel)
             {
                 SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y));
-                WriteConsole (IsWindowsTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
+                WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
             }
         }
 
@@ -259,29 +421,6 @@ internal 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 @@ internal partial class WindowsConsole
 
     public bool SetCursorPosition (Coord position)
     {
-        return SetConsoleCursorPosition (IsWindowsTerminal ? _outputHandle : _screenBuffer, position);
+        return SetConsoleCursorPosition (IsVirtualTerminal ? _outputHandle : _screenBuffer, position);
     }
 
     public void SetInitialCursorVisibility ()
@@ -305,14 +444,14 @@ internal partial class WindowsConsole
 
     public bool GetCursorVisibility (out CursorVisibility visibility)
     {
-        if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
+        if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
         {
             visibility = CursorVisibility.Invisible;
 
             return false;
         }
 
-        if (!GetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info))
+        if (!GetConsoleCursorInfo (IsVirtualTerminal ? _outputHandle : _screenBuffer, out ConsoleCursorInfo info))
         {
             int err = Marshal.GetLastWin32Error ();
 
@@ -380,7 +519,7 @@ internal partial class WindowsConsole
                 bVisible = ((uint)visibility & 0xFF00) != 0
             };
 
-            if (!SetConsoleCursorInfo (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref info))
+            if (!SetConsoleCursorInfo (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref info))
             {
                 return false;
             }
@@ -430,7 +569,7 @@ internal partial class WindowsConsole
 
     internal Size GetConsoleBufferWindow (out Point position)
     {
-        if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
+        if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
         {
             position = Point.Empty;
 
@@ -440,7 +579,7 @@ internal partial class WindowsConsole
         var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
         csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-        if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
             //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
             position = Point.Empty;
@@ -479,19 +618,19 @@ internal partial class WindowsConsole
         var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
         csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-        if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
 
-        Coord maxWinSize = GetLargestConsoleWindowSize (IsWindowsTerminal ? _outputHandle : _screenBuffer);
+        Coord maxWinSize = GetLargestConsoleWindowSize (IsVirtualTerminal ? _outputHandle : _screenBuffer);
         short newCols = Math.Min (cols, maxWinSize.X);
         short newRows = Math.Min (rows, maxWinSize.Y);
         csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1));
         csbi.srWindow = new SmallRect (0, 0, newCols, newRows);
         csbi.dwMaximumWindowSize = new Coord (newCols, newRows);
 
-        if (!SetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        if (!SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
@@ -509,11 +648,18 @@ internal 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 @@ internal partial class WindowsConsole
 
     internal Size SetConsoleOutputWindow (out Point position)
     {
-        if ((IsWindowsTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
+        if ((IsVirtualTerminal ? _outputHandle : _screenBuffer) == nint.Zero)
         {
             position = Point.Empty;
 
@@ -531,7 +677,7 @@ internal partial class WindowsConsole
         var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
         csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-        if (!GetConsoleScreenBufferInfoEx (IsWindowsTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
@@ -556,7 +702,7 @@ internal partial class WindowsConsole
         return sz;
     }
 
-    internal bool IsWindowsTerminal { get; set; }
+    internal bool IsVirtualTerminal { get; init; }
 
     private uint ConsoleMode
     {
@@ -573,6 +719,7 @@ internal partial class WindowsConsole
     public enum ConsoleModes : uint
     {
         EnableProcessedInput = 1,
+        EnableVirtualTerminalProcessing = 4,
         EnableMouseInput = 16,
         EnableQuickEditMode = 64,
         EnableExtendedFlags = 128
@@ -791,11 +938,11 @@ internal 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 @@ internal 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

+ 7 - 0
Terminal.Gui/ViewBase/Adornment/ShadowView.cs

@@ -151,6 +151,13 @@ internal class ShadowView : View
             return Attribute.Default;
         }
 
+        if (Driver?.Contents == null ||
+            location.Y < 0 || location.Y >= Driver.Contents.GetLength (0) ||
+            location.X < 0 || location.X >= Driver.Contents.GetLength (1))
+        {
+            return Attribute.Default;
+        }
+
         Attribute attr = Driver!.Contents! [location.Y, location.X].Attribute!.Value;
 
         var newAttribute =

+ 5 - 2
Terminal.Gui/ViewBase/View.cs

@@ -520,6 +520,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
     #endregion
 
 #if DEBUG_IDISPOSABLE
+#pragma warning disable CS0419 // Ambiguous reference in cref attribute
     /// <summary>
     ///     Gets or sets whether failure to appropriately call Dispose() on a View will result in an Assert.
     ///     The default is <see langword="true"/>.
@@ -529,15 +530,16 @@ public partial class View : IDisposable, ISupportInitializeNotification
     /// </summary>
     public static bool EnableDebugIDisposableAsserts { get; set; } = true;
 
+
     /// <summary>
-    ///     Gets whether <see cref="Dispose"/> was called on this view or not.
+    ///     Gets whether <see cref="View.Dispose"/> was called on this view or not.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
     public bool WasDisposed { get; private set; }
 
     /// <summary>
-    ///     Gets the number of times <see cref="Dispose"/> was called on this view.
+    ///     Gets the number of times <see cref="View.Dispose"/> was called on this view.
     ///     For debug purposes to verify objects are being disposed properly.
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
@@ -550,5 +552,6 @@ public partial class View : IDisposable, ISupportInitializeNotification
     ///     Only valid when DEBUG_IDISPOSABLE is defined.
     /// </summary>
     public static ConcurrentBag<View> Instances { get; private set; } = [];
+#pragma warning restore CS0419 // Ambiguous reference in cref attribute
 #endif
 }

+ 1 - 1
Terminal.Gui/ViewBase/ViewCollectionHelpers.cs

@@ -10,7 +10,7 @@ internal static class ViewCollectionHelpers
             // The list parameter might be the live `_subviews`, so freeze it under a lock
             lock (list)
             {
-                return [.. list]; // C# 12 slice copy (= new List<View>(list).ToArray())
+                return list.ToArray (); // It’s slightly less “fancy C# 12”, but much safer in multithreaded code
             }
         }
 

+ 7 - 4
Terminal.Gui/Views/CharMap/CharMap.cs

@@ -681,14 +681,20 @@ public class CharMap : View, IDesignable
                                    {
                                        CopyGlyph ();
                                        dlg!.RequestStop ();
+                                       a.Handled = true;
                                    };
 
             copyCodepoint.Accepting += (s, a) =>
                                        {
                                            CopyCodePoint ();
                                            dlg!.RequestStop ();
+                                           a.Handled = true;
                                        };
-            cancel.Accepting += (s, a) => dlg!.RequestStop ();
+            cancel.Accepting += (s, a) =>
+                                {
+                                    dlg!.RequestStop ();
+                                    a.Handled = true;
+                                };
 
             var rune = (Rune)SelectedCodePoint;
             var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 };
@@ -765,9 +771,6 @@ public class CharMap : View, IDesignable
                                    Strings.btnOk
                                   );
         }
-
-        // BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug
-        Application.MouseGrabHandler.GrabMouse (this);
     }
 
     #endregion Details Dialog

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

+ 0 - 6
Terminal.Gui/Views/Dialog.cs

@@ -109,12 +109,6 @@ public class Dialog : Window
     {
         get
         {
-#if DEBUG_IDISPOSABLE
-            if (EnableDebugIDisposableAsserts && WasDisposed)
-            {
-                throw new ObjectDisposedException (GetType ().FullName);
-            }
-#endif
             return _canceled;
         }
         set

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

+ 48 - 50
Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs

@@ -1,4 +1,5 @@
 using TerminalGuiFluentTesting;
+using TerminalGuiFluentTestingXunit;
 using Xunit.Abstractions;
 
 namespace IntegrationTests.FluentTests;
@@ -7,16 +8,13 @@ public class BasicFluentAssertionTests
 {
     private readonly TextWriter _out;
 
-    public BasicFluentAssertionTests (ITestOutputHelper outputHelper)
-    {
-        _out = new TestOutputWriter (outputHelper);
-    }
+    public BasicFluentAssertionTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); }
 
     [Theory]
     [ClassData (typeof (V2TestDrivers))]
     public void GuiTestContext_NewInstance_Runs (V2TestDriver d)
     {
-        using GuiTestContext context = With.A<Window> (40, 10, d);
+        using GuiTestContext context = With.A<Window> (40, 10, d, _out);
         Assert.True (Application.Top!.Running);
 
         context.WriteOutLogs (_out);
@@ -34,9 +32,6 @@ public class BasicFluentAssertionTests
         context.RaiseKeyDownEvent (Application.QuitKey);
         Assert.False (top!.Running);
 
-        Application.Top?.Dispose ();
-        Application.Shutdown ();
-
         context.WriteOutLogs (_out);
         context.Stop ();
     }
@@ -69,9 +64,10 @@ public class BasicFluentAssertionTests
 
         using GuiTestContext c = With.A<Window> (40, 10, d)
                                      .Add (lbl)
-                                     .Then (() => Assert.Equal (38, lbl.Frame.Width)) // Window has 2 border
+                                     .AssertEqual (38, lbl.Frame.Width) // Window has 2 border
                                      .ResizeConsole (20, 20)
-                                     .Then (() => Assert.Equal (18, lbl.Frame.Width))
+                                     .WaitIteration ()
+                                     .AssertEqual (18, lbl.Frame.Width)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -85,7 +81,7 @@ public class BasicFluentAssertionTests
         MenuItemv2 [] menuItems = [new ("_New File", string.Empty, () => { clicked = true; })];
 
         using GuiTestContext c = With.A<Window> (40, 10, d)
-                                     .WithContextMenu (new PopoverMenu (menuItems))
+                                     .WithContextMenu (new (menuItems))
                                      .ScreenShot ("Before open menu", _out)
 
                                      // Click in main area inside border
@@ -98,7 +94,6 @@ public class BasicFluentAssertionTests
                                                 Assert.NotNull (popover);
                                                 var popoverMenu = popover as PopoverMenu;
                                                 popoverMenu!.Root!.BorderStyle = LineStyle.Single;
-
                                             })
                                      .WaitIteration ()
                                      .ScreenShot ("After open menu", _out)
@@ -114,26 +109,30 @@ public class BasicFluentAssertionTests
     {
         var clicked = false;
 
-        MenuItemv2 [] menuItems = [
-                                      new ("One", "", null),
-                                      new ("Two", "", null),
-                                      new ("Three", "", null),
-                                      new ("Four", "", new (
-                                           [
-                                               new ("SubMenu1", "", null),
-                                               new ("SubMenu2", "", ()=>clicked=true),
-                                               new ("SubMenu3", "", null),
-                                               new ("SubMenu4", "", null),
-                                               new ("SubMenu5", "", null),
-                                               new ("SubMenu6", "", null),
-                                               new ("SubMenu7", "", null)
-                                           ])),
-                                      new  ("Five", "", null),
-                                      new  ("Six", "", null)
-                                  ];
+        MenuItemv2 [] menuItems =
+        [
+            new ("One", "", null),
+            new ("Two", "", null),
+            new ("Three", "", null),
+            new (
+                 "Four",
+                 "",
+                 new (
+                      [
+                          new ("SubMenu1", "", null),
+                          new ("SubMenu2", "", () => clicked = true),
+                          new ("SubMenu3", "", null),
+                          new ("SubMenu4", "", null),
+                          new ("SubMenu5", "", null),
+                          new ("SubMenu6", "", null),
+                          new ("SubMenu7", "", null)
+                      ])),
+            new ("Five", "", null),
+            new ("Six", "", null)
+        ];
 
         using GuiTestContext c = With.A<Window> (40, 10, d)
-                                     .WithContextMenu (new PopoverMenu (menuItems))
+                                     .WithContextMenu (new (menuItems))
                                      .ScreenShot ("Before open menu", _out)
 
                                      // Click in main area inside border
@@ -177,43 +176,43 @@ public class BasicFluentAssertionTests
                                                 Application.Top!.Add (w1, w2, w3);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.True (v5.HasFocus))
+                                     .AssertTrue (v5.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v1.HasFocus))
+                                     .AssertTrue (v1.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v3.HasFocus))
+                                     .AssertTrue (v3.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v1.HasFocus))
+                                     .AssertTrue (v1.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v5.HasFocus))
+                                     .AssertTrue (v5.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v3.HasFocus))
+                                     .AssertTrue (v3.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v5.HasFocus))
+                                     .AssertTrue (v5.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v1.HasFocus))
+                                     .AssertTrue (v1.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v3.HasFocus))
+                                     .AssertTrue (v3.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v1.HasFocus))
+                                     .AssertTrue (v1.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v5.HasFocus))
+                                     .AssertTrue (v5.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v3.HasFocus))
+                                     .AssertTrue (v3.HasFocus)
                                      .RaiseKeyDownEvent (Key.Tab)
-                                     .Then (() => Assert.True (v4.HasFocus))
+                                     .AssertTrue (v4.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v5.HasFocus))
+                                     .AssertTrue (v5.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v1.HasFocus))
+                                     .AssertTrue (v1.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v5.HasFocus))
+                                     .AssertTrue (v5.HasFocus)
                                      .RaiseKeyDownEvent (Key.Tab)
-                                     .Then (() => Assert.True (v6.HasFocus))
+                                     .AssertTrue (v6.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6.WithShift)
-                                     .Then (() => Assert.True (v4.HasFocus))
+                                     .AssertTrue (v4.HasFocus)
                                      .RaiseKeyDownEvent (Key.F6)
-                                     .Then (() => Assert.True (v6.HasFocus))
+                                     .AssertTrue (v6.HasFocus)
                                      .WriteOutLogs (_out)
                                      .Stop ();
         Assert.False (v1.HasFocus);
@@ -221,6 +220,5 @@ public class BasicFluentAssertionTests
         Assert.False (v3.HasFocus);
         Assert.False (v4.HasFocus);
         Assert.False (v5.HasFocus);
-        Assert.False (v6.HasFocus);
     }
 }

+ 74 - 62
Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs

@@ -41,15 +41,28 @@ public class FileDialogFluentTests
         return mockFileSystem;
     }
 
+    private Toplevel NewSaveDialog (out SaveDialog sd, bool modal = true)
+    {
+        return NewSaveDialog (out sd, out _, modal);
+    }
+
+    private Toplevel NewSaveDialog (out SaveDialog sd, out MockFileSystem fs,bool modal = true)
+    {
+        fs = CreateExampleFileSystem ();
+        sd = new SaveDialog (fs) { Modal = modal };
+        return sd;
+    }
+
+
     [Theory]
     [ClassData (typeof (V2TestDrivers))]
     public void CancelFileDialog_UsingEscape (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ());
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        using var c = With.A (()=>NewSaveDialog(out sd), 100, 20, d)
             .ScreenShot ("Save dialog", _out)
             .Escape ()
-            .Then (() => Assert.True (sd.Canceled))
+            .AssertTrue (sd!.Canceled)
             .Stop ();
     }
 
@@ -57,11 +70,11 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void CancelFileDialog_UsingCancelButton_TabThenEnter (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        using var c = With.A (() => NewSaveDialog (out sd,modal:false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .Focus<Button> (b => b.Text == "_Cancel")
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Enter ()
                           .Stop ();
     }
@@ -70,25 +83,24 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void CancelFileDialog_UsingCancelButton_LeftClickButton (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ());
-
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        using var c = With.A (() => NewSaveDialog (out sd), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .LeftClick<Button> (b => b.Text == "_Cancel")
                           .WriteOutLogs (_out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Stop ();
     }
     [Theory]
     [ClassData (typeof (V2TestDrivers))]
     public void CancelFileDialog_UsingCancelButton_AltC (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ());
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        using var c = With.A (() => NewSaveDialog (out sd), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .Send (Key.C.WithAlt)
                           .WriteOutLogs (_out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Stop ();
     }
 
@@ -96,14 +108,15 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_UsingOkButton_Enter (V2TestDriver d)
     {
-        var fs = CreateExampleFileSystem ();
-        var sd = new SaveDialog (fs);
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd,out fs), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .LeftClick<Button> (b => b.Text == "_Save")
+                          .WaitIteration ()
                           .WriteOutLogs (_out)
-                          .Then (() => Assert.False (sd.Canceled))
-                          .Then (() => AssertIsFileSystemRoot (fs, sd))
+                          .AssertFalse(sd.Canceled)
+                          .AssertEqual (GetFileSystemRoot (fs), sd.FileName)
                           .Stop ();
     }
 
@@ -111,14 +124,14 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_UsingOkButton_AltS (V2TestDriver d)
     {
-        var fs = CreateExampleFileSystem ();
-        var sd = new SaveDialog (fs);
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .Send (Key.S.WithAlt)
                           .WriteOutLogs (_out)
-                          .Then (() => Assert.False (sd.Canceled))
-                          .Then (() => AssertIsFileSystemRoot (fs, sd))
+                          .AssertFalse (sd.Canceled)
+                          .AssertEqual (GetFileSystemRoot (fs), sd.FileName)
                           .Stop ();
 
     }
@@ -127,42 +140,39 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_UsingOkButton_TabEnter (V2TestDriver d)
     {
-        var fs = CreateExampleFileSystem ();
-        var sd = new SaveDialog (fs) { Modal = false };
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs,modal:false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
                           .Focus<Button> (b => b.Text == "_Save")
                           .Enter ()
                           .WriteOutLogs (_out)
-                          .Then (() => Assert.False (sd.Canceled))
-                          .Then (() => AssertIsFileSystemRoot (fs, sd))
+                          .AssertFalse(sd.Canceled)
+                          .AssertEqual (GetFileSystemRoot(fs), sd.FileName)
                           .Stop ();
     }
 
-    private void AssertIsFileSystemRoot (IFileSystem fs, SaveDialog sd)
+    private string GetFileSystemRoot (IFileSystem fs)
     {
-        var expectedPath =
-            RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ?
+        return RuntimeInformation.IsOSPlatform (OSPlatform.Windows) ?
                 $@"C:{fs.Path.DirectorySeparatorChar}" :
                 "/";
-
-        Assert.Equal (expectedPath, sd.FileName);
-
     }
 
     [Theory]
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_PressingPopTree_ShouldNotChangeCancel (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs,modal:false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Focus<Button> (b => b.Text == "►►")
                           .Enter ()
                           .ScreenShot ("After pop tree", _out)
                           .WriteOutLogs (_out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Stop ();
 
     }
@@ -171,11 +181,11 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_PopTree_AndNavigate (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
-
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
                           .ScreenShot ("Save dialog", _out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .LeftClick<Button> (b => b.Text == "►►")
                           .ScreenShot ("After pop tree", _out)
                           .Focus<TreeView<IFileSystemInfo>> (_ => true)
@@ -185,7 +195,7 @@ public class FileDialogFluentTests
                           .ScreenShot ("After navigate down in tree", _out)
                           .Enter ()
                           .WaitIteration ()
-                          .Then (() => Assert.False (sd.Canceled))
+                          .AssertFalse (sd.Canceled)
                           .AssertContains ("empty-dir", sd.FileName)
                           .WriteOutLogs (_out)
                           .Stop ();
@@ -195,12 +205,12 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_PopTree_AndNavigate_PreserveFilenameOnDirectoryChanges_True (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
-        sd.Style.PreserveFilenameOnDirectoryChanges = true;
-
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
+                          .Then (()=>sd.Style.PreserveFilenameOnDirectoryChanges=true)
                           .ScreenShot ("Save dialog", _out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Focus<TextField> (_=>true)
                           // Clear selection by pressing right in 'file path' text box
                           .RaiseKeyDownEvent (Key.CursorRight)
@@ -228,7 +238,7 @@ public class FileDialogFluentTests
                           .AssertEndsWith ("hello", sd.Path)
                           .Enter ()
                           .WaitIteration ()
-                          .Then (() => Assert.False (sd.Canceled))
+                          .AssertFalse (sd.Canceled)
                           .AssertContains ("empty-dir", sd.FileName)
                           .WriteOutLogs (_out)
                           .Stop ();
@@ -238,12 +248,12 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers))]
     public void SaveFileDialog_PopTree_AndNavigate_PreserveFilenameOnDirectoryChanges_False (V2TestDriver d)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
-        sd.Style.PreserveFilenameOnDirectoryChanges = false;
-
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
+                          .Then (()=> sd.Style.PreserveFilenameOnDirectoryChanges = false)
                           .ScreenShot ("Save dialog", _out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Focus<TextField> (_ => true)
                           // Clear selection by pressing right in 'file path' text box
                           .RaiseKeyDownEvent (Key.CursorRight)
@@ -269,7 +279,7 @@ public class FileDialogFluentTests
                           .AssertDoesNotContain ("hello", sd.Path)
                           .Enter ()
                           .WaitIteration ()
-                          .Then (() => Assert.False (sd.Canceled))
+                          .AssertFalse (sd.Canceled)
                           .AssertContains ("empty-dir", sd.FileName)
                           .WriteOutLogs (_out)
                           .Stop ();
@@ -279,12 +289,12 @@ public class FileDialogFluentTests
     [ClassData (typeof (V2TestDrivers_WithTrueFalseParameter))]
     public void SaveFileDialog_TableView_UpDown_PreserveFilenameOnDirectoryChanges_True (V2TestDriver d, bool preserve)
     {
-        var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false };
-        sd.Style.PreserveFilenameOnDirectoryChanges = preserve;
-
-        using var c = With.A (sd, 100, 20, d)
+        SaveDialog? sd = null;
+        MockFileSystem? fs = null;
+        using var c = With.A (() => NewSaveDialog (out sd, out fs, modal: false), 100, 20, d)
+                          .Then (() => sd.Style.PreserveFilenameOnDirectoryChanges = preserve)
                           .ScreenShot ("Save dialog", _out)
-                          .Then (() => Assert.True (sd.Canceled))
+                          .AssertTrue (sd.Canceled)
                           .Focus<TextField> (_ => true)
                           // Clear selection by pressing right in 'file path' text box
                           .RaiseKeyDownEvent (Key.CursorRight)
@@ -344,6 +354,7 @@ public class FileDialogFluentTests
         }
 
         c.LeftClick<Button> (b => b.Text == "_Save");
+        c.WaitIteration ();
         c.AssertFalse (sd.Canceled);
 
         if (preserve)
@@ -357,7 +368,8 @@ public class FileDialogFluentTests
              .AssertDoesNotContain ("hello", sd.Path);
         }
 
-        c.WriteOutLogs (_out)
-         .Stop ();
+        c.WriteOutLogs (_out);
+        c.WaitIteration ();
+        c.Stop ();
     }
 }

+ 41 - 40
Tests/IntegrationTests/FluentTests/MenuBarv2Tests.cs

@@ -1,6 +1,7 @@
 using System.Globalization;
 using System.Reflection;
 using TerminalGuiFluentTesting;
+using TerminalGuiFluentTestingXunit;
 using Xunit.Abstractions;
 
 namespace IntegrationTests.FluentTests;
@@ -167,15 +168,15 @@ public class MenuBarv2Tests
                                                 Application.Top!.Add (menuBar);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
                                      .WaitIteration ()
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .WriteOutLogs (_out)
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation!.GetFocused ()!.Title))
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (menuBar?.IsOpen ()))
+                                     .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title)
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (menuBar?.IsOpen ())
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -205,14 +206,14 @@ public class MenuBarv2Tests
                                                 Application.Top!.Add (menuBar);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertIsNotType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation!.GetFocused ()!.Title))
+                                     .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
-                                     .Then (() => Assert.IsNotType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertIsNotType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -328,24 +329,24 @@ public class MenuBarv2Tests
                                      .WaitIteration ()
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (menuBar?.IsOpen ()))
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (menuBar?.IsOpen ())
+                                     .AssertEqual ("_New file", Application.Navigation?.GetFocused ()!.Title)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .Right ()
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
                                      .ScreenShot ("After right arrow", _out)
-                                     .Then (() => Assert.Equal ("Cu_t", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertEqual ("Cu_t", Application.Navigation?.GetFocused ()!.Title)
                                      .Right ()
                                      .ScreenShot ("After second right arrow", _out)
-                                     .Then (() => Assert.Equal ("_Online Help...", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertEqual ("_Online Help...", Application.Navigation?.GetFocused ()!.Title)
                                      .ScreenShot ("After third right arrow", _out)
                                      .Right ()
                                      .ScreenShot ("After fourth right arrow", _out)
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertEqual ("_New file", Application.Navigation?.GetFocused ()!.Title)
                                      .Left ()
                                      .ScreenShot ("After left arrow", _out)
-                                     .Then (() => Assert.Equal ("_Online Help...", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertEqual ("_Online Help...", Application.Navigation?.GetFocused ()!.Title)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -375,17 +376,17 @@ public class MenuBarv2Tests
                                                 Application.Top!.Add (menuBar);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertIsNotType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation!.GetFocused ()!.Title))
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (menuBar?.IsOpen ()))
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title)
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (menuBar?.IsOpen ())
+                                     .AssertEqual ("_New file", Application.Navigation?.GetFocused ()!.Title)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .RaiseKeyDownEvent (Application.QuitKey)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -414,19 +415,19 @@ public class MenuBarv2Tests
                                                 Application.Top!.Add (menuBar);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
                                      .RaiseKeyDownEvent (Key.CursorRight)
-                                     .Then (() => Assert.Equal ("Cu_t", Application.Navigation!.GetFocused ()!.Title))
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (menuBar?.IsOpen ()))
-                                     .Then (() => Assert.Equal ("Cu_t", Application.Navigation?.GetFocused ()!.Title))
+                                     .AssertEqual ("Cu_t", Application.Navigation!.GetFocused ()!.Title)
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (menuBar?.IsOpen ())
+                                     .AssertEqual ("Cu_t", Application.Navigation?.GetFocused ()!.Title)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .RaiseKeyDownEvent (Application.QuitKey)
                                      .WriteOutLogs (_out)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .Stop ();
     }
 
@@ -454,15 +455,15 @@ public class MenuBarv2Tests
                                                 Application.Top!.Add (menuBar);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation!.GetFocused ()!.Title))
-                                     .Then (() => Assert.True (Application.Top!.Running))
+                                     .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title)
+                                     .AssertTrue (Application.Top!.Running)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .RaiseKeyDownEvent (Application.QuitKey)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (Application.Top!.Running))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (Application.Top!.Running)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -496,14 +497,14 @@ public class MenuBarv2Tests
                                                 Application.Top!.Add (menuBar);
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .ScreenShot ("MenuBar initial state", _out)
                                      .RaiseKeyDownEvent (MenuBarv2.DefaultKey)
-                                     .Then (() => Assert.Equal ("_New file", Application.Navigation!.GetFocused ()!.Title))
+                                     .AssertEqual ("_New file", Application.Navigation!.GetFocused ()!.Title)
                                      .ScreenShot ($"After {MenuBarv2.DefaultKey}", _out)
                                      .RaiseKeyDownEvent (Application.QuitKey)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (Application.Top!.Running))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (Application.Top!.Running)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -540,7 +541,7 @@ public class MenuBarv2Tests
                                      .WaitIteration ()
                                      .Focus (testView)
                                      .RaiseKeyDownEvent (Key.Space)
-                                     .Then (() => Assert.Equal (1, spaceKeyDownCount))
+                                     .AssertEqual (1, spaceKeyDownCount)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -577,7 +578,7 @@ public class MenuBarv2Tests
                                      .WaitIteration ()
                                      .Focus (testView)
                                      .RaiseKeyDownEvent (Key.Enter)
-                                     .Then (() => Assert.Equal (1, enterKeyDownCount))
+                                     .AssertEqual (1, enterKeyDownCount)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }

+ 31 - 26
Tests/IntegrationTests/FluentTests/PopverMenuTests.cs

@@ -1,5 +1,6 @@
 using System.Globalization;
 using TerminalGuiFluentTesting;
+using TerminalGuiFluentTestingXunit;
 using Xunit.Abstractions;
 
 namespace IntegrationTests.FluentTests;
@@ -47,12 +48,15 @@ public class PopoverMenuTests
                                      .Stop ();
     }
 
+    private static object o = new  ();
 
     [Theory]
     [ClassData (typeof (V2TestDrivers))]
     public void Activate_Sets_Application_Navigation_Correctly (V2TestDriver d)
     {
-        using GuiTestContext c = With.A<Window> (50, 20, d)
+        lock (o)
+        {
+            using GuiTestContext c = With.A<Window> (50, 20, d)
                                      .Then (
                                             () =>
                                             {
@@ -80,16 +84,18 @@ public class PopoverMenuTests
                                                 view.SetFocus ();
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.IsNotType<MenuItemv2> (Application.Navigation!.GetFocused ()))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertIsNotType<MenuItemv2> (Application.Navigation!.GetFocused ())
                                      .ScreenShot ("PopoverMenu initial state", _out)
                                      .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ()))
                                      .WaitIteration ()
                                      .ScreenShot ($"After Show", _out)
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.Equal ("Cu_t", Application.Navigation!.GetFocused ()!.Title))
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertEqual ("Cu_t", Application.Navigation!.GetFocused ()!.Title)
                                      .WriteOutLogs (_out)
                                      .Stop ();
+        }
+        
     }
 
     [Theory]
@@ -125,19 +131,18 @@ public class PopoverMenuTests
                                             })
                                      .WaitIteration ()
                                      .ScreenShot ("PopoverMenu initial state", _out)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
                                      .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ()))
                                      .WaitIteration ()
                                      .ScreenShot ($"After Show", _out)
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
                                      .RaiseKeyDownEvent (Application.QuitKey)
-                                     .Then (() => Application.LayoutAndDraw (true))
                                      .WaitIteration ()
                                      .WriteOutLogs (_out)
                                      .ScreenShot ($"After {Application.QuitKey}", _out)
-                                     .Then (() => Assert.False (Application.Popover!.Popovers.Cast<PopoverMenu> ().FirstOrDefault()!.Visible))
-                                     .Then (() => Assert.Null (Application.Popover!.GetActivePopover()))
-                                     .Then (() => Assert.True (Application.Top!.Running))
+                                     .AssertFalse (Application.Popover!.Popovers.Cast<PopoverMenu> ().FirstOrDefault()!.Visible)
+                                     .AssertNull (Application.Popover!.GetActivePopover())
+                                     .AssertTrue (Application.Top!.Running)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -175,17 +180,17 @@ public class PopoverMenuTests
                                             })
                                      .WaitIteration ()
                                      .ScreenShot ("PopoverMenu initial state", _out)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.IsNotType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertIsNotType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ()))
                                      .WaitIteration ()
                                      .ScreenShot ($"After Show", _out)
-                                     .Then (() => Assert.True (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.IsType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertTrue (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertIsType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .RaiseKeyDownEvent (Application.QuitKey)
                                      .ScreenShot ($"After {Application.QuitKey}", _out)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.IsNotType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertIsNotType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -222,18 +227,18 @@ public class PopoverMenuTests
                                                 view.SetFocus ();
                                             })
                                      .WaitIteration ()
-                                     .Then (() => Assert.IsNotType<MenuItemv2>(Application.Navigation!.GetFocused()))
+                                     .AssertIsNotType<MenuItemv2>(Application.Navigation!.GetFocused())
                                      .ScreenShot ("PopoverMenu initial state", _out)
                                      .Then (() => Application.Popover!.Show (Application.Popover.Popovers.First ()))
                                      .WaitIteration ()
                                      .ScreenShot ("PopoverMenu after Show", _out)
-                                     .Then (() => Assert.Equal ("Cu_t", Application.Navigation!.GetFocused ()!.Title))
-                                     .Then (() => Assert.True (Application.Top!.Running))
+                                     .AssertEqual ("Cu_t", Application.Navigation!.GetFocused ()!.Title)
+                                     .AssertTrue (Application.Top!.Running)
                                      .RaiseKeyDownEvent (Application.QuitKey)
-                                     .Then (() => Application.LayoutAndDraw ())
+                                     .WaitIteration ()
                                      .ScreenShot ($"After {Application.QuitKey}", _out)
-                                     .Then (() => Assert.False (Application.Popover?.GetActivePopover () is PopoverMenu))
-                                     .Then (() => Assert.True (Application.Top!.Running))
+                                     .AssertFalse (Application.Popover?.GetActivePopover () is PopoverMenu)
+                                     .AssertTrue (Application.Top!.Running)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -271,7 +276,7 @@ public class PopoverMenuTests
                                      .WaitIteration ()
                                      .Focus (testView)
                                      .RaiseKeyDownEvent (Key.Space)
-                                     .Then (() => Assert.Equal (1, spaceKeyDownCount))
+                                     .AssertEqual (1, spaceKeyDownCount)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -308,7 +313,7 @@ public class PopoverMenuTests
                                      .WaitIteration ()
                                      .Focus (testView)
                                      .RaiseKeyDownEvent (Key.Enter)
-                                     .Then (() => Assert.Equal (1, enterKeyDownCount))
+                                     .AssertEqual (1, enterKeyDownCount)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }
@@ -345,7 +350,7 @@ public class PopoverMenuTests
                                      .WaitIteration ()
                                      .Focus (testView)
                                      .RaiseKeyDownEvent (Application.QuitKey)
-                                     .Then (() => Assert.Equal (1, quitKeyDownCount))
+                                     .AssertEqual (1, quitKeyDownCount)
                                      .WriteOutLogs (_out)
                                      .Stop ();
     }

+ 1 - 1
Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs

@@ -42,7 +42,7 @@ public class TreeViewFluentTests
                 .WaitIteration ()
                 .ScreenShot ("Before expanding", _out)
                 .AssertEqual (root, tv.GetObjectOnRow (0))
-                .Then (() => Assert.Null (tv.GetObjectOnRow (1)))
+                .AssertNull (tv.GetObjectOnRow (1))
                 .Right ()
                 .ScreenShot ("After expanding", _out)
                 .AssertMultiple (

+ 45 - 0
Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationFactory.cs

@@ -0,0 +1,45 @@
+#nullable enable
+using System.Drawing;
+using TerminalGuiFluentTesting;
+
+namespace Terminal.Gui.Drivers;
+
+#pragma warning disable CS1591
+public class FakeApplicationFactory
+{
+    /// <summary>
+    ///     Creates an initialized fake application which will be cleaned up when result object
+    ///     is disposed.
+    /// </summary>
+    /// <returns></returns>
+    public IDisposable SetupFakeApplication ()
+    {
+        var cts = new CancellationTokenSource ();
+        var fakeInput = new FakeNetInput (cts.Token);
+        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));
+
+        ApplicationImpl.ChangeInstance (v2);
+        v2.Init (null, "v2net");
+
+        ConsoleDriverFacade<ConsoleKeyInfo> d = (ConsoleDriverFacade<ConsoleKeyInfo>)Application.Driver!;
+
+        sizeMonitor.SizeChanging += (_, e) =>
+                                    {
+                                        if (e.Size != null)
+                                        {
+                                            Size s = e.Size.Value;
+                                            output.Size = s;
+                                            d.OutputBuffer.SetWindowSize (s.Width, s.Height);
+                                        }
+                                    };
+
+        return new FakeApplicationLifecycle (origApp, cts);
+    }
+}

+ 16 - 0
Tests/TerminalGuiFluentTesting/FakeDriver/FakeApplicationLifecycle.cs

@@ -0,0 +1,16 @@
+#nullable enable
+namespace Terminal.Gui.Drivers;
+
+#pragma warning disable CS1591
+internal class FakeApplicationLifecycle (IApplication origApp, CancellationTokenSource hardStop) : IDisposable
+{
+    /// <inheritdoc/>
+    public void Dispose ()
+    {
+        hardStop.Cancel ();
+
+        Application.Top?.Dispose ();
+        Application.Shutdown ();
+        ApplicationImpl.ChangeInstance (origApp);
+    }
+}

+ 20 - 0
Tests/TerminalGuiFluentTesting/FakeDriver/FakeDriverFactory.cs

@@ -0,0 +1,20 @@
+#nullable enable
+namespace Terminal.Gui.Drivers;
+
+#pragma warning disable CS1591
+public class FakeDriverFactory
+{
+    /// <summary>
+    ///     Creates a new instance of <see cref="FakeDriverV2"/> using default options
+    /// </summary>
+    /// <returns></returns>
+    public IFakeDriverV2 Create ()
+    {
+        return new FakeDriverV2 (
+                                 new (),
+                                 new (),
+                                 new (),
+                                 () => DateTime.Now,
+                                 new ());
+    }
+}

+ 58 - 0
Tests/TerminalGuiFluentTesting/FakeDriver/FakeDriverV2.cs

@@ -0,0 +1,58 @@
+#nullable enable
+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;
+
+/// <summary>
+///     Implementation of <see cref="IConsoleDriver"/> that uses fake input/output.
+///     This is a lightweight alternative to <see cref="GuiTestContext"/> (if you don't
+///     need the entire application main loop running).
+/// </summary>
+internal class FakeDriverV2 : ConsoleDriverFacade<ConsoleKeyInfo>, IFakeDriverV2
+{
+    internal FakeDriverV2 (
+        ConcurrentQueue<ConsoleKeyInfo> inputBuffer,
+        OutputBuffer outputBuffer,
+        FakeOutput fakeOutput,
+        Func<DateTime> datetimeFunc,
+        FakeSizeMonitor sizeMonitor
+    ) :
+        base (
+              new NetInputProcessor (inputBuffer),
+              outputBuffer,
+              fakeOutput,
+              new (new AnsiResponseParser (), datetimeFunc),
+              sizeMonitor)
+    {
+        FakeOutput fakeOutput1;
+        InputBuffer = inputBuffer;
+        SizeMonitor = sizeMonitor;
+        OutputBuffer = outputBuffer;
+        ConsoleOutput = fakeOutput1 = fakeOutput;
+
+        SizeChanged += (_, e) =>
+                       {
+                           if (e.Size != null)
+                           {
+                               Size s = e.Size.Value;
+                               fakeOutput1.Size = s;
+                               OutputBuffer.SetWindowSize (s.Width, s.Height);
+                           }
+                       };
+    }
+
+    public void SetBufferSize (int width, int height)
+    {
+        SizeMonitor.RaiseSizeChanging (new (width, height));
+        OutputBuffer.SetWindowSize (width, height);
+    }
+
+    public IConsoleOutput ConsoleOutput { get; }
+    public ConcurrentQueue<ConsoleKeyInfo> InputBuffer { get; }
+    public new OutputBuffer OutputBuffer { get; }
+    public FakeSizeMonitor SizeMonitor { get; }
+}

+ 0 - 0
Tests/TerminalGuiFluentTesting/FakeNetInput.cs → Tests/TerminalGuiFluentTesting/FakeDriver/FakeNetInput.cs


+ 20 - 0
Tests/TerminalGuiFluentTesting/FakeDriver/FakeSizeMonitor.cs

@@ -0,0 +1,20 @@
+#nullable enable
+using System.Drawing;
+
+namespace Terminal.Gui.Drivers;
+
+#pragma warning disable CS1591
+public class FakeSizeMonitor : IWindowSizeMonitor
+{
+    /// <inheritdoc/>
+    public event EventHandler<SizeChangedEventArgs>? SizeChanging;
+
+    /// <inheritdoc/>
+    public bool Poll () { return false; }
+
+    /// <summary>
+    ///     Raises the <see cref="SizeChanging"/> event.
+    /// </summary>
+    /// <param name="newSize"></param>
+    public void RaiseSizeChanging (Size newSize) { SizeChanging?.Invoke (this, new (newSize)); }
+}

+ 8 - 0
Tests/TerminalGuiFluentTesting/FakeDriver/IFakeDriverV2.cs

@@ -0,0 +1,8 @@
+#nullable enable
+namespace Terminal.Gui.Drivers;
+
+#pragma warning disable CS1591
+public interface IFakeDriverV2 : IConsoleDriver, IConsoleDriverFacade
+{
+    void SetBufferSize (int width, int height);
+}

+ 1 - 1
Tests/TerminalGuiFluentTesting/FakeOutput.cs

@@ -4,7 +4,7 @@ namespace TerminalGuiFluentTesting;
 
 internal class FakeOutput : IConsoleOutput
 {
-    public IOutputBuffer LastBuffer { get; set; }
+    public IOutputBuffer? LastBuffer { get; set; }
     public Size Size { get; set; }
 
     /// <inheritdoc/>

+ 290 - 162
Tests/TerminalGuiFluentTesting/GuiTestContext.cs

@@ -1,6 +1,8 @@
-using System.Drawing;
+using System.Diagnostics;
+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;
 
@@ -13,106 +15,105 @@ public class GuiTestContext : IDisposable
     private readonly CancellationTokenSource _cts = new ();
     private readonly CancellationTokenSource _hardStop = new (With.Timeout);
     private readonly Task _runTask;
-    private Exception _ex;
+    private Exception? _ex;
     private readonly FakeOutput _output = new ();
     private readonly FakeWindowsInput _winInput;
     private readonly FakeNetInput _netInput;
     private View? _lastView;
+    private readonly object _logsLock = new ();
     private readonly StringBuilder _logsSb;
     private readonly V2TestDriver _driver;
     private bool _finished;
-    private readonly object _threadLock = new ();
+    private readonly FakeSizeMonitor _fakeSizeMonitor;
 
-    internal GuiTestContext (Func<Toplevel> topLevelBuilder, int width, int height, V2TestDriver driver)
+    internal GuiTestContext (Func<Toplevel> topLevelBuilder, int width, int height, V2TestDriver driver, TextWriter? logWriter = null)
     {
-        lock (_threadLock)
-        {
-            IApplication origApp = ApplicationImpl.Instance;
-            ILogger? origLogger = Logging.Logger;
-            _logsSb = new ();
-            _driver = driver;
+        // Remove frame limit
+        Application.MaximumIterationsPerSecond = ushort.MaxValue;
+
+        IApplication origApp = ApplicationImpl.Instance;
+        ILogger? origLogger = Logging.Logger;
+        _logsSb = new ();
+        _driver = driver;
 
-            _netInput = new (_cts.Token);
-            _winInput = new (_cts.Token);
+        _netInput = new (_cts.Token);
+        _winInput = new (_cts.Token);
+
+        _output.Size = new (width, height);
+        _fakeSizeMonitor = new ();
 
-            _output.Size = new (width, height);
+        IComponentFactory cf = driver == V2TestDriver.V2Net
+                                   ? new FakeNetComponentFactory (_netInput, _output, _fakeSizeMonitor)
+                                   : (IComponentFactory)new FakeWindowsComponentFactory (_winInput, _output, _fakeSizeMonitor);
 
-            var v2 = new ApplicationV2 (
-                                        () => _netInput,
-                                        () => _output,
-                                        () => _winInput,
-                                        () => _output);
+        var v2 = new ApplicationV2 (cf);
 
-            var booting = new SemaphoreSlim (0, 1);
+        var booting = new SemaphoreSlim (0, 1);
 
-            // Start the application in a background thread
-            _runTask = Task.Run (() =>
+        // Start the application in a background thread
+        _runTask = Task.Run (
+                             () =>
+                             {
+                                 try
                                  {
-                                     while (Application.Top is { })
+                                     ApplicationImpl.ChangeInstance (v2);
+
+                                     ILogger logger = LoggerFactory.Create (
+                                                                            builder =>
+                                                                                builder.SetMinimumLevel (LogLevel.Trace)
+                                                                                       .AddProvider (
+                                                                                                     new TextWriterLoggerProvider (
+                                                                                                      new ThreadSafeStringWriter (_logsSb, _logsLock))))
+                                                                   .CreateLogger ("Test Logging");
+                                     Logging.Logger = logger;
+
+                                     v2.Init (null, GetDriverName ());
+
+                                     booting.Release ();
+
+                                     Toplevel t = topLevelBuilder ();
+                                     t.Closed += (s, e) => { _finished = true; };
+                                     Application.Run (t); // This will block, but it's on a background thread now
+
+                                     t.Dispose ();
+                                     Application.Shutdown ();
+                                     _cts.Cancel ();
+                                 }
+                                 catch (OperationCanceledException)
+                                 { }
+                                 catch (Exception ex)
+                                 {
+                                     _ex = ex;
+
+                                     if (logWriter != null)
                                      {
-                                         Task.Delay (300).Wait ();
+                                         WriteOutLogs (logWriter);
                                      }
-                                 })
-                           .ContinueWith (
-                                          (task, _) =>
-                                          {
-                                              try
-                                              {
-                                                  if (task.IsFaulted)
-                                                  {
-                                                      _ex = task.Exception ?? new Exception ("Unknown error in background task");
-                                                  }
-
-                                                  // Ensure we are not running on the main thread
-                                                  if (ApplicationImpl.Instance != origApp)
-                                                  {
-                                                      throw new InvalidOperationException (
-                                                                                           "Application instance is not the original one, this should not happen.");
-                                                  }
-
-                                                  ApplicationImpl.ChangeInstance (v2);
-
-                                                  ILogger logger = LoggerFactory.Create (builder =>
-                                                                                             builder.SetMinimumLevel (LogLevel.Trace)
-                                                                                                    .AddProvider (
-                                                                                                         new TextWriterLoggerProvider (
-                                                                                                              new StringWriter (_logsSb))))
-                                                                                .CreateLogger ("Test Logging");
-                                                  Logging.Logger = logger;
-
-                                                  v2.Init (null, GetDriverName ());
-
-                                                  booting.Release ();
-
-                                                  Toplevel t = topLevelBuilder ();
-                                                  t.Closed += (s, e) => { _finished = true; };
-                                                  Application.Run (t); // This will block, but it's on a background thread now
-
-                                                  t.Dispose ();
-                                                  Application.Shutdown ();
-                                              }
-                                              catch (OperationCanceledException)
-                                              { }
-                                              catch (Exception ex)
-                                              {
-                                                  _ex = ex;
-                                              }
-                                              finally
-                                              {
-                                                  ApplicationImpl.ChangeInstance (origApp);
-                                                  Logging.Logger = origLogger;
-                                                  _finished = true;
-                                              }
-                                          },
-                                          _cts.Token);
-
-            // Wait for booting to complete with a timeout to avoid hangs
-            if (!booting.WaitAsync (TimeSpan.FromSeconds (10)).Result)
-            {
-                throw new TimeoutException ("Application failed to start within the allotted time.");
-            }
 
-            WaitIteration ();
+                                     _hardStop.Cancel ();
+                                 }
+                                 finally
+                                 {
+                                     ApplicationImpl.ChangeInstance (origApp);
+                                     Logging.Logger = origLogger;
+                                     _finished = true;
+
+                                     Application.MaximumIterationsPerSecond = Application.DefaultMaximumIterationsPerSecond;
+                                 }
+                             },
+                             _cts.Token);
+
+        // Wait for booting to complete with a timeout to avoid hangs
+        if (!booting.WaitAsync (TimeSpan.FromSeconds (10)).Result)
+        {
+            throw new TimeoutException ("Application failed to start within the allotted time.");
+        }
+
+        ResizeConsole (width, height);
+
+        if (_ex != null)
+        {
+            throw new ("Application crashed", _ex);
         }
     }
 
@@ -137,7 +138,7 @@ public class GuiTestContext : IDisposable
             return this;
         }
 
-        Application.Invoke (() => { Application.RequestStop (); });
+        WaitIteration (() => { Application.RequestStop (); });
 
         // Wait for the application to stop, but give it a 1-second timeout
         if (!_runTask.Wait (TimeSpan.FromMilliseconds (1000)))
@@ -147,7 +148,19 @@ public class GuiTestContext : IDisposable
             // Timeout occurred, force the task to stop
             _hardStop.Cancel ();
 
-            throw new TimeoutException ("Application failed to stop within the allotted time.");
+            // App is having trouble shutting down, try sending some more shutdown stuff from this thread.
+            // If this doesn't work there will be test cascade failures as the main loop continues to run during next test.
+            try
+            {
+                Application.RequestStop ();
+                Application.Shutdown ();
+            }
+            catch (Exception)
+            {
+                throw new TimeoutException ("Application failed to stop within the allotted time.", _ex);
+            }
+
+            throw new TimeoutException ("Application failed to stop within the allotted time.", _ex);
         }
 
         _cts.Cancel ();
@@ -163,8 +176,13 @@ public class GuiTestContext : IDisposable
     /// <summary>
     ///     Hard stops the application and waits for the background thread to exit.
     /// </summary>
-    public void HardStop ()
+    public void HardStop (Exception? ex = null)
     {
+        if (ex != null)
+        {
+            _ex = ex;
+        }
+
         _hardStop.Cancel ();
         Stop ();
     }
@@ -179,7 +197,8 @@ public class GuiTestContext : IDisposable
         if (_hardStop.IsCancellationRequested)
         {
             throw new (
-                       "Application was hard stopped, typically this means it timed out or did not shutdown gracefully. Ensure you call Stop in your test");
+                       "Application was hard stopped, typically this means it timed out or did not shutdown gracefully. Ensure you call Stop in your test",
+                       _ex);
         }
 
         _hardStop.Cancel ();
@@ -213,19 +232,27 @@ public class GuiTestContext : IDisposable
     /// <returns></returns>
     public GuiTestContext ResizeConsole (int width, int height)
     {
-        _output.Size = new (width, height);
-
-        return WaitIteration ();
+        return WaitIteration (
+                              () =>
+                              {
+                                  _output.Size = new (width, height);
+                                  _fakeSizeMonitor.RaiseSizeChanging (_output.Size);
+
+                                  var d = (IConsoleDriverFacade)Application.Driver!;
+                                  d.OutputBuffer.SetWindowSize (width, height);
+                              });
     }
 
     public GuiTestContext ScreenShot (string title, TextWriter writer)
     {
-        writer.WriteLine (title + ":");
-        var text = Application.ToString ();
-
-        writer.WriteLine (text);
-
-        return this; //WaitIteration();
+        return WaitIteration (
+                              () =>
+                              {
+                                  writer.WriteLine (title + ":");
+                                  var text = Application.ToString ();
+
+                                  writer.WriteLine (text);
+                              });
     }
 
     /// <summary>
@@ -235,7 +262,10 @@ public class GuiTestContext : IDisposable
     /// <returns></returns>
     public GuiTestContext WriteOutLogs (TextWriter writer)
     {
-        writer.WriteLine (_logsSb.ToString ());
+        lock (_logsLock)
+        {
+            writer.WriteLine (_logsSb.ToString ());
+        }
 
         return this; //WaitIteration();
     }
@@ -254,14 +284,27 @@ public class GuiTestContext : IDisposable
             return this;
         }
 
+        if (Thread.CurrentThread.ManagedThreadId == Application.MainThreadId)
+        {
+            throw new NotSupportedException ("Cannot WaitIteration during Invoke");
+        }
+
         a ??= () => { };
         var ctsLocal = new CancellationTokenSource ();
 
         Application.Invoke (
                             () =>
                             {
-                                a ();
-                                ctsLocal.Cancel ();
+                                try
+                                {
+                                    a ();
+                                    ctsLocal.Cancel ();
+                                }
+                                catch (Exception e)
+                                {
+                                    _ex = e;
+                                    _hardStop.Cancel ();
+                                }
                             });
 
         // Blocks until either the token or the hardStopToken is cancelled.
@@ -286,10 +329,11 @@ public class GuiTestContext : IDisposable
     {
         try
         {
-            doAction ();
+            WaitIteration (doAction);
         }
-        catch (Exception)
+        catch (Exception ex)
         {
+            _ex = ex;
             HardStop ();
 
             throw;
@@ -322,10 +366,19 @@ public class GuiTestContext : IDisposable
 
     private GuiTestContext Click<T> (WindowsConsole.ButtonState btn, Func<T, bool> evaluator) where T : View
     {
-        T v = Find (evaluator);
-        Point screen = v.ViewportToScreen (new Point (0, 0));
+        T v;
+        var screen = Point.Empty;
+
+        GuiTestContext ctx = WaitIteration (
+                                            () =>
+                                            {
+                                                v = Find (evaluator);
+                                                screen = v.ViewportToScreen (new Point (0, 0));
+                                            });
 
-        return Click (btn, screen.X, screen.Y);
+        Click (btn, screen.X, screen.Y);
+
+        return ctx;
     }
 
     private GuiTestContext Click (WindowsConsole.ButtonState btn, int screenX, int screenY)
@@ -334,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 ()
@@ -356,7 +409,8 @@ public class GuiTestContext : IDisposable
                                                    }
                                                });
 
-                break;
+                return WaitUntil (() => _winInput.InputBuffer.IsEmpty);
+
             case V2TestDriver.V2Net:
 
                 int netButton = btn switch
@@ -370,17 +424,31 @@ public class GuiTestContext : IDisposable
 
                 foreach (ConsoleKeyInfo k in NetSequences.Click (netButton, screenX, screenY))
                 {
-                    SendNetKey (k);
+                    SendNetKey (k, false);
                 }
 
-                break;
+                return WaitIteration ();
             default:
                 throw new ArgumentOutOfRangeException ();
         }
+    }
 
-        return WaitIteration ();
+    private GuiTestContext WaitUntil (Func<bool> condition)
+    {
+        GuiTestContext? c = null;
+        var sw = Stopwatch.StartNew ();
 
-        ;
+        while (!condition ())
+        {
+            if (sw.Elapsed > With.Timeout)
+            {
+                throw new TimeoutException ("Failed to reach condition within the time limit");
+            }
+
+            c = WaitIteration ();
+        }
+
+        return c ?? this;
     }
 
     public GuiTestContext Down ()
@@ -403,8 +471,6 @@ public class GuiTestContext : IDisposable
         }
 
         return WaitIteration ();
-
-        ;
     }
 
     /// <summary>
@@ -631,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 ()
@@ -648,7 +714,15 @@ public class GuiTestContext : IDisposable
         WaitIteration ();
     }
 
-    private void SendNetKey (ConsoleKeyInfo consoleKeyInfo) { _netInput.InputBuffer.Enqueue (consoleKeyInfo); }
+    private void SendNetKey (ConsoleKeyInfo consoleKeyInfo, bool wait = true)
+    {
+        _netInput.InputBuffer!.Enqueue (consoleKeyInfo);
+
+        if (wait)
+        {
+            WaitUntil (() => _netInput.InputBuffer.IsEmpty);
+        }
+    }
 
     /// <summary>
     ///     Sends a special key e.g. cursor key that does not map to a specific character
@@ -656,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 ()
@@ -697,7 +771,7 @@ public class GuiTestContext : IDisposable
     /// <returns></returns>
     public GuiTestContext RaiseKeyDownEvent (Key key)
     {
-        Application.RaiseKeyDownEvent (key);
+        WaitIteration (() => Application.RaiseKeyDownEvent (key));
 
         return this; //WaitIteration();
     }
@@ -728,9 +802,11 @@ public class GuiTestContext : IDisposable
     ///     is found (of Type T) or all views are looped through (back to the beginning)
     ///     in which case triggers hard stop and Exception
     /// </summary>
-    /// <param name="evaluator">Delegate that returns true if the passed View is the one
-    /// you are trying to focus. Leave <see langword="null"/> to focus the first view of type
-    /// <typeparamref name="T"/></param>
+    /// <param name="evaluator">
+    ///     Delegate that returns true if the passed View is the one
+    ///     you are trying to focus. Leave <see langword="null"/> to focus the first view of type
+    ///     <typeparamref name="T"/>
+    /// </param>
     /// <returns></returns>
     /// <exception cref="ArgumentException"></exception>
     public GuiTestContext Focus<T> (Func<T, bool>? evaluator = null) where T : View
@@ -760,11 +836,14 @@ public class GuiTestContext : IDisposable
             // No, try tab to the next (or first)
             Tab ();
             WaitIteration ();
+
             next = t.MostFocused;
 
             if (next is null)
             {
-                Fail ("Failed to tab to a view which matched the Type and evaluator constraints of the test because MostFocused became or was always null");
+                Fail (
+                      "Failed to tab to a view which matched the Type and evaluator constraints of the test because MostFocused became or was always null"
+                      + DescribeSeenViews (seen));
 
                 return this;
             }
@@ -773,7 +852,9 @@ public class GuiTestContext : IDisposable
             // We have looped around to the start again if it was already there
             if (!seen.Add (next))
             {
-                Fail ("Failed to tab to a view which matched the Type and evaluator constraints of the test before looping back to the original View");
+                Fail (
+                      "Failed to tab to a view which matched the Type and evaluator constraints of the test before looping back to the original View"
+                      + DescribeSeenViews (seen));
 
                 return this;
             }
@@ -781,6 +862,8 @@ public class GuiTestContext : IDisposable
         while (true);
     }
 
+    private string DescribeSeenViews (HashSet<View> seen) { return Environment.NewLine + string.Join (Environment.NewLine, seen); }
+
     private T Find<T> (Func<T, bool> evaluator) where T : View
     {
         Toplevel? t = Application.Top;
@@ -830,25 +913,70 @@ public class GuiTestContext : IDisposable
 
     public GuiTestContext Send (Key key)
     {
-        if (Application.Driver is IConsoleDriverFacade facade)
-        {
-            facade.InputProcessor.OnKeyDown (key);
-            facade.InputProcessor.OnKeyUp (key);
-        }
-        else
-        {
-            Fail ("Expected Application.Driver to be IConsoleDriverFacade");
-        }
-
-        return this;
+        return WaitIteration (
+                              () =>
+                              {
+                                  if (Application.Driver is IConsoleDriverFacade facade)
+                                  {
+                                      facade.InputProcessor.OnKeyDown (key);
+                                      facade.InputProcessor.OnKeyUp (key);
+                                  }
+                                  else
+                                  {
+                                      Fail ("Expected Application.Driver to be IConsoleDriverFacade");
+                                  }
+                              });
     }
 
     /// <summary>
-    /// Returns the last set position of the cursor.
+    ///     Returns the last set position of the cursor.
     /// </summary>
     /// <returns></returns>
-    public Point GetCursorPosition ()
+    public Point GetCursorPosition () { return _output.CursorPosition; }
+}
+
+internal class FakeWindowsComponentFactory : WindowsComponentFactory
+{
+    private readonly FakeWindowsInput _winInput;
+    private readonly FakeOutput _output;
+    private readonly FakeSizeMonitor _fakeSizeMonitor;
+
+    public FakeWindowsComponentFactory (FakeWindowsInput winInput, FakeOutput output, FakeSizeMonitor fakeSizeMonitor)
+    {
+        _winInput = winInput;
+        _output = output;
+        _fakeSizeMonitor = fakeSizeMonitor;
+    }
+
+    /// <inheritdoc/>
+    public override IConsoleInput<WindowsConsole.InputRecord> CreateInput () { return _winInput; }
+
+    /// <inheritdoc/>
+    public override IConsoleOutput CreateOutput () { return _output; }
+
+    /// <inheritdoc/>
+    public override IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) { return _fakeSizeMonitor; }
+}
+
+internal class FakeNetComponentFactory : NetComponentFactory
+{
+    private readonly FakeNetInput _netInput;
+    private readonly FakeOutput _output;
+    private readonly FakeSizeMonitor _fakeSizeMonitor;
+
+    public FakeNetComponentFactory (FakeNetInput netInput, FakeOutput output, FakeSizeMonitor fakeSizeMonitor)
     {
-        return _output.CursorPosition;
+        _netInput = netInput;
+        _output = output;
+        _fakeSizeMonitor = fakeSizeMonitor;
     }
+
+    /// <inheritdoc/>
+    public override IConsoleInput<ConsoleKeyInfo> CreateInput () { return _netInput; }
+
+    /// <inheritdoc/>
+    public override IConsoleOutput CreateOutput () { return _output; }
+
+    /// <inheritdoc/>
+    public override IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) { return _fakeSizeMonitor; }
 }

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

+ 31 - 0
Tests/TerminalGuiFluentTesting/ThreadSafeStringWriter.cs

@@ -0,0 +1,31 @@
+using System.Text;
+
+namespace TerminalGuiFluentTesting;
+
+class ThreadSafeStringWriter : StringWriter
+{
+    private readonly object _lock;
+
+    public ThreadSafeStringWriter (StringBuilder sb, object syncLock) : base (sb)
+    {
+        _lock = syncLock;
+    }
+
+    public override void Write (char value)
+    {
+        lock (_lock)
+        {
+            base.Write (value);
+        }
+    }
+
+    public override void Write (string? value)
+    {
+        lock (_lock)
+        {
+            base.Write (value);
+        }
+    }
+
+    // (override other Write* methods as needed)
+}

+ 7 - 7
Tests/TerminalGuiFluentTesting/With.cs

@@ -12,24 +12,24 @@ public static class With
     /// <param name="width"></param>
     /// <param name="height"></param>
     /// <param name="v2TestDriver">Which v2 v2TestDriver to use for the test</param>
+    /// <param name="logWriter"></param>
     /// <returns></returns>
-    public static GuiTestContext A<T> (int width, int height, V2TestDriver v2TestDriver) where T : Toplevel, new ()
+    public static GuiTestContext A<T> (int width, int height, V2TestDriver v2TestDriver, TextWriter? logWriter = null) where T : Toplevel, new ()
     {
-        return new (() => new T (), width, height,v2TestDriver);
+        return new (() => new T (), width, height,v2TestDriver,logWriter);
     }
 
     /// <summary>
-    /// Overload that takes an existing instance <paramref name="toplevel"/>
-    /// instead of creating one.
+    /// Overload that takes a function to create instance <paramref name="toplevelFactory"/> after application is initialized.
     /// </summary>
-    /// <param name="toplevel"></param>
+    /// <param name="toplevelFactory"></param>
     /// <param name="width"></param>
     /// <param name="height"></param>
     /// <param name="v2TestDriver"></param>
     /// <returns></returns>
-    public static GuiTestContext A (Toplevel toplevel, int width, int height, V2TestDriver v2TestDriver)
+    public static GuiTestContext A (Func<Toplevel> toplevelFactory, int width, int height, V2TestDriver v2TestDriver)
     {
-        return new (()=>toplevel, width, height, v2TestDriver);
+        return new (toplevelFactory, width, height, v2TestDriver);
     }
     /// <summary>
     ///     The global timeout to allow for any given application to run for before shutting down.

+ 2 - 2
Tests/TerminalGuiFluentTestingXunit.Generator/TheGenerator.cs

@@ -123,9 +123,9 @@ public class TheGenerator : IIncrementalGenerator
                                {
                                    Assert.{{methodName}}{{typeParams}} ({{string.Join (",", paramNames)}});
                                }
-                               catch(Exception)
+                               catch(Exception ex)
                                {
-                                   context.HardStop ();
+                                   context.HardStop (ex);
                                    
                                
                                    throw;

+ 53 - 82
Tests/UnitTests/Application/ApplicationTests.cs

@@ -1,4 +1,5 @@
 using System.Diagnostics;
+using System.Reflection;
 using UnitTests;
 using Xunit.Abstractions;
 using static Terminal.Gui.Configuration.ConfigurationManager;
@@ -40,7 +41,9 @@ public class ApplicationTests
 
         Application.InitializedChanged += OnApplicationOnInitializedChanged;
 
-        Application.Init (new FakeDriver ());
+        var a = new AutoInitShutdownAttribute ();
+        a.Before (null);
+
         Assert.True (initialized);
         Assert.False (shutdown);
 
@@ -76,6 +79,8 @@ public class ApplicationTests
             _timeoutLock = null;
         }
 
+
+        a.After (null);
         return;
 
         void OnApplicationOnInitializedChanged (object s, EventArgs<bool> a)
@@ -143,15 +148,13 @@ public class ApplicationTests
     }
 
     [Fact]
+    [AutoInitShutdown]
     public void Begin_Null_Toplevel_Throws ()
     {
-        // Setup Mock driver
-        Init ();
-
         // Test null Toplevel
         Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
 
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -163,21 +166,21 @@ public class ApplicationTests
     public void Begin_Sets_Application_Top_To_Console_Size ()
     {
         Assert.Null (Application.Top);
+        AutoInitShutdownAttribute.FakeResize (new Size (80,25));
         Toplevel top = new ();
         Application.Begin (top);
         Assert.Equal (new (0, 0, 80, 25), Application.Top!.Frame);
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
+        AutoInitShutdownAttribute.FakeResize(new Size(5, 5));
         Assert.Equal (new (0, 0, 5, 5), Application.Top!.Frame);
         top.Dispose ();
     }
 
     [Fact]
+    [AutoInitShutdown]
     public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop ()
     {
         Assert.Null (Application.Top);
 
-        Init ();
-
         RunState rs = Application.Begin (new ());
         Application.Top!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop";
         Assert.Equal (rs.Toplevel, Application.Top);
@@ -193,25 +196,24 @@ public class ApplicationTests
         Toplevel top = Application.Top;
 
 #if DEBUG_IDISPOSABLE
-        Exception exception = Record.Exception (() => Shutdown ());
+        Exception exception = Record.Exception (Application.Shutdown);
         Assert.NotNull (exception);
         Assert.False (top.WasDisposed);
         top.Dispose ();
         Assert.True (top.WasDisposed);
 #endif
-        Shutdown ();
+        Application.Shutdown ();
         Assert.Null (Application.Top);
     }
 
     [Fact]
+    [AutoInitShutdown]
     public void Init_Begin_End_Cleans_Up ()
     {
         // Start stopwatch
         Stopwatch stopwatch = new Stopwatch ();
         stopwatch.Start ();
 
-        Init ();
-
         // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
         // if we don't stop
         Application.Iteration += (s, a) => { Application.RequestStop (); };
@@ -237,14 +239,12 @@ public class ApplicationTests
         Application.End (runstate);
 
         Assert.NotNull (Application.Top);
-        Assert.NotNull (Application.MainLoop);
         Assert.NotNull (Application.Driver);
 
         topLevel.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
 
         // Stop stopwatch
@@ -255,7 +255,6 @@ public class ApplicationTests
     }
 
     [Theory]
-    [InlineData (typeof (FakeDriver))]
     [InlineData (typeof (NetDriver))]
 
     //[InlineData (typeof (ANSIDriver))]
@@ -268,7 +267,7 @@ public class ApplicationTests
         Assert.NotNull (Application.Driver);
         Assert.NotEqual (driver, Application.Driver);
         Assert.Equal (driverType, Application.Driver?.GetType ());
-        Shutdown ();
+        Application.Shutdown ();
     }
 
     [Fact]
@@ -278,7 +277,7 @@ public class ApplicationTests
 
         Assert.NotNull (Application.Driver);
 
-        Shutdown ();
+        Application.Shutdown ();
     }
 
     [Theory]
@@ -468,7 +467,7 @@ public class ApplicationTests
                                                                                 new FakeDriver ()
                                                                                )
                                                  );
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -478,7 +477,7 @@ public class ApplicationTests
         Application.InternalInit (new FakeDriver ());
 
         Assert.Throws<InvalidOperationException> (() => Application.Init (new FakeDriver ()));
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -521,7 +520,7 @@ public class ApplicationTests
         Assert.NotNull (Application.Driver);
 
         topLevel.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -645,34 +644,13 @@ public class ApplicationTests
         Assert.Throws<ArgumentNullException> (static () => Application.SubscribeDriverEvents ());
     }
 
-    private void Init ()
-    {
-        Application.Init (new FakeDriver ());
-        Assert.NotNull (Application.Driver);
-        Assert.NotNull (Application.MainLoop);
-        Assert.NotNull (SynchronizationContext.Current);
-    }
-
-    private void Shutdown ()
-    {
-        if (ApplicationImpl.Instance is ApplicationV2)
-        {
-            ApplicationImpl.Instance.Shutdown ();
-        }
-        else
-        {
-            Application.Shutdown ();
-        }
-    }
 
     #region RunTests
 
     [Fact]
+    [AutoInitShutdown]
     public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws ()
     {
-        // Setup Mock driver
-        Init ();
-
         Application.Iteration += (s, e) => Application.RequestStop ();
 
         // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
@@ -681,7 +659,7 @@ public class ApplicationTests
         Assert.True (Application.Top is Window);
 
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -691,9 +669,6 @@ public class ApplicationTests
     [Fact]
     public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws ()
     {
-        // Setup Mock driver
-        Init ();
-
         Application.Iteration += (s, e) => Application.RequestStop ();
 
         // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
@@ -708,7 +683,7 @@ public class ApplicationTests
         Assert.True (Application.Top is Dialog);
 
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -716,10 +691,10 @@ public class ApplicationTests
     }
 
     [Fact]
+    [AutoInitShutdown]
     [TestRespondersDisposed]
     public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
     {
-        Init ();
 
         // Init doesn't create a Toplevel and assigned it to Application.Top
         // but Begin does
@@ -742,7 +717,7 @@ public class ApplicationTests
         Assert.True (initTop.WasDisposed);
 #endif
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -751,18 +726,16 @@ public class ApplicationTests
 
     [Fact]
     [TestRespondersDisposed]
+    [AutoInitShutdown]
     public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow ()
     {
-        // Setup Mock driver
-        Init ();
-
         Application.Iteration += (s, a) => { Application.RequestStop (); };
 
         // Init has been called and we're passing no driver to Run<TestTopLevel>. This is ok.
         Application.Run<Toplevel> ();
 
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -775,8 +748,8 @@ public class ApplicationTests
     {
         Application.ForceDriver = "FakeDriver";
 
-        Application.Init ();
-        Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
+        var a = new AutoInitShutdownAttribute ();
+        a.Before (null);
 
         Application.Iteration += (s, a) => { Application.RequestStop (); };
 
@@ -784,25 +757,26 @@ public class ApplicationTests
         Application.Run<Toplevel> ();
 
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
+
+        a.After (null);
     }
 
     [Fact]
     [TestRespondersDisposed]
+    [AutoInitShutdown]
     public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
     {
-        Init ();
-
         Application.Driver = null;
 
         // Init has been called, but Driver has been set to null. Bad.
         Assert.Throws<InvalidOperationException> (() => Application.Run<Toplevel> ());
 
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -821,7 +795,7 @@ public class ApplicationTests
         Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
 
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -838,7 +812,7 @@ public class ApplicationTests
         Application.Run<Toplevel> (null, new FakeDriver ());
 
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
 
         Assert.Null (Application.Top);
         Assert.Null (Application.MainLoop);
@@ -847,10 +821,11 @@ public class ApplicationTests
 
     [Fact]
     [TestRespondersDisposed]
+    [AutoInitShutdown]
     public void Run_RequestStop_Stops ()
     {
         // Setup Mock driver
-        Init ();
+        Application.Init ();
 
         var top = new Toplevel ();
         RunState rs = Application.Begin (top);
@@ -868,11 +843,9 @@ public class ApplicationTests
     }
 
     [Fact]
+    [AutoInitShutdown]
     public void Run_Sets_Running_True ()
     {
-        // Setup Mock driver
-        Init ();
-
         var top = new Toplevel ();
         RunState rs = Application.Begin (top);
         Assert.NotNull (rs);
@@ -894,11 +867,9 @@ public class ApplicationTests
 
     [Fact]
     [TestRespondersDisposed]
+    [AutoInitShutdown]
     public void Run_RunningFalse_Stops ()
     {
-        // Setup Mock driver
-        Init ();
-
         var top = new Toplevel ();
         RunState rs = Application.Begin (top);
         Assert.NotNull (rs);
@@ -915,10 +886,10 @@ public class ApplicationTests
     }
 
     [Fact]
+    [AutoInitShutdown]
     [TestRespondersDisposed]
     public void Run_Loaded_Ready_Unloaded_Events ()
     {
-        Init ();
         Toplevel top = new ();
         var count = 0;
         top.Loaded += (s, e) => count++;
@@ -933,17 +904,16 @@ public class ApplicationTests
 
     // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
     [Fact]
+    [AutoInitShutdown]
     public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving ()
     {
-        Init ();
-
         // Don't use Dialog here as it has more layout logic. Use Window instead.
         var w = new Window
         {
             Width = 5, Height = 5,
             Arrangement = ViewArrangement.Movable
         };
-        ((FakeDriver)Application.Driver!).SetBufferSize (10, 10);
+        AutoInitShutdownAttribute.FakeResize(new Size(10, 10));
         RunState rs = Application.Begin (w);
 
         // Don't use visuals to test as style of border can change over time.
@@ -963,10 +933,9 @@ public class ApplicationTests
     }
 
     [Fact]
+    [AutoInitShutdown]
     public void End_Does_Not_Dispose ()
     {
-        Init ();
-
         var top = new Toplevel ();
 
         Window w = new ();
@@ -1117,8 +1086,12 @@ public class ApplicationTests
     private readonly object _forceDriverLock = new ();
 
     [Theory]
-    [InlineData ("v2win", typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>))]
-    [InlineData ("v2net", typeof (ConsoleDriverFacade<ConsoleKeyInfo>))]
+
+    // This test wants to Run which results in console handle errors, it wants to rely non drivers checking ConsoleDriver.RunningUnitTests
+    // And suppressing things that might fail, this is anti pattern, instead we should test this kind of thing with  Mocking
+    //    [InlineData ("v2win", typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>))]
+    //    [InlineData ("v2net", typeof (ConsoleDriverFacade<ConsoleKeyInfo>))]
+
     [InlineData ("FakeDriver", typeof (FakeDriver))]
     [InlineData ("NetDriver", typeof (NetDriver))]
     [InlineData ("WindowsDriver", typeof (WindowsDriver))]
@@ -1160,7 +1133,7 @@ public class ApplicationTests
         Assert.NotNull (Application.Top);
         Assert.False (Application.Top!.Running);
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
         Assert.True (result);
     }
 
@@ -1176,7 +1149,7 @@ public class ApplicationTests
         Assert.NotNull (Application.Top);
         Assert.False (Application.Top!.Running);
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
     }
 
     [Fact]
@@ -1203,7 +1176,7 @@ public class ApplicationTests
         Assert.NotNull (Application.Top);
         Assert.False (Application.Top!.Running);
         Application.Top!.Dispose ();
-        Shutdown ();
+        Application.Shutdown ();
     }
 
     // TODO: Add tests for Run that test errorHandler
@@ -1225,7 +1198,6 @@ public class ApplicationTests
             isCompletedSuccessfully = true;
         }
 
-        Init ();
         Application.Shutdown ();
 
         Assert.False (isCompletedSuccessfully);
@@ -1237,7 +1209,6 @@ public class ApplicationTests
     [Fact]
     public void Shutdown_Resets_SyncContext ()
     {
-        Init ();
         Application.Shutdown ();
         Assert.Null (SynchronizationContext.Current);
     }

+ 41 - 4
Tests/UnitTests/AutoInitShutdownAttribute.cs

@@ -49,7 +49,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
     {
         AutoInit = autoInit;
         CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
-        _driverType = consoleDriverType ?? typeof (FakeDriver);
+        _driverType = consoleDriverType;
         FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
         FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException =
             fakeClipboardAlwaysThrowsNotSupportedException;
@@ -59,14 +59,17 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
 
     private readonly bool _verifyShutdown;
     private readonly Type _driverType;
+    private IDisposable _v2Cleanup;
 
     public override void After (MethodInfo methodUnderTest)
     {
-        Debug.WriteLine ($"After: {methodUnderTest.Name}");
+        Debug.WriteLine ($"After: {methodUnderTest?.Name ?? "Unknown Test"}");
 
         // Turn off diagnostic flags in case some test left them on
         View.Diagnostics = ViewDiagnosticFlags.Off;
 
+        _v2Cleanup?.Dispose ();
+
         if (AutoInit)
         {
             // try
@@ -109,7 +112,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
 
     public override void Before (MethodInfo methodUnderTest)
     {
-        Debug.WriteLine ($"Before: {methodUnderTest.Name}");
+        Debug.WriteLine ($"Before: {methodUnderTest?.Name ?? "Unknown Test"}");
 
         // Disable & force the ConfigurationManager to reset to its hardcoded defaults
         CM.Disable (true);
@@ -131,9 +134,43 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
                 View.Instances.Clear ();
             }
 #endif
-            Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType));
+            if (_driverType == null)
+            {
+                Application.Top = null;
+                Application.TopLevels.Clear ();
+
+                var fa = new FakeApplicationFactory ();
+                _v2Cleanup = fa.SetupFakeApplication ();
+                AutoInitShutdownAttribute.FakeResize (new Size (80,25));
+            }
+            else
+            {
+                Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType));
+            }
         }
     }
 
     private bool AutoInit { get; }
+
+    /// <summary>
+    /// 'Resizes' the application and forces layout. Only works if your test uses <see cref="AutoInitShutdownAttribute"/>
+    /// </summary>
+    /// <param name="size"></param>
+    public static void FakeResize (Size size)
+    {
+        var d = (IConsoleDriverFacade)Application.Driver!;
+        d.OutputBuffer.SetWindowSize (size.Width, size.Height);
+        ((FakeSizeMonitor)d.WindowSizeMonitor).RaiseSizeChanging (size);
+
+        Application.LayoutAndDrawImpl ();
+    }
+
+    /// <summary>
+    /// Runs a single iteration of the main loop (layout, draw, run timed events etc.)
+    /// </summary>
+    public static void RunIteration ()
+    {
+        var a = (ApplicationV2)ApplicationImpl.Instance;
+        a.Coordinator?.RunIteration ();
+    }
 }

+ 1 - 1
Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs

@@ -235,7 +235,7 @@ public class ConsoleDriverTests
     //		{
     //			var win = new Window ();
     //			Application.Begin (win);
-    //			((FakeDriver)Application.Driver!).SetBufferSize (20, 8);
+    //			AutoInitShutdownAttribute.FakeResize(new Size ( (20, 8);
 
     //			System.Threading.Tasks.Task.Run (() => {
     //				System.Threading.Tasks.Task.Delay (500).Wait ();

+ 32 - 14
Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 using System.Runtime.CompilerServices;
 using Microsoft.Extensions.Logging;
 using Moq;
+using TerminalGuiFluentTesting;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class ApplicationV2Tests
@@ -12,18 +13,34 @@ public class ApplicationV2Tests
         ConsoleDriver.RunningUnitTests = true;
     }
 
-    private ApplicationV2 NewApplicationV2 ()
+    private ApplicationV2 NewApplicationV2 (V2TestDriver driver = V2TestDriver.V2Net)
     {
-        var netInput = new Mock<INetInput> ();
-        SetupRunInputMockMethodToBlock (netInput);
-        var winInput = new Mock<IWindowsInput> ();
-        SetupRunInputMockMethodToBlock (winInput);
 
-        return new (
-                    () => netInput.Object,
-                    Mock.Of<IConsoleOutput>,
-                    () => winInput.Object,
-                    Mock.Of<IConsoleOutput>);
+        if (driver == V2TestDriver.V2Net)
+        {
+            var netInput = new Mock<INetInput> ();
+            SetupRunInputMockMethodToBlock (netInput);
+
+            var m = new Mock<IComponentFactory<ConsoleKeyInfo>> ();
+            m.Setup (f => f.CreateInput ()).Returns (netInput.Object);
+            m.Setup (f => f.CreateInputProcessor (It.IsAny<ConcurrentQueue<ConsoleKeyInfo>> ())).Returns (Mock.Of <IInputProcessor> ());
+            m.Setup (f => f.CreateOutput ()).Returns (Mock.Of<IConsoleOutput> ());
+            m.Setup (f => f.CreateWindowSizeMonitor (It.IsAny<IConsoleOutput> (),It.IsAny<IOutputBuffer> ())).Returns (Mock.Of<IWindowSizeMonitor> ());
+
+            return new (m.Object);
+        }
+        else
+        {
+
+            var winInput = new Mock<IConsoleInput<WindowsConsole.InputRecord>> ();
+            SetupRunInputMockMethodToBlock (winInput);
+            var m = new Mock<IComponentFactory<WindowsConsole.InputRecord>> ();
+            m.Setup (f => f.CreateInput ()).Returns (winInput.Object);
+            m.Setup (f => f.CreateInputProcessor (It.IsAny<ConcurrentQueue<WindowsConsole.InputRecord>> ())).Returns (Mock.Of<IInputProcessor> ());
+            m.Setup (f => f.CreateOutput ()).Returns (Mock.Of<IConsoleOutput> ());
+            m.Setup (f => f.CreateWindowSizeMonitor (It.IsAny<IConsoleOutput> (), It.IsAny<IOutputBuffer> ())).Returns (Mock.Of<IWindowSizeMonitor> ());
+            return new (m.Object);
+        }
     }
 
     [Fact]
@@ -68,7 +85,7 @@ public class ApplicationV2Tests
 
         ApplicationImpl.ChangeInstance (orig);
     }
-
+    /*
     [Fact]
     public void Init_ExplicitlyRequestWin ()
     {
@@ -150,8 +167,8 @@ public class ApplicationV2Tests
 
         ApplicationImpl.ChangeInstance (orig);
     }
-
-    private void SetupRunInputMockMethodToBlock (Mock<IWindowsInput> winInput)
+*/
+    private void SetupRunInputMockMethodToBlock (Mock<IConsoleInput<WindowsConsole.InputRecord>> winInput)
     {
         winInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
                 .Callback<CancellationToken> (token =>
@@ -473,7 +490,7 @@ public class ApplicationV2Tests
 
         return true;
     }
-
+    /*
     [Fact]
     public void Shutdown_Called_Repeatedly_DoNotDuplicateDisposeOutput ()
     {
@@ -500,6 +517,7 @@ public class ApplicationV2Tests
 
         ApplicationImpl.ChangeInstance (orig);
     }
+    */
 
     [Fact]
     public void Init_Called_Repeatedly_WarnsAndIgnores ()

+ 6 - 6
Tests/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs

@@ -14,15 +14,15 @@ public class MainLoopCoordinatorTests
         var beforeLogger = Logging.Logger;
         Logging.Logger = mockLogger.Object;
 
-        var c = new MainLoopCoordinator<char> (new TimedEvents (),
-                                               // Runs on a separate thread (input thread)
-                                               () => throw new Exception ("Crash on boot"),
+        var m = new Mock<IComponentFactory<char>> ();
+        // Runs on a separate thread (input thread)
+        m.Setup (f => f.CreateInput ()).Throws (new Exception ("Crash on boot"));
 
+        var c = new MainLoopCoordinator<char> (new TimedEvents (),
                                                // Rest runs on main thread
                                                new ConcurrentQueue<char> (),
-                                               Mock.Of <IInputProcessor>(),
-                                               ()=>Mock.Of<IConsoleOutput>(),
-                                               Mock.Of<IMainLoop<char>>());
+                                               Mock.Of<IMainLoop<char>>(),
+                                               m.Object);
 
         // StartAsync boots the main loop and the input thread. But if the input class bombs
         // on startup it is important that the exception surface at the call site and not lost

+ 11 - 1
Tests/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs

@@ -18,10 +18,20 @@ public class MainLoopTTests
         Assert.Throws<NotInitializedException> (() => m.AnsiRequestScheduler);
         Assert.Throws<NotInitializedException> (() => m.WindowSizeMonitor);
 
+        var componentFactory = new Mock<IComponentFactory<int>> ();
+
+        componentFactory.Setup (
+                                c => c.CreateWindowSizeMonitor (
+                                                                It.IsAny<IConsoleOutput> (),
+                                                                It.IsAny<IOutputBuffer> ()))
+                        .Returns (Mock.Of <IWindowSizeMonitor>());
+
         m.Initialize (new TimedEvents (),
                       new ConcurrentQueue<int> (),
                       Mock.Of <IInputProcessor>(),
-                      Mock.Of<IConsoleOutput>());
+                      Mock.Of<IConsoleOutput>(),
+                      componentFactory.Object
+                     );
 
         Assert.NotNull (m.TimedEvents);
         Assert.NotNull (m.InputBuffer);

+ 62 - 67
Tests/UnitTests/Dialogs/DialogTests.cs

@@ -13,8 +13,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
-
         var title = "1234";
         var btn1Text = "yes";
         var btn1 = $"{Glyphs.LeftBracket} {btn1Text} {Glyphs.RightBracket}";
@@ -23,7 +21,7 @@ public class DialogTests (ITestOutputHelper output)
 
         // We test with one button first, but do this to get the width right for 2
         int width = $@"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}".Length;
-        d.SetBufferSize (width, 1);
+        AutoInitShutdownAttribute.FakeResize(new(width, 1));
 
         // Override CM
         Dialog.DefaultButtonAlignment = Alignment.Center;
@@ -46,7 +44,7 @@ public class DialogTests (ITestOutputHelper output)
         runState = Begin (dlg);
         var buttonRow = $"{Glyphs.VLine}    {btn1}     {Glyphs.VLine}";
 
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
 
@@ -54,7 +52,7 @@ public class DialogTests (ITestOutputHelper output)
         buttonRow = $"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}";
         dlg.AddButton (new () { Text = btn2Text });
 
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -73,7 +71,7 @@ public class DialogTests (ITestOutputHelper output)
         dlg.Border!.Thickness = new (1, 0, 1, 0);
         runState = Begin (dlg);
 
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
 
         buttonRow = $"{Glyphs.VLine}{btn1}         {Glyphs.VLine}";
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
@@ -81,7 +79,7 @@ public class DialogTests (ITestOutputHelper output)
         // Now add a second button
         buttonRow = $"{Glyphs.VLine}{btn1}   {btn2}{Glyphs.VLine}";
         dlg.AddButton (new () { Text = btn2Text });
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -100,7 +98,7 @@ public class DialogTests (ITestOutputHelper output)
         dlg.Border!.Thickness = new (1, 0, 1, 0);
         runState = Begin (dlg);
 
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
 
         buttonRow = $"{Glyphs.VLine}{new (' ', width - btn1.Length - 2)}{btn1}{Glyphs.VLine}";
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
@@ -109,7 +107,7 @@ public class DialogTests (ITestOutputHelper output)
         buttonRow = $"{Glyphs.VLine}  {btn1} {btn2}{Glyphs.VLine}";
         dlg.AddButton (new () { Text = btn2Text });
 
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -127,7 +125,7 @@ public class DialogTests (ITestOutputHelper output)
         // Create with no top or bottom border to simplify testing button layout (no need to account for title etc..)
         dlg.Border!.Thickness = new (1, 0, 1, 0);
         runState = Begin (dlg);
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
 
         buttonRow = $"{Glyphs.VLine}{btn1}{new (' ', width - btn1.Length - 2)}{Glyphs.VLine}";
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
@@ -136,7 +134,7 @@ public class DialogTests (ITestOutputHelper output)
         buttonRow = $"{Glyphs.VLine}{btn1} {btn2}  {Glyphs.VLine}";
         dlg.AddButton (new () { Text = btn2Text });
 
-        RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -148,7 +146,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -166,7 +163,8 @@ public class DialogTests (ITestOutputHelper output)
 
         var buttonRow = $"{Glyphs.VLine} {btn1} {btn2} {btn3} {btn4} {Glyphs.VLine}";
         int width = buttonRow.Length;
-        d.SetBufferSize (buttonRow.Length, 3);
+
+        AutoInitShutdownAttribute.FakeResize(new (buttonRow.Length, 3));
 
         // Default - Center
         (runState, Dialog dlg) = BeginButtonTestDialog (
@@ -195,6 +193,7 @@ public class DialogTests (ITestOutputHelper output)
                                                  new Button { Text = btn3Text },
                                                  new Button { Text = btn4Text }
                                                 );
+
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -240,7 +239,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -258,7 +256,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = string.Empty;
 
         var width = 30;
-        d.SetBufferSize (width, 1);
+        AutoInitShutdownAttribute.FakeResize(new(width, 1));
 
         // Default - Center
         buttonRow =
@@ -335,7 +333,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -355,7 +352,7 @@ public class DialogTests (ITestOutputHelper output)
         //                         123456                          1234567
         var buttonRow = $"{Glyphs.VLine}      {btn1} {btn2} {btn3} {btn4}      {Glyphs.VLine}";
         int width = buttonRow.Length;
-        d.SetBufferSize (buttonRow.Length, 1);
+        AutoInitShutdownAttribute.FakeResize (new (buttonRow.Length, 1));
 
         // Default - Center
         (runState, Dialog dlg) = BeginButtonTestDialog (
@@ -429,7 +426,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -451,7 +447,7 @@ public class DialogTests (ITestOutputHelper output)
         //                         123456                           123456
         var buttonRow = $"{Glyphs.VLine}      {btn1} {btn2} {btn3} {btn4}      {Glyphs.VLine}";
         int width = buttonRow.GetColumns ();
-        d.SetBufferSize (width, 3);
+        AutoInitShutdownAttribute.FakeResize(new(width, 3));
 
         // Default - Center
         (runState, Dialog dlg) = BeginButtonTestDialog (
@@ -523,7 +519,6 @@ public class DialogTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void ButtonAlignment_One ()
     {
-        var d = (FakeDriver)Driver!;
         RunState? runState = null;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
@@ -537,7 +532,7 @@ public class DialogTests (ITestOutputHelper output)
             $"{Glyphs.VLine}  {Glyphs.LeftBracket} {btnText} {Glyphs.RightBracket}  {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        d.SetBufferSize (width, 1);
+        AutoInitShutdownAttribute.FakeResize(new(width, 1));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -601,7 +596,7 @@ public class DialogTests (ITestOutputHelper output)
             $"{Glyphs.VLine}   {Glyphs.LeftBracket} {btnText} {Glyphs.RightBracket}   {Glyphs.VLine}";
         width = buttonRow.Length;
 
-        d.SetBufferSize (width, 1);
+        AutoInitShutdownAttribute.FakeResize(new(width, 1));
 
         (runState, dlg) = BeginButtonTestDialog (
                                                  title,
@@ -665,7 +660,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -682,7 +676,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $@"{Glyphs.VLine} {btn1} {btn2} {btn3} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        d.SetBufferSize (buttonRow.Length, 3);
+        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -751,7 +745,6 @@ public class DialogTests (ITestOutputHelper output)
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -766,7 +759,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $@"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        d.SetBufferSize (buttonRow.Length, 3);
+        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -832,7 +825,6 @@ public class DialogTests (ITestOutputHelper output)
         RunState? runState = null;
         var firstIteration = false;
 
-        var d = (FakeDriver)Driver!;
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -847,7 +839,7 @@ public class DialogTests (ITestOutputHelper output)
         var buttonRow = $@"{Glyphs.VLine} {btn1} {btn2} {Glyphs.VLine}";
         int width = buttonRow.Length;
 
-        d.SetBufferSize (buttonRow.Length, 3);
+        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
 
         Dialog dlg = null;
         Button button1, button2;
@@ -857,8 +849,10 @@ public class DialogTests (ITestOutputHelper output)
         button2 = new () { Text = btn2Text };
         (runState, dlg) = BeginButtonTestDialog (title, width, Alignment.Center, button1, button2);
         button1.Visible = false;
-        RunIteration (ref runState, firstIteration);
+        AutoInitShutdownAttribute.RunIteration ();
+
         buttonRow = $@"{Glyphs.VLine}         {btn2} {Glyphs.VLine}";
+
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -869,7 +863,8 @@ public class DialogTests (ITestOutputHelper output)
         button2 = new () { Text = btn2Text };
         (runState, dlg) = BeginButtonTestDialog (title, width, Alignment.Fill, button1, button2);
         button1.Visible = false;
-        RunIteration (ref runState, firstIteration);
+        AutoInitShutdownAttribute.RunIteration ();
+
         buttonRow = $@"{Glyphs.VLine}          {btn2}{Glyphs.VLine}";
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
@@ -881,7 +876,8 @@ public class DialogTests (ITestOutputHelper output)
         button2 = new () { Text = btn2Text };
         (runState, dlg) = BeginButtonTestDialog (title, width, Alignment.End, button1, button2);
         button1.Visible = false;
-        RunIteration (ref runState, firstIteration);
+        AutoInitShutdownAttribute.RunIteration ();
+
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
         dlg.Dispose ();
@@ -892,7 +888,8 @@ public class DialogTests (ITestOutputHelper output)
         button2 = new () { Text = btn2Text };
         (runState, dlg) = BeginButtonTestDialog (title, width, Alignment.Start, button1, button2);
         button1.Visible = false;
-        RunIteration (ref runState, firstIteration);
+        AutoInitShutdownAttribute.RunIteration ();
+
         buttonRow = $@"{Glyphs.VLine}        {btn2}  {Glyphs.VLine}";
         DriverAssert.AssertDriverContentsWithFrameAre ($"{buttonRow}", output);
         End (runState);
@@ -903,7 +900,7 @@ public class DialogTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void Dialog_In_Window_With_Size_One_Button_Aligns ()
     {
-        ((FakeDriver)Driver!).SetBufferSize (20, 5);
+        AutoInitShutdownAttribute.FakeResize(new(20, 5));
 
         // Override CM
         Window.DefaultBorderStyle = LineStyle.Single;
@@ -931,7 +928,7 @@ public class DialogTests (ITestOutputHelper output)
 
                           dlg.Loaded += (s, a) =>
                                         {
-                                            LayoutAndDraw ();
+                                            AutoInitShutdownAttribute.RunIteration ();
 
                                             var expected = @$"
 ┌──────────────────┐
@@ -1008,7 +1005,7 @@ public class DialogTests (ITestOutputHelper output)
                 )]
     public void Dialog_In_Window_Without_Size_One_Button_Aligns (int height, string expected)
     {
-        ((FakeDriver)Driver!).SetBufferSize (20, height);
+        AutoInitShutdownAttribute.FakeResize(new (20, height));
         var win = new Window ();
 
         int iterations = -1;
@@ -1036,7 +1033,7 @@ public class DialogTests (ITestOutputHelper output)
                          }
                          else if (iterations == 1)
                          {
-                             LayoutAndDraw ();
+                             AutoInitShutdownAttribute.RunIteration ();
 
                              // BUGBUG: This seems wrong; is it a bug in Dim.Percent(85)?? No
                              _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
@@ -1055,7 +1052,7 @@ public class DialogTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void Dialog_Opened_From_Another_Dialog ()
     {
-        ((FakeDriver)Driver!).SetBufferSize (30, 10);
+        AutoInitShutdownAttribute.FakeResize(new (30, 10));
 
         // Override CM
         Dialog.DefaultButtonAlignment = Alignment.Center;
@@ -1108,7 +1105,6 @@ public class DialogTests (ITestOutputHelper output)
                              case 0:
                                  Top!.SetNeedsLayout ();
                                  Top.SetNeedsDraw ();
-                                 LayoutAndDraw ();
 
                                  break;
 
@@ -1116,8 +1112,10 @@ public class DialogTests (ITestOutputHelper output)
                                  Assert.False (btn1.NewKeyDownEvent (Key.Space));
 
                                  break;
-                             case 2:
-                                 LayoutAndDraw ();
+
+                             // Now this happens on iteration 3 because Space triggers Run on the new dialog which itself causes another iteration
+                             // as it starts. Meaning we haven't exited case 1 when we enter case 2 from next Run stack frame.
+                             case 3:
 
                                  expected = @$"
   ┌───────────────────────┐
@@ -1133,8 +1131,7 @@ public class DialogTests (ITestOutputHelper output)
                                  Assert.False (btn2!.NewKeyDownEvent (Key.Space));
 
                                  break;
-                             case 3:
-                                 LayoutAndDraw ();
+                             case 5:
 
                                  DriverAssert.AssertDriverContentsWithFrameAre (
                                                                                 @$"
@@ -1152,15 +1149,14 @@ public class DialogTests (ITestOutputHelper output)
                                  Assert.False (Top!.NewKeyDownEvent (Key.Enter));
 
                                  break;
-                             case 4:
-                                 LayoutAndDraw ();
+                             case 7:
 
                                  DriverAssert.AssertDriverContentsWithFrameAre (expected, output);
 
                                  Assert.False (btn3!.NewKeyDownEvent (Key.Space));
 
                                  break;
-                             case 5:
+                             case 9:
                                  DriverAssert.AssertDriverContentsWithFrameAre ("", output);
 
                                  RequestStop ();
@@ -1172,7 +1168,7 @@ public class DialogTests (ITestOutputHelper output)
         Run ().Dispose ();
         Shutdown ();
 
-        Assert.Equal (5, iterations);
+        Assert.Equal (9, iterations);
     }
 
     [Fact]
@@ -1198,7 +1194,7 @@ public class DialogTests (ITestOutputHelper output)
             Height = Dim.Percent (85)
         };
         Begin (d);
-        ((FakeDriver)Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new(100, 100));
 
         // Default location is centered, so 100 / 2 - 85 / 2 = 7
         var expected = 7;
@@ -1212,7 +1208,7 @@ public class DialogTests (ITestOutputHelper output)
     {
         var d = new Dialog { X = 1, Y = 1 };
         Begin (d);
-        ((FakeDriver)Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new(100, 100));
 
         // Default location is centered, so 100 / 2 - 85 / 2 = 7
         var expected = 1;
@@ -1234,7 +1230,7 @@ public class DialogTests (ITestOutputHelper output)
         var expected = 5;
         var d = new Dialog { X = expected, Y = expected, Height = 5, Width = 5 };
         Begin (d);
-        ((FakeDriver)Driver!).SetBufferSize (20, 10);
+        AutoInitShutdownAttribute.FakeResize(new(20, 10));
 
         // Default location is centered, so 100 / 2 - 85 / 2 = 7
         Assert.Equal (new (expected, expected), d.Frame.Location);
@@ -1256,9 +1252,7 @@ public class DialogTests (ITestOutputHelper output)
     public void One_Button_Works ()
     {
         RunState? runState = null;
-
-        var d = (FakeDriver)Driver!;
-
+        
         Button.DefaultShadow = ShadowStyle.None;
 
         var title = "";
@@ -1268,7 +1262,7 @@ public class DialogTests (ITestOutputHelper output)
             $"{Glyphs.VLine}   {Glyphs.LeftBracket} {btnText} {Glyphs.RightBracket}   {Glyphs.VLine}";
 
         int width = buttonRow.Length;
-        d.SetBufferSize (buttonRow.Length, 10);
+        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 10));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (
                                                         title,
@@ -1292,7 +1286,7 @@ public class DialogTests (ITestOutputHelper output)
         };
 
         Begin (d);
-        ((FakeDriver)Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new(100, 100));
 
         // Default size is Percent(85) 
         Assert.Equal (new ((int)(100 * .85), (int)(100 * .85)), d.Frame.Size);
@@ -1308,7 +1302,7 @@ public class DialogTests (ITestOutputHelper output)
         var d = new Dialog { Width = 50, Height = 50 };
 
         Begin (d);
-        ((FakeDriver)Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new(100, 100));
 
         // Default size is Percent(85) 
         Assert.Equal (new (50, 50), d.Frame.Size);
@@ -1316,18 +1310,16 @@ public class DialogTests (ITestOutputHelper output)
     }
 
     [Fact]
-    [SetupFakeDriver]
+    [AutoInitShutdown]
     public void Zero_Buttons_Works ()
     {
         RunState? runState = null;
 
-        var d = (FakeDriver)Driver!;
-
         var title = "1234";
 
         var buttonRow = $"{Glyphs.VLine}        {Glyphs.VLine}";
         int width = buttonRow.Length;
-        d.SetBufferSize (buttonRow.Length, 3);
+        AutoInitShutdownAttribute.FakeResize(new(buttonRow.Length, 3));
 
         (runState, Dialog dlg) = BeginButtonTestDialog (title, width, Alignment.Center, null);
 
@@ -1368,8 +1360,8 @@ public class DialogTests (ITestOutputHelper output)
 
         dlg.SetNeedsDraw ();
         dlg.SetNeedsLayout ();
-        dlg.Layout ();
-        dlg.Draw ();
+
+        AutoInitShutdownAttribute.RunIteration ();
 
         return (runState, dlg);
     }
@@ -1439,9 +1431,9 @@ public class DialogTests (ITestOutputHelper output)
         // Run another view without dispose the prior will throw an assertion
 #if DEBUG_IDISPOSABLE
         Dialog dlg2 = new ();
-        dlg2.Ready += Dlg_Ready;
-        Exception exception = Record.Exception (() => Run (dlg2));
-        Assert.NotNull (exception);
+        dlg2.Ready += Dlg_Ready; 
+        //   Exception exception = Record.Exception (() => Run (dlg2));
+   //     Assert.NotNull (exception);
 
         dlg.Dispose ();
 
@@ -1455,9 +1447,12 @@ public class DialogTests (ITestOutputHelper output)
 
         dlg2.Dispose ();
 
+        // tznind REMOVED: Why wouldn't you be able to read cancelled after dispose - that makes no sense
         // Now an assertion will throw accessing the Canceled property
-        exception = Record.Exception (() => Assert.True (dlg.Canceled))!;
-        Assert.NotNull (exception);
+        //var exception = Record.Exception (() => Assert.True (dlg.Canceled))!;
+        //Assert.NotNull (exception);
+        //Assert.StartsWith ("Cannot access a disposed object.", exception.Message);
+
         Assert.True (Top.WasDisposed);
         Shutdown ();
         Assert.True (dlg2.WasDisposed);
@@ -1491,7 +1486,7 @@ public class DialogTests (ITestOutputHelper output)
             Y = 1
         };
 
-        ((FakeDriver)Driver!).SetBufferSize (20, 20);
+        AutoInitShutdownAttribute.FakeResize(new(20, 20));
 
         int iterations = 0;
         Iteration += (s, a) =>

+ 16 - 25
Tests/UnitTests/Dialogs/MessageBoxTests.cs

@@ -155,7 +155,7 @@ public class MessageBoxTests
     {
         int iterations = -1;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); // 15 x 15 gives us enough room for a button with one char (9x1)
+        AutoInitShutdownAttribute.FakeResize(new Size(15, 15)); // 15 x 15 gives us enough room for a button with one char (9x1)
         Dialog.DefaultShadow = ShadowStyle.None;
         Button.DefaultShadow = ShadowStyle.None;
 
@@ -189,7 +189,7 @@ public class MessageBoxTests
         int iterations = -1;
         var top = new Toplevel ();
         top.BorderStyle = LineStyle.None;
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, 10));
 
         var btn =
             $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} btn {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}";
@@ -217,9 +217,8 @@ public class MessageBoxTests
 
                                          Application.RequestStop ();
                                      }
-                                     else if (iterations == 1)
+                                     else if (iterations == 2)
                                      {
-                                         Application.LayoutAndDraw ();
 
                                          DriverAssert.AssertDriverContentsWithFrameAre (
                                                                                         @"
@@ -234,9 +233,8 @@ public class MessageBoxTests
                                          // Really long text
                                          MessageBox.Query (string.Empty, new ('f', 500), 0, false, "btn");
                                      }
-                                     else if (iterations == 2)
+                                     else if (iterations == 4)
                                      {
-                                         Application.LayoutAndDraw ();
 
                                          DriverAssert.AssertDriverContentsWithFrameAre (
                                                                                         @"
@@ -261,7 +259,7 @@ public class MessageBoxTests
         int iterations = -1;
         var top = new Toplevel ();
         top.BorderStyle = LineStyle.None;
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, 10));
 
         var btn =
             $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} btn {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}";
@@ -289,9 +287,8 @@ public class MessageBoxTests
 
                                          Application.RequestStop ();
                                      }
-                                     else if (iterations == 1)
+                                     else if (iterations == 2)
                                      {
-                                         Application.LayoutAndDraw ();
 
                                          DriverAssert.AssertDriverContentsWithFrameAre (
                                                                                         @"
@@ -309,9 +306,8 @@ public class MessageBoxTests
                                          // Really long text
                                          MessageBox.Query (string.Empty, new ('f', 500), 0, true, "btn");
                                      }
-                                     else if (iterations == 2)
+                                     else if (iterations == 4)
                                      {
-                                         Application.LayoutAndDraw ();
 
                                          DriverAssert.AssertDriverContentsWithFrameAre (
                                                                                         @"
@@ -347,7 +343,7 @@ public class MessageBoxTests
     public void Size_Not_Default_Message (int height, int width, string message)
     {
         int iterations = -1;
-        ((FakeDriver)Application.Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new Size(100, 100));
 
         Application.Iteration += (s, a) =>
                                  {
@@ -361,7 +357,7 @@ public class MessageBoxTests
                                      }
                                      else if (iterations == 1)
                                      {
-                                         Application.LayoutAndDraw ();
+                                         AutoInitShutdownAttribute.RunIteration ();
 
                                          Assert.IsType<Dialog> (Application.Top);
                                          Assert.Equal (new (height, width), Application.Top.Frame.Size);
@@ -384,7 +380,7 @@ public class MessageBoxTests
     public void Size_Not_Default_Message_Button (int height, int width, string message)
     {
         int iterations = -1;
-        ((FakeDriver)Application.Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new Size(100, 100));
 
         Application.Iteration += (s, a) =>
                                  {
@@ -398,7 +394,7 @@ public class MessageBoxTests
                                      }
                                      else if (iterations == 1)
                                      {
-                                         Application.LayoutAndDraw ();
+                                         AutoInitShutdownAttribute.RunIteration ();
 
                                          Assert.IsType<Dialog> (Application.Top);
                                          Assert.Equal (new (height, width), Application.Top.Frame.Size);
@@ -417,7 +413,7 @@ public class MessageBoxTests
     public void Size_Not_Default_No_Message (int height, int width)
     {
         int iterations = -1;
-        ((FakeDriver)Application.Driver!).SetBufferSize (100, 100);
+        AutoInitShutdownAttribute.FakeResize(new Size(100, 100));
 
         Application.Iteration += (s, a) =>
                                  {
@@ -431,7 +427,7 @@ public class MessageBoxTests
                                      }
                                      else if (iterations == 1)
                                      {
-                                         Application.LayoutAndDraw ();
+                                         AutoInitShutdownAttribute.RunIteration ();
 
                                          Assert.IsType<Dialog> (Application.Top);
                                          Assert.Equal (new (height, width), Application.Top.Frame.Size);
@@ -446,7 +442,7 @@ public class MessageBoxTests
     public void UICatalog_AboutBox ()
     {
         int iterations = -1;
-        ((FakeDriver)Application.Driver).SetBufferSize (70, 15);
+        AutoInitShutdownAttribute.FakeResize (new Size (70, 15));
 
         // Override CM
         MessageBox.DefaultButtonAlignment = Alignment.End;
@@ -469,10 +465,8 @@ public class MessageBoxTests
 
                                          Application.RequestStop ();
                                      }
-                                     else if (iterations == 1)
+                                     else if (iterations == 2)
                                      {
-                                         Application.LayoutAndDraw ();
-
                                          var expectedText = """
                                                             ┌────────────────────────────────────────────────────────────────────┐
                                                             │   ╔═══════════════════════════════════════════════════════════╗    │
@@ -505,16 +499,13 @@ public class MessageBoxTests
 
     [Theory]
     [MemberData (nameof (AcceptingKeys))]
+    [AutoInitShutdown]
     public void Button_IsDefault_True_Return_His_Index_On_Accepting (Key key)
     {
-        Application.Init (new FakeDriver ());
-
         Application.Iteration += (_, _) => Assert.True (Application.RaiseKeyDownEvent (key));
         int res = MessageBox.Query ("hey", "IsDefault", "Yes", "No");
 
         Assert.Equal (0, res);
-
-        Application.Shutdown ();
     }
 
     public static IEnumerable<object []> AcceptingKeys ()

+ 5 - 8
Tests/UnitTests/Dialogs/WizardTests.cs

@@ -392,14 +392,13 @@ public class WizardTests ()
     // and that the title is correct
     public void OneStepWizard_Shows ()
     {
-        var d = (FakeDriver)Application.Driver;
 
         var title = "1234";
         var stepTitle = "ABCD";
 
         var width = 30;
         var height = 7;
-        d.SetBufferSize (width, height);
+        AutoInitShutdownAttribute.FakeResize (new Size (width, height));
 
         //	var btnBackText = "Back";
         var btnBack = string.Empty; // $"{Glyphs.LeftBracket} {btnBackText} {Glyphs.RightBracket}";
@@ -482,14 +481,14 @@ public class WizardTests ()
     // this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle")
     public void Setting_Title_Works ()
     {
-        var d = (FakeDriver)Application.Driver;
-
+        var d = (IConsoleDriverFacade)Application.Driver;
+        
         var title = "1234";
         var stepTitle = " - ABCD";
 
         var width = 40;
         var height = 4;
-        d.SetBufferSize (width, height);
+        d.OutputBuffer.SetWindowSize (width,height);
 
         var btnNextText = "Finish";
 
@@ -646,14 +645,12 @@ public class WizardTests ()
     // and that the title is correct
     public void ZeroStepWizard_Shows ()
     {
-        var d = (FakeDriver)Application.Driver;
-
         var title = "1234";
         var stepTitle = "";
 
         var width = 30;
         var height = 6;
-        d.SetBufferSize (width, height);
+        AutoInitShutdownAttribute.FakeResize (new Size (width, height));
 
         var btnBackText = "Back";
         var btnBack = $"{Glyphs.LeftBracket} {btnBackText} {Glyphs.RightBracket}";

+ 1 - 1
Tests/UnitTests/Drawing/RulerTests.cs

@@ -30,7 +30,7 @@ public class RulerTests
     [AutoInitShutdown]
     public void Draw_Default ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (25, 25);
+        AutoInitShutdownAttribute.FakeResize(new Size(25, 25));
 
         var r = new Ruler ();
         r.Draw (Point.Empty);

+ 7 - 6
Tests/UnitTests/Drawing/ThicknessTests.cs

@@ -10,7 +10,7 @@ public class ThicknessTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void DrawTests ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (60, 60);
+        AutoInitShutdownAttribute.FakeResize(new Size(60, 60));
         var t = new Thickness (0, 0, 0, 0);
         var r = new Rectangle (5, 5, 40, 15);
 
@@ -125,10 +125,11 @@ public class ThicknessTests (ITestOutputHelper output)
         top.Add (f);
         RunState rs = Application.Begin (top);
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (45, 20);
+        AutoInitShutdownAttribute.FakeResize(new Size(45, 20));
         var t = new Thickness (0, 0, 0, 0);
         var r = new Rectangle (2, 2, 40, 15);
-        Application.RunIteration (ref rs);
+
+        AutoInitShutdownAttribute.RunIteration ();
 
         t.Draw (r, ViewDiagnosticFlags.Ruler, "Test");
 
@@ -160,7 +161,7 @@ public class ThicknessTests (ITestOutputHelper output)
         t = new (1, 1, 1, 1);
         r = new (1, 1, 40, 15);
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
         t.Draw (r, ViewDiagnosticFlags.Ruler, "Test");
 
         DriverAssert.AssertDriverContentsAre (
@@ -191,7 +192,7 @@ public class ThicknessTests (ITestOutputHelper output)
         t = new (1, 2, 3, 4);
         r = new (2, 2, 40, 15);
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
         t.Draw (r, ViewDiagnosticFlags.Ruler, "Test");
 
         DriverAssert.AssertDriverContentsWithFrameAre (
@@ -222,7 +223,7 @@ public class ThicknessTests (ITestOutputHelper output)
         t = new (-1, 1, 1, 1);
         r = new (5, 5, 40, 15);
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
         t.Draw (r, ViewDiagnosticFlags.Ruler, "Test");
 
         DriverAssert.AssertDriverContentsWithFrameAre (

+ 17 - 8
Tests/UnitTests/FileServices/FileDialogTests.cs

@@ -28,9 +28,11 @@ public class FileDialogTests ()
         dlg.Dispose ();
     }
 
-    [Fact]
+    [Theory]
+    [InlineData ("Bob", "csv")]
+    [InlineData ("𝔹ob", "CSV")]
     [AutoInitShutdown]
-    public void DirectTyping_Allowed ()
+    public void DirectTyping_Allowed (string path, string extension)
     {
         FileDialog dlg = GetInitializedFileDialog ();
         TextField tf = dlg.SubViews.OfType<TextField> ().First (t => t.HasFocus);
@@ -46,15 +48,15 @@ public class FileDialogTests ()
                      );
 
         // continue typing the rest of the path
-        Send ("BOB");
+        Send (path);
         Send ('.', ConsoleKey.OemPeriod);
-        Send ("CSV");
+        Send (extension);
 
         Assert.True (dlg.Canceled);
 
         Send ('\n', ConsoleKey.Enter);
         Assert.False (dlg.Canceled);
-        Assert.Equal ("bob.csv", Path.GetFileName (dlg.Path));
+        Assert.Equal ($"{path}.{extension}", Path.GetFileName (dlg.Path));
         dlg.Dispose ();
     }
 
@@ -72,7 +74,7 @@ public class FileDialogTests ()
 
         dlg.Path = openIn + Path.DirectorySeparatorChar;
 
-        Send ("X");
+        Send ("x");
 
         // nothing selected yet
         Assert.True (dlg.Canceled);
@@ -375,7 +377,7 @@ public class FileDialogTests ()
         Send ('>', ConsoleKey.LeftArrow);
         Send ('>', ConsoleKey.RightArrow);
 
-        Send ("SUBFOLDER");
+        Send ("subfolder");
 
         // Dialog has not yet been confirmed with a choice
         Assert.True (dlg.Canceled);
@@ -772,7 +774,14 @@ public class FileDialogTests ()
     {
         foreach (char ch in chars)
         {
-            Application.Driver?.SendKeys (ch, ConsoleKey.NoName, false, false, false);
+            ConsoleKeyInfo consoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (new (ch, ConsoleKey.None, false, false, false));
+
+            Application.Driver?.SendKeys (
+                                          ch,
+                                          consoleKeyInfo.Key,
+                                          (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+                                          (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+                                          (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
         }
     }
 

+ 8 - 1
Tests/UnitTests/SetupFakeDriverAttribute.cs

@@ -1,5 +1,6 @@
 using System.Diagnostics;
 using System.Reflection;
+using TerminalGuiFluentTesting;
 using Xunit.Sdk;
 
 namespace UnitTests;
@@ -34,7 +35,13 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
 
         Application.ResetState (true);
         Assert.Null (Application.Driver);
-        Application.Driver = new FakeDriver { Rows = 25, Cols = 25 };
+
+        var ff = new FakeDriverFactory ();
+        var driver = ff.Create ();
+
+        Application.Driver = driver;
+        driver.SetBufferSize (25, 25);
+
         Assert.Equal (new (0, 0, 25, 25), Application.Screen);
         // Ensures subscribing events, at least for the SizeChanged event
         Application.SubscribeDriverEvents ();

+ 11 - 10
Tests/UnitTests/Text/AutocompleteTests.cs

@@ -10,6 +10,7 @@ public class AutocompleteTests (ITestOutputHelper output)
     [AutoInitShutdown]
     public void CursorLeft_CursorRight_Mouse_Button_Pressed_Does_Not_Show_Popup ()
     {
+        AutoInitShutdownAttribute.FakeResize (new Size (50,5));
         var tv = new TextView { Width = 50, Height = 5, Text = "This a long line and against TextView." };
 
         var g = (SingleWordSuggestionGenerator)tv.Autocomplete.SuggestionGenerator;
@@ -26,7 +27,7 @@ public class AutocompleteTests (ITestOutputHelper output)
         {
             Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
             top.SetNeedsDraw ();
-            Application.RunIteration (ref rs);
+            AutoInitShutdownAttribute.RunIteration ();
 
             if (i < 4 || i > 5)
             {
@@ -54,7 +55,7 @@ This a long line and against TextView.
                                       )
                     );
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -66,7 +67,7 @@ This a long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.G));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -77,7 +78,7 @@ This ag long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.CursorLeft));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -88,7 +89,7 @@ This ag long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.CursorLeft));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -99,7 +100,7 @@ This ag long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.CursorLeft));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -111,7 +112,7 @@ This ag long line and against TextView.",
         {
             Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
             top.SetNeedsDraw ();
-            Application.RunIteration (ref rs);
+            AutoInitShutdownAttribute.RunIteration ();
 
             DriverAssert.AssertDriverContentsWithFrameAre (
                                                            @"
@@ -123,7 +124,7 @@ This ag long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.Backspace));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -135,7 +136,7 @@ This a long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.N));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"
@@ -146,7 +147,7 @@ This an long line and against TextView.
 
         Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
         top.SetNeedsDraw ();
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"

+ 2 - 2
Tests/UnitTests/Text/TextFormatterTests.cs

@@ -3935,7 +3935,7 @@ ssb
     [SetupFakeDriver]
     public void FillRemaining_True_False ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (22, 5);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (22, 5);
 
         Attribute [] attrs =
         {
@@ -4157,7 +4157,7 @@ Nice       Work")]
         Size tfSize = tf.FormatAndGetSize ();
         Assert.Equal (new (59, 13), tfSize);
 
-        ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height);
+        ((IFakeDriverV2)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height);
 
         Application.Driver.FillRect (Application.Screen, (Rune)'*');
         tf.Draw (Application.Screen, Attribute.Default, Attribute.Default);

+ 1 - 0
Tests/UnitTests/UnitTests.csproj

@@ -46,6 +46,7 @@
 	<ItemGroup>
 		<ProjectReference Include="..\..\Terminal.Gui\Terminal.Gui.csproj" />
 		<ProjectReference Include="..\..\Examples\UICatalog\UICatalog.csproj" />
+		<ProjectReference Include="..\TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj" />
 	</ItemGroup>
 	<ItemGroup>
 		<None Update="xunit.runner.json">

+ 15 - 15
Tests/UnitTests/View/Adornment/BorderTests.cs

@@ -97,8 +97,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (width, 5);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(width, 5));
+        AutoInitShutdownAttribute.RunIteration ();
         var expected = string.Empty;
 
         switch (width)
@@ -230,7 +230,7 @@ public class BorderTests (ITestOutputHelper output)
 
         RunState rs = Application.Begin (win);
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (width, 4);
+        AutoInitShutdownAttribute.FakeResize(new Size(width, 4));
         Application.RunIteration (ref rs, false);
         var expected = string.Empty;
 
@@ -364,8 +364,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (width, 4);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(width, 4));
+        AutoInitShutdownAttribute.RunIteration ();
         var expected = string.Empty;
 
         switch (width)
@@ -487,8 +487,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, height);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, height));
+        AutoInitShutdownAttribute.RunIteration ();
         var expected = string.Empty;
 
         switch (height)
@@ -549,8 +549,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (width, 3);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(width, 3));
+        AutoInitShutdownAttribute.RunIteration ();
         var expected = string.Empty;
 
         switch (width)
@@ -733,8 +733,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (top);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(5, 5));
+        AutoInitShutdownAttribute.RunIteration ();
 
         var expected = @"
 ╔═══╗
@@ -761,8 +761,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (top);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(10, 4));
+        AutoInitShutdownAttribute.RunIteration ();
 
         var expected = @"
 ╔════════╗
@@ -784,8 +784,8 @@ public class BorderTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (3, 3);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(3, 3));
+        AutoInitShutdownAttribute.RunIteration ();
 
         var expected = @"
 ┌─┐

+ 2 - 2
Tests/UnitTests/View/Adornment/MarginTests.cs

@@ -9,7 +9,7 @@ public class MarginTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void Margin_Is_Transparent ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5);
 
         var view = new View { Height = 3, Width = 3 };
         view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness;
@@ -44,7 +44,7 @@ public class MarginTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void Margin_ViewPortSettings_Not_Transparent_Is_NotTransparent ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5);
 
         var view = new View { Height = 3, Width = 3 };
         view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness;

+ 1 - 1
Tests/UnitTests/View/Adornment/PaddingTests.cs

@@ -9,7 +9,7 @@ public class PaddingTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void Padding_Uses_Parent_Scheme ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5);
         var view = new View { Height = 3, Width = 3 };
         view.Padding!.Thickness = new (1);
         view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness;

+ 2 - 2
Tests/UnitTests/View/Adornment/ShadowStyleTests.cs

@@ -30,7 +30,7 @@ public class ShadowStyleTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void ShadowView_Colors (ShadowStyle style, string expectedAttrs)
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5);
         Color fg = Color.Red;
         Color bg = Color.Green;
 
@@ -100,7 +100,7 @@ public class ShadowStyleTests (ITestOutputHelper output)
     [SetupFakeDriver]
     public void Visual_Test (ShadowStyle style, string expected)
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5);
 
         var superView = new Toplevel
         {

+ 2 - 2
Tests/UnitTests/View/Draw/ClearViewportTests.cs

@@ -209,7 +209,7 @@ public class ClearViewportTests (ITestOutputHelper output)
         var top = new Toplevel ();
         top.Add (view);
         Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, 10));
 
         var expected = @"
 ┌──────────────────┐
@@ -274,7 +274,7 @@ public class ClearViewportTests (ITestOutputHelper output)
         var top = new Toplevel ();
         top.Add (view);
         Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, 10));
 
         var expected = @"
 ┌──────────────────┐

+ 1 - 1
Tests/UnitTests/View/Draw/ClipTests.cs

@@ -171,7 +171,7 @@ public class ClipTests (ITestOutputHelper _output)
     [Trait ("Category", "Unicode")]
     public void Clipping_Wide_Runes ()
     {
-        ((FakeDriver)Application.Driver!).SetBufferSize (30, 1);
+        ((IFakeDriverV2)Application.Driver!).SetBufferSize (30, 1);
 
         var top = new View
         {

+ 2 - 2
Tests/UnitTests/View/Draw/DrawEventTests.cs

@@ -20,8 +20,8 @@ public class DrawEventTests
 
         var top = new Toplevel ();
         top.Add (view, tv);
-        RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
+        Application.Begin (top);
+        AutoInitShutdownAttribute.RunIteration ();
 
         Assert.True (viewCalled);
         Assert.True (tvCalled);

+ 27 - 26
Tests/UnitTests/View/Draw/DrawTests.cs

@@ -32,7 +32,7 @@ public class DrawTests (ITestOutputHelper output)
         top.Add (win);
 
         Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
+        AutoInitShutdownAttribute.FakeResize(new Size(10, 4)) ;
 
         const string expectedOutput = """
 
@@ -75,8 +75,8 @@ public class DrawTests (ITestOutputHelper output)
         top.Add (viewRight, viewBottom);
 
         var rs = Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (7, 7);
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.FakeResize(new Size(7, 7));
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -302,7 +302,8 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.X = -1;
-        Application.LayoutAndDraw ();
+
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -317,12 +318,12 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.X = -2;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre (@"", output);
 
         content.X = 0;
         content.Y = -1;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -337,7 +338,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -6;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -352,7 +353,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -19;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -363,12 +364,12 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -20;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ("", output);
 
         content.X = -2;
         content.Y = 0;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ("", output);
         top.Dispose ();
     }
@@ -408,7 +409,7 @@ public class DrawTests (ITestOutputHelper output)
         top.SubViewsLaidOut += Top_LayoutComplete;
         Application.Begin (top);
 
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
                                                       
@@ -419,7 +420,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.X = -1;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -431,7 +432,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -1;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -442,12 +443,12 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -2;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ("", output);
 
         content.X = -20;
         content.Y = 0;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ("", output);
         top.Dispose ();
 
@@ -495,7 +496,7 @@ public class DrawTests (ITestOutputHelper output)
         top.Add (container);
         Application.Begin (top);
 
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
                                                       
@@ -509,7 +510,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.X = -1;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -524,12 +525,12 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.X = -2;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre (@"", output);
 
         content.X = 0;
         content.Y = -1;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -544,7 +545,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -6;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -559,7 +560,7 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -19;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       """
@@ -570,12 +571,12 @@ public class DrawTests (ITestOutputHelper output)
                                                      );
 
         content.Y = -20;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ("", output);
 
         content.X = -2;
         content.Y = 0;
-        Application.LayoutAndDraw ();
+        AutoInitShutdownAttribute.RunIteration ();
         DriverAssert.AssertDriverContentsWithFrameAre ("", output);
         top.Dispose ();
     }
@@ -615,7 +616,7 @@ public class DrawTests (ITestOutputHelper output)
         top.Add (win);
 
         Application.Begin (top);
-        ((FakeDriver)Application.Driver!).SetBufferSize (10, 4);
+        AutoInitShutdownAttribute.FakeResize(new Size(10, 4));
 
         var expected = """
 
@@ -684,7 +685,7 @@ public class DrawTests (ITestOutputHelper output)
         Toplevel top = new ();
         top.Add (label, view);
         RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"
@@ -785,7 +786,7 @@ At 0,0
         Toplevel top = new ();
         top.Add (label, view);
         RunState runState = Application.Begin (top);
-        Application.RunIteration (ref runState);
+        AutoInitShutdownAttribute.RunIteration ();
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                       @"

+ 2 - 0
Tests/UnitTests/View/Layout/Pos.AnchorEndTests.cs

@@ -26,6 +26,8 @@ public class PosAnchorEndTests ()
         top.Add (win);
         RunState rs = Application.Begin (top);
 
+        AutoInitShutdownAttribute.FakeResize (new Size (80,25));
+
         Assert.Equal (new (0, 0, 80, 25), top.Frame);
         Assert.Equal (new (0, 0, 80, 25), win.Frame);
         Assert.Equal (new (68, 22, 10, 1), tv.Frame);

+ 4 - 4
Tests/UnitTests/View/Layout/Pos.CenterTests.cs

@@ -35,8 +35,8 @@ public class PosCenterTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, height);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, height));
+        AutoInitShutdownAttribute.RunIteration ();
         var expected = string.Empty;
 
         switch (height)
@@ -182,8 +182,8 @@ public class PosCenterTests (ITestOutputHelper output)
         RunState rs = Application.Begin (win);
         var firstIteration = false;
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (width, 7);
-        Application.RunIteration (ref rs, firstIteration);
+        AutoInitShutdownAttribute.FakeResize(new Size(width, 7));
+        AutoInitShutdownAttribute.RunIteration ();
         var expected = string.Empty;
 
         switch (width)

+ 3 - 2
Tests/UnitTests/View/Layout/SetLayoutTests.cs

@@ -25,17 +25,18 @@ public class SetLayoutTests (ITestOutputHelper output)
         Application.Top.Add (view);
 
         var rs = Application.Begin (Application.Top);
+        AutoInitShutdownAttribute.FakeResize (new Size (80,25));
 
         Assert.Equal (new (0, 0, 80, 25), new Rectangle (0, 0, Application.Screen.Width, Application.Screen.Height));
         Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Top.Frame);
         Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame);
 
-        ((FakeDriver)Application.Driver!).SetBufferSize (20, 10);
+        AutoInitShutdownAttribute.FakeResize(new Size(20, 10))    ;
         Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Top.Frame);
 
         Assert.Equal (new (0, 0, 20, 10), Application.Top.Frame);
 
         Application.End (rs);
-
+        Application.Top.Dispose ();
     }
 }

+ 1 - 1
Tests/UnitTests/View/Mouse/MouseTests.cs

@@ -49,7 +49,7 @@ public class MouseTests : TestsAllViews
         Application.RaiseMouseEvent (new () { ScreenPosition = new (xy, xy), Flags = MouseFlags.Button1Pressed });
 
         Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
-        Application.RunIteration (ref rs);
+        AutoInitShutdownAttribute.RunIteration ();
 
         Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location);
         top.Dispose ();

+ 2 - 3
Tests/UnitTests/View/Navigation/CanFocusTests.cs

@@ -6,11 +6,10 @@ public class CanFocusTests
 {
     // TODO: Figure out what this test is supposed to be testing
     [Fact]
+    [AutoInitShutdown]
     public void CanFocus_Faced_With_Container_Before_Run ()
     {
-        Application.Init (new FakeDriver ());
-
-        Toplevel t = new ();
+        using Toplevel t = new ();
 
         var w = new Window ();
         var f = new FrameView ();

Some files were not shown because too many files changed in this diff