diff --git a/Examples/Example/Example.cs b/Examples/Example/Example.cs
index 9d3fd863f..4d8552e3a 100644
--- a/Examples/Example/Example.cs
+++ b/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);
}
}
diff --git a/Examples/UICatalog/Scenarios/ColorPicker.cs b/Examples/UICatalog/Scenarios/ColorPicker.cs
index a058af4af..cf770aa8f 100644
--- a/Examples/UICatalog/Scenarios/ColorPicker.cs
+++ b/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.
diff --git a/Examples/UICatalog/Scenarios/Images.cs b/Examples/UICatalog/Scenarios/Images.cs
index 5a5d2a7d7..6e5fc7f9a 100644
--- a/Examples/UICatalog/Scenarios/Images.cs
+++ b/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
{
diff --git a/Examples/UICatalog/Scenarios/LineDrawing.cs b/Examples/UICatalog/Scenarios/LineDrawing.cs
index e8608c051..3badc02dc 100644
--- a/Examples/UICatalog/Scenarios/LineDrawing.cs
+++ b/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;
}
diff --git a/Examples/UICatalog/UICatalog.cs b/Examples/UICatalog/UICatalog.cs
index 21634ac0b..5d22a9c12 100644
--- a/Examples/UICatalog/UICatalog.cs
+++ b/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;
}
diff --git a/Examples/UICatalog/UICatalogRunnable.cs b/Examples/UICatalog/UICatalogRunnable.cs
index 997bb9236..748c2a5dc 100644
--- a/Examples/UICatalog/UICatalogRunnable.cs
+++ b/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 ();
}
diff --git a/Terminal.Gui/App/Application.Driver.cs b/Terminal.Gui/App/Application.Driver.cs
index 427ba4de5..be0faff2d 100644
--- a/Terminal.Gui/App/Application.Driver.cs
+++ b/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
-
- ///
- [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 (oldValue, _force16Colors));
- }
- }
-
- /// Raised when changes.
- public static event EventHandler>? 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
///
[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 (oldValue, _forceDriver));
+ ForceDriverChanged?.Invoke (null, new (oldValue, _forceDriver));
}
}
/// Raised when changes.
public static event EventHandler>? ForceDriverChanged;
- ///
- [Obsolete ("The legacy static Application object is going away.")]
- public static List Sixel => ApplicationImpl.Instance.Sixel;
+ ///
+ public static ConcurrentQueue GetSixels () => ApplicationImpl.Instance.Driver?.GetSixels ()!;
/// Gets a list of types and type names that are available.
///
@@ -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 })
{
diff --git a/Terminal.Gui/App/ApplicationImpl.Driver.cs b/Terminal.Gui/App/ApplicationImpl.Driver.cs
index 11fabb91a..ed350a52e 100644
--- a/Terminal.Gui/App/ApplicationImpl.Driver.cs
+++ b/Terminal.Gui/App/ApplicationImpl.Driver.cs
@@ -7,15 +7,9 @@ internal partial class ApplicationImpl
///
public IDriver? Driver { get; set; }
- ///
- public bool Force16Colors { get; set; }
-
///
public string ForceDriver { get; set; } = string.Empty;
- ///
- public List Sixel { get; } = new ();
-
///
/// Creates the appropriate based on platform and driverName.
///
@@ -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;
diff --git a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs b/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
index 53691ea7b..cd3448fc3 100644
--- a/Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
+++ b/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 e) { Force16Colors = e.NewValue; }
-
private void OnForceDriverChanged (object? sender, ValueChangedEventArgs e) { ForceDriver = e.NewValue; }
///
@@ -374,7 +359,6 @@ internal partial class ApplicationImpl
///
private void UnsubscribeApplicationEvents ()
{
- Application.Force16ColorsChanged -= OnForce16ColorsChanged;
Application.ForceDriverChanged -= OnForceDriverChanged;
}
}
diff --git a/Terminal.Gui/App/ApplicationImpl.cs b/Terminal.Gui/App/ApplicationImpl.cs
index 9910e7019..2b2af9382 100644
--- a/Terminal.Gui/App/ApplicationImpl.cs
+++ b/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 ();
}
diff --git a/Terminal.Gui/App/IApplication.cs b/Terminal.Gui/App/IApplication.cs
index 9933ab178..37fed7abe 100644
--- a/Terminal.Gui/App/IApplication.cs
+++ b/Terminal.Gui/App/IApplication.cs
@@ -449,13 +449,6 @@ public interface IApplication : IDisposable
///
IClipboard? Clipboard { get; }
- ///
- /// Gets or sets whether will be forced to output only the 16 colors defined in
- /// . The default is , meaning 24-bit (TrueColor) colors will be
- /// output as long as the selected supports TrueColor.
- ///
- bool Force16Colors { get; set; }
-
///
/// 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; }
///
- /// 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 .
- /// Setting the location to anything but (0, 0) is not supported and will throw .
+ /// Gets or sets the size of the screen. By default, this is the size of the screen as reported by the
+ /// .
///
///
///
@@ -498,12 +490,6 @@ public interface IApplication : IDisposable
///
bool ClearScreenNextIteration { get; set; }
- ///
- /// Collection of sixel images to write out to screen when updating.
- /// Only add to this collection if you are sure terminal supports sixel format.
- ///
- List Sixel { get; }
-
#endregion Screen and Driver
#region Keyboard
diff --git a/Terminal.Gui/App/Mouse/MouseImpl.cs b/Terminal.Gui/App/Mouse/MouseImpl.cs
index 7840df3fc..fe8c26d37 100644
--- a/Terminal.Gui/App/Mouse/MouseImpl.cs
+++ b/Terminal.Gui/App/Mouse/MouseImpl.cs
@@ -20,14 +20,8 @@ internal class MouseImpl : IMouse, IDisposable
Application.IsMouseDisabledChanged += OnIsMouseDisabledChanged;
}
- private IApplication? _app;
-
///
- public IApplication? App
- {
- get => _app;
- set => _app = value;
- }
+ public IApplication? App { get; set; }
///
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)
diff --git a/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs b/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
index a9e1ae8aa..1e77d41bb 100644
--- a/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
+++ b/Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
@@ -32,8 +32,9 @@ public class SixelSupportDetector ()
///
public void Detect (Action 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 ()
diff --git a/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs b/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
index 4f8ab1fc0..fb4ff0c9b 100644
--- a/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
+++ b/Terminal.Gui/Drivers/DotNetDriver/NetOutput.cs
@@ -81,7 +81,7 @@ public class NetOutput : OutputBase, IOutput
///
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 ()));
diff --git a/Terminal.Gui/Drivers/Driver.cs b/Terminal.Gui/Drivers/Driver.cs
new file mode 100644
index 000000000..02bc73a64
--- /dev/null
+++ b/Terminal.Gui/Drivers/Driver.cs
@@ -0,0 +1,31 @@
+namespace Terminal.Gui.Drivers;
+
+///
+/// Holds global driver settings.
+///
+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.
+ ///
+ /// Determines if driver instances should use 16 colors instead of the default TrueColors.
+ ///
+ ///
+ [ConfigurationProperty (Scope = typeof (SettingsScope))]
+ public static bool Force16Colors
+ {
+ get => _force16Colors;
+ set
+ {
+ bool oldValue = _force16Colors;
+ _force16Colors = value;
+ Force16ColorsChanged?.Invoke (null, new ValueChangedEventArgs (oldValue, _force16Colors));
+ }
+ }
+
+ /// Raised when changes.
+ public static event EventHandler>? Force16ColorsChanged;
+}
diff --git a/Terminal.Gui/Drivers/DriverImpl.cs b/Terminal.Gui/Drivers/DriverImpl.cs
index e4f238c34..ac5e513bd 100644
--- a/Terminal.Gui/Drivers/DriverImpl.cs
+++ b/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;
///
internal class DriverImpl : IDriver
{
- private readonly IOutput _output;
- private readonly AnsiRequestScheduler _ansiRequestScheduler;
- private CursorVisibility _lastCursor = CursorVisibility.Default;
-
///
/// Initializes a new instance of the class.
///
@@ -63,184 +60,23 @@ internal class DriverImpl : IDriver
};
SizeMonitor = sizeMonitor;
-
- sizeMonitor.SizeChanged += (_, e) =>
- {
- SetScreenSize (e.Size!.Value.Width, e.Size.Value.Height);
-
- //SizeChanged?.Invoke (this, e);
- };
+ SizeMonitor.SizeChanged += OnSizeMonitorOnSizeChanged;
CreateClipboard ();
+
+ Driver.Force16ColorsChanged += OnDriverOnForce16ColorsChanged;
}
- ///
- public event EventHandler? SizeChanged;
+ #region Driver Lifecycle
///
- public IInputProcessor InputProcessor { get; }
+ public void Init () { throw new NotSupportedException (); }
///
- public IOutputBuffer OutputBuffer { get; }
+ public void Refresh () { _output.Write (OutputBuffer); }
///
- public ISizeMonitor SizeMonitor { get; }
-
- private void CreateClipboard ()
- {
- if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake"))
- {
- if (Clipboard is null)
- {
- Clipboard = new FakeClipboard ();
- }
-
- return;
- }
-
- PlatformID p = Environment.OSVersion.Platform;
-
- if (p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows)
- {
- Clipboard = new WindowsClipboard ();
- }
- else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
- {
- Clipboard = new MacOSXClipboard ();
- }
- else if (PlatformDetection.IsWSLPlatform ())
- {
- Clipboard = new WSLClipboard ();
- }
-
- // Clipboard is set to FakeClipboard at initialization
- }
-
- ///
-
- public Rectangle Screen =>
-
- //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);
-
- ///
- public virtual void SetScreenSize (int width, int height)
- {
- OutputBuffer.SetSize (width, height);
- _output.SetSize (width, height);
- SizeChanged?.Invoke (this, new (new (width, height)));
- }
-
- ///
-
- public Region? Clip
- {
- get => OutputBuffer.Clip;
- set => OutputBuffer.Clip = value;
- }
-
- ///
-
- public IClipboard? Clipboard { get; private set; } = new FakeClipboard ();
-
- ///
-
- public int Col => OutputBuffer.Col;
-
- ///
-
- public int Cols
- {
- get => OutputBuffer.Cols;
- set => OutputBuffer.Cols = value;
- }
-
- ///
-
- public Cell [,]? Contents
- {
- get => OutputBuffer.Contents;
- set => OutputBuffer.Contents = value;
- }
-
- ///
-
- public int Left
- {
- get => OutputBuffer.Left;
- set => OutputBuffer.Left = value;
- }
-
- ///
-
- public int Row => OutputBuffer.Row;
-
- ///
-
- public int Rows
- {
- get => OutputBuffer.Rows;
- set => OutputBuffer.Rows = value;
- }
-
- ///
-
- public int Top
- {
- get => OutputBuffer.Top;
- set => OutputBuffer.Top = value;
- }
-
- // TODO: Probably not everyone right?
-
- ///
-
- public bool SupportsTrueColor => true;
-
- ///
-
- public bool Force16Colors
- {
- get => Application.Force16Colors || !SupportsTrueColor;
- set => Application.Force16Colors = value || !SupportsTrueColor;
- }
-
- ///
-
- public Attribute CurrentAttribute
- {
- get => OutputBuffer.CurrentAttribute;
- set => OutputBuffer.CurrentAttribute = value;
- }
-
- ///
- public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); }
-
- ///
- public void AddRune (char c) { OutputBuffer.AddRune (c); }
-
- ///
- public void AddStr (string str) { OutputBuffer.AddStr (str); }
-
- /// Clears the of the driver.
- public void ClearContents ()
- {
- OutputBuffer.ClearContents ();
- ClearedContents?.Invoke (this, new MouseEventArgs ());
- }
-
- ///
- public event EventHandler? ClearedContents;
-
- ///
- public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); }
-
- ///
- public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); }
+ public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant ();
///
public virtual string GetVersionInfo ()
@@ -250,42 +86,6 @@ internal class DriverImpl : IDriver
return type;
}
- ///
- public bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value);
-
- /// Tests whether the specified coordinate are valid for drawing the specified Text.
- /// Used to determine if one or two columns are required.
- /// The column.
- /// The row.
- ///
- /// if the coordinate is outside the screen bounds or outside of
- /// .
- /// otherwise.
- ///
- public bool IsValidLocation (string text, int col, int row) { return OutputBuffer.IsValidLocation (text, col, row); }
-
- ///
- public void Move (int col, int row) { OutputBuffer.Move (col, row); }
-
- // TODO: Probably part of output
-
- ///
- public bool SetCursorVisibility (CursorVisibility visibility)
- {
- _lastCursor = visibility;
- _output.SetCursorVisibility (visibility);
-
- return true;
- }
-
- ///
- public bool GetCursorVisibility (out CursorVisibility current)
- {
- current = _lastCursor;
-
- return true;
- }
-
///
public void Suspend ()
{
@@ -323,17 +123,209 @@ internal class DriverImpl : IDriver
}
///
- public void UpdateCursor () { _output.SetCursorPosition (Col, Row); }
-
- ///
- public void Init () { throw new NotSupportedException (); }
-
- ///
- public void End ()
+ public bool IsLegacyConsole
{
- // TODO: Nope
+ get => _output.IsLegacyConsole;
+ set => _output.IsLegacyConsole = value;
}
+ ///
+ public void Dispose ()
+ {
+ SizeMonitor.SizeChanged -= OnSizeMonitorOnSizeChanged;
+ Driver.Force16ColorsChanged -= OnDriverOnForce16ColorsChanged;
+ _output.Dispose ();
+ }
+
+ #endregion Driver Lifecycle
+
+ #region Driver Components
+
+ private readonly IOutput _output;
+
+ ///
+ public IInputProcessor InputProcessor { get; }
+
+ ///
+ public IOutputBuffer OutputBuffer { get; }
+
+ ///
+ public ISizeMonitor SizeMonitor { get; }
+
+ ///
+ public IClipboard? Clipboard { get; private set; } = new FakeClipboard ();
+
+ private void CreateClipboard ()
+ {
+ if (InputProcessor.DriverName is { } && InputProcessor.DriverName.Contains ("fake"))
+ {
+ if (Clipboard is null)
+ {
+ Clipboard = new FakeClipboard ();
+ }
+
+ return;
+ }
+
+ PlatformID p = Environment.OSVersion.Platform;
+
+ if (p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows)
+ {
+ Clipboard = new WindowsClipboard ();
+ }
+ else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
+ {
+ Clipboard = new MacOSXClipboard ();
+ }
+ else if (PlatformDetection.IsWSLPlatform ())
+ {
+ Clipboard = new WSLClipboard ();
+ }
+
+ // Clipboard is set to FakeClipboard at initialization
+ }
+
+ #endregion Driver Components
+
+ #region Screen and Display
+
+ ///
+ public Rectangle Screen => new (0, 0, OutputBuffer.Cols, OutputBuffer.Rows);
+
+ ///
+ public virtual void SetScreenSize (int width, int height)
+ {
+ OutputBuffer.SetSize (width, height);
+ _output.SetSize (width, height);
+ SizeChanged?.Invoke (this, new (new (width, height)));
+ }
+
+ ///
+ public event EventHandler? SizeChanged;
+
+ private void OnSizeMonitorOnSizeChanged (object? _, SizeChangedEventArgs e) { SetScreenSize (e.Size!.Value.Width, e.Size.Value.Height); }
+
+ ///
+ public int Cols
+ {
+ get => OutputBuffer.Cols;
+ set => OutputBuffer.Cols = value;
+ }
+
+ ///
+ public int Rows
+ {
+ get => OutputBuffer.Rows;
+ set => OutputBuffer.Rows = value;
+ }
+
+ ///
+ public int Left
+ {
+ get => OutputBuffer.Left;
+ set => OutputBuffer.Left = value;
+ }
+
+ ///
+ public int Top
+ {
+ get => OutputBuffer.Top;
+ set => OutputBuffer.Top = value;
+ }
+
+ #endregion Screen and Display
+
+ #region Color Support
+
+ ///
+ public bool SupportsTrueColor => !IsLegacyConsole;
+
+ ///
+ public bool Force16Colors
+ {
+ get => _output.Force16Colors;
+ set => _output.Force16Colors = value;
+ }
+
+ private void OnDriverOnForce16ColorsChanged (object? _, ValueChangedEventArgs e) { Force16Colors = e.NewValue; }
+
+ #endregion Color Support
+
+ #region Content Buffer
+
+ ///
+ public Cell [,]? Contents
+ {
+ get => OutputBuffer.Contents;
+ set => OutputBuffer.Contents = value;
+ }
+
+ ///
+ public Region? Clip
+ {
+ get => OutputBuffer.Clip;
+ set => OutputBuffer.Clip = value;
+ }
+
+ /// Clears the of the driver.
+ public void ClearContents ()
+ {
+ OutputBuffer.ClearContents ();
+ ClearedContents?.Invoke (this, new MouseEventArgs ());
+ }
+
+ ///
+ public event EventHandler? ClearedContents;
+
+ #endregion Content Buffer
+
+ #region Drawing and Rendering
+
+ ///
+ public int Col => OutputBuffer.Col;
+
+ ///
+ public int Row => OutputBuffer.Row;
+
+ ///
+ public Attribute CurrentAttribute
+ {
+ get => OutputBuffer.CurrentAttribute;
+ set => OutputBuffer.CurrentAttribute = value;
+ }
+
+ ///
+ public void Move (int col, int row) { OutputBuffer.Move (col, row); }
+
+ ///
+ public bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value);
+
+ /// Tests whether the specified coordinate are valid for drawing the specified Text.
+ /// Used to determine if one or two columns are required.
+ /// The column.
+ /// The row.
+ ///
+ /// if the coordinate is outside the screen bounds or outside of
+ /// .
+ /// otherwise.
+ ///
+ public bool IsValidLocation (string text, int col, int row) => OutputBuffer.IsValidLocation (text, col, row);
+
+ ///
+ public void AddRune (Rune rune) { OutputBuffer.AddRune (rune); }
+
+ ///
+ public void AddRune (char c) { OutputBuffer.AddRune (c); }
+
+ ///
+ public void AddStr (string str) { OutputBuffer.AddStr (str); }
+
+ ///
+ public void FillRect (Rectangle rect, Rune rune = default) { OutputBuffer.FillRect (rect, rune); }
+
+ ///
+ public void FillRect (Rectangle rect, char c) { OutputBuffer.FillRect (rect, c); }
+
///
public Attribute SetAttribute (Attribute newAttribute)
{
@@ -346,35 +338,11 @@ internal class DriverImpl : IDriver
///
public Attribute GetAttribute () => OutputBuffer.CurrentAttribute;
- /// Event fired when a key is pressed down. This is a precursor to .
- public event EventHandler? KeyDown;
-
- ///
- public event EventHandler? KeyUp;
-
- /// Event fired when a mouse event occurs.
- public event EventHandler? MouseEvent;
-
///
public void WriteRaw (string ansi) { _output.Write (ansi); }
///
- public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); }
-
- ///
- public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (this, request); }
-
- ///
- public AnsiRequestScheduler GetRequestScheduler () => _ansiRequestScheduler;
-
- ///
- public void Refresh ()
- {
- _output.Write (OutputBuffer);
- }
-
- ///
- public string? GetName () => InputProcessor.DriverName?.ToLowerInvariant ();
+ public ConcurrentQueue GetSixels () => _output.GetSixels ();
///
public new string ToString ()
@@ -403,9 +371,59 @@ internal class DriverImpl : IDriver
return sb.ToString ();
}
- ///
- public string ToAnsi ()
+ ///
+ public string ToAnsi () => _output.ToAnsi (OutputBuffer);
+
+ #endregion Drawing and Rendering
+
+ #region Cursor
+
+ private CursorVisibility _lastCursor = CursorVisibility.Default;
+
+ ///
+ public void UpdateCursor () { _output.SetCursorPosition (Col, Row); }
+
+ ///
+ public bool GetCursorVisibility (out CursorVisibility current)
{
- return _output.ToAnsi (OutputBuffer);
+ current = _lastCursor;
+
+ return true;
}
+
+ ///
+ public bool SetCursorVisibility (CursorVisibility visibility)
+ {
+ _lastCursor = visibility;
+ _output.SetCursorVisibility (visibility);
+
+ return true;
+ }
+
+ #endregion Cursor
+
+ #region Input Events
+
+ /// Event fired when a mouse event occurs.
+ public event EventHandler? MouseEvent;
+
+ /// Event fired when a key is pressed down. This is a precursor to .
+ public event EventHandler? KeyDown;
+
+ ///
+ public event EventHandler? KeyUp;
+
+ ///
+ public void EnqueueKeyEvent (Key key) { InputProcessor.EnqueueKeyDownEvent (key); }
+
+ #endregion Input Events
+
+ #region ANSI Escape Sequences
+
+ private readonly AnsiRequestScheduler _ansiRequestScheduler;
+
+ ///
+ public virtual void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (this, request); }
+
+ #endregion ANSI Escape Sequences
}
diff --git a/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs b/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs
index 8fd790f19..0bf504bab 100644
--- a/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs
+++ b/Terminal.Gui/Drivers/FakeDriver/FakeOutput.cs
@@ -86,10 +86,21 @@ public class FakeOutput : OutputBase, IOutput
///
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);
+ }
}
///
diff --git a/Terminal.Gui/Drivers/IDriver.cs b/Terminal.Gui/Drivers/IDriver.cs
index 5e677140d..0447ee95e 100644
--- a/Terminal.Gui/Drivers/IDriver.cs
+++ b/Terminal.Gui/Drivers/IDriver.cs
@@ -1,16 +1,61 @@
+using System.Collections.Concurrent;
+
namespace Terminal.Gui.Drivers;
/// Base interface for Terminal.Gui Driver implementations.
///
/// There are currently four implementations: UnixDriver, WindowsDriver, DotNetDriver, and FakeDriver
///
-public interface IDriver
+public interface IDriver : IDisposable
{
+ #region Driver Lifecycle
+
+ /// Initializes the driver
+ void Init ();
+
+ ///
+ /// INTERNAL: Updates the terminal with the current output buffer. Should not be used by applications. Drawing occurs
+ /// once each Application main loop iteration.
+ ///
+ void Refresh ();
+
///
/// Gets the name of the driver implementation.
///
string? GetName ();
+ /// Returns the name of the driver and relevant library version information.
+ ///
+ string GetVersionInfo ();
+
+ /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.
+ /// This is only implemented in UnixDriver.
+ void Suspend ();
+
+ ///
+ /// Gets whether the driver has detected the console requires legacy console API (Windows Console API without ANSI/VT
+ /// support).
+ /// Returns for legacy consoles that don't support modern ANSI escape sequences (e.g. Windows
+ /// conhost);
+ /// for modern terminals with ANSI/VT support.
+ ///
+ ///
+ ///
+ /// 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 as they support ANSI by default.
+ ///
+ ///
+ /// When , the driver must use legacy Windows Console API functions
+ /// (e.g., WriteConsoleW, SetConsoleTextAttribute) instead of ANSI escape sequences.
+ ///
+ ///
+ bool IsLegacyConsole { get; internal set; }
+
+ #endregion Driver Lifecycle
+
+ #region Driver Components
+
///
/// Class responsible for processing native driver input objects
/// e.g. into events
@@ -18,20 +63,13 @@ public interface IDriver
///
IInputProcessor InputProcessor { get; }
- ///
- /// Describes the desired screen state. Data source for .
- ///
- IOutputBuffer OutputBuffer { get; }
-
- ///
- /// Interface for classes responsible for reporting the current
- /// size of the terminal window.
- ///
- ISizeMonitor SizeMonitor { get; }
-
/// Get the operating system clipboard.
IClipboard? Clipboard { get; }
+ #endregion Driver Components
+
+ #region Screen and Display
+
/// Gets the location and size of the terminal screen.
Rectangle Screen { get; }
@@ -43,21 +81,46 @@ public interface IDriver
void SetScreenSize (int width, int height);
///
- /// Gets or sets the clip rectangle that and are subject
- /// to.
+ /// The event fired when the screen changes (size, position, etc.).
+ /// is the source of truth for screen dimensions.
///
- /// The rectangle describing the of region.
- Region? Clip { get; set; }
-
- ///
- /// Gets the column last set by . and are used by
- /// and to determine where to add content.
- ///
- int Col { get; }
+ event EventHandler? SizeChanged;
/// The number of columns visible in the terminal.
int Cols { get; set; }
+ /// The number of rows visible in the terminal.
+ int Rows { get; set; }
+
+ /// The leftmost column in the terminal.
+ int Left { get; set; }
+
+ /// The topmost row in the terminal.
+ int Top { get; set; }
+
+ #endregion Screen and Display
+
+ #region Color Support
+
+ /// Gets whether the supports TrueColor output.
+ bool SupportsTrueColor { get; }
+
+ ///
+ /// Gets or sets whether the should use 16 colors instead of the default TrueColors.
+ ///
+ ///
+ ///
+ /// Will be forced to if is
+ /// , indicating that the cannot support TrueColor.
+ ///
+ ///
+ ///
+ bool Force16Colors { get; set; }
+
+ #endregion Color Support
+
+ #region Content Buffer
+
// BUGBUG: This should not be publicly settable.
///
/// Gets or sets the contents of the application output. The driver outputs this buffer to the terminal.
@@ -65,8 +128,30 @@ public interface IDriver
///
Cell [,]? Contents { get; set; }
- /// The leftmost column in the terminal.
- int Left { get; set; }
+ ///
+ /// Gets or sets the clip rectangle that and are subject
+ /// to.
+ ///
+ /// The rectangle describing the of region.
+ Region? Clip { get; set; }
+
+ /// Clears the of the driver.
+ void ClearContents ();
+
+ ///
+ /// Fills the specified rectangle with the specified rune, using
+ ///
+ event EventHandler ClearedContents;
+
+ #endregion Content Buffer
+
+ #region Drawing and Rendering
+
+ ///
+ /// Gets the column last set by . and are used by
+ /// and to determine where to add content.
+ ///
+ int Col { get; }
///
/// Gets the row last set by . and are used by
@@ -74,27 +159,6 @@ public interface IDriver
///
int Row { get; }
- /// The number of rows visible in the terminal.
- int Rows { get; set; }
-
- /// The topmost row in the terminal.
- int Top { get; set; }
-
- /// Gets whether the supports TrueColor output.
- bool SupportsTrueColor { get; }
-
- ///
- /// Gets or sets whether the should use 16 colors instead of the default TrueColors.
- /// See to change this setting via .
- ///
- ///
- ///
- /// Will be forced to if is
- /// , indicating that the cannot support TrueColor.
- ///
- ///
- bool Force16Colors { get; set; }
-
///
/// The that will be used for the next or
///
@@ -102,15 +166,23 @@ public interface IDriver
///
Attribute CurrentAttribute { get; set; }
- /// Returns the name of the driver and relevant library version information.
- ///
- string GetVersionInfo ();
-
///
- /// Provide proper writing to send escape sequence recognized by the .
+ /// Updates and to the specified column and row in
+ /// .
+ /// Used by and to determine
+ /// where to add content.
///
- ///
- void WriteRaw (string ansi);
+ ///
+ /// This does not move the cursor on the screen, it only updates the internal state of the driver.
+ ///
+ /// If or are negative or beyond
+ /// and
+ /// , the method still sets those properties.
+ ///
+ ///
+ /// Column to move to.
+ /// Row to move to.
+ void Move (int col, int row);
/// Tests if the specified rune is supported by the driver.
///
@@ -131,24 +203,6 @@ public interface IDriver
///
bool IsValidLocation (string text, int col, int row);
- ///
- /// Updates and to the specified column and row in
- /// .
- /// Used by and to determine
- /// where to add content.
- ///
- ///
- /// This does not move the cursor on the screen, it only updates the internal state of the driver.
- ///
- /// If or are negative or beyond
- /// and
- /// , the method still sets those properties.
- ///
- ///
- /// Column to move to.
- /// Row to move to.
- void Move (int col, int row);
-
/// Adds the specified rune to the display at the current cursor position.
///
///
@@ -189,14 +243,6 @@ public interface IDriver
/// String.
void AddStr (string str);
- /// Clears the of the driver.
- void ClearContents ();
-
- ///
- /// Fills the specified rectangle with the specified rune, using
- ///
- event EventHandler ClearedContents;
-
/// Fills the specified rectangle with the specified rune, using
///
/// The value of is honored. Any parts of the rectangle not in the clip will not be
@@ -214,44 +260,6 @@ public interface IDriver
///
void FillRect (Rectangle rect, char c);
- /// Gets the terminal cursor visibility.
- /// The current
- /// upon success
- bool GetCursorVisibility (out CursorVisibility visibility);
-
- ///
- /// INTERNAL: Updates the terminal with the current output buffer. Should not be used by applications. Drawing occurs
- /// once each Application main loop iteration.
- ///
- void Refresh ();
-
- /// Sets the terminal cursor visibility.
- /// The wished
- /// upon success
- bool SetCursorVisibility (CursorVisibility visibility);
-
- ///
- /// The event fired when the screen changes (size, position, etc.).
- /// is the source of truth for screen dimensions.
- ///
- event EventHandler? SizeChanged;
-
- /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.
- /// This is only implemented in UnixDriver.
- void Suspend ();
-
- ///
- /// Sets the position of the terminal cursor to and
- /// .
- ///
- void UpdateCursor ();
-
- /// Initializes the driver
- void Init ();
-
- /// Ends the execution of the console driver.
- void End ();
-
/// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.
/// Implementations should call base.SetAttribute(c).
/// C.
@@ -261,6 +269,55 @@ public interface IDriver
/// The current attribute.
Attribute GetAttribute ();
+ ///
+ /// Provide proper writing to send escape sequence recognized by the .
+ ///
+ ///
+ void WriteRaw (string ansi);
+
+ ///
+ /// 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.
+ ///
+ ConcurrentQueue GetSixels ();
+
+ ///
+ /// Gets a string representation of .
+ ///
+ ///
+ public string ToString ();
+
+ ///
+ /// Gets an ANSI escape sequence representation of . This is the
+ /// same output as would be written to the terminal to recreate the current screen contents.
+ ///
+ ///
+ public string ToAnsi ();
+
+ #endregion Drawing and Rendering
+
+ #region Cursor
+
+ ///
+ /// Sets the position of the terminal cursor to and
+ /// .
+ ///
+ void UpdateCursor ();
+
+ /// Gets the terminal cursor visibility.
+ /// The current
+ /// upon success
+ bool GetCursorVisibility (out CursorVisibility visibility);
+
+ /// Sets the terminal cursor visibility.
+ /// The wished
+ /// upon success
+ bool SetCursorVisibility (CursorVisibility visibility);
+
+ #endregion Cursor
+
+ #region Input Events
+
/// Event fired when a mouse event occurs.
event EventHandler? MouseEvent;
@@ -281,28 +338,15 @@ public interface IDriver
///
void EnqueueKeyEvent (Key key);
+ #endregion Input Events
+
+ #region ANSI Escape Sequences
+
///
/// Queues the given for execution
///
///
public void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
- ///
- /// Gets the for the driver
- ///
- ///
- public AnsiRequestScheduler GetRequestScheduler ();
-
- ///
- /// Gets a string representation of .
- ///
- ///
- public string ToString ();
-
- ///
- /// Gets an ANSI escape sequence representation of . This is the
- /// same output as would be written to the terminal to recreate the current screen contents.
- ///
- ///
- public string ToAnsi ();
+ #endregion ANSI Escape Sequences
}
diff --git a/Terminal.Gui/Drivers/IOutput.cs b/Terminal.Gui/Drivers/IOutput.cs
index 0eb647dec..d8ddc791e 100644
--- a/Terminal.Gui/Drivers/IOutput.cs
+++ b/Terminal.Gui/Drivers/IOutput.cs
@@ -1,4 +1,6 @@
-namespace Terminal.Gui.Drivers;
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.Drivers;
///
/// The low-level interface drivers implement to provide output capabilities; encapsulates platform-specific
@@ -6,6 +8,15 @@
///
public interface IOutput : IDisposable
{
+ ///
+ bool Force16Colors { get; set; }
+
+ ///
+ bool IsLegacyConsole { get; set; }
+
+ ///
+ ConcurrentQueue GetSixels ();
+
///
/// Gets the current position of the console cursor.
///
@@ -17,7 +28,7 @@ public interface IOutput : IDisposable
/// of characters not pixels).
///
///
- public Size GetSize ();
+ Size GetSize ();
///
/// Moves the console cursor to the given location.
diff --git a/Terminal.Gui/Drivers/OutputBase.cs b/Terminal.Gui/Drivers/OutputBase.cs
index d335c12c1..ebf403e66 100644
--- a/Terminal.Gui/Drivers/OutputBase.cs
+++ b/Terminal.Gui/Drivers/OutputBase.cs
@@ -1,3 +1,5 @@
+using System.Collections.Concurrent;
+
namespace Terminal.Gui.Drivers;
///
@@ -5,7 +7,44 @@ namespace Terminal.Gui.Drivers;
///
public abstract class OutputBase
{
- private CursorVisibility? _cachedCursorVisibility;
+ private bool _force16Colors;
+
+ ///
+ public bool Force16Colors
+ {
+ get => _force16Colors;
+ set
+ {
+ if (IsLegacyConsole && !value)
+ {
+ return;
+ }
+
+ _force16Colors = value;
+ }
+ }
+
+ private bool _isLegacyConsole;
+
+ ///
+ public bool IsLegacyConsole
+ {
+ get => _isLegacyConsole;
+ set
+ {
+ _isLegacyConsole = value;
+
+ if (value) // If legacy console (true), force 16 colors
+ {
+ Force16Colors = true;
+ }
+ }
+ }
+
+ private readonly ConcurrentQueue _sixels = [];
+
+ /// >
+ public ConcurrentQueue 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;
diff --git a/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs b/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs
index dfbf63ead..6c1366777 100644
--- a/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs
+++ b/Terminal.Gui/Drivers/UnixDriver/UnixOutput.cs
@@ -39,7 +39,7 @@ internal class UnixOutput : OutputBase, IOutput
///
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 ()));
diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs
index b351696a2..9ca53790a 100644
--- a/Terminal.Gui/Drivers/WindowsDriver/WindowsOutput.cs
+++ b/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
- {
- ReadOnlySpan span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
+ base.Write (outputBuffer);
- bool result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+ ReadOnlySpan span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
- if (!result)
+ bool result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+
+ if (!result)
+ {
+ int err = Marshal.GetLastWin32Error ();
+
+ if (err == 1)
{
- int err = Marshal.GetLastWin32Error ();
+ Logging.Error ($"Error: {Marshal.GetLastWin32Error ()} in {nameof (WindowsOutput)}");
- if (err == 1)
- {
- Logging.Logger.LogError ($"Error: {Marshal.GetLastWin32Error ()} in {nameof (WindowsOutput)}");
+ return;
+ }
- return;
- }
- if (err != 0)
- {
- throw new Win32Exception (err);
- }
+ if (err != 0)
+ {
+ 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,24 +335,21 @@ internal partial class WindowsOutput : OutputBase, IOutput
///
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)
+ {
+ Write (output);
+ output.Clear ();
+ var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
+ SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
+ }
+ else
{
output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
}
- else
- {
- var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
- SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
- }
}
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
///
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,16 +516,16 @@ internal partial class WindowsOutput : OutputBase, IOutput
_lastCursorPosition = new (col, row);
- if (_isVirtualTerminal)
+ if (IsLegacyConsole)
+ {
+ SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+ }
+ else
{
var sb = new StringBuilder ();
EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1);
Write (sb.ToString ());
}
- else
- {
- SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
- }
}
///
@@ -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;
}
diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json
index 141429fbb..bf515d26e 100644
--- a/Terminal.Gui/Resources/config.json
+++ b/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"
- },
+ }
}
}
],
diff --git a/Terminal.Gui/ViewBase/Adornment/Margin.cs b/Terminal.Gui/ViewBase/Adornment/Margin.cs
index 2c123796b..54e9c2a67 100644
--- a/Terminal.Gui/ViewBase/Adornment/Margin.cs
+++ b/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);
}
diff --git a/Terminal.Gui/ViewBase/View.Content.cs b/Terminal.Gui/ViewBase/View.Content.cs
index 8d6345a65..6214643f5 100644
--- a/Terminal.Gui/ViewBase/View.Content.cs
+++ b/Terminal.Gui/ViewBase/View.Content.cs
@@ -335,7 +335,7 @@ public partial class View
///
///
/// Altering the Viewport Size will eventually (when the view is next laid out) cause the
- /// and methods to be called.
+ /// and methods to be called.
///
///
public virtual Rectangle Viewport
diff --git a/Terminal.Gui/ViewBase/View.Drawing.cs b/Terminal.Gui/ViewBase/View.Drawing.cs
index 45fc26ed1..14893f9e3 100644
--- a/Terminal.Gui/ViewBase/View.Drawing.cs
+++ b/Terminal.Gui/ViewBase/View.Drawing.cs
@@ -568,7 +568,7 @@ public partial class View // Drawing APIs
///
///
/// Subscribe to this event to draw custom content for the View. Use the drawing methods available on
- /// such as , , and .
+ /// such as , , and .
///
///
/// The event is invoked after and have been drawn, but before any are drawn.
diff --git a/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs b/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs
index 907305471..5874bec71 100644
--- a/Terminal.Gui/Views/Color/ColorPicker.Prompt.cs
+++ b/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;
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index e43c465db..b2cac5dd1 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -287,7 +287,7 @@ public class ComboBox : View, IDesignable
public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); }
///
- 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);
diff --git a/Terminal.Gui/Views/Shortcut.cs b/Terminal.Gui/Views/Shortcut.cs
index 65d24ea8b..87d0f9f89 100644
--- a/Terminal.Gui/Views/Shortcut.cs
+++ b/Terminal.Gui/Views/Shortcut.cs
@@ -391,7 +391,7 @@ public class Shortcut : View, IOrientation, IDesignable
///
///
/// This example illustrates how to add a to a that toggles the
- /// property.
+ /// property.
///
///
/// 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);
///
diff --git a/Terminal.Gui/Views/Slider/Slider.cs b/Terminal.Gui/Views/Slider/Slider.cs
index b23001e15..0535beff0 100644
--- a/Terminal.Gui/Views/Slider/Slider.cs
+++ b/Terminal.Gui/Views/Slider/Slider.cs
@@ -779,7 +779,7 @@ public class Slider : View, IOrientation
#region Drawing
///
- protected override bool OnDrawingContent (DrawContext? context)
+ protected override bool OnDrawingContent (DrawContext context)
{
// TODO: make this more surgical to reduce repaint
diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs
index 98976be15..451636799 100644
--- a/Terminal.Gui/Views/TableView/TableView.cs
+++ b/Terminal.Gui/Views/TableView/TableView.cs
@@ -931,7 +931,7 @@ public class TableView : View, IDesignable
}
///
- protected override bool OnDrawingContent (DrawContext? context)
+ protected override bool OnDrawingContent (DrawContext context)
{
Move (0, 0);
diff --git a/Terminal.Gui/Views/TextInput/TextField.cs b/Terminal.Gui/Views/TextInput/TextField.cs
index 5d6b4e25c..ca9e6f519 100644
--- a/Terminal.Gui/Views/TextInput/TextField.cs
+++ b/Terminal.Gui/Views/TextInput/TextField.cs
@@ -922,7 +922,7 @@ public class TextField : View, IDesignable
}
///
- protected override bool OnDrawingContent (DrawContext? context)
+ protected override bool OnDrawingContent (DrawContext context)
{
_isDrawing = true;
diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs
index a167ccc10..a0ca872c1 100644
--- a/Terminal.Gui/Views/TreeView/TreeView.cs
+++ b/Terminal.Gui/Views/TreeView/TreeView.cs
@@ -1148,7 +1148,7 @@ public class TreeView : View, ITreeView where T : class
public event EventHandler> ObjectActivated;
///
- protected override bool OnDrawingContent (DrawContext? context)
+ protected override bool OnDrawingContent (DrawContext context)
{
if (roots is null)
{
diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings
index 7d566d39e..a33c71b8d 100644
--- a/Terminal.sln.DotSettings
+++ b/Terminal.sln.DotSettings
@@ -414,6 +414,9 @@
True
5
True
+ True
+ True
+
True
True
True
diff --git a/Tests/UnitTests/Application/SynchronizatonContextTests.cs b/Tests/UnitTests/Application/SynchronizatonContextTests.cs
index 39fee532f..019c9ba6b 100644
--- a/Tests/UnitTests/Application/SynchronizatonContextTests.cs
+++ b/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)
diff --git a/Tests/UnitTests/Configuration/SourcesManagerTests.cs b/Tests/UnitTests/Configuration/SourcesManagerTests.cs
index 20e750ab0..b80b989d2 100644
--- a/Tests/UnitTests/Configuration/SourcesManagerTests.cs
+++ b/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).
+ }
}
diff --git a/Tests/UnitTests/FakeDriverBase.cs b/Tests/UnitTests/FakeDriverBase.cs
index 0e6011e34..9647a4194 100644
--- a/Tests/UnitTests/FakeDriverBase.cs
+++ b/Tests/UnitTests/FakeDriverBase.cs
@@ -4,7 +4,7 @@ namespace UnitTests;
/// Enables tests to create a FakeDriver for testing purposes.
///
[Collection ("Global Test Setup")]
-public abstract class FakeDriverBase /*: IDisposable*/
+public abstract class FakeDriverBase/* : IDisposable*/
{
///
/// Creates a new FakeDriver instance with the specified buffer size.
diff --git a/Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs b/Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs
index 65b76bde6..4e2ec8bb0 100644
--- a/Tests/UnitTestsParallelizable/Application/NestedRunTimeoutTests.cs
+++ b/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!");
diff --git a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs
index 1bca6304e..1af739791 100644
--- a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs
+++ b/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
diff --git a/Tests/UnitTestsParallelizable/Drawing/AttributeTests.cs b/Tests/UnitTestsParallelizable/Drawing/AttributeTests.cs
index 9b6e7b547..3f428abc4 100644
--- a/Tests/UnitTestsParallelizable/Drawing/AttributeTests.cs
+++ b/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]
diff --git a/Tests/UnitTestsParallelizable/Drawing/CellTests.cs b/Tests/UnitTestsParallelizable/Drawing/CellTests.cs
index f6da2e852..9383e4592 100644
--- a/Tests/UnitTestsParallelizable/Drawing/CellTests.cs
+++ b/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 (() => new Cell () { Grapheme = s });
}
-
}
diff --git a/Tests/UnitTestsParallelizable/Drawing/SixelEncoderTests.cs b/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelEncoderTests.cs
similarity index 74%
rename from Tests/UnitTestsParallelizable/Drawing/SixelEncoderTests.cs
rename to Tests/UnitTestsParallelizable/Drawing/Sixel/SixelEncoderTests.cs
index 3a1ed881a..bf48046c2 100644
--- a/Tests/UnitTestsParallelizable/Drawing/SixelEncoderTests.cs
+++ b/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));
+ }
}
diff --git a/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportDetectorTests.cs b/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportDetectorTests.cs
new file mode 100644
index 000000000..1ee4f5a9b
--- /dev/null
+++ b/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 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 ()))
+ .Callback (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 ()), Times.AtLeast (2));
+ }
+
+ [Fact]
+ public void Detect_DoesNotSetSupported_WhenDeviceAttributesDoNotContain4()
+ {
+ // Arrange
+ var driverMock = new Mock(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 ()))
+ .Callback (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 ()), 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 driverMock = new (
+ MockBehavior.Strict,
+ new FakeInputProcessor (null!),
+ new OutputBufferImpl (),
+ output,
+ new AnsiRequestScheduler (new AnsiResponseParser ()),
+ new SizeMonitorImpl (output)
+ );
+ driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny ()))
+ .Callback (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 ()), 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 driverMock = new (
+ MockBehavior.Strict,
+ new FakeInputProcessor (null!),
+ new OutputBufferImpl (),
+ output,
+ new AnsiRequestScheduler (new AnsiResponseParser ()),
+ new SizeMonitorImpl (output)
+ );
+
+ driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny ()))
+ .Callback (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);
+ }
+}
diff --git a/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportResultTests.cs b/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelSupportResultTests.cs
new file mode 100644
index 000000000..6127bff2a
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelToRenderTests.cs b/Tests/UnitTestsParallelizable/Drawing/Sixel/SixelToRenderTests.cs
new file mode 100644
index 000000000..af65ac3f1
--- /dev/null
+++ b/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 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 ()))
+ .Callback (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 ()), Times.AtLeast (2));
+ }
+
+ [Fact]
+ public void Detect_WhenDirectResolutionFails_ComputesResolutionFromWindowSizes ()
+ {
+ // Arrange
+ Mock 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 ()))
+ .Callback (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 ()), Times.AtLeast (3));
+ }
+
+ [Fact]
+ public void Detect_WhenDeviceAttributesDoNotIndicateSupport_ReturnsNotSupported ()
+ {
+ // Arrange
+ Mock 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 ()))
+ .Callback (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 ()), 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 driverMock = new (
+ MockBehavior.Strict,
+ new FakeInputProcessor (null!),
+ new OutputBufferImpl (),
+ output,
+ new AnsiRequestScheduler (new AnsiResponseParser ()),
+ new SizeMonitorImpl (output)
+ );
+
+ driverMock.Setup (d => d.QueueAnsiRequest (It.IsAny ()))
+ .Callback (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 ()), Times.AtLeastOnce ());
+ }
+ finally
+ {
+ // Restore environment
+ Environment.SetEnvironmentVariable ("XTERM_VERSION", prev);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs b/Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
index f0596fc83..2c2aa0aaa 100644
--- a/Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
+++ b/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 ();
}
}
diff --git a/Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs b/Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs
index 917f36f4f..cdf980343 100644
--- a/Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs
+++ b/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
diff --git a/Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs b/Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs
index 83d133a93..ad8ede54d 100644
--- a/Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs
+++ b/Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs
@@ -14,6 +14,6 @@ public class DriverColorTests : FakeDriverBase
driver.Force16Colors = true;
Assert.True (driver.Force16Colors);
- driver.End ();
+ driver.Dispose ();
}
}
diff --git a/Tests/UnitTestsParallelizable/Drivers/DriverTests.cs b/Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
index 928dd923b..38a40ab11 100644
--- a/Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
+++ b/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]
diff --git a/Tests/UnitTestsParallelizable/Drivers/LegacyConsoleTests.cs b/Tests/UnitTestsParallelizable/Drivers/LegacyConsoleTests.cs
new file mode 100644
index 000000000..9d6b8b457
--- /dev/null
+++ b/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);
+ }
+}
diff --git a/Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs b/Tests/UnitTestsParallelizable/Drivers/OutputBaseTests.cs
new file mode 100644
index 000000000..20f8cd81b
--- /dev/null
+++ b/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 ();
+ }
+}
\ No newline at end of file
diff --git a/Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs b/Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs
index 7db07eeca..fb74998bd 100644
--- a/Tests/UnitTestsParallelizable/Drivers/ToAnsiTests.cs
+++ b/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]
diff --git a/Tests/UnitTestsParallelizable/Drivers/Windows/WindowsKeyConverterTests.cs b/Tests/UnitTestsParallelizable/Drivers/Windows/WindowsKeyConverterTests.cs
index 2cad545da..0c4e022ea 100644
--- a/Tests/UnitTestsParallelizable/Drivers/Windows/WindowsKeyConverterTests.cs
+++ b/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 '_'
diff --git a/Tests/UnitTestsParallelizable/Text/StringTests.cs b/Tests/UnitTestsParallelizable/Text/StringTests.cs
index 1c6e848cd..51525992c 100644
--- a/Tests/UnitTestsParallelizable/Text/StringTests.cs
+++ b/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]
diff --git a/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs b/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs
index b7f580cf9..107024fa5 100644
--- a/Tests/UnitTestsParallelizable/Text/TextFormatterTests.cs
+++ b/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 ();
}
}
diff --git a/Tests/UnitTestsParallelizable/ViewBase/Layout/Pos.CenterTests.cs b/Tests/UnitTestsParallelizable/ViewBase/Layout/Pos.CenterTests.cs
index a7fc4783d..006d9c5e2 100644
--- a/Tests/UnitTestsParallelizable/ViewBase/Layout/Pos.CenterTests.cs
+++ b/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 ();
}
}
diff --git a/docfx/docs/application.md b/docfx/docs/application.md
index 634e2ae60..86e08c185 100644
--- a/docfx/docs/application.md
+++ b/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 ();
}
-// SIMPLEST (manual disposal):
+// ALTERNATIVE (manual disposal):
IApplication app = Application.Create ().Init ();
app.Run ();
Color? result = app.GetResult ();
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
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 ())
{
- ///
- /// Gets the driver for this view.
- ///
- public IDriver? Driver => GetDriver ();
-
- ///
- /// Gets the driver, checking application context if needed.
- /// Override to customize driver resolution.
- ///
- public virtual IDriver? GetDriver () => App?.Driver;
+ app.ForceDriver = "fake";
+ app.Init ();
}
+
+// ALTERNATIVE: Set on legacy static Application (obsolete)
+Application.ForceDriver = "dotnet";
+Application.Init ();
```
-**Usage:**
+**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 types, List 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
+```
+
+**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 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
diff --git a/docfx/docs/drivers.md b/docfx/docs/drivers.md
index df57efcfd..4eb90d179 100644
--- a/docfx/docs/drivers.md
+++ b/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
-// Method 2: Pass driver name to Init
-Application.Init(driverName: "unix");
-
-// Method 3: Pass a custom IDriver instance
-var customDriver = new MyCustomDriver();
-Application.Init(driver: customDriver);
+```json
+{
+ "ForceDriver": "fake"
+}
```
-Valid driver names: `"dotnet"`, `"windows"`, `"unix"`, `"fake"`
+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
+
+Use `GetDriverTypes()` to discover which drivers are available at runtime:
+
+```csharp
+(List driverTypes, List driverNames) = Application.GetDriverTypes();
+
+Console.WriteLine("Available drivers:");
+foreach (string? name in driverNames)
+{
+ Console.WriteLine($" - {name}");
+}
+
+// Output:
+// Available drivers:
+// - 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:
-- **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`
+#### 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
+
+#### 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)