浏览代码

Fixes #4004 & #4445 - Merge of `Application.ForceDriver and Driver.Force16Colors` and `windows" broken in conhost and cmd` (#4448)

* Fixes #4004. Driver "windows" broken in conhost and cmd

* Fix unit tests

* Remove IsVirtualTerminal from IApplication. Add IDriverInternal and IOutputInternal interfaces

* Fix result.IsSupported

* Remove internal interfaces and add them in the implementations classes

* Move Sixel from IApplication to IDriver interface it's a characteristic of the driver

* Only if IOutput is OutputBase then set the internal properties

* Prevents driver windows error on Unix system

* Fix scenario sixel error

* Comment some tests because is keyboard layout dependent and shifted key is needed to produce them (Pt)

* Add 🇵🇹 regional indicators test proving they ca be joined as only one grapheme

* SetConsoleActiveScreenBuffer is already called by the constructor and is only needed once

* Finally fixed non virtual terminal in windows driver

* Add more Sixel unit tests

* Add unit tests for OutputBase class

* Avoid emit escape sequence

* Fix assertion failure in UICatalog

* Let each driver to deal with the Sixel write

* When Shutdown is called by the static Application then the ApplicationImpl.ResetStateStatic should be also called

* Add more OutputBase with Sixel unit tests

* Fix some issues with IsVirtualTerminal and Force16Colors with unit tests improvement

* Add Sixel Detect method unit test

* Make Sixel IsSupported and SupportsTransparency consistent with more unit tests

* Fix namespaces and unit test

* Covering more ApplicationImpl Sixel unit test

* Remove DriverImplProxy because sometimes fails in parallel unit tests

* Fix Init_KeyBindings_Are_Not_Reset unit test failing

* Revert "Fix Init_KeyBindings_Are_Not_Reset unit test failing"

This reverts commit 0ab298bc56525221bf061a3e5f016dd8c7bd0fe0.

* Fix Force16Colors but still use Application.Force16Colors because of CM

* Enforce conditional

* Revert change

* Moving to a new file

* Add the same workaround as the All_Scenarios_Benchmark unit test

* Fixes #4440. TextView with ReadOnly as true, MoveRight doesn't select text up to the end of the line

* Fixes #4442. TextField PositionCursor doesn't treat zero width as one column

* Each character must return at least one column, with the exception of Tab.

* Add unit test for the ScrollOffset

* Each character must return at least one column, with the exception of Tab.

* Add unit test for the LeftColumn

* WIP

* Refactor DriverImpl and OutputBase for maintainability

Refactored `DriverImpl` to remove `IDisposable` and streamline event
handling, including replacing `OnSizeMonitorOnSizeChanged` with an
inline lambda. Reintroduced `SizeChanged` and updated `SetScreenSize`
to invoke it. Moved `SupportsTrueColor` from `OutputBase` to
`DriverImpl` and reintroduced `Force16Colors` with updated logic.

Reintroduced and updated several `OutputBuffer`-related properties
and methods in `DriverImpl`, including `Screen`, `Clip`, `Cols`, and
`Contents`. Moved `Clipboard` from `OutputBase` to `DriverImpl` and
initialized it with `FakeClipboard`. Simplified `Refresh` and `ToAnsi`
methods in `DriverImpl`.

Removed `Force16Colors` from `OutputBase` and simplified method
signatures, including `ToAnsi` and `BuildAnsiForRegion`. Fixed a
parameter name typo in `AppendOrWriteAttribute`. Made minor code
formatting adjustments.

These changes improve code maintainability, reduce redundancy, and
align the implementation with updated design requirements.

* Refactor Force16Colors handling and improve UICatalog

Refactored the `Force16Colors` property:
- Moved it from `DriverImpl` to `IOutput` and `OutputBase`.
- Simplified its management by removing redundant logic.
- Added `OnDriverOnForce16ColorsChanged` to handle updates.

Updated `UICatalogRunnable`:
- Replaced `Driver.Force16Colors` with `Application.Driver.Force16Colors`.
- Added an `F7` shortcut to toggle `Force16Colors`.
- Removed redundant event handlers and improved formatting.

Updated `config.json`:
- Replaced `Application.Force16Colors` with `Driver.Force16Colors`.
- Improved theme configuration formatting for readability.

Other changes:
- Removed the `force16Colors` parameter from `IOutput.ToAnsi`.
- Improved diagnostics handling in `UICatalogRunnable`.
- General code cleanup for readability and maintainability.

* Refactor `Force16Colors` access and improve null safety

Refactored `Force16Colors` property access to use `Application.Driver!`
for null safety and consistency. Updated event handlers to align with
this pattern. Replaced nullable `DrawContext?` parameters with
non-nullable `DrawContext` in `OnDrawingContent` overrides across
multiple classes to enforce stricter nullability checks.

Removed unused `_cachedCursorVisibility` field in `OutputBase.cs` and
cleaned up commented-out legacy code in `UICatalogRunnable.cs`. Updated
XML documentation to reflect method signature changes and property
references. Refactored `Shortcut` example in documentation for
consistency.

Replaced `Application.LayoutAndDraw` with `SetNeedsDraw` for marking
views as needing redraw. Performed general code cleanup to remove
redundant code and improve consistency.

* Refactor ForceDriver and Force16Colors properties

Removed `[Obsolete]` from `Application.ForceDriver`, making it a stable API. Added comments to clarify its role as a configuration property and its synchronization with `IApplication.ForceDriver`. Introduced `_forceDriver` as a private backing field.

Removed `Force16Colors` from `ApplicationImpl` and eliminated reset logic for `ForceDriver` and `Force16Colors` during shutdown, shifting state management responsibility to the library user.

Updated comments in `Driver.cs` to document `Force16Colors` as a configuration property and its synchronization with `IDriver.Force16Colors`. Retained `_force16Colors` as a private backing field for configuration overrides.

* Updated docs

* There is no way to detect Sixel transparency and so relying in VTS or Xterm with transparency

* Fix detect Sixel unit tests with the adjusting code

* Refactored Output.

* MErging

* - Added `OnDriverOnForce16ColorsChanged` method to handle `Driver.Force16ColorsChanged` events and update the `Force16Colors` property.

- Implemented `IDisposable` to ensure proper cleanup of resources, including unsubscribing from `SizeMonitor.SizeChanged` and `Driver.Force16ColorsChanged` events, and disposing of `_output`.
- Replaced inline `SizeMonitor.SizeChanged` event handler with a dedicated method, `OnSizeMonitorOnSizeChanged`, for better readability and maintainability.

- Simplified the `Screen` property by removing commented-out code and directly returning a `Rectangle` based on `OutputBuffer` dimensions.
- Updated the `Force16Colors` property to use `_output` for both getting and setting its value.
- Performed general cleanup, including removing unused code and improving code structure.

* merged

* Refactor Sixel handling with ConcurrentQueue

Replaced `List<SixelToRender>` with `ConcurrentQueue<SixelToRender>`
to improve thread safety and performance in sixel management.
Updated the `Images` class to avoid unnecessary removal and
re-creation of sixel objects by updating existing ones in place.

Refactored `Application.Sixel` to return a `ConcurrentQueue` and
introduced `GetSixels` in `IDriver` and `IOutput` for consistent
access. Updated `OutputBase` to use a private `ConcurrentQueue`
and adjusted rendering logic accordingly.

Removed legacy and redundant code, including `Application.Driver?.Sixel.Clear()`
and unused properties in `DriverImpl` and `ApplicationImpl`. Updated
tests in `OutputBaseTests` to align with the new implementation.

Added `using System.Collections.Concurrent` where necessary and
improved documentation to reflect the changes. These updates
enhance thread safety, simplify the codebase, and align with
modern concurrent programming practices.

* Tweak

* Refactor DriverImpl to use Dispose and improve modularity

Replaced `Driver.End()` with `Driver.Dispose()` across the codebase, aligning with the `IDisposable` pattern for proper resource cleanup. Updated `DriverImpl` to implement `Dispose`, ensuring event unsubscriptions and resource disposal.

Enhanced `DriverImpl` structure by organizing code into logical regions, improving modularity and readability. Refactored and reintroduced methods and properties like `Clipboard`, `Screen`, `SetScreenSize`, `Cols`, `Rows`, and others for better encapsulation.

Updated the `IDriver` interface to include `IDisposable` and reorganized it into regions. Added new methods and properties such as `Init`, `Refresh`, `Suspend`, `QueueAnsiRequest`, and `ToAnsi`.

Refactored unit tests to replace `driver.End()` with `driver.Dispose()` and ensured proper resource cleanup. Improved code comments and documentation for better clarity.

Aligned with modern C# practices, adopting features like null-coalescing operators and pattern matching. Removed redundant code, addressed some TODOs, and modularized the codebase for maintainability and extensibility.

* Refactor driver docs and update View.Driver usage

Updated `application.md` to clarify the purpose of the `View.Driver` property, replacing the obsolete `Application.Driver`. Added a reference to the "Drivers Deep Dive" documentation for further details.

Refactored the `OnDrawContent` method to use the `Driver` property, ensuring compatibility with the new driver architecture.

Added a new section, "Testing with the New Architecture," to `application.md`, highlighting the improved testability of the instance-based architecture.

Expanded and reorganized `drivers.md` to provide a detailed breakdown of the `IDriver` interface, including lifecycle, components, screen and display, color support, content buffer, drawing, cursor, input events, and ANSI escape sequences. Introduced new subsections for clarity and emphasized the modular design for maintainability.

Added a note in `drivers.md` discouraging direct access to the `Driver` and recommending higher-level abstractions like `Terminal.Gui.App.Application.Screen` and `Terminal.Gui.ViewBase.View` methods for positioning and drawing.

* Refactor IsVirtualTerminal to IsLegacyConsole

Replaced the `IsVirtualTerminal` property with `IsLegacyConsole` across the codebase to better represent legacy versus modern terminal environments. Updated logic in `SixelSupportDetector`, `DriverImpl`, and `OutputBase` to use the new property.

Refactored tests to align with the updated property, including renaming test methods, adjusting mock setups, and replacing `VirtualTerminalTests` with `LegacyConsoleTests`.

Simplified `WindowsOutput` implementation to handle console modes and sixel rendering based on `IsLegacyConsole`. Removed redundant code related to `IsVirtualTerminal`.

Improved code readability and maintainability by using more descriptive property names and ensuring consistency across the codebase. Updated `.DotSettings` with new entries.

* Update Examples/UICatalog/Scenarios/LineDrawing.cs

Co-authored-by: Copilot <[email protected]>

* Update Examples/UICatalog/Scenarios/Images.cs

Co-authored-by: Copilot <[email protected]>

* Update Examples/UICatalog/Scenarios/Images.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/App/IApplication.cs

Co-authored-by: Copilot <[email protected]>

* Update Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

Co-authored-by: Copilot <[email protected]>

* Update Examples/UICatalog/Scenarios/ColorPicker.cs

Co-authored-by: Copilot <[email protected]>

* Update Examples/UICatalog/Scenarios/ColorPicker.cs

Co-authored-by: Copilot <[email protected]>

* Fix formatting and typo in code and documentation

Improved code readability in `LineDrawing.cs` by fixing spacing
around the ternary operator in `Width` and `Y` property assignments.
Corrected a typo in `drivers.md` by changing "Configuraiton Manager"
to "Configuration Manager" for accurate documentation.

* Test failure casued by assert left in by accident.

* Added a workaround in `OutputBase.cs` to address dirty cell handling in legacy console mode by marking all buffer cells as dirty.

Refactored `_disableMouseCb` event handling in `UICatalogRunnable.cs` to use the `Selecting` event for toggling `Application.IsMouseDisabled`. Simplified `MouseImpl.cs` by converting `App` to an auto-implemented property and removing redundant namespace usage.

Streamlined logging in `WindowsOutput.cs` by replacing verbose `Logging.Logger` calls with shorter alternatives (`Logging.Information`, `Logging.Error`, etc.).

* Update theme and remove unused ListView component

The application's default theme configuration was updated from "Light" to "Amber Phosphor" by modifying the `ConfigurationManager.RuntimeConfig` value.

Additionally, the `ListView` component in the `ExampleWindow` class was removed. This included its initialization, layout properties (`Y`, `Height`, `Width`), and its data source (["One", "Two", "Three", "Four"]).

* Increase safety timeout in NestedRunTimeoutTests to 10s

The timeout duration for the safety mechanism in the
`NestedRunTimeoutTests` class was increased from 5000ms (5s)
to 10000ms (10s). This change allows the app more time to
complete before triggering the safety timeout, reducing the
likelihood of premature termination during long-running tests.

Refactor and enhance test coverage

Refactored `Load_WithInvalidJson_AddsJsonError` test in `SourcesManagerTests.cs` to improve organization and added a note about its impact on parallel execution. Increased the safety timeout in `NestedRunTimeoutTests.cs` from 5 seconds to 10 seconds to address potential premature test timeouts.

* Handle null Driver gracefully in event subscription

Replaced `ArgumentNullException.ThrowIfNull(Driver)` with a null-check conditional in `SubscribeDriverEvents` and `UnsubscribeDriverEvents`. If `Driver` is `null`, the methods now log an error using `Logging.Error` and return early. This prevents potential exceptions and improves error handling.

---------

Co-authored-by: BDisp <[email protected]>
Co-authored-by: Copilot <[email protected]>
Tig 1 周之前
父节点
当前提交
d303943809
共有 58 个文件被更改,包括 1948 次插入776 次删除
  1. 2 11
      Examples/Example/Example.cs
  2. 2 2
      Examples/UICatalog/Scenarios/ColorPicker.cs
  3. 15 14
      Examples/UICatalog/Scenarios/Images.cs
  4. 4 4
      Examples/UICatalog/Scenarios/LineDrawing.cs
  5. 9 5
      Examples/UICatalog/UICatalog.cs
  6. 36 37
      Examples/UICatalog/UICatalogRunnable.cs
  7. 9 25
      Terminal.Gui/App/Application.Driver.cs
  8. 12 8
      Terminal.Gui/App/ApplicationImpl.Driver.cs
  9. 3 19
      Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
  10. 0 13
      Terminal.Gui/App/ApplicationImpl.cs
  11. 2 16
      Terminal.Gui/App/IApplication.cs
  12. 2 8
      Terminal.Gui/App/Mouse/MouseImpl.cs
  13. 5 4
      Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
  14. 1 1
      Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
  15. 31 0
      Terminal.Gui/Drivers/Driver.cs
  16. 181 163
      Terminal.Gui/Drivers/DriverImpl.cs
  17. 16 5
      Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs
  18. 161 117
      Terminal.Gui/Drivers/IDriver.cs
  19. 13 2
      Terminal.Gui/Drivers/IOutput.cs
  20. 93 20
      Terminal.Gui/Drivers/OutputBase.cs
  21. 1 1
      Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs
  22. 75 99
      Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs
  23. 5 5
      Terminal.Gui/Resources/config.json
  24. 2 3
      Terminal.Gui/ViewBase/Adornment/Margin.cs
  25. 1 1
      Terminal.Gui/ViewBase/View.Content.cs
  26. 1 1
      Terminal.Gui/ViewBase/View.Drawing.cs
  27. 6 6
      Terminal.Gui/Views/Color/ColorPicker.Prompt.cs
  28. 2 2
      Terminal.Gui/Views/ComboBox.cs
  29. 3 3
      Terminal.Gui/Views/Shortcut.cs
  30. 1 1
      Terminal.Gui/Views/Slider/Slider.cs
  31. 1 1
      Terminal.Gui/Views/TableView/TableView.cs
  32. 1 1
      Terminal.Gui/Views/TextInput/TextField.cs
  33. 1 1
      Terminal.Gui/Views/TreeView/TreeView.cs
  34. 3 0
      Terminal.sln.DotSettings
  35. 1 1
      Tests/UnitTests/Application/SynchronizatonContextTests.cs
  36. 28 0
      Tests/UnitTests/Configuration/SourcesManagerTests.cs
  37. 1 1
      Tests/UnitTests/FakeDriverBase.cs
  38. 1 1
      Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs
  39. 0 25
      Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs
  40. 2 2
      Tests/UnitTestsParallelizable/Drawing/AttributeTests.cs
  41. 2 1
      Tests/UnitTestsParallelizable/Drawing/CellTests.cs
  42. 115 5
      Tests/UnitTestsParallelizable/Drawing/Sixel/SixelEncoderTests.cs
  43. 228 0
      Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportDetectorTests.cs
  44. 62 0
      Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportResultTests.cs
  45. 252 0
      Tests/UnitTestsParallelizable/Drawing/Sixel/SixelToRenderTests.cs
  46. 5 5
      Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
  47. 3 3
      Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs
  48. 1 1
      Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs
  49. 1 1
      Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
  50. 54 0
      Tests/UnitTestsParallelizable/Drivers/LegacyConsoleTests.cs
  51. 218 0
      Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs
  52. 20 20
      Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs
  53. 3 3
      Tests/UnitTestsParallelizable/Drivers/Windows/WindowsKeyConverterTests.cs
  54. 2 0
      Tests/UnitTestsParallelizable/Text/StringTests.cs
  55. 4 4
      Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs
  56. 2 2
      Tests/UnitTestsParallelizable/ViewBase/Layout/Pos.CenterTests.cs
  57. 124 84
      docfx/docs/application.md
  58. 119 18
      docfx/docs/drivers.md

+ 2 - 11
Examples/Example/Example.cs

@@ -8,8 +8,8 @@ using Terminal.Gui.Configuration;
 using Terminal.Gui.ViewBase;
 using Terminal.Gui.Views;
 
-// Override the default configuration for the application to use the Light theme
-ConfigurationManager.RuntimeConfig = """{ "Theme": "Light" }""";
+// Override the default configuration for the application to use the Amber Phosphor theme
+ConfigurationManager.RuntimeConfig = """{ "Theme": "Amber Phosphor" }""";
 ConfigurationManager.Enable (ConfigLocations.All);
 
 IApplication app = Application.Create ();
@@ -90,14 +90,5 @@ public sealed class ExampleWindow : Window
 
         // Add the views to the Window
         Add (usernameLabel, userNameText, passwordLabel, passwordText, btnLogin);
-
-        var lv = new ListView
-        {
-            Y = Pos.AnchorEnd (),
-            Height = Dim.Auto (),
-            Width = Dim.Auto ()
-        };
-        lv.SetSource (["One", "Two", "Three", "Four"]);
-        Add (lv);
     }
 }

+ 2 - 2
Examples/UICatalog/Scenarios/ColorPicker.cs

@@ -186,11 +186,11 @@ public class ColorPickers : Scenario
         {
             X = Pos.Right (cbSupportsTrueColor) + 1,
             Y = Pos.Top (lblDriverName),
-            CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+            CheckedState = Application.Driver.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
             Enabled = canTrueColor,
             Text = "Force16Colors"
         };
-        cbUseTrueColor.CheckedStateChanging += (_, evt) => { Application.Force16Colors = evt.Result == CheckState.Checked; };
+        cbUseTrueColor.CheckedStateChanging += (_, evt) => { Application.Driver!.Force16Colors = evt.Result == CheckState.Checked; };
         app.Add (lblDriverName, cbSupportsTrueColor, cbUseTrueColor);
 
         // Set default colors.

+ 15 - 14
Examples/UICatalog/Scenarios/Images.cs

@@ -122,11 +122,11 @@ public class Images : Scenario
         {
             X = Pos.Right (cbSupportsTrueColor) + 2,
             Y = 0,
-            CheckedState = !Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+            CheckedState = !Driver.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
             Enabled = canTrueColor,
             Text = "Use true color"
         };
-        cbUseTrueColor.CheckedStateChanging += (_, evt) => Application.Force16Colors = evt.Result == CheckState.UnChecked;
+        cbUseTrueColor.CheckedStateChanging += (_, evt) => Driver.Force16Colors = evt.Result == CheckState.UnChecked;
         _win.Add (cbUseTrueColor);
 
         var btnOpenImage = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 0, Text = "Open Image" };
@@ -219,18 +219,21 @@ public class Images : Scenario
         Color [,] bmp = _fire.GetFirePixels ();
 
         // TODO: Static way of doing this, suboptimal
-        if (_fireSixel != null)
+        // ConcurrentQueue doesn't support Remove, so we update the existing object
+        if (_fireSixel == null)
         {
-            Application.Sixel.Remove (_fireSixel);
+            _fireSixel = new ()
+            {
+                SixelData = _fireEncoder.EncodeSixel (bmp),
+                ScreenPosition = new (0, 0)
+            };
+            Application.GetSixels ().Enqueue (_fireSixel);
         }
-
-        _fireSixel = new ()
+        else
         {
-            SixelData = _fireEncoder.EncodeSixel (bmp),
-            ScreenPosition = new (0, 0)
-        };
-
-        Application.Sixel.Add (_fireSixel);
+            _fireSixel.SixelData = _fireEncoder.EncodeSixel (bmp);
+            _fireSixel.ScreenPosition = new (0, 0);
+        }
 
         _win.SetNeedsDraw ();
 
@@ -245,8 +248,6 @@ public class Images : Scenario
         _sixelNotSupported.Dispose ();
         _sixelSupported.Dispose ();
         _isDisposed = true;
-
-        Application.Sixel.Clear ();
     }
 
     private void OpenImage (object sender, CommandEventArgs e)
@@ -513,7 +514,7 @@ public class Images : Scenario
                 ScreenPosition = _screenLocationForSixel
             };
 
-            Application.Sixel.Add (_sixelImage);
+            Application.GetSixels ().Enqueue (_sixelImage);
         }
         else
         {

+ 4 - 4
Examples/UICatalog/Scenarios/LineDrawing.cs

@@ -133,14 +133,14 @@ public class LineDrawing : Scenario
         var d = new Dialog
         {
             Title = title,
-            Width = Application.Force16Colors ? 35 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
+            Width = Driver.Force16Colors ? 35 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
             Height = 10
         };
 
         var btnOk = new Button
         {
             X = Pos.Center () - 5,
-            Y = Application.Force16Colors ? 6 : 4,
+            Y = Driver.Force16Colors ? 6 : 4,
             Text = "Ok",
             Width = Dim.Auto (),
             IsDefault = true
@@ -174,7 +174,7 @@ public class LineDrawing : Scenario
         d.AddButton (btnCancel);
 
         View cp;
-        if (Application.Force16Colors)
+        if (Driver.Force16Colors)
         {
             cp = new ColorPicker16
             {
@@ -197,7 +197,7 @@ public class LineDrawing : Scenario
 
         Application.Run (d);
         d.Dispose ();
-        newColor = Application.Force16Colors ? ((ColorPicker16)cp).SelectedColor : ((ColorPicker)cp).SelectedColor;
+        newColor = Driver.Force16Colors ? ((ColorPicker16)cp).SelectedColor : ((ColorPicker)cp).SelectedColor;
 
         return accept;
     }

+ 9 - 5
Examples/UICatalog/UICatalog.cs

@@ -196,7 +196,7 @@ public class UICatalog
 
         UICatalogMain (Options);
 
-        Debug.Assert (Application.ForceDriver == string.Empty);
+        Application.ForceDriver = string.Empty;
 
         return 0;
     }
@@ -433,8 +433,10 @@ public class UICatalog
 
             // This call to Application.Shutdown brackets the Application.Init call
             // made by Scenario.Init() above
-            // TODO: Throw if shutdown was not called already
-            Application.Shutdown ();
+            if (Application.Driver is { })
+            {
+                Application.Shutdown ();
+            }
 
             VerifyObjectsWereDisposed ();
 
@@ -482,8 +484,10 @@ public class UICatalog
 
         scenario.Dispose ();
 
-        // TODO: Throw if shutdown was not called already
-        Application.Shutdown ();
+        if (Application.Driver is { })
+        {
+            Application.Shutdown ();
+        }
 
         return results;
     }

+ 36 - 37
Examples/UICatalog/UICatalogRunnable.cs

@@ -43,9 +43,12 @@ public class UICatalogRunnable : Runnable
         IsRunningChanged += IsRunningChangedHandler;
 
         // Restore previous selections
-        if (_categoryList.Source?.Count > 0) {
+        if (_categoryList.Source?.Count > 0)
+        {
             _categoryList.SelectedItem = _cachedCategoryIndex ?? 0;
-        } else {
+        }
+        else
+        {
             _categoryList.SelectedItem = null;
         }
         _scenarioList.SelectedRow = _cachedScenarioIndex;
@@ -176,7 +179,7 @@ public class UICatalogRunnable : Runnable
             _force16ColorsMenuItemCb = new ()
             {
                 Title = "Force _16 Colors",
-                CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+                CheckedState = Application.Driver!.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
                 // Best practice for CheckBoxes in menus is to disable focus and highlight states
                 CanFocus = false,
                 HighlightStates = MouseState.None
@@ -184,7 +187,7 @@ public class UICatalogRunnable : Runnable
 
             _force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) =>
                                                              {
-                                                                 if (Application.Force16Colors
+                                                                 if (Application.Driver!.Force16Colors
                                                                      && args.Result == CheckState.UnChecked
                                                                      && !Application.Driver!.SupportsTrueColor)
                                                                  {
@@ -194,10 +197,10 @@ public class UICatalogRunnable : Runnable
 
             _force16ColorsMenuItemCb.CheckedStateChanged += (sender, args) =>
                                                             {
-                                                                Application.Force16Colors = args.Value == CheckState.Checked;
+                                                                Application.Driver!.Force16Colors = args.Value == CheckState.Checked;
 
                                                                 _force16ColorsShortcutCb!.CheckedState = args.Value;
-                                                                Application.LayoutAndDraw ();
+                                                                SetNeedsDraw ();
                                                             };
 
             menuItems.Add (
@@ -298,8 +301,8 @@ public class UICatalogRunnable : Runnable
             _diagnosticFlagsSelector.Selecting += (sender, args) =>
                                                   {
                                                       _diagnosticFlags = (ViewDiagnosticFlags)((int)args.Context!.Source!.Data!);// (ViewDiagnosticFlags)_diagnosticFlagsSelector.Value;
-                                                     Diagnostics = _diagnosticFlags;
-                                                 };
+                                                      Diagnostics = _diagnosticFlags;
+                                                  };
 
             MenuItem diagFlagMenuItem = new MenuItem ()
             {
@@ -326,8 +329,13 @@ public class UICatalogRunnable : Runnable
                 HighlightStates = MouseState.None
             };
 
-            _disableMouseCb.CheckedStateChanged += (_, args) => { Application.IsMouseDisabled = args.Value == CheckState.Checked; };
+            //_disableMouseCb.CheckedStateChanged += (_, args) => { Application.IsMouseDisabled = args.Value == CheckState.Checked; };
+            _disableMouseCb.Selecting += (sender, args) =>
+                                         {
+                                             Application.IsMouseDisabled = !Application.IsMouseDisabled;
+                                             _disableMouseCb.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.None;
 
+                                         };
             menuItems.Add (
                            new MenuItem
                            {
@@ -646,39 +654,30 @@ public class UICatalogRunnable : Runnable
         _force16ColorsShortcutCb = new ()
         {
             Title = "16 color mode",
-            CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
-            CanFocus = false
+            CheckedState = Application.Driver!.Force16Colors ? CheckState.Checked : CheckState.UnChecked,
+            CanFocus = true
         };
 
-        _force16ColorsShortcutCb.CheckedStateChanging += (sender, args) =>
-                                                         {
-                                                             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 ();
-                                                         };
+        Shortcut force16ColorsShortcut = new ()
+        {
+            CanFocus = false,
+            CommandView = _force16ColorsShortcutCb,
+            HelpText = "",
+            BindKeyToApplication = true,
+            Key = Key.F7
+        };
 
+        force16ColorsShortcut.Accepting += (sender, args) =>
+                                           {
+                                               Application.Driver.Force16Colors = !Application.Driver.Force16Colors;
+                                               _force16ColorsMenuItemCb!.CheckedState = Application.Driver.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
+                                               SetNeedsDraw ();
+                                               args.Handled = true;
+                                           };
         statusBar.Add (
                        _shQuit,
                        statusBarShortcut,
-                       new Shortcut
-                       {
-                           CanFocus = false,
-                           CommandView = _force16ColorsShortcutCb,
-                           HelpText = "",
-                           BindKeyToApplication = true,
-                           Key = Key.F7
-                       },
+                       force16ColorsShortcut,
                        _shVersion
                       );
 
@@ -714,7 +713,7 @@ public class UICatalogRunnable : Runnable
         }
 
         _disableMouseCb!.CheckedState = Application.IsMouseDisabled ? CheckState.Checked : CheckState.UnChecked;
-        _force16ColorsShortcutCb!.CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
+        _force16ColorsShortcutCb!.CheckedState = Application.Driver!.Force16Colors ? CheckState.Checked : CheckState.UnChecked;
 
         Application.TopRunnableView?.SetNeedsDraw ();
     }

+ 9 - 25
Terminal.Gui/App/Application.Driver.cs

@@ -1,4 +1,6 @@
 
+
+using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.App;
@@ -13,30 +15,13 @@ public static partial class Application // Driver abstractions
         internal set => ApplicationImpl.Instance.Driver = value;
     }
 
-    private static bool _force16Colors = false; // Resources/config.json overrides
-
-    /// <inheritdoc cref="IApplication.Force16Colors"/>
-    [ConfigurationProperty (Scope = typeof (SettingsScope))]
-    [Obsolete ("The legacy static Application object is going away.")]
-    public static bool Force16Colors
-    {
-        get => _force16Colors;
-        set
-        {
-            bool oldValue = _force16Colors;
-            _force16Colors = value;
-            Force16ColorsChanged?.Invoke (null, new ValueChangedEventArgs<bool> (oldValue, _force16Colors));
-        }
-    }
-
-    /// <summary>Raised when <see cref="Force16Colors"/> changes.</summary>
-    public static event EventHandler<ValueChangedEventArgs<bool>>? Force16ColorsChanged;
-
+    // NOTE: ForceDriver is a configuration property (Application.ForceDriver).
+    // NOTE: IApplication also has a ForceDriver property, which is an instance property
+    // NOTE: set whenever this static property is set.
     private static string _forceDriver = string.Empty; // Resources/config.json overrides
 
     /// <inheritdoc cref="IApplication.ForceDriver"/>
     [ConfigurationProperty (Scope = typeof (SettingsScope))]
-    [Obsolete ("The legacy static Application object is going away.")]
     public static string ForceDriver
     {
         get => _forceDriver;
@@ -44,16 +29,15 @@ public static partial class Application // Driver abstractions
         {
             string oldValue = _forceDriver;
             _forceDriver = value;
-            ForceDriverChanged?.Invoke (null, new ValueChangedEventArgs<string> (oldValue, _forceDriver));
+            ForceDriverChanged?.Invoke (null, new (oldValue, _forceDriver));
         }
     }
 
     /// <summary>Raised when <see cref="ForceDriver"/> changes.</summary>
     public static event EventHandler<ValueChangedEventArgs<string>>? ForceDriverChanged;
 
-    /// <inheritdoc cref="IApplication.Sixel"/>
-    [Obsolete ("The legacy static Application object is going away.")] 
-    public static List<SixelToRender> Sixel => ApplicationImpl.Instance.Sixel;
+    /// <inheritdoc cref="IDriver.GetSixels"/>
+    public static ConcurrentQueue<SixelToRender> GetSixels () => ApplicationImpl.Instance.Driver?.GetSixels ()!;
 
     /// <summary>Gets a list of <see cref="IDriver"/> types and type names that are available.</summary>
     /// <returns></returns>
@@ -67,7 +51,7 @@ public static partial class Application // Driver abstractions
         // Only inspect the IDriver assembly
         var asm = typeof (IDriver).Assembly;
 
-        foreach (Type? type in asm.GetTypes ())
+        foreach (Type type in asm.GetTypes ())
         {
             if (typeof (IDriver).IsAssignableFrom (type) && type is { IsAbstract: false, IsClass: true })
             {

+ 12 - 8
Terminal.Gui/App/ApplicationImpl.Driver.cs

@@ -7,15 +7,9 @@ internal partial class ApplicationImpl
     /// <inheritdoc/>
     public IDriver? Driver { get; set; }
 
-    /// <inheritdoc/>
-    public bool Force16Colors { get; set; }
-
     /// <inheritdoc/>
     public string ForceDriver { get; set; } = string.Empty;
 
-    /// <inheritdoc/>
-    public List<SixelToRender> Sixel { get; } = new ();
-
     /// <summary>
     ///     Creates the appropriate <see cref="IDriver"/> based on platform and driverName.
     /// </summary>
@@ -85,6 +79,8 @@ internal partial class ApplicationImpl
         {
             throw new ("Driver was null even after booting MainLoopCoordinator");
         }
+
+        Driver.Force16Colors = Terminal.Gui.Drivers.Driver.Force16Colors;
     }
 
     private readonly IComponentFactory? _componentFactory;
@@ -149,7 +145,11 @@ internal partial class ApplicationImpl
 
     internal void SubscribeDriverEvents ()
     {
-        ArgumentNullException.ThrowIfNull (Driver);
+        if (Driver is null)
+        {
+            Logging.Error($"Driver is null");
+            return;
+        }
 
         Driver.SizeChanged += Driver_SizeChanged;
         Driver.KeyDown += Driver_KeyDown;
@@ -159,7 +159,11 @@ internal partial class ApplicationImpl
 
     internal void UnsubscribeDriverEvents ()
     {
-        ArgumentNullException.ThrowIfNull (Driver);
+        if (Driver is null)
+        {
+            Logging.Error ($"Driver is null");
+            return;
+        }
 
         Driver.SizeChanged -= Driver_SizeChanged;
         Driver.KeyDown -= Driver_KeyDown;

+ 3 - 19
Terminal.Gui/App/ApplicationImpl.Lifecycle.cs

@@ -269,7 +269,7 @@ internal partial class ApplicationImpl
         if (Driver is { })
         {
             UnsubscribeDriverEvents ();
-            Driver?.End ();
+            Driver.Dispose ();
             Driver = null;
         }
 
@@ -300,23 +300,11 @@ internal partial class ApplicationImpl
         // === 7. Clear navigation and screen state ===
         ScreenChanged = null;
 
-        //Navigation = null;
-
         // === 8. Reset initialization state ===
         Initialized = false;
         MainThreadId = null;
 
-        // === 9. Clear graphics ===
-        Sixel.Clear ();
-
-        // === 10. Reset ForceDriver ===
-        // Note: ForceDriver and Force16Colors are reset
-        // If they need to persist across Init/Shutdown cycles
-        // then the user of the library should manage that state
-        Force16Colors = false;
-        ForceDriver = string.Empty;
-
-        // === 11. Reset synchronization context ===
+        // === 9. Reset synchronization context ===
         // IMPORTANT: Always reset sync context, even if not initialized
         // This ensures cleanup works correctly even if Shutdown is called without Init
         // Reset synchronization context to allow the user to run async/await,
@@ -325,7 +313,7 @@ internal partial class ApplicationImpl
         // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
         SynchronizationContext.SetSynchronizationContext (null);
 
-        // === 12. Unsubscribe from Application static property change events ===
+        // === 10. Unsubscribe from Application static property change events ===
         UnsubscribeApplicationEvents ();
     }
 
@@ -364,9 +352,6 @@ internal partial class ApplicationImpl
     }
 #endif
 
-    // Event handlers for Application static property changes
-    private void OnForce16ColorsChanged (object? sender, ValueChangedEventArgs<bool> e) { Force16Colors = e.NewValue; }
-
     private void OnForceDriverChanged (object? sender, ValueChangedEventArgs<string> e) { ForceDriver = e.NewValue; }
 
     /// <summary>
@@ -374,7 +359,6 @@ internal partial class ApplicationImpl
     /// </summary>
     private void UnsubscribeApplicationEvents ()
     {
-        Application.Force16ColorsChanged -= OnForce16ColorsChanged;
         Application.ForceDriverChanged -= OnForceDriverChanged;
     }
 }

+ 0 - 13
Terminal.Gui/App/ApplicationImpl.cs

@@ -15,7 +15,6 @@ internal partial class ApplicationImpl : IApplication
     internal ApplicationImpl ()
     {
         // Subscribe to Application static property change events
-        Application.Force16ColorsChanged += OnForce16ColorsChanged;
         Application.ForceDriverChanged += OnForceDriverChanged;
     }
 
@@ -143,18 +142,6 @@ internal partial class ApplicationImpl : IApplication
         // If an instance exists, reset it
         _instance?.ResetState (ignoreDisposed);
 
-        // Reset Application static properties to their defaults
-        // This ensures tests start with clean state
-        Application.ForceDriver = string.Empty;
-        Application.Force16Colors = false;
-        Application.IsMouseDisabled = false;
-        Application.QuitKey = Key.Esc;
-        Application.ArrangeKey = Key.F5.WithCtrl;
-        Application.NextTabGroupKey = Key.F6;
-        Application.NextTabKey = Key.Tab;
-        Application.PrevTabGroupKey = Key.F6.WithShift;
-        Application.PrevTabKey = Key.Tab.WithShift;
-
         // Always reset the model tracking to allow tests to use either model after reset
         ResetModelUsageTracking ();
     }

+ 2 - 16
Terminal.Gui/App/IApplication.cs

@@ -449,13 +449,6 @@ public interface IApplication : IDisposable
     /// </remarks>
     IClipboard? Clipboard { get; }
 
-    /// <summary>
-    ///     Gets or sets whether <see cref="Driver"/> will be forced to output only the 16 colors defined in
-    ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be
-    ///     output as long as the selected <see cref="IDriver"/> supports TrueColor.
-    /// </summary>
-    bool Force16Colors { get; set; }
-
     /// <summary>
     ///     Forces the use of the specified driver (one of "fake", "dotnet", "windows", or "unix"). If not
     ///     specified, the driver is selected based on the platform.
@@ -463,9 +456,8 @@ public interface IApplication : IDisposable
     string ForceDriver { get; set; }
 
     /// <summary>
-    ///     Gets or location and size of the application in the terminal. By default, the location is (0, 0) and the size
-    ///     is the size of the terminal as reported by the <see cref="IDriver"/>.
-    ///     Setting the location to anything but (0, 0) is not supported and will throw <see cref="NotSupportedException"/>.
+    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the
+    ///     <see cref="IDriver"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
@@ -498,12 +490,6 @@ public interface IApplication : IDisposable
     /// </remarks>
     bool ClearScreenNextIteration { get; set; }
 
-    /// <summary>
-    ///     Collection of sixel images to write out to screen when updating.
-    ///     Only add to this collection if you are sure terminal supports sixel format.
-    /// </summary>
-    List<SixelToRender> Sixel { get; }
-
     #endregion Screen and Driver
 
     #region Keyboard

+ 2 - 8
Terminal.Gui/App/Mouse/MouseImpl.cs

@@ -20,14 +20,8 @@ internal class MouseImpl : IMouse, IDisposable
         Application.IsMouseDisabledChanged += OnIsMouseDisabledChanged;
     }
 
-    private IApplication? _app;
-
     /// <inheritdoc/>
-    public IApplication? App
-    {
-        get => _app;
-        set => _app = value;
-    }
+    public IApplication? App { get; set; }
 
     /// <inheritdoc/>
     public Point? LastMousePosition { get; set; }
@@ -248,7 +242,7 @@ internal class MouseImpl : IMouse, IDisposable
                 continue;
             }
 
-            CancelEventArgs eventArgs = new System.ComponentModel.CancelEventArgs ();
+            CancelEventArgs eventArgs = new CancelEventArgs ();
             bool? cancelled = view.NewMouseEnterEvent (eventArgs);
 
             if (cancelled is true || eventArgs.Cancel)

+ 5 - 4
Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs

@@ -32,8 +32,9 @@ public class SixelSupportDetector ()
     /// </returns>
     public void Detect (Action<SixelSupportResult> resultCallback)
     {
-        var result = new SixelSupportResult ();
-        result.SupportsTransparency = IsVirtualTerminal () || IsXtermWithTransparency ();
+        SixelSupportResult result = new SixelSupportResult ();
+        bool isLegacyConsole = IsLegacyConsole ();
+        result.SupportsTransparency = !isLegacyConsole || (!isLegacyConsole && IsXtermWithTransparency ());
         IsSixelSupportedByDar (result, resultCallback);
     }
 
@@ -155,9 +156,9 @@ public class SixelSupportDetector ()
 
     private static bool ResponseIndicatesSupport (string response) { return response.Split (';').Contains ("4"); }
 
-    private static bool IsVirtualTerminal ()
+    private bool IsLegacyConsole ()
     {
-        return !string.IsNullOrWhiteSpace (Environment.GetEnvironmentVariable ("WT_SESSION"));
+        return _driver is { IsLegacyConsole: true };
     }
 
     private static bool IsXtermWithTransparency ()

+ 1 - 1
Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs

@@ -81,7 +81,7 @@ public class NetOutput : OutputBase, IOutput
     /// <inheritdoc/>
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
-        if (Application.Force16Colors)
+        if (Force16Colors)
         {
             output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
             output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));

+ 31 - 0
Terminal.Gui/Drivers/Driver.cs

@@ -0,0 +1,31 @@
+namespace Terminal.Gui.Drivers;
+
+/// <summary>
+///     Holds global driver settings.
+/// </summary>
+public sealed class Driver
+{
+    private static bool _force16Colors = false; // Resources/config.json overrides
+
+    // NOTE: Force16Colors is a configuration property (Driver.Force16Colors).
+    // NOTE: IDriver also has a Force16Colors property, which is an instance property
+    // NOTE: set whenever this static property is set.
+    /// <summary>
+    ///     Determines if driver instances should use 16 colors instead of the default TrueColors.
+    /// </summary>
+    /// <seealso cref="IDriver.Force16Colors"/>
+    [ConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool Force16Colors
+    {
+        get => _force16Colors;
+        set
+        {
+            bool oldValue = _force16Colors;
+            _force16Colors = value;
+            Force16ColorsChanged?.Invoke (null, new ValueChangedEventArgs<bool> (oldValue, _force16Colors));
+        }
+    }
+
+    /// <summary>Raised when <see cref="Force16Colors"/> changes.</summary>
+    public static event EventHandler<ValueChangedEventArgs<bool>>? Force16ColorsChanged;
+}

+ 181 - 163
Terminal.Gui/Drivers/DriverImpl.cs

@@ -1,4 +1,5 @@
-using System.Runtime.InteropServices;
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
 
 namespace Terminal.Gui.Drivers;
 
@@ -28,10 +29,6 @@ namespace Terminal.Gui.Drivers;
 /// </remarks>
 internal class DriverImpl : IDriver
 {
-    private readonly IOutput _output;
-    private readonly AnsiRequestScheduler _ansiRequestScheduler;
-    private CursorVisibility _lastCursor = CursorVisibility.Default;
-
     /// <summary>
     ///     Initializes a new instance of the <see cref="DriverImpl"/> class.
     /// </summary>
@@ -63,19 +60,88 @@ internal class DriverImpl : IDriver
                                      };
 
         SizeMonitor = sizeMonitor;
+        SizeMonitor.SizeChanged += OnSizeMonitorOnSizeChanged;
 
-        sizeMonitor.SizeChanged += (_, e) =>
-                                   {
-                                       SetScreenSize (e.Size!.Value.Width, e.Size.Value.Height);
+        CreateClipboard ();
 
-                                       //SizeChanged?.Invoke (this, e);
-                                   };
+        Driver.Force16ColorsChanged += OnDriverOnForce16ColorsChanged;
+    }
 
-        CreateClipboard ();
+    #region Driver Lifecycle
+
+    /// <inheritdoc/>
+    public void Init () { throw new NotSupportedException (); }
+
+    /// <inheritdoc/>
+    public void Refresh () { _output.Write (OutputBuffer); }
+
+    /// <inheritdoc/>
+    public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant ();
+
+    /// <inheritdoc/>
+    public virtual string GetVersionInfo ()
+    {
+        string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
+
+        return type;
     }
 
     /// <inheritdoc/>
-    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
+    public void Suspend ()
+    {
+        // BUGBUG: This is all platform-specific and should not be implemented here.
+        // BUGBUG: This needs to be in each platform's driver implementation.
+        if (Environment.OSVersion.Platform != PlatformID.Unix)
+        {
+            return;
+        }
+
+        Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+        try
+        {
+            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);
+        }
+        catch (Exception ex)
+        {
+            Logging.Error ($"Error suspending terminal: {ex.Message}");
+        }
+
+        Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+    }
+
+    /// <inheritdoc/>
+    public bool IsLegacyConsole
+    {
+        get => _output.IsLegacyConsole;
+        set => _output.IsLegacyConsole = value;
+    }
+
+    /// <inheritdoc/>
+    public void Dispose ()
+    {
+        SizeMonitor.SizeChanged -= OnSizeMonitorOnSizeChanged;
+        Driver.Force16ColorsChanged -= OnDriverOnForce16ColorsChanged;
+        _output.Dispose ();
+    }
+
+    #endregion Driver Lifecycle
+
+    #region Driver Components
+
+    private readonly IOutput _output;
 
     /// <inheritdoc/>
     public IInputProcessor InputProcessor { get; }
@@ -86,6 +152,9 @@ internal class DriverImpl : IDriver
     /// <inheritdoc/>
     public ISizeMonitor SizeMonitor { get; }
 
+    /// <inheritdoc/>
+    public IClipboard? Clipboard { get; private set; } = new FakeClipboard ();
+
     private void CreateClipboard ()
     {
         if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake"))
@@ -116,16 +185,12 @@ internal class DriverImpl : IDriver
         // Clipboard is set to FakeClipboard at initialization
     }
 
-    /// <inheritdoc/>
+    #endregion Driver Components
 
-    public Rectangle Screen =>
+    #region Screen and Display
 
-        //if (Application.RunningUnitTests && _output is WindowsConsoleOutput or NetOutput)
-        //{
-        //    // In unit tests, we don't have a real output, so we return an empty rectangle.
-        //    return Rectangle.Empty;
-        //}
-        new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows);
+    /// <inheritdoc/>
+    public Rectangle Screen => new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows);
 
     /// <inheritdoc/>
     public virtual void SetScreenSize (int width, int height)
@@ -136,23 +201,11 @@ internal class DriverImpl : IDriver
     }
 
     /// <inheritdoc/>
+    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
 
-    public Region? Clip
-    {
-        get => OutputBuffer.Clip;
-        set => OutputBuffer.Clip = value;
-    }
-
-    /// <inheritdoc/>
-
-    public IClipboard? Clipboard { get; private set; } = new FakeClipboard ();
+    private void OnSizeMonitorOnSizeChanged (object? _, SizeChangedEventArgs e) { SetScreenSize (e.Size!.Value.Width, e.Size.Value.Height); }
 
     /// <inheritdoc/>
-
-    public int Col => OutputBuffer.Col;
-
-    /// <inheritdoc/>
-
     public int Cols
     {
         get => OutputBuffer.Cols;
@@ -160,15 +213,13 @@ internal class DriverImpl : IDriver
     }
 
     /// <inheritdoc/>
-
-    public Cell [,]? Contents
+    public int Rows
     {
-        get => OutputBuffer.Contents;
-        set => OutputBuffer.Contents = value;
+        get => OutputBuffer.Rows;
+        set => OutputBuffer.Rows = value;
     }
 
     /// <inheritdoc/>
-
     public int Left
     {
         get => OutputBuffer.Left;
@@ -176,55 +227,45 @@ internal class DriverImpl : IDriver
     }
 
     /// <inheritdoc/>
-
-    public int Row => OutputBuffer.Row;
-
-    /// <inheritdoc/>
-
-    public int Rows
-    {
-        get => OutputBuffer.Rows;
-        set => OutputBuffer.Rows = value;
-    }
-
-    /// <inheritdoc/>
-
     public int Top
     {
         get => OutputBuffer.Top;
         set => OutputBuffer.Top = value;
     }
 
-    // TODO: Probably not everyone right?
-
-    /// <inheritdoc/>
+    #endregion Screen and Display
 
-    public bool SupportsTrueColor => true;
+    #region Color Support
 
     /// <inheritdoc/>
+    public bool SupportsTrueColor => !IsLegacyConsole;
 
+    /// <inheritdoc/>
     public bool Force16Colors
     {
-        get => Application.Force16Colors || !SupportsTrueColor;
-        set => Application.Force16Colors = value || !SupportsTrueColor;
+        get => _output.Force16Colors;
+        set => _output.Force16Colors = value;
     }
 
-    /// <inheritdoc/>
+    private void OnDriverOnForce16ColorsChanged (object? _, ValueChangedEventArgs<bool> e) { Force16Colors = e.NewValue; }
 
-    public Attribute CurrentAttribute
-    {
-        get => OutputBuffer.CurrentAttribute;
-        set => OutputBuffer.CurrentAttribute = value;
-    }
+    #endregion Color Support
 
-    /// <inheritdoc/>
-    public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); }
+    #region Content Buffer
 
     /// <inheritdoc/>
-    public void AddRune (char c) { OutputBuffer.AddRune (c); }
+    public Cell [,]? Contents
+    {
+        get => OutputBuffer.Contents;
+        set => OutputBuffer.Contents = value;
+    }
 
     /// <inheritdoc/>
-    public void AddStr (string str) { OutputBuffer.AddStr (str); }
+    public Region? Clip
+    {
+        get => OutputBuffer.Clip;
+        set => OutputBuffer.Clip = value;
+    }
 
     /// <summary>Clears the <see cref="IDriver.Contents"/> of the driver.</summary>
     public void ClearContents ()
@@ -236,20 +277,26 @@ internal class DriverImpl : IDriver
     /// <inheritdoc/>
     public event EventHandler<EventArgs>? ClearedContents;
 
+    #endregion Content Buffer
+
+    #region Drawing and Rendering
+
     /// <inheritdoc/>
-    public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); }
+    public int Col => OutputBuffer.Col;
 
     /// <inheritdoc/>
-    public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); }
+    public int Row => OutputBuffer.Row;
 
     /// <inheritdoc/>
-    public virtual string GetVersionInfo ()
+    public Attribute CurrentAttribute
     {
-        string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
-
-        return type;
+        get => OutputBuffer.CurrentAttribute;
+        set => OutputBuffer.CurrentAttribute = value;
     }
 
+    /// <inheritdoc/>
+    public void Move (int col, int row) { OutputBuffer.Move (col, row); }
+
     /// <inheritdoc/>
     public bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value);
 
@@ -262,77 +309,22 @@ internal class DriverImpl : IDriver
     ///     <see cref="IDriver.Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    public bool IsValidLocation (string text, int col, int row) { return OutputBuffer.IsValidLocation (text, col, row); }
+    public bool IsValidLocation (string text, int col, int row) => OutputBuffer.IsValidLocation (text, col, row);
 
     /// <inheritdoc/>
-    public void Move (int col, int row) { OutputBuffer.Move (col, row); }
-
-    // TODO: Probably part of output
-
-    /// <inheritdoc/>
-    public bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        _lastCursor = visibility;
-        _output.SetCursorVisibility (visibility);
-
-        return true;
-    }
-
-    /// <inheritdoc/>
-    public bool GetCursorVisibility (out CursorVisibility current)
-    {
-        current = _lastCursor;
-
-        return true;
-    }
+    public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); }
 
     /// <inheritdoc/>
-    public void Suspend ()
-    {
-        // BUGBUG: This is all platform-specific and should not be implemented here.
-        // BUGBUG: This needs to be in each platform's driver implementation.
-        if (Environment.OSVersion.Platform != PlatformID.Unix)
-        {
-            return;
-        }
-
-        Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
-
-        try
-        {
-            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);
-        }
-        catch (Exception ex)
-        {
-            Logging.Error ($"Error suspending terminal: {ex.Message}");
-        }
-
-        Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
-    }
+    public void AddRune (char c) { OutputBuffer.AddRune (c); }
 
     /// <inheritdoc/>
-    public void UpdateCursor () { _output.SetCursorPosition (Col, Row); }
+    public void AddStr (string str) { OutputBuffer.AddStr (str); }
 
     /// <inheritdoc/>
-    public void Init () { throw new NotSupportedException (); }
+    public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); }
 
     /// <inheritdoc/>
-    public void End ()
-    {
-        // TODO: Nope
-    }
+    public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); }
 
     /// <inheritdoc/>
     public Attribute SetAttribute (Attribute newAttribute)
@@ -346,35 +338,11 @@ internal class DriverImpl : IDriver
     /// <inheritdoc/>
     public Attribute GetAttribute () => OutputBuffer.CurrentAttribute;
 
-    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="IDriver.KeyUp"/>.</summary>
-    public event EventHandler<Key>? KeyDown;
-
-    /// <inheritdoc/>
-    public event EventHandler<Key>? KeyUp;
-
-    /// <summary>Event fired when a mouse event occurs.</summary>
-    public event EventHandler<MouseEventArgs>? MouseEvent;
-
     /// <inheritdoc/>
     public void WriteRaw (string ansi) { _output.Write (ansi); }
 
     /// <inheritdoc/>
-    public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); }
-
-    /// <inheritdoc/>
-    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (this, request); }
-
-    /// <inheritdoc/>
-    public AnsiRequestScheduler GetRequestScheduler () => _ansiRequestScheduler;
-
-    /// <inheritdoc/>
-    public void Refresh ()
-    {
-        _output.Write (OutputBuffer);
-    }
-
-    /// <inheritdoc/>
-    public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant ();
+    public ConcurrentQueue<SixelToRender> GetSixels () => _output.GetSixels ();
 
     /// <inheritdoc/>
     public new string ToString ()
@@ -403,9 +371,59 @@ internal class DriverImpl : IDriver
         return sb.ToString ();
     }
 
-    /// <inheritdoc />
-    public string ToAnsi ()
+    /// <inheritdoc/>
+    public string ToAnsi () => _output.ToAnsi (OutputBuffer);
+
+    #endregion Drawing and Rendering
+
+    #region Cursor
+
+    private CursorVisibility _lastCursor = CursorVisibility.Default;
+
+    /// <inheritdoc/>
+    public void UpdateCursor () { _output.SetCursorPosition (Col, Row); }
+
+    /// <inheritdoc/>
+    public bool GetCursorVisibility (out CursorVisibility current)
     {
-        return _output.ToAnsi (OutputBuffer);
+        current = _lastCursor;
+
+        return true;
     }
+
+    /// <inheritdoc/>
+    public bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _lastCursor = visibility;
+        _output.SetCursorVisibility (visibility);
+
+        return true;
+    }
+
+    #endregion Cursor
+
+    #region Input Events
+
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    public event EventHandler<MouseEventArgs>? MouseEvent;
+
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="IDriver.KeyUp"/>.</summary>
+    public event EventHandler<Key>? KeyDown;
+
+    /// <inheritdoc/>
+    public event EventHandler<Key>? KeyUp;
+
+    /// <inheritdoc/>
+    public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); }
+
+    #endregion Input Events
+
+    #region ANSI Escape Sequences
+
+    private readonly AnsiRequestScheduler _ansiRequestScheduler;
+
+    /// <inheritdoc/>
+    public virtual void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (this, request); }
+
+    #endregion ANSI Escape Sequences
 }

+ 16 - 5
Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs

@@ -86,10 +86,21 @@ public class FakeOutput : OutputBase, IOutput
     /// <inheritdoc/>
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
-        if (Application.Force16Colors)
+        if (Force16Colors)
         {
-            output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
-            output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
+            if (!IsLegacyConsole)
+            {
+                output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
+                output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
+
+                EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+            }
+            else
+            {
+                Write (output);
+                Console.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor16 ();
+                Console.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor16 ();
+            }
         }
         else
         {
@@ -106,9 +117,9 @@ public class FakeOutput : OutputBase, IOutput
                                                       attr.Background.G,
                                                       attr.Background.B
                                                      );
-        }
 
-        EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+            EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+        }
     }
 
     /// <inheritdoc/>

+ 161 - 117
Terminal.Gui/Drivers/IDriver.cs

@@ -1,37 +1,75 @@
+using System.Collections.Concurrent;
+
 namespace Terminal.Gui.Drivers;
 
 /// <summary>Base interface for Terminal.Gui Driver implementations.</summary>
 /// <remarks>
 ///     There are currently four implementations: UnixDriver, WindowsDriver, DotNetDriver, and FakeDriver
 /// </remarks>
-public interface IDriver
+public interface IDriver : IDisposable
 {
+    #region Driver Lifecycle
+
+    /// <summary>Initializes the driver</summary>
+    void Init ();
+
     /// <summary>
-    ///     Gets the name of the driver implementation.
+    ///     INTERNAL: Updates the terminal with the current output buffer. Should not be used by applications. Drawing occurs
+    ///     once each Application main loop iteration.
     /// </summary>
-    string? GetName ();
+    void Refresh ();
 
     /// <summary>
-    ///     Class responsible for processing native driver input objects
-    ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
-    ///     and detecting and processing ansi escape sequences.
+    ///     Gets the name of the driver implementation.
     /// </summary>
-    IInputProcessor InputProcessor { get; }
+    string? GetName ();
+
+    /// <summary>Returns the name of the driver and relevant library version information.</summary>
+    /// <returns></returns>
+    string GetVersionInfo ();
+
+    /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
+    /// <remarks>This is only implemented in UnixDriver.</remarks>
+    void Suspend ();
 
     /// <summary>
-    ///     Describes the desired screen state. Data source for <see cref="IOutput"/>.
+    ///     Gets whether the driver has detected the console requires legacy console API (Windows Console API without ANSI/VT
+    ///     support).
+    ///     Returns <see langword="true"/> for legacy consoles that don't support modern ANSI escape sequences (e.g. Windows
+    ///     conhost);
+    ///     <see langword="false"/> for modern terminals with ANSI/VT support.
     /// </summary>
-    IOutputBuffer OutputBuffer { get; }
+    /// <remarks>
+    ///     <para>
+    ///         This property indicates whether the terminal supports modern ANSI escape sequences for input/output.
+    ///         On Windows, this maps to whether Virtual Terminal processing is enabled.
+    ///         On Unix-like systems, this is typically <see langword="false"/> as they support ANSI by default.
+    ///     </para>
+    ///     <para>
+    ///         When <see langword="true"/>, the driver must use legacy Windows Console API functions
+    ///         (e.g., WriteConsoleW, SetConsoleTextAttribute) instead of ANSI escape sequences.
+    ///     </para>
+    /// </remarks>
+    bool IsLegacyConsole { get; internal set; }
+
+    #endregion Driver Lifecycle
+
+    #region Driver Components
 
     /// <summary>
-    ///     Interface for classes responsible for reporting the current
-    ///     size of the terminal window.
+    ///     Class responsible for processing native driver input objects
+    ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
+    ///     and detecting and processing ansi escape sequences.
     /// </summary>
-    ISizeMonitor SizeMonitor { get; }
+    IInputProcessor InputProcessor { get; }
 
     /// <summary>Get the operating system clipboard.</summary>
     IClipboard? Clipboard { get; }
 
+    #endregion Driver Components
+
+    #region Screen and Display
+
     /// <summary>Gets the location and size of the terminal screen.</summary>
     Rectangle Screen { get; }
 
@@ -43,49 +81,32 @@ public interface IDriver
     void SetScreenSize (int width, int height);
 
     /// <summary>
-    ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
-    ///     to.
-    /// </summary>
-    /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
-    Region? Clip { get; set; }
-
-    /// <summary>
-    ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
-    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+    ///     The event fired when the screen changes (size, position, etc.).
+    ///     <see cref="Screen"/> is the source of truth for screen dimensions.
     /// </summary>
-    int Col { get; }
+    event EventHandler<SizeChangedEventArgs>? SizeChanged;
 
     /// <summary>The number of columns visible in the terminal.</summary>
     int Cols { get; set; }
 
-    // BUGBUG: This should not be publicly settable.
-    /// <summary>
-    ///     Gets or sets 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>
-    Cell [,]? Contents { get; set; }
+    /// <summary>The number of rows visible in the terminal.</summary>
+    int Rows { get; set; }
 
     /// <summary>The leftmost column in the terminal.</summary>
     int Left { get; set; }
 
-    /// <summary>
-    ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
-    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
-    /// </summary>
-    int Row { get; }
-
-    /// <summary>The number of rows visible in the terminal.</summary>
-    int Rows { get; set; }
-
     /// <summary>The topmost row in the terminal.</summary>
     int Top { get; set; }
 
+    #endregion Screen and Display
+
+    #region Color Support
+
     /// <summary>Gets whether the <see cref="IDriver"/> supports TrueColor output.</summary>
     bool SupportsTrueColor { get; }
 
     /// <summary>
     ///     Gets or sets whether the <see cref="IDriver"/> should use 16 colors instead of the default TrueColors.
-    ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
@@ -93,8 +114,51 @@ public interface IDriver
     ///         <see langword="false"/>, indicating that the <see cref="IDriver"/> cannot support TrueColor.
     ///     </para>
     /// </remarks>
+    /// <seealso cref="Driver.Force16Colors"/>
     bool Force16Colors { get; set; }
 
+    #endregion Color Support
+
+    #region Content Buffer
+
+    // BUGBUG: This should not be publicly settable.
+    /// <summary>
+    ///     Gets or sets 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>
+    Cell [,]? Contents { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
+    ///     to.
+    /// </summary>
+    /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
+    Region? Clip { get; set; }
+
+    /// <summary>Clears the <see cref="IDriver.Contents"/> of the driver.</summary>
+    void ClearContents ();
+
+    /// <summary>
+    ///     Fills the specified rectangle with the specified rune, using <see cref="IDriver.CurrentAttribute"/>
+    /// </summary>
+    event EventHandler<EventArgs> ClearedContents;
+
+    #endregion Content Buffer
+
+    #region Drawing and Rendering
+
+    /// <summary>
+    ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
+    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+    /// </summary>
+    int Col { get; }
+
+    /// <summary>
+    ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
+    ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+    /// </summary>
+    int Row { get; }
+
     /// <summary>
     ///     The <see cref="System.Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or
     ///     <see cref="AddStr"/>
@@ -102,15 +166,23 @@ public interface IDriver
     /// </summary>
     Attribute CurrentAttribute { get; set; }
 
-    /// <summary>Returns the name of the driver and relevant library version information.</summary>
-    /// <returns></returns>
-    string GetVersionInfo ();
-
     /// <summary>
-    ///     Provide proper writing to send escape sequence recognized by the <see cref="IDriver"/>.
+    ///     Updates <see cref="IDriver.Col"/> and <see cref="IDriver.Row"/> to the specified column and row in
+    ///     <see cref="IDriver.Contents"/>.
+    ///     Used by <see cref="IDriver.AddRune(System.Text.Rune)"/> and <see cref="IDriver.AddStr"/> to determine
+    ///     where to add content.
     /// </summary>
-    /// <param name="ansi"></param>
-    void WriteRaw (string ansi);
+    /// <remarks>
+    ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
+    ///     <para>
+    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="IDriver.Cols"/>
+    ///         and
+    ///         <see cref="IDriver.Rows"/>, the method still sets those properties.
+    ///     </para>
+    /// </remarks>
+    /// <param name="col">Column to move to.</param>
+    /// <param name="row">Row to move to.</param>
+    void Move (int col, int row);
 
     /// <summary>Tests if the specified rune is supported by the driver.</summary>
     /// <param name="rune"></param>
@@ -131,24 +203,6 @@ public interface IDriver
     /// </returns>
     bool IsValidLocation (string text, int col, int row);
 
-    /// <summary>
-    ///     Updates <see cref="IDriver.Col"/> and <see cref="IDriver.Row"/> to the specified column and row in
-    ///     <see cref="IDriver.Contents"/>.
-    ///     Used by <see cref="IDriver.AddRune(System.Text.Rune)"/> and <see cref="IDriver.AddStr"/> to determine
-    ///     where to add content.
-    /// </summary>
-    /// <remarks>
-    ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
-    ///     <para>
-    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="IDriver.Cols"/>
-    ///         and
-    ///         <see cref="IDriver.Rows"/>, the method still sets those properties.
-    ///     </para>
-    /// </remarks>
-    /// <param name="col">Column to move to.</param>
-    /// <param name="row">Row to move to.</param>
-    void Move (int col, int row);
-
     /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
     /// <remarks>
     ///     <para>
@@ -189,14 +243,6 @@ public interface IDriver
     /// <param name="str">String.</param>
     void AddStr (string str);
 
-    /// <summary>Clears the <see cref="IDriver.Contents"/> of the driver.</summary>
-    void ClearContents ();
-
-    /// <summary>
-    ///     Fills the specified rectangle with the specified rune, using <see cref="IDriver.CurrentAttribute"/>
-    /// </summary>
-    event EventHandler<EventArgs> ClearedContents;
-
     /// <summary>Fills the specified rectangle with the specified rune, using <see cref="IDriver.CurrentAttribute"/></summary>
     /// <remarks>
     ///     The value of <see cref="IDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be
@@ -214,31 +260,43 @@ public interface IDriver
     /// <param name="c"></param>
     void FillRect (Rectangle rect, char c);
 
-    /// <summary>Gets the terminal cursor visibility.</summary>
-    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
-    /// <returns><see langword="true"/> upon success</returns>
-    bool GetCursorVisibility (out CursorVisibility visibility);
+    /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
+    /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
+    /// <param name="c">C.</param>
+    Attribute SetAttribute (Attribute c);
+
+    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
+    /// <returns>The current attribute.</returns>
+    Attribute GetAttribute ();
 
     /// <summary>
-    ///     INTERNAL: Updates the terminal with the current output buffer. Should not be used by applications. Drawing occurs
-    ///     once each Application main loop iteration.
+    ///     Provide proper writing to send escape sequence recognized by the <see cref="IDriver"/>.
     /// </summary>
-    void Refresh ();
+    /// <param name="ansi"></param>
+    void WriteRaw (string ansi);
 
-    /// <summary>Sets the terminal cursor visibility.</summary>
-    /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
-    /// <returns><see langword="true"/> upon success</returns>
-    bool SetCursorVisibility (CursorVisibility visibility);
+    /// <summary>
+    ///     Gets the queue of sixel images to write out to screen when updating.
+    ///     If the terminal does not support Sixel, adding to this queue has no effect.
+    /// </summary>
+    ConcurrentQueue<SixelToRender> GetSixels ();
 
     /// <summary>
-    ///     The event fired when the screen changes (size, position, etc.).
-    ///     <see cref="Screen"/> is the source of truth for screen dimensions.
+    ///     Gets a string representation of <see cref="Contents"/>.
     /// </summary>
-    event EventHandler<SizeChangedEventArgs>? SizeChanged;
+    /// <returns></returns>
+    public string ToString ();
 
-    /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
-    /// <remarks>This is only implemented in UnixDriver.</remarks>
-    void Suspend ();
+    /// <summary>
+    ///     Gets an ANSI escape sequence representation of <see cref="Contents"/>. This is the
+    ///     same output as would be written to the terminal to recreate the current screen contents.
+    /// </summary>
+    /// <returns></returns>
+    public string ToAnsi ();
+
+    #endregion Drawing and Rendering
+
+    #region Cursor
 
     /// <summary>
     ///     Sets the position of the terminal cursor to <see cref="IDriver.Col"/> and
@@ -246,20 +304,19 @@ public interface IDriver
     /// </summary>
     void UpdateCursor ();
 
-    /// <summary>Initializes the driver</summary>
-    void Init ();
+    /// <summary>Gets the terminal cursor visibility.</summary>
+    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
+    /// <returns><see langword="true"/> upon success</returns>
+    bool GetCursorVisibility (out CursorVisibility visibility);
 
-    /// <summary>Ends the execution of the console driver.</summary>
-    void End ();
+    /// <summary>Sets the terminal cursor visibility.</summary>
+    /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
+    /// <returns><see langword="true"/> upon success</returns>
+    bool SetCursorVisibility (CursorVisibility visibility);
 
-    /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
-    /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
-    /// <param name="c">C.</param>
-    Attribute SetAttribute (Attribute c);
+    #endregion Cursor
 
-    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
-    /// <returns>The current attribute.</returns>
-    Attribute GetAttribute ();
+    #region Input Events
 
     /// <summary>Event fired when a mouse event occurs.</summary>
     event EventHandler<MouseEventArgs>? MouseEvent;
@@ -281,28 +338,15 @@ public interface IDriver
     /// <param name="key"></param>
     void EnqueueKeyEvent (Key key);
 
+    #endregion Input Events
+
+    #region ANSI Escape Sequences
+
     /// <summary>
     ///     Queues the given <paramref name="request"/> for execution
     /// </summary>
     /// <param name="request"></param>
     public void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
 
-    /// <summary>
-    ///     Gets the <see cref="AnsiRequestScheduler"/> for the driver
-    /// </summary>
-    /// <returns></returns>
-    public AnsiRequestScheduler GetRequestScheduler ();
-
-    /// <summary>
-    ///     Gets a string representation of <see cref="Contents"/>.
-    /// </summary>
-    /// <returns></returns>
-    public string ToString ();
-
-    /// <summary>
-    ///     Gets an ANSI escape sequence representation of <see cref="Contents"/>. This is the
-    ///     same output as would be written to the terminal to recreate the current screen contents.
-    /// </summary>
-    /// <returns></returns>
-    public string ToAnsi ();
+    #endregion ANSI Escape Sequences
 }

+ 13 - 2
Terminal.Gui/Drivers/IOutput.cs

@@ -1,4 +1,6 @@
-namespace Terminal.Gui.Drivers;
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
 
 /// <summary>
 ///     The low-level interface drivers implement to provide output capabilities; encapsulates platform-specific
@@ -6,6 +8,15 @@
 /// </summary>
 public interface IOutput : IDisposable
 {
+    /// <seealso cref="IDriver.Force16Colors"/>
+    bool Force16Colors { get; set; }
+
+    /// <seealso cref="IDriver.IsLegacyConsole"/>
+    bool IsLegacyConsole { get; set; }
+
+    /// <seealso cref="IDriver.GetSixels"/>
+    ConcurrentQueue<SixelToRender> GetSixels ();
+
     /// <summary>
     ///     Gets the current position of the console cursor.
     /// </summary>
@@ -17,7 +28,7 @@ public interface IOutput : IDisposable
     ///     of characters not pixels).
     /// </summary>
     /// <returns></returns>
-    public Size GetSize ();
+    Size GetSize ();
 
     /// <summary>
     ///     Moves the console cursor to the given location.

+ 93 - 20
Terminal.Gui/Drivers/OutputBase.cs

@@ -1,3 +1,5 @@
+using System.Collections.Concurrent;
+
 namespace Terminal.Gui.Drivers;
 
 /// <summary>
@@ -5,7 +7,44 @@ namespace Terminal.Gui.Drivers;
 /// </summary>
 public abstract class OutputBase
 {
-    private CursorVisibility? _cachedCursorVisibility;
+    private bool _force16Colors;
+
+    /// <inheritdoc cref="IOutput.Force16Colors"/>
+    public bool Force16Colors
+    {
+        get => _force16Colors;
+        set
+        {
+            if (IsLegacyConsole && !value)
+            {
+                return;
+            }
+
+            _force16Colors = value;
+        }
+    }
+
+    private bool _isLegacyConsole;
+
+    /// <inheritdoc cref="IOutput.IsLegacyConsole"/>
+    public bool IsLegacyConsole
+    {
+        get => _isLegacyConsole;
+        set
+        {
+            _isLegacyConsole = value;
+
+            if (value) // If legacy console (true), force 16 colors
+            {
+                Force16Colors = true;
+            }
+        }
+    }
+
+    private readonly ConcurrentQueue<SixelToRender> _sixels = [];
+
+    /// <inheritdoc cref="IOutput.GetSixels"/>>
+    public ConcurrentQueue<SixelToRender> GetSixels () => _sixels;
 
     // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
     private TextStyle _redrawTextStyle = TextStyle.None;
@@ -28,7 +67,22 @@ public abstract class OutputBase
         Attribute? redrawAttr = null;
         int lastCol = -1;
 
-        CursorVisibility? savedVisibility = _cachedCursorVisibility;
+        if (IsLegacyConsole)
+        {
+            // BUGBUG: This is a workaround for some regression in legacy console mode where
+            // BUGBUG: dirty cells are not handled correctly. Mark all cells dirty as a workaround.
+            lock (buffer.Contents!)
+            {
+                for (var row = 0; row < buffer.Rows; row++)
+                {
+                    for (var c = 0; c < buffer.Cols; c++)
+                    {
+                        buffer.Contents [row, c].IsDirty = true;
+                    }
+                }
+            }
+        }
+
         SetCursorVisibility (CursorVisibility.Invisible);
 
         for (int row = top; row < rows; row++)
@@ -82,24 +136,36 @@ public abstract class OutputBase
 
             if (output.Length > 0)
             {
-                SetCursorPositionImpl (lastCol, row);
+                if (IsLegacyConsole)
+                {
+                    Write (output);
+                }
+                else
+                {
+                    SetCursorPositionImpl (lastCol, row);
 
-                // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
-                StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
-                Write (processed);
+                    // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
+                    StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
+                    Write (processed);
+                }
             }
         }
 
-        // BUGBUG: The Sixel impl depends on the legacy static Application object
-        // BUGBUG: Disabled for now
-        //foreach (SixelToRender s in  Application.Sixel)
-        //{
-        //    if (!string.IsNullOrWhiteSpace (s.SixelData))
-        //    {
-        //        SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
-        //        Console.Out.Write (s.SixelData);
-        //    }
-        //}
+        if (IsLegacyConsole)
+        {
+            return;
+        }
+
+        foreach (SixelToRender s in GetSixels ())
+        {
+            if (string.IsNullOrWhiteSpace (s.SixelData))
+            {
+                continue;
+            }
+
+            SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
+            Write ((StringBuilder)new (s.SixelData));
+        }
 
 
         // DO NOT restore cursor visibility here - let ApplicationMainLoop.SetCursor() handle it
@@ -168,7 +234,7 @@ public abstract class OutputBase
                     continue;
                 }
 
-                Cell cell = buffer.Contents![row, col];
+                Cell cell = buffer.Contents! [row, col];
                 AppendCellAnsi (cell, output, ref lastAttr, ref redrawTextStyle, endCol, ref col);
             }
 
@@ -232,9 +298,16 @@ public abstract class OutputBase
     {
         SetCursorPositionImpl (lastCol, row);
 
-        // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
-        StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
-        Write (processed);
+        if (IsLegacyConsole)
+        {
+            Write (output);
+        }
+        else
+        {
+            // Wrap URLs with OSC 8 hyperlink sequences using the new Osc8UrlLinker
+            StringBuilder processed = Osc8UrlLinker.WrapOsc8 (output);
+            Write (processed);
+        }
 
         output.Clear ();
         lastCol += outputWidth;

+ 1 - 1
Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs

@@ -39,7 +39,7 @@ internal class UnixOutput : OutputBase, IOutput
     /// <inheritdoc />
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
-        if (Application.Force16Colors)
+        if (Force16Colors)
         {
             output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
             output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));

+ 75 - 99
Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs

@@ -97,13 +97,12 @@ internal partial class WindowsOutput : OutputBase, IOutput
     private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
     private readonly nint _outputHandle;
     private nint _screenBuffer;
-    private readonly bool _isVirtualTerminal;
     private readonly ConsoleColor _foreground;
     private readonly ConsoleColor _background;
 
     public WindowsOutput ()
     {
-        Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}");
+        Logging.Information ($"Creating {nameof (WindowsOutput)}");
 
         if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
         {
@@ -113,22 +112,9 @@ internal partial class WindowsOutput : OutputBase, IOutput
         // Get the standard output handle which is the current screen buffer.
         _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
         GetConsoleMode (_outputHandle, out uint mode);
-        _isVirtualTerminal = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
+        IsLegacyConsole = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0;
 
-        if (_isVirtualTerminal)
-        {
-            if (Environment.GetEnvironmentVariable ("VSAPPIDNAME") is null)
-            {
-                //Enable alternative screen buffer.
-                Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-            }
-            else
-            {
-                _foreground = Console.ForegroundColor;
-                _background = Console.BackgroundColor;
-            }
-        }
-        else
+        if (IsLegacyConsole)
         {
             CreateScreenBuffer ();
 
@@ -145,12 +131,19 @@ internal partial class WindowsOutput : OutputBase, IOutput
             {
                 throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
             }
-
-            // Force 16 colors if not in virtual terminal mode.
-            // BUGBUG: This is bad. It does not work if the app was crated without
-            // BUGBUG: Apis.
-            //ApplicationImpl.Instance.Force16Colors = true;
-
+        }
+        else
+        {
+            if (Environment.GetEnvironmentVariable ("VSAPPIDNAME") is null)
+            {
+                //Enable alternative screen buffer.
+                Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+            }
+            else
+            {
+                _foreground = Console.ForegroundColor;
+                _background = Console.BackgroundColor;
+            }
         }
 
         GetSize ();
@@ -189,7 +182,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
             return;
         }
 
-        if (!WriteConsole (_isVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
+        if (!WriteConsole (!IsLegacyConsole ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
         }
@@ -220,19 +213,19 @@ internal partial class WindowsOutput : OutputBase, IOutput
         var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
         csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-        if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        if (!GetConsoleScreenBufferInfoEx (!IsLegacyConsole ? _outputHandle : _screenBuffer, ref csbi))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
 
-        WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+        WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (!IsLegacyConsole ? _outputHandle : _screenBuffer);
         short newCols = Math.Min (cols, maxWinSize.X);
         short newRows = Math.Min (rows, maxWinSize.Y);
         csbi.dwSize = new (newCols, Math.Max (newRows, (short)1));
         csbi.srWindow = new (0, 0, newCols, newRows);
         csbi.dwMaximumWindowSize = new (newCols, newRows);
 
-        if (!SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+        if (!SetConsoleScreenBufferInfoEx (!IsLegacyConsole ? _outputHandle : _screenBuffer, ref csbi))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
@@ -252,11 +245,11 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
     private void SetConsoleOutputWindow (WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi)
     {
-        if ((_isVirtualTerminal
+        if ((!IsLegacyConsole
                  ? _outputHandle
                  : _screenBuffer)
             != nint.Zero
-            && !SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+            && !SetConsoleScreenBufferInfoEx (!IsLegacyConsole ? _outputHandle : _screenBuffer, ref csbi))
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
@@ -264,65 +257,52 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
     public override void Write (IOutputBuffer outputBuffer)
     {
-        // BUGBUG: This is bad. It does not work if the app was crated without
-        // BUGBUG: Apis.
-        //_force16Colors = ApplicationImpl.Instance.Driver!.Force16Colors;
-        _force16Colors = false;
         _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.
+        // for 16 color mode we will write to a backing buffer, then flip it to the active one at the end to avoid jitter.
         _consoleBuffer = 0;
 
-        if (_force16Colors)
+        if (Force16Colors)
         {
-            if (_isVirtualTerminal)
-            {
-                _consoleBuffer = _outputHandle;
-            }
-            else
-            {
-                _consoleBuffer = _screenBuffer;
-            }
+            _consoleBuffer = !IsLegacyConsole ? _outputHandle : _screenBuffer;
         }
         else
         {
             _consoleBuffer = _outputHandle;
         }
 
-        base.Write (outputBuffer);
-
         try
         {
-            if (_force16Colors && !_isVirtualTerminal)
-            {
-                SetConsoleActiveScreenBuffer (_consoleBuffer);
-            }
-            else
+            base.Write (outputBuffer);
+
+            ReadOnlySpan<char> span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
+
+            bool result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+
+            if (!result)
             {
-                ReadOnlySpan<char> span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
+                int err = Marshal.GetLastWin32Error ();
+
+                if (err == 1)
+                {
+                    Logging.Error ($"Error: {Marshal.GetLastWin32Error ()} in {nameof (WindowsOutput)}");
 
-                bool result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+                    return;
+                }
 
-                if (!result)
+                if (err != 0)
                 {
-                    int err = Marshal.GetLastWin32Error ();
-
-                    if (err == 1)
-                    {
-                        Logging.Logger.LogError ($"Error: {Marshal.GetLastWin32Error ()} in {nameof (WindowsOutput)}");
-
-                        return;
-                    }
-                    if (err != 0)
-                    {
-                        throw new Win32Exception (err);
-                    }
+                    throw new Win32Exception (err);
                 }
             }
         }
+        catch (DllNotFoundException)
+        {
+            // Running unit tests or in an environment where writing is not possible.
+        }
         catch (Exception e)
         {
-            Logging.Logger.LogError ($"Error: {e.Message} in {nameof (WindowsOutput)}");
+            Logging.Error ($"Error: {e.Message} in {nameof (WindowsOutput)}");
 
             if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
             {
@@ -341,7 +321,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
         var str = output.ToString ();
 
-        if (_force16Colors && !_isVirtualTerminal)
+        if (Force16Colors && IsLegacyConsole)
         {
             char [] a = str.ToCharArray ();
             WriteConsole (_screenBuffer, a, (uint)a.Length, out _, nint.Zero);
@@ -355,23 +335,20 @@ internal partial class WindowsOutput : OutputBase, IOutput
     /// <inheritdoc/>
     protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
     {
-        // BUGBUG: This is bad. It does not work if the app was crated without
-        // BUGBUG: Apis.
-        // bool force16Colors = ApplicationImpl.Instance.Force16Colors;
-        bool force16Colors = false;
-
-        if (force16Colors)
+        if (Force16Colors)
         {
-            if (_isVirtualTerminal)
+            if (IsLegacyConsole)
             {
-                output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
-                output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
-                EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+                Write (output);
+                output.Clear ();
+                var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
+                SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
             }
             else
             {
-                var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
-                SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
+                output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
+                output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
+                EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
             }
         }
         else
@@ -438,7 +415,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
             var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
             csbi.cbSize = (uint)Marshal.SizeOf (csbi);
 
-            if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+            if (!GetConsoleScreenBufferInfoEx (!IsLegacyConsole ? _outputHandle : _screenBuffer, ref csbi))
             {
                 //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
                 cursorPosition = default (WindowsConsole.Coord);
@@ -468,7 +445,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
         try
         {
-            maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+            maxWinSize = GetLargestConsoleWindowSize (!IsLegacyConsole ? _outputHandle : _screenBuffer);
         }
         catch
         {
@@ -481,7 +458,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
     /// <inheritdoc/>
     protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
     {
-        if (_force16Colors && !_isVirtualTerminal)
+        if (Force16Colors && IsLegacyConsole)
         {
             SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY));
         }
@@ -505,7 +482,7 @@ internal partial class WindowsOutput : OutputBase, IOutput
             return;
         }
 
-        if (!_isVirtualTerminal)
+        if (IsLegacyConsole)
         {
             var info = new WindowsConsole.ConsoleCursorInfo
             {
@@ -539,15 +516,15 @@ internal partial class WindowsOutput : OutputBase, IOutput
 
         _lastCursorPosition = new (col, row);
 
-        if (_isVirtualTerminal)
+        if (IsLegacyConsole)
         {
-            var sb = new StringBuilder ();
-            EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1);
-            Write (sb.ToString ());
+            SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
         }
         else
         {
-            SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+            var sb = new StringBuilder ();
+            EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1);
+            Write (sb.ToString ());
         }
     }
 
@@ -558,7 +535,6 @@ internal partial class WindowsOutput : OutputBase, IOutput
     }
 
     private bool _isDisposed;
-    private bool _force16Colors;
     private nint _consoleBuffer;
     private readonly StringBuilder _everythingStringBuilder = new ();
 
@@ -570,7 +546,16 @@ internal partial class WindowsOutput : OutputBase, IOutput
             return;
         }
 
-        if (_isVirtualTerminal)
+        if (IsLegacyConsole)
+        {
+            if (_screenBuffer != nint.Zero)
+            {
+                CloseHandle (_screenBuffer);
+            }
+
+            _screenBuffer = nint.Zero;
+        }
+        else
         {
             if (Environment.GetEnvironmentVariable ("VSAPPIDNAME") is null)
             {
@@ -585,15 +570,6 @@ internal partial class WindowsOutput : OutputBase, IOutput
                 Console.Clear ();
             }
         }
-        else
-        {
-            if (_screenBuffer != nint.Zero)
-            {
-                CloseHandle (_screenBuffer);
-            }
-
-            _screenBuffer = nint.Zero;
-        }
 
         _isDisposed = true;
     }

+ 5 - 5
Terminal.Gui/Resources/config.json

@@ -19,8 +19,8 @@
   // --------------- Application Settings ---------------
   "Key.Separator": "+",
 
+  "Driver.Force16Colors": false,
   "Application.ArrangeKey": "Ctrl+F5",
-  "Application.Force16Colors": false,
   //"Application.ForceDriver": "", // TODO: ForceDriver should be nullable
   "Application.IsMouseDisabled": false,
   "Application.NextTabGroupKey": "F6",
@@ -136,14 +136,14 @@
                 "Foreground": "White",
                 "Background": "DarkBlue"
               }
-            },
+            }
           },
           {
             "Dialog": {
               "Normal": {
                 "Foreground": "BrightBlue",
                 "Background": "LightGray"
-              },
+              }
             }
           },
           {
@@ -152,7 +152,7 @@
                 "Foreground": "White",
                 "Background": "Blue",
                 "Style": "Bold"
-              },
+              }
             }
           },
           {
@@ -161,7 +161,7 @@
                 "Foreground": "Red",
                 "Background": "WhiteSmoke",
                 "Style": "Italic"
-              },
+              }
             }
           }
         ],

+ 2 - 3
Terminal.Gui/ViewBase/Adornment/Margin.cs

@@ -75,7 +75,7 @@ public class Margin : Adornment
 
         while (stack.Count > 0)
         {
-            var view = stack.Pop ();
+            View view = stack.Pop ();
 
             if (view.Margin is { } margin && margin.Thickness != Thickness.Empty && margin.GetCachedClip () != null)
             {
@@ -87,10 +87,9 @@ public class Margin : Adornment
                 margin.ClearCachedClip ();
             }
 
-            Debug.Assert (view.NeedsDraw == false);
             view.ClearNeedsDraw ();
 
-            foreach (var subview in view.SubViews)
+            foreach (View subview in view.SubViews)
             {
                 stack.Push (subview);
             }

+ 1 - 1
Terminal.Gui/ViewBase/View.Content.cs

@@ -335,7 +335,7 @@ public partial class View
     ///     </para>
     ///     <para>
     ///         Altering the Viewport Size will eventually (when the view is next laid out) cause the
-    ///         <see cref="Layout()"/> and <see cref="OnDrawingContent()"/> methods to be called.
+    ///         <see cref="Layout()"/> and <see cref="OnDrawingContent(DrawContext)"/> methods to be called.
     ///     </para>
     /// </remarks>
     public virtual Rectangle Viewport

+ 1 - 1
Terminal.Gui/ViewBase/View.Drawing.cs

@@ -568,7 +568,7 @@ public partial class View // Drawing APIs
     /// <remarks>
     ///     <para>
     ///         Subscribe to this event to draw custom content for the View. Use the drawing methods available on <see cref="View"/>
-    ///         such as <see cref="View.AddRune(int, int, Rune)"/>, <see cref="View.AddStr(string)"/>, and <see cref="View.FillRect(Rectangle, Rune?)"/>.
+    ///         such as <see cref="View.AddRune(int, int, Rune)"/>, <see cref="View.AddStr(string)"/>, and <see cref="View.FillRect(Rectangle, Rune)"/>.
     ///     </para>
     ///     <para>
     ///         The event is invoked after <see cref="ClearingViewport"/> and <see cref="Text"/> have been drawn, but before any <see cref="SubViews"/> are drawn.

+ 6 - 6
Terminal.Gui/Views/Color/ColorPicker.Prompt.cs

@@ -21,14 +21,14 @@ public partial class ColorPicker
         var d = new Dialog
         {
             Title = title,
-            Width = Application.Force16Colors ? 37 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
+            Width = app.Driver!.Force16Colors ? 37 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
             Height = 20
         };
 
         var btnOk = new Button
         {
             X = Pos.Center () - 5,
-            Y = Application.Force16Colors ? 6 : 4,
+            Y = app.Driver!.Force16Colors ? 6 : 4,
             Text = "Ok",
             Width = Dim.Auto (),
             IsDefault = true
@@ -63,7 +63,7 @@ public partial class ColorPicker
 
         View cpForeground;
 
-        if (Application.Force16Colors)
+        if (app.Driver!.Force16Colors)
         {
             cpForeground = new ColorPicker16
             {
@@ -88,7 +88,7 @@ public partial class ColorPicker
 
         View cpBackground;
 
-        if (Application.Force16Colors)
+        if (app.Driver!.Force16Colors)
         {
             cpBackground = new ColorPicker16
             {
@@ -117,8 +117,8 @@ public partial class ColorPicker
 
         app.Run (d);
         d.Dispose ();
-        Color newForeColor = Application.Force16Colors ? ((ColorPicker16)cpForeground).SelectedColor : ((ColorPicker)cpForeground).SelectedColor;
-        Color newBackColor = Application.Force16Colors ? ((ColorPicker16)cpBackground).SelectedColor : ((ColorPicker)cpBackground).SelectedColor;
+        Color newForeColor = app.Driver!.Force16Colors ? ((ColorPicker16)cpForeground).SelectedColor : ((ColorPicker)cpForeground).SelectedColor;
+        Color newBackColor = app.Driver!.Force16Colors ? ((ColorPicker16)cpBackground).SelectedColor : ((ColorPicker)cpBackground).SelectedColor;
         newAttribute = new (newForeColor, newBackColor);
         app.Dispose ();
         return accept;

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

@@ -287,7 +287,7 @@ public class ComboBox : View, IDesignable
     public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); }
 
     /// <inheritdoc/>
-    protected override bool OnDrawingContent (DrawContext? context)
+    protected override bool OnDrawingContent (DrawContext context)
     {
 
         if (!_autoHide)
@@ -881,7 +881,7 @@ public class ComboBox : View, IDesignable
             return res;
         }
 
-        protected override bool OnDrawingContent (DrawContext? context)
+        protected override bool OnDrawingContent (DrawContext context)
         {
             Attribute current = GetAttributeForRole (VisualRole.Focus);
             SetAttribute (current);

+ 3 - 3
Terminal.Gui/Views/Shortcut.cs

@@ -391,7 +391,7 @@ public class Shortcut : View, IOrientation, IDesignable
     /// <example>
     ///     <para>
     ///         This example illustrates how to add a <see cref="Shortcut"/> to a <see cref="StatusBar"/> that toggles the
-    ///         <see cref="IApplication.Force16Colors"/> property.
+    ///         <see cref="IDriver.Force16Colors"/> property.
     ///     </para>
     ///     <code>
     ///     var force16ColorsShortcut = new Shortcut
@@ -406,8 +406,8 @@ public class Shortcut : View, IOrientation, IDesignable
     ///     cb.Toggled += (s, e) =>
     ///     {
     ///         var cb = s as CheckBox;
-    ///         Application.Force16Colors = cb!.Checked == true;
-    ///         Application.Refresh();
+    ///         App.Driver.Force16Colors = cb!.Checked == true;
+    ///         App.river.Refresh();
     ///     };
     ///     StatusBar.Add(force16ColorsShortcut);
     /// </code>

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

@@ -779,7 +779,7 @@ public class Slider<T> : View, IOrientation
     #region Drawing
 
     /// <inheritdoc/>
-    protected override bool OnDrawingContent (DrawContext? context)
+    protected override bool OnDrawingContent (DrawContext context)
     {
         // TODO: make this more surgical to reduce repaint
 

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

@@ -931,7 +931,7 @@ public class TableView : View, IDesignable
     }
 
     ///<inheritdoc/>
-    protected override bool OnDrawingContent (DrawContext? context)
+    protected override bool OnDrawingContent (DrawContext context)
     {
         Move (0, 0);
 

+ 1 - 1
Terminal.Gui/Views/TextInput/TextField.cs

@@ -922,7 +922,7 @@ public class TextField : View, IDesignable
     }
 
     /// <inheritdoc/>
-    protected override bool OnDrawingContent (DrawContext? context)
+    protected override bool OnDrawingContent (DrawContext context)
     {
         _isDrawing = true;
 

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

@@ -1148,7 +1148,7 @@ public class TreeView<T> : View, ITreeView where T : class
     public event EventHandler<ObjectActivatedEventArgs<T>> ObjectActivated;
 
     ///<inheritdoc/>
-    protected override bool OnDrawingContent (DrawContext? context)
+    protected override bool OnDrawingContent (DrawContext context)
     {
         if (roots is null)
         {

+ 3 - 0
Terminal.sln.DotSettings

@@ -414,6 +414,9 @@
 	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
 	<s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">5</s:Int64>
 	<s:Boolean x:Key="/Default/GrammarAndSpelling/GrammarChecking/Exceptions/=Attribute_0020attribute/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=conhost/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Decscusr/@EntryIndexedValue">True</s:Boolean>
+	
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Gainsboro/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Gonek/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Guppie/@EntryIndexedValue">True</s:Boolean>

+ 1 - 1
Tests/UnitTests/Application/SynchronizatonContextTests.cs

@@ -26,7 +26,7 @@ public class SyncrhonizationContextTests
     [InlineData ("fake")]
     [InlineData ("windows")]
     [InlineData ("dotnet")]
-   // [InlineData ("unix")]
+    [InlineData ("unix")]
     public void SynchronizationContext_Post (string driverName = null)
     {
         lock (_lockPost)

+ 28 - 0
Tests/UnitTests/Configuration/SourcesManagerTests.cs

@@ -44,4 +44,32 @@ public class SourcesManagerTests
             ConfigurationManager.ThrowOnJsonErrors = false;
         }
     }
+
+
+    // NOTE: This test causes the static CM._jsonErrors to be modified; can't use in a parallel test
+    [Fact]
+    public void Load_WithInvalidJson_AddsJsonError ()
+    {
+        // Arrange
+        var sourcesManager = new SourcesManager ();
+
+        var settingsScope = new SettingsScope ();
+        var invalidJson = "{ invalid json }";
+        var stream = new MemoryStream ();
+        var writer = new StreamWriter (stream);
+        writer.Write (invalidJson);
+        writer.Flush ();
+        stream.Position = 0;
+
+        var source = "Load_WithInvalidJson_AddsJsonError";
+        var location = ConfigLocations.AppCurrent;
+
+        // Act
+        bool result = sourcesManager.Load (settingsScope, stream, source, location);
+
+        // Assert
+        Assert.False (result);
+
+        // Assuming AddJsonError logs errors, verify the error was logged (mock or inspect logs if possible).
+    }
 }

+ 1 - 1
Tests/UnitTests/FakeDriverBase.cs

@@ -4,7 +4,7 @@ namespace UnitTests;
 ///     Enables tests to create a FakeDriver for testing purposes.
 /// </summary>
 [Collection ("Global Test Setup")]
-public abstract class FakeDriverBase /*: IDisposable*/
+public abstract class FakeDriverBase/* : IDisposable*/
 {
     /// <summary>
     ///     Creates a new FakeDriver instance with the specified buffer size.

+ 1 - 1
Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs

@@ -164,7 +164,7 @@ public class NestedRunTimeoutTests (ITestOutputHelper output)
         var requestStopTimeoutFired = false;
 
         app.AddTimeout (
-                        TimeSpan.FromMilliseconds (5000),
+                        TimeSpan.FromMilliseconds (10000),
                         () =>
                         {
                             output.WriteLine ("SAFETY: RequestStop Timeout fired - test took too long!");

+ 0 - 25
Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs

@@ -55,31 +55,6 @@ public class SourcesManagerTests
         Assert.Contains (source, sourcesManager.Sources.Values);
     }
 
-    [Fact]
-    public void Load_WithInvalidJson_AddsJsonError ()
-    {
-        // Arrange
-        var sourcesManager = new SourcesManager ();
-
-        var settingsScope = new SettingsScope ();
-        var invalidJson = "{ invalid json }";
-        var stream = new MemoryStream ();
-        var writer = new StreamWriter (stream);
-        writer.Write (invalidJson);
-        writer.Flush ();
-        stream.Position = 0;
-
-        var source = "Load_WithInvalidJson_AddsJsonError";
-        var location = ConfigLocations.AppCurrent;
-
-        // Act
-        bool result = sourcesManager.Load (settingsScope, stream, source, location);
-
-        // Assert
-        Assert.False (result);
-
-        // Assuming AddJsonError logs errors, verify the error was logged (mock or inspect logs if possible).
-    }
 
     #endregion
 

+ 2 - 2
Tests/UnitTestsParallelizable/Drawing/AttributeTests.cs

@@ -141,7 +141,7 @@ public class AttributeTests : FakeDriverBase
         Assert.Equal (bg, attr.Foreground);
         Assert.Equal (bg, attr.Background);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -273,7 +273,7 @@ public class AttributeTests : FakeDriverBase
         Assert.Equal (fg, attr.Foreground);
         Assert.Equal (bg, attr.Background);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]

+ 2 - 1
Tests/UnitTestsParallelizable/Drawing/CellTests.cs

@@ -23,6 +23,7 @@ public class CellTests
     [InlineData ("æ", new uint [] { 0x00E6 })]
     [InlineData ("a︠", new uint [] { 0x0061, 0xFE20 })]
     [InlineData ("e︡", new uint [] { 0x0065, 0xFE21 })]
+    [InlineData ("🇵🇹", new uint [] { 0x1F1F5, 0x1F1F9 })]
     public void Runes_From_Grapheme (string? grapheme, uint [] expected)
     {
         // Arrange
@@ -88,6 +89,7 @@ public class CellTests
         yield return ["👨‍👩‍👦‍👦", null, "[\"👨‍👩‍👦‍👦\":]"];
         yield return ["A", new Attribute (Color.Red) { Style = TextStyle.Blink }, "[\"A\":[Red,Red,Blink]]"];
         yield return ["\U0001F469\u200D\u2764\uFE0F\u200D\U0001F48B\u200D\U0001F468", null, "[\"👩‍❤️‍💋‍👨\":]"];
+        yield return ["\uD83C\uDDF5\uD83C\uDDF9", null, "[\"🇵🇹\":]"];
     }
 
     [Fact]
@@ -176,5 +178,4 @@ public class CellTests
         // And if your Grapheme setter normalizes, assignment should throw as well
         Assert.Throws<ArgumentException> (() => new Cell () { Grapheme = s });
     }
-
 }

+ 115 - 5
Tests/UnitTestsParallelizable/Drawing/SixelEncoderTests.cs → Tests/UnitTestsParallelizable/Drawing/Sixel/SixelEncoderTests.cs

@@ -37,7 +37,7 @@ public class SixelEncoderTests
         {
             for (var y = 0; y < 12; y++)
             {
-                pixels [x, y] = new (255, 0, 0);
+                pixels [x, y] = new (255, 0);
             }
         }
 
@@ -48,7 +48,7 @@ public class SixelEncoderTests
         // Since image is only red we should only have 1 color definition
         Color c1 = Assert.Single (encoder.Quantizer.Palette);
 
-        Assert.Equal (new (255, 0, 0), c1);
+        Assert.Equal (new (255, 0), c1);
         Assert.Equal (expected, result);
     }
 
@@ -124,7 +124,7 @@ public class SixelEncoderTests
                 // Create a 3x3 checkerboard by alternating the color based on pixel coordinates
                 if ((x / 3 + y / 3) % 2 == 0)
                 {
-                    pixels [x, y] = new (0, 0, 0); // Black
+                    pixels [x, y] = new (0, 0); // Black
                 }
                 else
                 {
@@ -142,7 +142,7 @@ public class SixelEncoderTests
         Color black = encoder.Quantizer.Palette.ElementAt (0);
         Color white = encoder.Quantizer.Palette.ElementAt (1);
 
-        Assert.Equal (new (0, 0, 0), black);
+        Assert.Equal (new (0, 0), black);
         Assert.Equal (new (255, 255, 255), white);
 
         // Compare the generated SIXEL string with the expected one
@@ -213,7 +213,7 @@ public class SixelEncoderTests
                 // For simplicity, we'll make every other row transparent
                 if (y % 2 == 0)
                 {
-                    pixels [x, y] = new (255, 0, 0); // Red pixel
+                    pixels [x, y] = new (255, 0); // Red pixel
                 }
                 else
                 {
@@ -229,4 +229,114 @@ public class SixelEncoderTests
         // Assert: Expect the result to match the expected sixel output
         Assert.Equal (expected, result);
     }
+
+    [Fact]
+    public void EncodeSixel_OnePixel_ReturnsExpectedSequence ()
+    {
+        // Arrange: 1x1 red pixel
+        Color [,] pixels = new Color [1, 1];
+        pixels [0, 0] = new (255, 0);
+
+        var encoder = new SixelEncoder ();
+
+        // Act
+        string result = encoder.EncodeSixel (pixels);
+
+        // Build expected output
+        string expected = "\u001bP" // start
+                          + "0;0;0"
+                          + "q"
+                          + "\"1;1;1;1" // no-scaling + width;height
+                          + "#0;2;100;0;0" // palette
+                          + "#0@$" // single column, single row -> code 1 -> char(1+63) = '@', then $ terminator
+                          + "\u001b\\";
+
+        Assert.Equal (expected, result);
+    }
+
+    [Fact]
+    public void EncodeSixel_WidthRepeat_UsesSequenceRepeatSyntax ()
+    {
+        // Arrange: width 5, height 1, all same color so sequence repeat > 3
+        int width = 5;
+        Color [,] pixels = new Color [width, 1];
+
+        for (var x = 0; x < width; x++)
+        {
+            pixels [x, 0] = new (255, 0);
+        }
+
+        var encoder = new SixelEncoder ();
+
+        // Act
+        string result = encoder.EncodeSixel (pixels);
+
+        // Assert contains the repeat sequence for 5 identical columns: "!5"
+        Assert.Contains ("!5", result);
+
+        // And final payload for the color should include the palette definition
+        Assert.Contains ("#0;2;100;0;0", result);
+    }
+
+    [Fact]
+    public void EncodeSixel_HeightNotMultipleOfSix_IncludesBandSeparator ()
+    {
+        // Arrange: width 2, height 7 to force two bands (6 rows + 1 row)
+        Color [,] pixels = new Color [2, 7];
+
+        for (var x = 0; x < 2; x++)
+        {
+            for (var y = 0; y < 7; y++)
+            {
+                pixels [x, y] = new (0, 0, 255);
+            }
+        }
+
+        var encoder = new SixelEncoder ();
+
+        // Act
+        string result = encoder.EncodeSixel (pixels);
+
+        // Assert: there must be a band separator '-' between the bands
+        Assert.Contains ("-", result);
+    }
+
+    [Fact]
+    public void EncodeSixel_AnyTransparentPixel_SetsTransparencyFlagInHeader ()
+    {
+        // Arrange: 2x2 with one fully transparent pixel
+        Color [,] pixels = new Color [2, 2];
+        pixels [0, 0] = new (255, 0);
+        pixels [0, 1] = new (0, 0, 0, 0); // fully transparent
+        pixels [1, 0] = new (0, 255);
+        pixels [1, 1] = new (0, 0, 255);
+
+        var encoder = new SixelEncoder ();
+
+        // Act
+        string result = encoder.EncodeSixel (pixels);
+
+        // defaultRatios should be "0;1;0" when any pixel has alpha == 0
+        Assert.Contains ("\u001bP0;1;0q", result);
+    }
+
+    [Fact]
+    public void EncodeSixel_MaxPaletteHonored_WhenReducedMaxColors ()
+    {
+        // Arrange: create three distinct colors but restrict max palette to 2
+        Color [,] pixels = new Color [3, 1];
+        pixels [0, 0] = new (255, 0);
+        pixels [1, 0] = new (0, 255);
+        pixels [2, 0] = new (0, 0, 255);
+
+        var encoder = new SixelEncoder ();
+        encoder.Quantizer.MaxColors = 2;
+
+        // Act
+        string result = encoder.EncodeSixel (pixels);
+
+        // Assert: palette count must respect MaxColors (<= 2) and encoding must not throw
+        Assert.True (encoder.Quantizer.Palette.Count <= 2);
+        Assert.False (string.IsNullOrEmpty (result));
+    }
 }

+ 228 - 0
Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportDetectorTests.cs

@@ -0,0 +1,228 @@
+#nullable enable
+using Moq;
+
+namespace DrawingTests;
+
+public class SixelSupportDetectorTests
+{
+    [Fact]
+    public void Detect_SetsSupportedAndResolution_WhenDeviceAttributesContain4_AndResolutionResponds()
+    {
+        // Arrange
+        Mock<IDriver> driverMock = new (MockBehavior.Strict);
+
+        // Setup IsLegacyConsole - false means modern terminal with ANSI support
+        driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
+
+        // Expect QueueAnsiRequest to be called at least twice:
+        // 1) CSI_SendDeviceAttributes (terminator "c")
+        // 2) CSI_RequestSixelResolution (terminator "t")
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                      {
+                                                          // Respond to the SendDeviceAttributes request with a value that indicates support (contains "4")
+                                                          if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                          {
+                                                              req.ResponseReceived.Invoke ("1;4;7c");
+                                                          }
+                                                          else if (req.Request == EscSeqUtils.CSI_RequestSixelResolution.Request)
+                                                          {
+                                                              // Reply with a resolution response matching regex "\[\d+;(\d+);(\d+)t$"
+                                                              // Group 1 -> ry, Group 2 -> rx. The detector constructs resolution as new(rx, ry)
+                                                              req.ResponseReceived.Invoke ("[6;20;10t");
+                                                          }
+                                                          else
+                                                          {
+                                                              // Any other request - call abandoned to avoid hanging
+                                                              req.Abandoned?.Invoke ();
+                                                          }
+                                                      })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.NotNull (final);
+        Assert.True (final.IsSupported); // Response contained "4"
+        // Resolution should be constructed as new(rx, ry) where rx=10, ry=20 from our reply "[6;20;10t"
+        Assert.Equal (10, final.Resolution.Width);
+        Assert.Equal (20, final.Resolution.Height);
+
+        driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (2));
+    }
+
+    [Fact]
+    public void Detect_DoesNotSetSupported_WhenDeviceAttributesDoNotContain4()
+    {
+        // Arrange
+        var driverMock = new Mock<IDriver>(MockBehavior.Strict);
+
+        // Setup IsLegacyConsole - false means modern terminal with ANSI support
+        driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
+
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                      {
+                                                          // SendDeviceAttributes -> reply without "4"
+                                                          if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                          {
+                                                              req.ResponseReceived.Invoke ("1;0;7c");
+                                                          }
+                                                          else
+                                                          {
+                                                              // Any other requests should be abandoned
+                                                              req.Abandoned?.Invoke ();
+                                                          }
+                                                      })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.NotNull (final);
+        Assert.False (final.IsSupported);
+        // On no support, the direct resolution request path isn't followed so resolution remains the default
+        Assert.Equal (10, final.Resolution.Width);
+        Assert.Equal (20, final.Resolution.Height);
+
+        driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (1));
+    }
+
+    [Theory]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Detect_SetsSupported_WhenIsLegacyConsoleIsFalseAndResponseContain4OrFalse (bool isLegacyConsole)
+    {
+        // Arrange
+        var responseReceived = false;
+        var output = new FakeOutput ();
+        output.IsLegacyConsole = isLegacyConsole;
+
+        Mock<DriverImpl> driverMock = new (
+                                           MockBehavior.Strict,
+                                           new FakeInputProcessor (null!),
+                                           new OutputBufferImpl (),
+                                           output,
+                                           new AnsiRequestScheduler (new AnsiResponseParser ()),
+                                           new SizeMonitorImpl (output)
+                                          );
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                        {
+                                                            if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                            {
+                                                                responseReceived = true;
+
+                                                                if (!isLegacyConsole)
+                                                                {
+                                                                    // Response does contain "4" (so DAR indicates has sixel)
+                                                                    req.ResponseReceived.Invoke ("?1;4;0;7c");
+                                                                }
+                                                                else
+                                                                {
+                                                                    // Response does NOT contain "4" (so DAR indicates no sixel)
+                                                                    req.ResponseReceived.Invoke ("");
+                                                                }
+                                                            }
+                                                            else
+                                                            {
+                                                                // Abandon all requests
+                                                                req.Abandoned?.Invoke ();
+                                                            }
+                                                        })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.Equal (isLegacyConsole, driverMock.Object.IsLegacyConsole);
+        Assert.NotNull (final);
+
+        if (!isLegacyConsole)
+        {
+            Assert.True (final.IsSupported);
+        }
+        else
+        {
+            // Not a real VT, so should be supported
+            Assert.False (final.IsSupported);
+        }
+        Assert.True (responseReceived);
+        driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (1));
+    }
+
+    [Theory]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Detect_SetsSupported_WhenIsLegacyConsoleIsTrueOrFalse_With_Response (bool isLegacyConsole)
+    {
+        // Arrange
+        var responseReceived = false;
+        var output = new FakeOutput ();
+        output.IsLegacyConsole = isLegacyConsole;
+
+        Mock<DriverImpl> driverMock = new (
+                                           MockBehavior.Strict,
+                                           new FakeInputProcessor (null!),
+                                           new OutputBufferImpl (),
+                                           output,
+                                           new AnsiRequestScheduler (new AnsiResponseParser ()),
+                                           new SizeMonitorImpl (output)
+                                          );
+
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                        {
+                                                            if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                            {
+                                                                responseReceived = true;
+
+                                                                // Respond to the SendDeviceAttributes request with a value that indicates support (contains "4")
+                                                                // Respond to the SendDeviceAttributes request with an empty value that indicates non-support
+                                                                req.ResponseReceived.Invoke (!driverMock.Object.IsLegacyConsole ? "1;4;7c" : "");
+                                                            }
+
+                                                            // Abandon all requests
+                                                            req.Abandoned?.Invoke ();
+                                                        })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.Equal (isLegacyConsole, driverMock.Object.IsLegacyConsole);
+        Assert.NotNull (final);
+
+        if (!isLegacyConsole)
+        {
+            Assert.True (final.IsSupported);
+            Assert.True (final.SupportsTransparency);
+        }
+        else
+        {
+            // Not a real VT, so shouldn't be supported
+            Assert.False (final.IsSupported);
+            Assert.False (final.SupportsTransparency);
+        }
+
+        Assert.True (responseReceived);
+    }
+}

+ 62 - 0
Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportResultTests.cs

@@ -0,0 +1,62 @@
+#nullable enable
+
+namespace DrawingTests;
+
+public class SixelSupportResultTests
+{
+    [Fact]
+    public void Defaults_AreCorrect ()
+    {
+        // Arrange & Act
+        var result = new SixelSupportResult ();
+
+        // Assert
+        Assert.False (result.IsSupported);
+        Assert.Equal (10, result.Resolution.Width);
+        Assert.Equal (20, result.Resolution.Height);
+        Assert.Equal (256, result.MaxPaletteColors);
+        Assert.False (result.SupportsTransparency);
+    }
+
+    [Fact]
+    public void Properties_CanBeModified ()
+    {
+        // Arrange
+        var result = new SixelSupportResult ();
+
+        // Act
+        result.IsSupported = true;
+        result.Resolution = new Size (24, 48);
+        result.MaxPaletteColors = 16;
+        result.SupportsTransparency = true;
+
+        // Assert
+        Assert.True (result.IsSupported);
+        Assert.Equal (24, result.Resolution.Width);
+        Assert.Equal (48, result.Resolution.Height);
+        Assert.Equal (16, result.MaxPaletteColors);
+        Assert.True (result.SupportsTransparency);
+    }
+
+    [Fact]
+    public void Resolution_IsValueType_CopyDoesNotAffectOriginal ()
+    {
+        // Arrange
+        var result = new SixelSupportResult ();
+        Size original = result.Resolution;
+
+        // Act
+        // Mutate a local copy and ensure original remains unchanged
+        Size copy = original;
+        copy.Width = 123;
+        copy.Height = 456;
+
+        // Assert
+        Assert.Equal (10, result.Resolution.Width);
+        Assert.Equal (20, result.Resolution.Height);
+        Assert.Equal (10, original.Width);
+        Assert.Equal (20, original.Height);
+        Assert.Equal (123, copy.Width);
+        Assert.Equal (456, copy.Height);
+    }
+}

+ 252 - 0
Tests/UnitTestsParallelizable/Drawing/Sixel/SixelToRenderTests.cs

@@ -0,0 +1,252 @@
+#nullable enable
+using Moq;
+
+namespace DrawingTests;
+
+public class SixelToRenderTests
+{
+    [Fact]
+    public void SixelToRender_Properties_AreGettableAndSettable ()
+    {
+        SixelToRender s = new SixelToRender
+        {
+            SixelData = "SIXEL-DATA",
+            ScreenPosition = new (3, 5)
+        };
+
+        Assert.Equal ("SIXEL-DATA", s.SixelData);
+        Assert.Equal (3, s.ScreenPosition.X);
+        Assert.Equal (5, s.ScreenPosition.Y);
+    }
+
+    [Fact]
+    public void SixelSupportResult_DefaultValues_AreExpected ()
+    {
+        var r = new SixelSupportResult ();
+
+        Assert.False (r.IsSupported);
+        Assert.Equal (10, r.Resolution.Width);
+        Assert.Equal (20, r.Resolution.Height);
+        Assert.Equal (256, r.MaxPaletteColors);
+        Assert.False (r.SupportsTransparency);
+    }
+
+    [Fact]
+    public void Detect_WhenDeviceAttributesIndicateSupport_GetsResolutionDirectly ()
+    {
+        // Arrange
+        Mock<IDriver> driverMock = new (MockBehavior.Strict);
+
+        // Setup IsLegacyConsole - false means modern terminal with ANSI support
+        driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
+
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                      {
+                                                          if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                          {
+                                                              // Response contains "4" -> indicates sixel support
+                                                              req.ResponseReceived.Invoke ("?1;4;7c");
+                                                          }
+                                                          else if (req.Request == EscSeqUtils.CSI_RequestSixelResolution.Request)
+                                                          {
+                                                              // Return resolution: "[6;20;10t" (group1=20 -> ry, group2=10 -> rx)
+                                                              req.ResponseReceived.Invoke ("[6;20;10t");
+                                                          }
+                                                          else
+                                                          {
+                                                              req.Abandoned?.Invoke ();
+                                                          }
+                                                      })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.NotNull (final);
+        Assert.True (final.IsSupported);
+        Assert.Equal (10, final.Resolution.Width);
+        Assert.Equal (20, final.Resolution.Height);
+
+        driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (2));
+    }
+
+    [Fact]
+    public void Detect_WhenDirectResolutionFails_ComputesResolutionFromWindowSizes ()
+    {
+        // Arrange
+        Mock<IDriver> driverMock = new (MockBehavior.Strict);
+
+        // Setup IsLegacyConsole - false means modern terminal with ANSI support
+        driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
+
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                      {
+                                                          switch (req.Request)
+                                                          {
+                                                              case var r when r == EscSeqUtils.CSI_SendDeviceAttributes.Request:
+                                                                  // Indicate sixel support so flow continues to try resolution
+                                                                  req.ResponseReceived.Invoke ("?1;4;7c");
+                                                                  break;
+
+                                                              case var r when r == EscSeqUtils.CSI_RequestSixelResolution.Request:
+                                                                  // Simulate failure to return resolution directly
+                                                                  req.Abandoned?.Invoke ();
+                                                                  break;
+
+                                                              case var r when r == EscSeqUtils.CSI_RequestWindowSizeInPixels.Request:
+                                                                  // Pixel dimensions reply: [4;600;1200t  -> pixelHeight=600; pixelWidth=1200
+                                                                  req.ResponseReceived.Invoke ("[4;600;1200t");
+                                                                  break;
+
+                                                              case var r when r == EscSeqUtils.CSI_ReportWindowSizeInChars.Request:
+                                                                  // Character dimensions reply: [8;30;120t -> charHeight=30; charWidth=120
+                                                                  req.ResponseReceived.Invoke ("[8;30;120t");
+                                                                  break;
+
+                                                              default:
+                                                                  req.Abandoned?.Invoke ();
+                                                                  break;
+                                                          }
+                                                      })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.NotNull (final);
+        Assert.True (final.IsSupported);
+        // Expect cell width = round(1200 / 120) = 10, cell height = round(600 / 30) = 20
+        Assert.Equal (10, final.Resolution.Width);
+        Assert.Equal (20, final.Resolution.Height);
+
+        driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeast (3));
+    }
+
+    [Fact]
+    public void Detect_WhenDeviceAttributesDoNotIndicateSupport_ReturnsNotSupported ()
+    {
+        // Arrange
+        Mock<IDriver> driverMock = new (MockBehavior.Strict);
+
+        // Setup IsLegacyConsole - false means modern terminal with ANSI support
+        driverMock.Setup (d => d.IsLegacyConsole).Returns (false);
+
+        driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                  .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                      {
+                                                          if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                          {
+                                                              // Response does NOT contain "4"
+                                                              req.ResponseReceived.Invoke ("?1;0;7c");
+                                                          }
+                                                          else
+                                                          {
+                                                              req.Abandoned?.Invoke ();
+                                                          }
+                                                      })
+                  .Verifiable ();
+
+        var detector = new SixelSupportDetector (driverMock.Object);
+
+        SixelSupportResult? final = null;
+
+        // Act
+        detector.Detect (r => final = r);
+
+        // Assert
+        Assert.NotNull (final);
+        Assert.False (final.IsSupported);
+
+        driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeastOnce ());
+    }
+
+    [Theory]
+    [InlineData ("", true, false, false, false)]
+    [InlineData ("", true, true, false, false)]
+    [InlineData ("?1;0;7c", false, false, false, true)]
+    [InlineData ("?1;0;7c", false, true, false, true)]
+    [InlineData ("?1;4;0;7c", false, false, true, true)]
+    [InlineData ("?1;4;0;7c", false, true, true, true)]
+    public void Detect_WhenXtermEnvironmentIndicatesTransparency_SupportsTransparencyEvenIfDAReturnsNo4 (
+        string darResponse,
+        bool isLegacyConsole,
+        bool isXtermWithTransparency,
+        bool expectedIsSupported,
+        bool expectedSupportsTransparency
+    )
+    {
+        // Arrange - set XTERM_VERSION env var to indicate real xterm with transparency
+        string? prev = Environment.GetEnvironmentVariable ("XTERM_VERSION");
+
+        try
+        {
+            var output = new FakeOutput ();
+            output.IsLegacyConsole = isLegacyConsole;
+
+            Mock<DriverImpl> driverMock = new (
+                                               MockBehavior.Strict,
+                                               new FakeInputProcessor (null!),
+                                               new OutputBufferImpl (),
+                                               output,
+                                               new AnsiRequestScheduler (new AnsiResponseParser ()),
+                                               new SizeMonitorImpl (output)
+                                              );
+
+            driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()))
+                      .Callback<AnsiEscapeSequenceRequest> (req =>
+                                                          {
+                                                              if (req.Request == EscSeqUtils.CSI_SendDeviceAttributes.Request)
+                                                              {
+                                                                  // Response does NOT contain "4" (so DAR indicates no sixel)
+                                                                  req.ResponseReceived.Invoke (darResponse);
+                                                              }
+                                                              else
+                                                              {
+                                                                  req.Abandoned?.Invoke ();
+                                                              }
+                                                          })
+                      .Verifiable ();
+
+            var detector = new SixelSupportDetector (driverMock.Object);
+
+            SixelSupportResult? final = null;
+
+            if (isXtermWithTransparency)
+            {
+                Environment.SetEnvironmentVariable ("XTERM_VERSION", "370");
+            }
+
+            // Act
+            detector.Detect (r => final = r);
+
+            // Assert
+            Assert.NotNull (final);
+            Assert.Equal (isLegacyConsole, driverMock.Object.IsLegacyConsole);
+
+            // DAR did not indicate sixel support
+            Assert.Equal (expectedIsSupported, final.IsSupported);
+
+            // But because XTERM_VERSION >= 370 we expect SupportsTransparency to have been initially true and remain true
+            Assert.Equal (expectedSupportsTransparency, final.SupportsTransparency);
+
+            driverMock.Verify (d => d.QueueAnsiRequest (It.IsAny<AnsiEscapeSequenceRequest> ()), Times.AtLeastOnce ());
+        }
+        finally
+        {
+            // Restore environment
+            Environment.SetEnvironmentVariable ("XTERM_VERSION", prev);
+        }
+    }
+}

+ 5 - 5
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs

@@ -21,7 +21,7 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         driver.AddRune (new Rune ('a'));
         Assert.Equal ("a", driver.Contents? [0, 0].Grapheme);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -73,7 +73,7 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         //		Application.Refresh ();
         //		DriverAsserts.AssertDriverContentsWithFrameAre (@"
         //ắ", output);
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -92,7 +92,7 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
             }
         }
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -133,7 +133,7 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
             }
         }
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -177,6 +177,6 @@ public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
         //	}
         //}
 
-        driver.End ();
+        driver.Dispose ();
     }
 }

+ 3 - 3
Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs

@@ -17,7 +17,7 @@ public class ContentsTests (ITestOutputHelper output) : FakeDriverBase
         driver.AddStr ("\u0301!"); // acute accent + exclamation mark
         DriverAssert.AssertDriverContentsAre (expected, output, driver);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -66,7 +66,7 @@ public class ContentsTests (ITestOutputHelper output) : FakeDriverBase
         driver.AddStr (combined);
         DriverAssert.AssertDriverContentsAre (expected, output, driver);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Fact]
@@ -96,7 +96,7 @@ public class ContentsTests (ITestOutputHelper output) : FakeDriverBase
         driver.Move (500, 500);
         Assert.Equal (500, driver.Col);
         Assert.Equal (500, driver.Row);
-        driver.End ();
+        driver.Dispose ();
     }
 
     // TODO: Add these unit tests

+ 1 - 1
Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs

@@ -14,6 +14,6 @@ public class DriverColorTests : FakeDriverBase
         driver.Force16Colors = true;
         Assert.True (driver.Force16Colors);
 
-        driver.End ();
+        driver.Dispose ();
     }
 }

+ 1 - 1
Tests/UnitTestsParallelizable/Drivers/DriverTests.cs

@@ -47,7 +47,7 @@ public class DriverTests (ITestOutputHelper output) : FakeDriverBase
         Assert.False (driver.IsValidLocation (text, driver.Cols, driver.Rows - 1));
         Assert.False (driver.IsValidLocation (text, driver.Cols, driver.Rows));
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Theory]

+ 54 - 0
Tests/UnitTestsParallelizable/Drivers/LegacyConsoleTests.cs

@@ -0,0 +1,54 @@
+#nullable enable
+using UnitTests;
+
+namespace DriverTests;
+
+public class LegacyConsoleTests : FakeDriverBase
+{
+    [Fact]
+    public void IsLegacyConsole_Returns_Expected_Values ()
+    {
+        IDriver? driver = CreateFakeDriver ();
+        Assert.False (driver.IsLegacyConsole);
+    }
+
+    [Fact]
+    public void IsLegacyConsole_False_Force16Colors_True_False ()
+    {
+        IDriver? driver = CreateFakeDriver ();
+
+        Assert.False (driver.IsLegacyConsole);
+        Assert.False (driver.Force16Colors);
+
+        driver.Force16Colors = true;
+        Assert.False (driver.IsLegacyConsole);
+        Assert.True (driver.Force16Colors);
+    }
+
+    [Fact]
+    public void IsLegacyConsole_True_Force16Colors_Is_Always_True ()
+    {
+        IDriver? driver = CreateFakeDriver ();
+
+        Assert.False (driver.IsLegacyConsole);
+        Assert.False (driver.Force16Colors);
+
+        driver.IsLegacyConsole = true;
+        Assert.True (driver.Force16Colors);
+
+        driver.Force16Colors = false;
+        Assert.True (driver.Force16Colors);
+    }
+
+    [Fact]
+    public void IsLegacyConsole_True_False_SupportsTrueColor_Is_Always_True_False ()
+    {
+        IDriver? driver = CreateFakeDriver ();
+
+        Assert.False (driver.IsLegacyConsole);
+        Assert.True (driver.SupportsTrueColor);
+
+        driver.IsLegacyConsole = true;
+        Assert.False (driver.SupportsTrueColor);
+    }
+}

+ 218 - 0
Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs

@@ -0,0 +1,218 @@
+#nullable enable
+
+namespace DriverTests;
+
+public class OutputBaseTests
+{
+    [Fact]
+    public void ToAnsi_SingleCell_NoAttribute_ReturnsGraphemeAndNewline ()
+    {
+        // Arrange
+        var output = new FakeOutput ();
+        IOutputBuffer buffer = output.LastBuffer!;
+        buffer.SetSize (1, 1);
+
+        // Act
+        buffer.AddStr ("A");
+        string ansi = output.ToAnsi (buffer);
+
+        // Assert: single grapheme plus newline (BuildAnsiForRegion appends a newline per row)
+        Assert.Contains ("A" + Environment.NewLine, ansi);
+    }
+
+    [Theory]
+    [InlineData (true, false)]
+    [InlineData (true, true)]
+    [InlineData (false, false)]
+    [InlineData (false, true)]
+    public void ToAnsi_WithAttribute_AppendsCorrectColorSequence_BasedOnIsLegacyConsole_And_Force16Colors (bool isLegacyConsole, bool force16Colors)
+    {
+        // Arrange
+        var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
+
+        // Create DriverImpl and associate it with the FakeOutput to test Sixel output
+        IDriver driver = new DriverImpl (
+                                 new FakeInputProcessor (null!),
+                                 new OutputBufferImpl (),
+                                 output,
+                                 new (new AnsiResponseParser ()),
+                                 new SizeMonitorImpl (output));
+
+        driver.Force16Colors = force16Colors;
+
+        IOutputBuffer buffer = output.LastBuffer!;
+        buffer.SetSize (1, 1);
+
+        // Use a known RGB color and attribute
+        var fg = new Color (1, 2, 3);
+        var bg = new Color (4, 5, 6);
+        buffer.CurrentAttribute = new Attribute (fg, bg);
+        buffer.AddStr ("X");
+
+        // Act
+        string ansi = output.ToAnsi (buffer);
+
+        // Assert: when true color expected, we should see the RGB CSI; otherwise we should see the 16-color CSI
+        if (!isLegacyConsole && !force16Colors)
+        {
+            Assert.Contains ("\u001b[38;2;1;2;3m", ansi);
+        }
+        else if (!isLegacyConsole && force16Colors)
+        {
+            var expected16 = EscSeqUtils.CSI_SetForegroundColor (fg.GetAnsiColorCode ());
+            Assert.Contains (expected16, ansi);
+        }
+        else
+        {
+            var expected16 = (ConsoleColor)fg.GetClosestNamedColor16 ();
+            Assert.Equal (ConsoleColor.Black, expected16);
+            Assert.DoesNotContain ('\u001b', ansi);
+        }
+
+        // Grapheme and newline should always be present
+        Assert.Contains ("X" + Environment.NewLine, ansi);
+    }
+
+    [Fact]
+    public void Write_WritesDirtyCellsAndClearsDirtyFlags ()
+    {
+        // Arrange
+        var output = new FakeOutput ();
+        IOutputBuffer buffer = output.LastBuffer!;
+        buffer.SetSize (2, 1);
+
+        // Mark two characters as dirty by writing them into the buffer
+        buffer.AddStr ("AB");
+
+        // Sanity: ensure cells are dirty before calling Write
+        Assert.True (buffer.Contents! [0, 0].IsDirty);
+        Assert.True (buffer.Contents! [0, 1].IsDirty);
+
+        // Act
+        output.Write (buffer); // calls OutputBase.Write via FakeOutput
+
+        // Assert: content was written to the fake output and dirty flags cleared
+        Assert.Contains ("AB", output.Output);
+        Assert.False (buffer.Contents! [0, 0].IsDirty);
+        Assert.False (buffer.Contents! [0, 1].IsDirty);
+    }
+
+    [Theory]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Write_Virtual_Or_NonVirtual_Uses_WriteToConsole_And_Clears_Dirty_Flags (bool isLegacyConsole)
+    {
+        // Arrange
+        // FakeOutput exposes this because it's in test scope
+        var output = new FakeOutput { IsLegacyConsole = isLegacyConsole };
+        IOutputBuffer buffer = output.LastBuffer!;
+        buffer.SetSize (3, 1);
+
+        // Write 'A' at col 0 and 'C' at col 2; leave col 1 untouched (not dirty)
+        buffer.Move (0, 0);
+        buffer.AddStr ("A");
+        buffer.Move (2, 0);
+        buffer.AddStr ("C");
+
+        // Confirm some dirtiness before to write
+        Assert.True (buffer.Contents! [0, 0].IsDirty);
+        Assert.True (buffer.Contents! [0, 2].IsDirty);
+
+        // Act
+        output.Write (buffer);
+
+        // Assert: both characters were written (use Contains to avoid CI side effects)
+        Assert.Contains ("A", output.Output);
+        Assert.Contains ("C", output.Output);
+
+        // Dirty flags cleared for the written cells
+        Assert.False (buffer.Contents! [0, 0].IsDirty);
+        Assert.False (buffer.Contents! [0, 2].IsDirty);
+
+        // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
+        Assert.Equal (new Point (0, 0), output.GetCursorPosition ());
+
+        // Now write 'X' at col 0 to verify subsequent writes also work
+        buffer.Move (0, 0);
+        buffer.AddStr ("X");
+
+        // Confirm dirtiness state before to write
+        Assert.True (buffer.Contents! [0, 0].IsDirty);
+        Assert.False (buffer.Contents! [0, 2].IsDirty);
+
+        output.Write (buffer);
+
+        // Assert: both characters were written (use Contains to avoid CI side effects)
+        Assert.Contains ("A", output.Output);
+        Assert.Contains ("C", output.Output);
+
+        // Dirty flags cleared for the written cells
+        Assert.False (buffer.Contents! [0, 0].IsDirty);
+        Assert.False (buffer.Contents! [0, 2].IsDirty);
+
+        // Verify SetCursorPositionImpl was invoked by WriteToConsole (cursor set to a written column)
+        Assert.Equal (new Point (0, 0), output.GetCursorPosition ());
+    }
+
+    [Theory]
+    [InlineData (true)]
+    [InlineData (false)]
+    public void Write_EmitsSixelDataAndPositionsCursor (bool isLegacyConsole)
+    {
+        // Arrange
+        var output = new FakeOutput ();
+        IOutputBuffer buffer = output.LastBuffer!;
+        buffer.SetSize (1, 1);
+
+        // Ensure the buffer has some content so Write traverses rows
+        buffer.AddStr (".");
+
+        // Create a Sixel to render
+        var s = new SixelToRender
+        {
+            SixelData = "SIXEL-DATA",
+            ScreenPosition = new Point (4, 2)
+        };
+
+        // Create DriverImpl and associate it with the FakeOutput to test Sixel output
+        IDriver driver = new DriverImpl (
+                                 new FakeInputProcessor (null!),
+                                 new OutputBufferImpl (),
+                                 output,
+                                 new (new AnsiResponseParser ()),
+                                 new SizeMonitorImpl (output));
+
+        // Add the Sixel to the driver
+        driver.GetSixels ().Enqueue (s);
+
+        // FakeOutput exposes this because it's in test scope
+        output.IsLegacyConsole = isLegacyConsole;
+
+        // Act
+        output.Write (buffer);
+
+        if (!isLegacyConsole)
+        {
+            // Assert: Sixel data was emitted (use Contains to avoid equality/side-effects)
+            Assert.Contains ("SIXEL-DATA", output.Output);
+
+            // Cursor was moved to Sixel position
+            Assert.Equal (s.ScreenPosition, output.GetCursorPosition ());
+        }
+        else
+        {
+            // Assert: Sixel data was NOT emitted
+            Assert.DoesNotContain ("SIXEL-DATA", output.Output);
+
+            // Cursor was NOT moved to Sixel position
+            Assert.NotEqual (s.ScreenPosition, output.GetCursorPosition ());
+        }
+
+        IApplication app = Application.Create ();
+        app.Driver = driver;
+
+        Assert.Equal (driver.GetSixels (), app.Driver.GetSixels ());
+
+        app.Dispose ();
+    }
+}

+ 20 - 20
Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs

@@ -34,9 +34,9 @@ public class ToAnsiTests : FakeDriverBase
         // Should contain the text
         Assert.Contains ("Hello", ansi);
         Assert.Contains ("World", ansi);
-        
+
         // Should have proper structure with newlines
-        string[] lines = ansi.Split (['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
+        string [] lines = ansi.Split (['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
         Assert.Equal (3, lines.Length);
     }
 
@@ -73,7 +73,7 @@ public class ToAnsiTests : FakeDriverBase
     public void ToAnsi_With_Background_Colors (bool force16Colors, string expected)
     {
         IDriver driver = CreateFakeDriver (10, 2);
-        Application.Force16Colors = force16Colors;
+        driver.Force16Colors = force16Colors;
 
         // Set background color
         driver.CurrentAttribute = new (Color.White, Color.Red);
@@ -204,13 +204,13 @@ public class ToAnsiTests : FakeDriverBase
         Assert.Equal (50, ansi.Count (c => c == '\n'));
     }
 
-    [Fact (Skip = "Use Application.")]
+    [Fact]
     public void ToAnsi_RGB_Colors ()
     {
         IDriver driver = CreateFakeDriver (10, 1);
 
         // Use RGB colors (when not forcing 16 colors)
-        Application.Force16Colors = false;
+        driver.Force16Colors = false;
         try
         {
             driver.CurrentAttribute = new Attribute (new Color (255, 0, 0), new Color (0, 255, 0));
@@ -224,17 +224,17 @@ public class ToAnsiTests : FakeDriverBase
         }
         finally
         {
-            Application.Force16Colors = true; // Reset
+            driver.Force16Colors = true; // Reset
         }
     }
 
-    [Fact (Skip = "Use Application.")]
+    [Fact]
     public void ToAnsi_Force16Colors ()
     {
         IDriver driver = CreateFakeDriver (10, 1);
 
         // Force 16 colors
-        Application.Force16Colors = true;
+        driver.Force16Colors = true;
         driver.CurrentAttribute = new Attribute (Color.Red, Color.Blue);
         driver.AddStr ("16Color");
 
@@ -268,15 +268,15 @@ public class ToAnsiTests : FakeDriverBase
         foreach (string colorName in colors)
         {
             Color fg = colorName switch
-                       {
-                           "Red" => Color.Red,
-                           "Green" => Color.Green,
-                           "Blue" => Color.Blue,
-                           "Yellow" => Color.Yellow,
-                           "Magenta" => Color.Magenta,
-                           "Cyan" => Color.Cyan,
-                           _ => Color.White
-                       };
+            {
+                "Red" => Color.Red,
+                "Green" => Color.Green,
+                "Blue" => Color.Blue,
+                "Yellow" => Color.Yellow,
+                "Magenta" => Color.Magenta,
+                "Cyan" => Color.Cyan,
+                _ => Color.White
+            };
 
             driver.CurrentAttribute = new (fg, Color.Black);
             driver.AddStr (colorName);
@@ -343,10 +343,10 @@ public class ToAnsiTests : FakeDriverBase
 
         string ansi = driver.ToAnsi ();
 
-        string[] lines = ansi.Split ('\n');
+        string [] lines = ansi.Split ('\n');
         Assert.Equal (4, lines.Length); // 3 content lines + 1 empty line at end
-        Assert.Contains ("First", lines[0]);
-        Assert.Contains ("Third", lines[2]);
+        Assert.Contains ("First", lines [0]);
+        Assert.Contains ("Third", lines [2]);
     }
 
     [Fact]

+ 3 - 3
Tests/UnitTestsParallelizable/Drivers/Windows/WindowsKeyConverterTests.cs

@@ -269,15 +269,15 @@ public class WindowsKeyConverterTests
     #region ToKey Tests - OEM Keys
 
     [Theory]
-    [InlineData (';', ConsoleKey.Oem1, false, (KeyCode)';')]
+    //[InlineData (';', ConsoleKey.Oem1, false, (KeyCode)';')] // Keyboard layout dependent and shifted key is needed to produce ';' (Pt)
     [InlineData (':', ConsoleKey.Oem1, true, (KeyCode)':')]
-    [InlineData ('/', ConsoleKey.Oem2, false, (KeyCode)'/')]
+    //[InlineData ('/', ConsoleKey.Oem2, false, (KeyCode)'/')] // Keyboard layout dependent and shifted key is needed to produce '/' (Pt)
     [InlineData ('?', ConsoleKey.Oem2, true, (KeyCode)'?')]
     [InlineData (',', ConsoleKey.OemComma, false, (KeyCode)',')]
     [InlineData ('<', ConsoleKey.OemComma, true, (KeyCode)'<')]
     [InlineData ('.', ConsoleKey.OemPeriod, false, (KeyCode)'.')]
     [InlineData ('>', ConsoleKey.OemPeriod, true, (KeyCode)'>')]
-    [InlineData ('=', ConsoleKey.OemPlus, false, (KeyCode)'=')] // Un-shifted OemPlus is '='
+    //[InlineData ('=', ConsoleKey.OemPlus, false, (KeyCode)'=')] // Keyboard layout dependent and shifted key is needed to produce '=' (Pt)
     [InlineData ('+', ConsoleKey.OemPlus, true, (KeyCode)'+')] // Shifted OemPlus is '+'
     [InlineData ('-', ConsoleKey.OemMinus, false, (KeyCode)'-')]
     [InlineData ('_', ConsoleKey.OemMinus, true, (KeyCode)'_')] // Shifted OemMinus is '_'

+ 2 - 0
Tests/UnitTestsParallelizable/Text/StringTests.cs

@@ -77,6 +77,7 @@ public class StringTests
     [InlineData ("ힰ", 0, 1, 0)]    // U+D7B0 ힰ Hangul Jungseong O-Yeo
     [InlineData ("ᄀힰ", 2, 1, 2)]  // ᄀ U+1100 HANGUL CHOSEONG KIYEOK (consonant) with U+D7B0 ힰ Hangul Jungseong O-Yeo
     //[InlineData ("षि", 2, 1, 2)] // U+0937 ष DEVANAGARI LETTER SSA with U+093F ि COMBINING DEVANAGARI VOWEL SIGN I
+    [InlineData ("🇵🇹", 2, 1, 2)] // 🇵 U+1F1F5 — REGIONAL INDICATOR SYMBOL LETTER P with 🇹 U+1F1F9 — REGIONAL INDICATOR SYMBOL LETTER T (flag of Portugal)
     public void TestGetColumns_MultiRune_WideBMP_Graphemes (string str, int expectedRunesWidth, int expectedGraphemesCount, int expectedWidth)
     {
         Assert.Equal (expectedRunesWidth, str.EnumerateRunes ().Sum (r => r.GetColumns ()));
@@ -165,6 +166,7 @@ public class StringTests
         yield return [new [] { "👩‍", "🧒" }, "👩‍🧒"]; // Grapheme sequence
         yield return [new [] { "α", "β", "γ" }, "αβγ"]; // Unicode letters
         yield return [new [] { "A", null, "B" }, "AB"]; // Null ignored by string.Concat
+        yield return [new [] { "🇵", "🇹" }, "🇵🇹"]; // Grapheme sequence
     }
 
     [Theory]

+ 4 - 4
Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs

@@ -3121,7 +3121,7 @@ ssb
                  default (Rectangle));
         DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Theory]
@@ -3158,7 +3158,7 @@ ssb
                  default (Rectangle));
         DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Theory]
@@ -3196,7 +3196,7 @@ ssb
                  default (Rectangle));
         DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
 
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Theory]
@@ -3233,6 +3233,6 @@ ssb
                  default (Rectangle));
         DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
 
-        driver.End ();
+        driver.Dispose ();
     }
 }

+ 2 - 2
Tests/UnitTestsParallelizable/ViewBase/Layout/Pos.CenterTests.cs

@@ -201,7 +201,7 @@ public class PosCenterTests (ITestOutputHelper output) : FakeDriverBase
 
         _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output, driver);
         win.Dispose ();
-        driver.End ();
+        driver.Dispose ();
     }
 
     [Theory]
@@ -372,6 +372,6 @@ public class PosCenterTests (ITestOutputHelper output) : FakeDriverBase
 
         _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, _output, driver);
         win.Dispose ();
-        driver.End ();
+        driver.Dispose ();
     }
 }

+ 124 - 84
docfx/docs/application.md

@@ -81,14 +81,6 @@ sequenceDiagram
 **Terminal.Gui v2** supports both static and instance-based patterns. The static `Application` class is marked obsolete but still functional for backward compatibility. The recommended pattern is to use `Application.Create()` to get an `IApplication` instance:
 
 ```csharp
-// OLD (v1 / early v2 - still works but obsolete):
-Application.Init ();
-Window top = new ();
-top.Add (myView);
-Application.Run (top);
-top.Dispose ();
-Application.Shutdown (); // Obsolete - use Dispose() instead
-
 // RECOMMENDED (v2 - instance-based with using statement):
 using (IApplication app = Application.Create ().Init ())
 {
@@ -105,11 +97,19 @@ using (IApplication app = Application.Create ().Init ())
     Color? result = app.GetResult<Color> ();
 }
 
-// SIMPLEST (manual disposal):
+// ALTERNATIVE (manual disposal):
 IApplication app = Application.Create ().Init ();
 app.Run<ColorPickerDialog> ();
 Color? result = app.GetResult<Color> ();
 app.Dispose ();
+
+// OLD (v1 / early v2 - obsolete, avoid in new code):
+Application.Init ();
+Window top = new ();
+top.Add (myView);
+Application.Run (top);
+top.Dispose ();
+Application.Shutdown (); // Obsolete - use Dispose() instead
 ```
 
 **Note:** The static `Application` class delegates to a singleton instance accessible via `Application.Instance`. `Application.Create()` creates a **new** application instance, enabling multiple application contexts and better testability.
@@ -149,11 +149,12 @@ public class MyView : View
 {
     public override void OnEnter (View view)
     {
-        // Use View.App instead of static Application
-        App?.TopRunnable?.SetNeedsDraw ();
+        // Use View.App instead of obsolete static Application
+        IApplication? app = App;
+        app?.TopRunnable?.SetNeedsDraw ();
         
         // Access SessionStack
-        if (App?.SessionStack.Count > 0)
+        if (app?.SessionStack?.Count > 0)
         {
             // Work with sessions
         }
@@ -171,7 +172,7 @@ public class MyView : View
     public MyView (IApplication app)
     {
         _app = app;
-        // Now completely decoupled from static Application
+        // Completely decoupled from obsolete static Application
     }
     
     public void DoWork ()
@@ -275,7 +276,7 @@ public class FileDialog : Runnable<string?>
         okButton.Accepting += (s, e) =>
         {
             Result = _pathField.Text;
-            Application.RequestStop ();
+            App?.RequestStop ();
         };
         
         Add (_pathField, okButton);
@@ -321,11 +322,13 @@ protected override bool OnIsRunningChanging (bool oldValue, bool newValue)
         // Optionally cancel stop (e.g., unsaved changes)
         if (HasUnsavedChanges ())
         {
-            var response = MessageBox.Query ("Save?", "Save changes?", "Yes", "No", "Cancel");
+            int response = MessageBox.Query ("Save?", "Save changes?", "Yes", "No", "Cancel");
+            
             if (response == 2)
             {
                 return true;  // Cancel stop
             }
+            
             if (response == 0)
             {
                 Save ();
@@ -691,37 +694,77 @@ app.End (token1);
 // app.TopRunnable == null, SessionStack.Count == 0
 ```
 
-## View.Driver Property
+## Driver Management
 
-Similar to `View.App`, views now have a `Driver` property:
+### ForceDriver Configuration Property
+
+The `ForceDriver` property is a configuration property that allows you to specify which driver to use. It can be set via code or through the configuration system (e.g., `config.json`):
 
 ```csharp
-public class View
+// RECOMMENDED: Set on instance
+using (IApplication app = Application.Create ())
 {
-    /// <summary>
-    /// Gets the driver for this view.
-    /// </summary>
-    public IDriver? Driver => GetDriver ();
-    
-    /// <summary>
-    /// Gets the driver, checking application context if needed.
-    /// Override to customize driver resolution.
-    /// </summary>
-    public virtual IDriver? GetDriver () => App?.Driver;
+    app.ForceDriver = "fake";
+    app.Init ();
+}
+
+// ALTERNATIVE: Set on legacy static Application (obsolete)
+Application.ForceDriver = "dotnet";
+Application.Init ();
+```
+
+**Valid driver names**: `"dotnet"`, `"windows"`, `"unix"`, `"fake"`
+
+### ForceDriverChanged Event
+
+The static `Application.ForceDriverChanged` event is raised when the `ForceDriver` property changes:
+
+```csharp
+// ForceDriverChanged event (on legacy static Application)
+Application.ForceDriverChanged += (sender, e) =>
+{
+    Debug.WriteLine ($"Driver changed from '{e.OldValue}' to '{e.NewValue}'");
+};
+
+Application.ForceDriver = "fake";
+```
+
+### Getting Available Drivers
+
+You can query which driver types are available using `GetDriverTypes()`:
+
+```csharp
+// Get available driver types and names
+(List<Type?> types, List<string?> names) = Application.GetDriverTypes();
+
+foreach (string? name in names)
+{
+    Debug.WriteLine($"Available driver: {name}");
 }
+// Output:
+// Available driver: dotnet
+// Available driver: windows
+// Available driver: unix
+// Available driver: fake
 ```
 
-**Usage:**
+**Note**: This method uses reflection and is marked with `[RequiresUnreferencedCode]` for AOT compatibility considerations.
+
+## View.Driver Property
+
+Similar to `View.App`, views now have a `Driver` property for accessing driver functionality.
 
 ```csharp
 public override void OnDrawContent (Rectangle viewport)
 {
-    // Use view's driver instead of Application.Driver
+    // Use view's driver instead of obsolete Application.Driver
     Driver?.Move (0, 0);
     Driver?.AddStr ("Hello");
 }
 ```
 
+**Note**: See [Drivers Deep Dive](drivers.md) for complete driver architecture details, including the organized interface structure with lifecycle, components, display, rendering, cursor, and input regions.
+
 ## Testing with the New Architecture
 
 The instance-based architecture dramatically improves testability:
@@ -734,7 +777,8 @@ public void MyView_DisplaysCorrectly ()
 {
     // Create mock application
     Mock<IApplication> mockApp = new ();
-    mockApp.Setup (a => a.TopRunnable).Returns (new Runnable ());
+    Runnable runnable = new ();
+    mockApp.Setup (a => a.TopRunnable).Returns (runnable);
     
     // Create view with mock app
     MyView view = new () { App = mockApp.Object };
@@ -743,7 +787,7 @@ public void MyView_DisplaysCorrectly ()
     view.SetNeedsDraw ();
     Assert.True (view.NeedsDraw);
     
-    // No Application.Shutdown() needed!
+    // No disposal needed for mock!
 }
 ```
 
@@ -753,21 +797,28 @@ public void MyView_DisplaysCorrectly ()
 [Fact]
 public void MyView_WorksWithRealApplication ()
 {
-    using IApplication app = Application.Create ();
-    app.Init ("fake");
-    
-    MyView view = new ();
-    Window top = new ();
-    top.Add (view);
-    
-    app.Begin (top);
-    
-    // View.App automatically set
-    Assert.NotNull (view.App);
-    Assert.Same (app, view.App);
-    
-    // Test view behavior
-    view.DoSomething ();
+    using (IApplication app = Application.Create ())
+    {
+        app.Init ("fake");
+        
+        MyView view = new ();
+        Window top = new ();
+        top.Add (view);
+        
+        SessionToken? token = app.Begin (top);
+        
+        // View.App automatically set
+        Assert.NotNull (view.App);
+        Assert.Same (app, view.App);
+        
+        // Test view behavior
+        view.DoSomething ();
+        
+        if (token is { })
+        {
+            app.End (token);
+        }
+    }
 }
 ```
 
@@ -776,7 +827,7 @@ public void MyView_WorksWithRealApplication ()
 ### DO: Use View.App
 
 ```csharp
-✅ GOOD:
+// ✅ GOOD - Use View.App (modern instance-based pattern):
 public void Refresh ()
 {
     App?.TopRunnableView?.SetNeedsDraw ();
@@ -786,7 +837,7 @@ public void Refresh ()
 ### DON'T: Use Static Application
 
 ```csharp
-❌ AVOID:
+// ❌ AVOID - Obsolete static Application:
 public void Refresh ()
 {
     Application.TopRunnableView?.SetNeedsDraw (); // Obsolete!
@@ -796,33 +847,38 @@ public void Refresh ()
 ### DO: Pass IApplication as Dependency
 
 ```csharp
-✅ GOOD:
+// ✅ GOOD - Dependency injection:
 public class Service
 {
-    public Service (IApplication app) { }
+    private readonly IApplication _app;
+    
+    public Service (IApplication app)
+    {
+        _app = app;
+    }
 }
 ```
 
 ### DON'T: Use Static Application in New Code
 
 ```csharp
-❌ AVOID (obsolete pattern):
+// ❌ AVOID - Obsolete static Application in new code:
 public void Refresh ()
 {
-    Application.TopRunnableView?.SetNeedsDraw (); // Obsolete static access
+    Application.TopRunnableView?.SetNeedsDraw (); // Obsolete!
 }
 
-✅ PREFERRED:
+// ✅ PREFERRED - Use View.App property:
 public void Refresh ()
 {
-    App?.TopRunnableView?.SetNeedsDraw (); // Use View.App property
+    App?.TopRunnableView?.SetNeedsDraw ();
 }
 ```
 
 ### DO: Override GetApp() for Custom Resolution
 
 ```csharp
-✅ GOOD:
+// ✅ GOOD - Custom application resolution:
 public class SpecialView : View
 {
     private IApplication? _customApp;
@@ -842,41 +898,25 @@ The instance-based architecture enables multiple applications:
 
 ```csharp
 // Application 1
-using IApplication app1 = Application.Create ();
-app1.Init ("windows");
-Window top1 = new () { Title = "App 1" };
-// ... configure top1
+using (IApplication app1 = Application.Create ())
+{
+    app1.Init ("fake");
+    Window top1 = new () { Title = "App 1" };
+    // ... configure and run top1
+}
 
 // Application 2 (different driver!)
-using IApplication app2 = Application.Create ();
-app2.Init ("unix");
-Window top2 = new () { Title = "App 2" };
-// ... configure top2
+using (IApplication app2 = Application.Create ())
+{
+    app2.Init ("fake");
+    Window top2 = new () { Title = "App 2" };
+    // ... configure and run top2
+}
 
 // Views in top1 use app1
 // Views in top2 use app2
 ```
 
-### Application-Agnostic Views
-
-Create views that work with any application:
-
-```csharp
-public class UniversalView : View
-{
-    public void ShowMessage (string message)
-    {
-        // Works regardless of which application context
-        IApplication? app = GetApp ();
-        if (app != null)
-        {
-            MessageBox msg = new (message);
-            app.Begin (msg);
-        }
-    }
-}
-```
-
 ## See Also
 
 - [Navigation](navigation.md) - Navigation with the instance-based architecture

+ 119 - 18
docfx/docs/drivers.md

@@ -24,22 +24,75 @@ The appropriate driver is automatically selected based on the platform when you
 
 ### Explicit Driver Selection
 
-You can explicitly specify a driver in three ways:
+You can explicitly specify a driver in several ways:
 
-```csharp
-// Method 1: Set ForceDriver property before Init
-Application.ForceDriver = "dotnet";
-Application.Init();
+Method 1: Set ForceDriver using Configuration Manager
+
+```json
+{
+  "ForceDriver": "fake"
+}
+```
+
+Method 2: Pass driver name to Init
 
-// Method 2: Pass driver name to Init
+```csharp
 Application.Init(driverName: "unix");
+```
+
+Method 3: Set ForceDriver on instance
+
+```csharp
+using (IApplication app = Application.Create())
+{
+    app.ForceDriver = "fake";
+    app.Init();
+}
+```
+
+**Valid driver names**: `"dotnet"`, `"windows"`, `"unix"`, `"fake"`
+
+### ForceDriver as Configuration Property
+
+The `ForceDriver` property is a configuration property marked with `[ConfigurationProperty]`, which means:
+
+- It can be set through the configuration system (e.g., `config.json`)
+- Changes raise the `ForceDriverChanged` event
+- It persists across application instances when using the static `Application` class
+
+```csharp
+// Subscribe to driver changes
+Application.ForceDriverChanged += (sender, e) =>
+{
+    Console.WriteLine($"Driver changed: {e.OldValue} → {e.NewValue}");
+};
+
+// Change driver
+Application.ForceDriver = "fake";
+```
+
+### Discovering Available Drivers
 
-// Method 3: Pass a custom IDriver instance
-var customDriver = new MyCustomDriver();
-Application.Init(driver: customDriver);
+Use `GetDriverTypes()` to discover which drivers are available at runtime:
+
+```csharp
+(List<Type?> driverTypes, List<string?> driverNames) = Application.GetDriverTypes();
+
+Console.WriteLine("Available drivers:");
+foreach (string? name in driverNames)
+{
+    Console.WriteLine($"  - {name}");
+}
+
+// Output:
+// Available drivers:
+//   - dotnet
+//   - windows
+//   - unix
+//   - fake
 ```
 
-Valid driver names: `"dotnet"`, `"windows"`, `"unix"`, `"fake"`
+**Note**: `GetDriverTypes()` uses reflection to discover driver implementations and is marked with `[RequiresUnreferencedCode("AOT")]` and `[Obsolete]` as part of the legacy static API.
 
 ## Architecture
 
@@ -151,15 +204,49 @@ When `IApplication.Shutdown()` is called:
 
 ### IDriver
 
-The main driver interface that the framework uses internally. Provides:
+The main driver interface that the framework uses internally. `IDriver` is organized into logical regions:
+
+#### Driver Lifecycle
+- `Init()`, `Refresh()`, `End()` - Core lifecycle methods
+- `GetName()`, `GetVersionInfo()` - Driver identification
+- `Suspend()` - Platform-specific suspend support
+
+#### Driver Components
+- `InputProcessor` - Processes input into Terminal.Gui events
+- `OutputBuffer` - Manages screen buffer state
+- `SizeMonitor` - Detects terminal size changes
+- `Clipboard` - OS clipboard integration
+
+#### Screen and Display
+- `Screen`, `Cols`, `Rows`, `Left`, `Top` - Screen dimensions
+- `SetScreenSize()`, `SizeChanged` - Size management
+
+#### Color Support
+- `SupportsTrueColor` - 24-bit color capability
+- `Force16Colors` - Force 16-color mode
 
-- **Screen Management**: `Screen`, `Cols`, `Rows`, `Contents`
-- **Drawing Operations**: `AddRune()`, `AddStr()`, `Move()`, `FillRect()`
-- **Cursor Management**: `SetCursorVisibility()`, `UpdateCursor()`
-- **Attribute Management**: `CurrentAttribute`, `SetAttribute()`, `MakeColor()`
-- **Clipping**: `Clip` property
-- **Events**: `KeyDown`, `KeyUp`, `MouseEvent`, `SizeChanged`
-- **Platform Features**: `SupportsTrueColor`, `Force16Colors`, `Clipboard`
+#### Content Buffer
+- `Contents` - Screen buffer array
+- `Clip` - Clipping region
+- `ClearContents()`, `ClearedContents` - Buffer management
+
+#### Drawing and Rendering
+- `Col`, `Row`, `CurrentAttribute` - Drawing state
+- `Move()`, `AddRune()`, `AddStr()`, `FillRect()` - Drawing operations
+- `SetAttribute()`, `GetAttribute()` - Attribute management
+- `WriteRaw()`, `GetSixels()` - Raw output and graphics
+- `Refresh()`, `ToString()`, `ToAnsi()` - Output rendering
+
+#### Cursor
+- `UpdateCursor()` - Position cursor
+- `GetCursorVisibility()`, `SetCursorVisibility()` - Visibility management
+
+#### Input Events
+- `KeyDown`, `KeyUp`, `MouseEvent` - Input events
+- `EnqueueKeyEvent()` - Test support
+
+#### ANSI Escape Sequences
+- `QueueAnsiRequest()` - ANSI request handling
 
 **Note:** The driver is internal to Terminal.Gui. View classes should not access `Driver` directly. Instead:
 - Use @Terminal.Gui.App.Application.Screen to get screen dimensions
@@ -167,6 +254,20 @@ The main driver interface that the framework uses internally. Provides:
 - Use @Terminal.Gui.ViewBase.View.AddRune and @Terminal.Gui.ViewBase.View.AddStr for drawing
 - ViewBase infrastructure classes (in `Terminal.Gui/ViewBase/`) can access Driver when needed for framework implementation
 
+### Driver Creation and Selection
+
+The driver selection logic in `ApplicationImpl.Driver.cs` prioritizes component factory type over the driver name parameter:
+
+1. **Component Factory Type**: If an `IComponentFactory` is already set, it determines the driver
+2. **Driver Name Parameter**: The `driverName` parameter to `Init()` is checked next
+3. **ForceDriver Property**: The `ForceDriver` configuration property is evaluated
+4. **Platform Detection**: If none of the above specify a driver, the platform is detected:
+   - Windows (Win32NT, Win32S, Win32Windows) → `WindowsDriver`
+   - Unix/Linux/macOS → `UnixDriver`
+   - Other platforms → `DotNetDriver` (fallback)
+
+This prioritization ensures flexibility while maintaining deterministic behavior.
+
 ## Platform-Specific Details
 
 ### DotNetDriver (NetComponentFactory)