diff --git a/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs b/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs
index 4750a32e87..39dee104cb 100644
--- a/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs
+++ b/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs
@@ -96,6 +96,11 @@ public NetWinVTConsole ()
public void Cleanup ()
{
+ if (!FlushConsoleInputBuffer (_inputHandle))
+ {
+ throw new ApplicationException ($"Failed to flush input buffer, error code: {GetLastError ()}.");
+ }
+
if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
{
throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
@@ -123,4 +128,7 @@ public void Cleanup ()
[DllImport ("kernel32.dll")]
private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+ [DllImport ("kernel32.dll", SetLastError = true)]
+ private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
}
diff --git a/Terminal.Gui/Drivers/V2/IOutputBuffer.cs b/Terminal.Gui/Drivers/V2/IOutputBuffer.cs
index 80f33a0c09..6f51ab1ad7 100644
--- a/Terminal.Gui/Drivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/Drivers/V2/IOutputBuffer.cs
@@ -9,12 +9,6 @@ namespace Terminal.Gui.Drivers;
///
public interface IOutputBuffer
{
- ///
- /// As performance is a concern, we keep track of the dirty lines and only refresh those.
- /// This is in addition to the dirty flag on each cell.
- ///
- public bool [] DirtyLines { get; }
-
///
/// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
///
diff --git a/Terminal.Gui/Drivers/V2/InputProcessor.cs b/Terminal.Gui/Drivers/V2/InputProcessor.cs
index 607ad3a23e..c860ba796e 100644
--- a/Terminal.Gui/Drivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/Drivers/V2/InputProcessor.cs
@@ -162,4 +162,43 @@ private IEnumerable ReleaseParserHeldKeysIfStale ()
///
///
protected abstract void ProcessAfterParsing (T input);
+
+ internal char _highSurrogate = '\0';
+
+ internal bool IsValidInput (Key key, out Key result)
+ {
+ result = key;
+
+ if (char.IsHighSurrogate ((char)key))
+ {
+ _highSurrogate = (char)key;
+
+ return false;
+ }
+
+ if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key))
+ {
+ result = (KeyCode)new Rune (_highSurrogate, (char)key).Value;
+ _highSurrogate = '\0';
+
+ return true;
+ }
+
+ if (char.IsSurrogate ((char)key))
+ {
+ return false;
+ }
+
+ if (_highSurrogate > 0)
+ {
+ _highSurrogate = '\0';
+ }
+
+ if (key.KeyCode == 0)
+ {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs
index a2cc34c497..a70b089567 100644
--- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs
@@ -25,7 +25,6 @@ internal class MainLoopCoordinator : IMainLoopCoordinator
private ConsoleDriverFacade _facade;
private Task _inputTask;
private readonly ITimedEvents _timedEvents;
- private readonly bool _isWindowsTerminal;
private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
@@ -61,7 +60,6 @@ IMainLoop loop
_inputProcessor = inputProcessor;
_outputFactory = outputFactory;
_loop = loop;
- _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
}
///
@@ -162,11 +160,6 @@ private void BuildFacadeIfPossible ()
_loop.AnsiRequestScheduler,
_loop.WindowSizeMonitor);
- if (!_isWindowsTerminal)
- {
- Application.Force16Colors = _facade.Force16Colors = true;
- }
-
Application.Driver = _facade;
_startupSemaphore.Release ();
diff --git a/Terminal.Gui/Drivers/V2/NetInput.cs b/Terminal.Gui/Drivers/V2/NetInput.cs
index 518c77e557..10b5e1cf65 100644
--- a/Terminal.Gui/Drivers/V2/NetInput.cs
+++ b/Terminal.Gui/Drivers/V2/NetInput.cs
@@ -40,6 +40,12 @@ public NetInput ()
}
}
+ //Enable alternative screen buffer.
+ Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+ //Set cursor key to application.
+ Console.Out.Write (EscSeqUtils.CSI_HideCursor);
+
Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
Console.TreatControlCAsInput = true;
}
@@ -68,8 +74,14 @@ protected override IEnumerable Read ()
public override void Dispose ()
{
base.Dispose ();
- _adjustConsole?.Cleanup ();
-
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+ //Disable alternative screen buffer.
+ Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+ //Set cursor key to cursor.
+ Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+ _adjustConsole?.Cleanup ();
}
}
diff --git a/Terminal.Gui/Drivers/V2/NetInputProcessor.cs b/Terminal.Gui/Drivers/V2/NetInputProcessor.cs
index 4e11d402a4..36c885e355 100644
--- a/Terminal.Gui/Drivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/Drivers/V2/NetInputProcessor.cs
@@ -41,8 +41,13 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
protected override void ProcessAfterParsing (ConsoleKeyInfo input)
{
var key = KeyConverter.ToKey (input);
- OnKeyDown (key);
- OnKeyUp (key);
+
+ // If the key is not valid, we don't want to raise any events.
+ if (IsValidInput (key, out key))
+ {
+ OnKeyDown (key);
+ OnKeyUp (key);
+ }
}
/* For building test cases */
diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs
index 32ae497645..17956a3dfa 100644
--- a/Terminal.Gui/Drivers/V2/NetOutput.cs
+++ b/Terminal.Gui/Drivers/V2/NetOutput.cs
@@ -6,15 +6,10 @@ namespace Terminal.Gui.Drivers;
/// Implementation of that uses native dotnet
/// methods e.g.
///
-public class NetOutput : IConsoleOutput
+public class NetOutput : OutputBase, IConsoleOutput
{
private readonly bool _isWinPlatform;
- private CursorVisibility? _cachedCursorVisibility;
-
- // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
- private TextStyle _redrawTextStyle = TextStyle.None;
-
///
/// Creates a new instance of the class.
///
@@ -30,176 +25,10 @@ public NetOutput ()
{
_isWinPlatform = true;
}
-
- //Enable alternative screen buffer.
- Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
- //Set cursor key to application.
- Console.Out.Write (EscSeqUtils.CSI_HideCursor);
}
///
- public void Write (ReadOnlySpan text)
- {
- Console.Out.Write (text);
- }
-
- ///
- public void Write (IOutputBuffer buffer)
- {
- if (ConsoleDriver.RunningUnitTests)
- {
- return;
- }
-
- if (Console.WindowHeight < 1
- || buffer.Contents.Length != buffer.Rows * buffer.Cols
- || buffer.Rows != Console.WindowHeight)
- {
- // return;
- }
-
- var top = 0;
- var left = 0;
- int rows = buffer.Rows;
- int cols = buffer.Cols;
- var output = new StringBuilder ();
- Attribute? redrawAttr = null;
- int lastCol = -1;
-
- CursorVisibility? savedVisibility = _cachedCursorVisibility;
- SetCursorVisibility (CursorVisibility.Invisible);
-
- const int maxCharsPerRune = 2;
- Span runeBuffer = stackalloc char[maxCharsPerRune];
-
- for (int row = top; row < rows; row++)
- {
- if (Console.WindowHeight < 1)
- {
- return;
- }
-
- if (!buffer.DirtyLines [row])
- {
- continue;
- }
-
- if (!SetCursorPositionImpl (0, row))
- {
- return;
- }
-
- buffer.DirtyLines [row] = false;
- output.Clear ();
-
- for (int col = left; col < cols; col++)
- {
- lastCol = -1;
- var outputWidth = 0;
-
- for (; col < cols; col++)
- {
- if (!buffer.Contents [row, col].IsDirty)
- {
- if (output.Length > 0)
- {
- WriteToConsole (output, ref lastCol, row, ref outputWidth);
- }
- else if (lastCol == -1)
- {
- lastCol = col;
- }
-
- if (lastCol + 1 < cols)
- {
- lastCol++;
- }
-
- continue;
- }
-
- if (lastCol == -1)
- {
- lastCol = col;
- }
-
- Attribute attr = buffer.Contents [row, col].Attribute.Value;
-
- // Performance: Only send the escape sequence if the attribute has changed.
- if (attr != redrawAttr)
- {
- redrawAttr = attr;
-
- EscSeqUtils.CSI_AppendForegroundColorRGB (
- output,
- attr.Foreground.R,
- attr.Foreground.G,
- attr.Foreground.B
- );
-
- EscSeqUtils.CSI_AppendBackgroundColorRGB (
- output,
- attr.Background.R,
- attr.Background.G,
- attr.Background.B
- );
-
- EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style);
-
- _redrawTextStyle = attr.Style;
- }
-
- outputWidth++;
-
- // Avoid Rune.ToString() by appending the rune chars.
- Rune rune = buffer.Contents [row, col].Rune;
- int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
- ReadOnlySpan runeChars = runeBuffer[..runeCharsWritten];
- output.Append (runeChars);
-
- if (buffer.Contents [row, col].CombiningMarks.Count > 0)
- {
- // AtlasEngine does not support NON-NORMALIZED combining marks in a way
- // compatible with the driver architecture. Any CMs (except in the first col)
- // are correctly combined with the base char, but are ALSO treated as 1 column
- // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
- //
- // For now, we just ignore the list of CMs.
- //foreach (var combMark in Contents [row, col].CombiningMarks) {
- // output.Append (combMark);
- //}
- // WriteToConsole (output, ref lastCol, row, ref outputWidth);
- }
- else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
- {
- WriteToConsole (output, ref lastCol, row, ref outputWidth);
- SetCursorPositionImpl (col - 1, row);
- }
-
- buffer.Contents [row, col].IsDirty = false;
- }
- }
-
- if (output.Length > 0)
- {
- SetCursorPositionImpl (lastCol, row);
- Console.Out.Write (output);
- }
- }
-
- foreach (SixelToRender s in Application.Sixel)
- {
- if (!string.IsNullOrWhiteSpace (s.SixelData))
- {
- SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
- Console.Out.Write (s.SixelData);
- }
- }
-
- SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
- _cachedCursorVisibility = savedVisibility;
- }
+ public void Write (ReadOnlySpan text) { Console.Out.Write (text); }
///
public Size GetWindowSize ()
@@ -213,23 +42,37 @@ public Size GetWindowSize ()
return new (Console.WindowWidth, Console.WindowHeight);
}
- private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+ ///
+ public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
+
+ private Point? _lastCursorPosition;
+
+ ///
+ protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
{
- SetCursorPositionImpl (lastCol, row);
- Console.Out.Write (output);
- output.Clear ();
- lastCol += outputWidth;
- outputWidth = 0;
+ EscSeqUtils.CSI_AppendForegroundColorRGB (
+ output,
+ attr.Foreground.R,
+ attr.Foreground.G,
+ attr.Foreground.B
+ );
+
+ EscSeqUtils.CSI_AppendBackgroundColorRGB (
+ output,
+ attr.Background.R,
+ attr.Background.G,
+ attr.Background.B
+ );
+
+ EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
}
///
- public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
+ protected override void Write (StringBuilder output) { Console.Out.Write (output); }
- private Point _lastCursorPosition;
-
- private bool SetCursorPositionImpl (int col, int row)
+ protected override bool SetCursorPositionImpl (int col, int row)
{
- if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+ if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
{
return true;
}
@@ -259,21 +102,10 @@ private bool SetCursorPositionImpl (int col, int row)
}
///
- public void Dispose ()
- {
- Console.ResetColor ();
-
- //Disable alternative screen buffer.
- Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
- //Set cursor key to cursor.
- Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-
- Console.Out.Close ();
- }
+ public void Dispose () { }
///
- public void SetCursorVisibility (CursorVisibility visibility)
+ public override void SetCursorVisibility (CursorVisibility visibility)
{
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
}
diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs
new file mode 100644
index 0000000000..b28551e4bd
--- /dev/null
+++ b/Terminal.Gui/Drivers/V2/OutputBase.cs
@@ -0,0 +1,163 @@
+namespace Terminal.Gui.Drivers;
+
+public abstract class OutputBase
+{
+ private CursorVisibility? _cachedCursorVisibility;
+
+ // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
+ private TextStyle _redrawTextStyle = TextStyle.None;
+
+ ///
+ public virtual void Write (IOutputBuffer buffer)
+ {
+ if (ConsoleDriver.RunningUnitTests)
+ {
+ return;
+ }
+
+ if (Console.WindowHeight < 1
+ || buffer.Contents.Length != buffer.Rows * buffer.Cols
+ || buffer.Rows != Console.WindowHeight)
+ {
+ // return;
+ }
+
+ var top = 0;
+ var left = 0;
+ int rows = buffer.Rows;
+ int cols = buffer.Cols;
+ var output = new StringBuilder ();
+ Attribute? redrawAttr = null;
+ int lastCol = -1;
+
+ CursorVisibility? savedVisibility = _cachedCursorVisibility;
+ SetCursorVisibility (CursorVisibility.Invisible);
+
+ const int maxCharsPerRune = 2;
+ Span runeBuffer = stackalloc char [maxCharsPerRune];
+
+ for (int row = top; row < rows; row++)
+ {
+ if (Console.WindowHeight < 1)
+ {
+ return;
+ }
+
+ if (!SetCursorPositionImpl (0, row))
+ {
+ return;
+ }
+
+ output.Clear ();
+
+ for (int col = left; col < cols; col++)
+ {
+ lastCol = -1;
+ var outputWidth = 0;
+
+ for (; col < cols; col++)
+ {
+ if (!buffer.Contents [row, col].IsDirty)
+ {
+ if (output.Length > 0)
+ {
+ WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ }
+ else if (lastCol == -1)
+ {
+ lastCol = col;
+ }
+
+ if (lastCol + 1 < cols)
+ {
+ lastCol++;
+ }
+
+ continue;
+ }
+
+ if (lastCol == -1)
+ {
+ lastCol = col;
+ }
+
+ Attribute attr = buffer.Contents [row, col].Attribute.Value;
+
+ // Performance: Only send the escape sequence if the attribute has changed.
+ if (attr != redrawAttr)
+ {
+ redrawAttr = attr;
+
+ AppendOrWriteAttribute (output, attr, _redrawTextStyle);
+
+ _redrawTextStyle = attr.Style;
+ }
+
+ outputWidth++;
+
+ // Avoid Rune.ToString() by appending the rune chars.
+ Rune rune = buffer.Contents [row, col].Rune;
+ int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
+ ReadOnlySpan runeChars = runeBuffer [..runeCharsWritten];
+ output.Append (runeChars);
+
+ if (buffer.Contents [row, col].CombiningMarks.Count > 0)
+ {
+ // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+ // compatible with the driver architecture. Any CMs (except in the first col)
+ // are correctly combined with the base char, but are ALSO treated as 1 column
+ // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
+ //
+ // For now, we just ignore the list of CMs.
+ //foreach (var combMark in Contents [row, col].CombiningMarks) {
+ // output.Append (combMark);
+ //}
+ // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ }
+ else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+ {
+ WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ SetCursorPositionImpl (col - 1, row);
+ }
+
+ buffer.Contents [row, col].IsDirty = false;
+ }
+ }
+
+ if (output.Length > 0)
+ {
+ SetCursorPositionImpl (lastCol, row);
+ Write (output);
+ }
+ }
+
+ foreach (SixelToRender s in Application.Sixel)
+ {
+ if (!string.IsNullOrWhiteSpace (s.SixelData))
+ {
+ SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
+ Console.Out.Write (s.SixelData);
+ }
+ }
+
+ SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
+ _cachedCursorVisibility = savedVisibility;
+ }
+
+ protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
+
+ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+ {
+ SetCursorPositionImpl (lastCol, row);
+ Write (output);
+ output.Clear ();
+ lastCol += outputWidth;
+ outputWidth = 0;
+ }
+
+ protected abstract void Write (StringBuilder output);
+
+ protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
+
+ public abstract void SetCursorVisibility (CursorVisibility visibility);
+}
diff --git a/Terminal.Gui/Drivers/V2/WindowsInput.cs b/Terminal.Gui/Drivers/V2/WindowsInput.cs
index 11d01cb608..6207e18864 100644
--- a/Terminal.Gui/Drivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/Drivers/V2/WindowsInput.cs
@@ -4,7 +4,7 @@
namespace Terminal.Gui.Drivers;
-internal class WindowsInput : ConsoleInput, IWindowsInput
+internal class WindowsInput : ConsoleInput, IWindowsInput
{
private readonly nint _inputHandle;
@@ -35,6 +35,9 @@ out uint lpNumberOfEventsRead
private readonly uint _originalConsoleMode;
+ [DllImport ("kernel32.dll", SetLastError = true)]
+ private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
+
public WindowsInput ()
{
Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
@@ -50,16 +53,16 @@ public WindowsInput ()
_originalConsoleMode = v;
uint newConsoleMode = _originalConsoleMode;
- newConsoleMode |= (uint)(WindowsConsole.ConsoleModes.EnableMouseInput | WindowsConsole.ConsoleModes.EnableExtendedFlags);
- newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableQuickEditMode;
- newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableProcessedInput;
+ newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
+ newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
+ newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
SetConsoleMode (_inputHandle, newConsoleMode);
}
protected override bool Peek ()
{
const int bufferSize = 1; // We only need to check if there's at least one event
- nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize);
+ nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize);
try
{
@@ -89,10 +92,10 @@ protected override bool Peek ()
}
}
- protected override IEnumerable Read ()
+ protected override IEnumerable Read ()
{
const int bufferSize = 1;
- nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize);
+ nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf () * bufferSize);
try
{
@@ -104,7 +107,7 @@ protected override bool Peek ()
return numberEventsRead == 0
? []
- : new [] { Marshal.PtrToStructure (pRecord) };
+ : new [] { Marshal.PtrToStructure (pRecord) };
}
catch (Exception)
{
@@ -123,6 +126,11 @@ public override void Dispose ()
return;
}
+ if (!FlushConsoleInputBuffer (_inputHandle))
+ {
+ throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
+ }
+
SetConsoleMode (_inputHandle, _originalConsoleMode);
}
}
diff --git a/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs
index cdd2e1dfe4..aa50b20b43 100644
--- a/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs
@@ -71,7 +71,8 @@ protected override void ProcessAfterParsing (InputRecord input)
{
var key = KeyConverter.ToKey (input);
- if (key != (Key)0)
+ // If the key is not valid, we don't want to raise any events.
+ if (IsValidInput (key, out key))
{
OnKeyDown (key!);
OnKeyUp (key!);
@@ -82,10 +83,29 @@ public MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord e)
{
var mouseFlags = MouseFlags.None;
- mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button1Pressed, MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
- mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
- mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
-
+ mouseFlags = UpdateMouseFlags (
+ mouseFlags,
+ e.ButtonState,
+ WindowsConsole.ButtonState.Button1Pressed,
+ MouseFlags.Button1Pressed,
+ MouseFlags.Button1Released,
+ 0);
+
+ mouseFlags = UpdateMouseFlags (
+ mouseFlags,
+ e.ButtonState,
+ WindowsConsole.ButtonState.Button2Pressed,
+ MouseFlags.Button2Pressed,
+ MouseFlags.Button2Released,
+ 1);
+
+ mouseFlags = UpdateMouseFlags (
+ mouseFlags,
+ e.ButtonState,
+ WindowsConsole.ButtonState.Button4Pressed,
+ MouseFlags.Button4Pressed,
+ MouseFlags.Button4Released,
+ 3);
// Deal with button 3 separately because it is considered same as 'rightmost button'
if (e.ButtonState.HasFlag (WindowsConsole.ButtonState.Button3Pressed) || e.ButtonState.HasFlag (WindowsConsole.ButtonState.RightmostButtonPressed))
diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs
index fb8e26815f..5152d3a238 100644
--- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs
@@ -1,13 +1,12 @@
#nullable enable
-using System.Buffers;
using System.ComponentModel;
using System.Runtime.InteropServices;
+using System.Text;
using Microsoft.Extensions.Logging;
-using static Terminal.Gui.Drivers.WindowsConsole;
namespace Terminal.Gui.Drivers;
-internal partial class WindowsOutput : IConsoleOutput
+internal partial class WindowsOutput : OutputBase, IConsoleOutput
{
[LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs (UnmanagedType.Bool)]
@@ -19,11 +18,15 @@ private static partial bool WriteConsole (
nint lpReserved
);
- [DllImport ("kernel32.dll", SetLastError = true)]
- private static extern bool CloseHandle (nint handle);
+ [LibraryImport ("kernel32.dll", SetLastError = true)]
+ private static partial nint GetStdHandle (int nStdHandle);
- [DllImport ("kernel32.dll", SetLastError = true)]
- private static extern nint CreateConsoleScreenBuffer (
+ [LibraryImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static partial bool CloseHandle (nint handle);
+
+ [LibraryImport ("kernel32.dll", SetLastError = true)]
+ private static partial nint CreateConsoleScreenBuffer (
DesiredAccess dwDesiredAccess,
ShareMode dwShareMode,
nint secutiryAttributes,
@@ -32,6 +35,7 @@ nint screenBufferData
);
[DllImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi);
[Flags]
@@ -50,19 +54,52 @@ private enum DesiredAccess : uint
internal static nint INVALID_HANDLE_VALUE = new (-1);
- [DllImport ("kernel32.dll", SetLastError = true)]
- private static extern bool SetConsoleActiveScreenBuffer (nint handle);
+ [LibraryImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static partial bool SetConsoleActiveScreenBuffer (nint handle);
- [DllImport ("kernel32.dll")]
- private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
+ [LibraryImport ("kernel32.dll")]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static partial bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
[DllImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo);
- private readonly nint _screenBuffer;
+ [LibraryImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ public static partial bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes);
+
+ [LibraryImport ("kernel32.dll")]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static partial bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
+
+ [LibraryImport ("kernel32.dll")]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static partial bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+ [LibraryImport ("kernel32.dll", SetLastError = true)]
+ private static partial WindowsConsole.Coord GetLargestConsoleWindowSize (
+ nint hConsoleOutput
+ );
+
+ [DllImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo);
+
+ [DllImport ("kernel32.dll", SetLastError = true)]
+ [return: MarshalAs (UnmanagedType.Bool)]
+ private static extern bool SetConsoleWindowInfo (
+ nint hConsoleOutput,
+ bool bAbsolute,
+ [In] ref WindowsConsole.SmallRect lpConsoleWindow
+ );
- // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
- private TextStyle _redrawTextStyle = TextStyle.None;
+ private const int STD_OUTPUT_HANDLE = -11;
+ private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
+ private readonly nint _outputHandle;
+ private nint _screenBuffer;
+ private readonly bool _isVirtualTerminal;
public WindowsOutput ()
{
@@ -73,13 +110,48 @@ public WindowsOutput ()
return;
}
+ // Get the standard output handle which is the current screen buffer.
+ _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+ GetConsoleMode (_outputHandle, out uint mode);
+ _isVirtualTerminal = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
+
+ if (_isVirtualTerminal)
+ {
+ //Enable alternative screen buffer.
+ Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+ }
+ else
+ {
+ CreateScreenBuffer ();
+
+ if (!GetConsoleMode (_screenBuffer, out mode))
+ {
+ throw new ApplicationException ($"Failed to get screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
+ }
+
+ const uint ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002;
+
+ mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; // Disable wrap
+
+ if (!SetConsoleMode (_screenBuffer, mode))
+ {
+ throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
+ }
+
+ // Force 16 colors if not in virtual terminal mode.
+ Application.Force16Colors = true;
+ }
+ }
+
+ private void CreateScreenBuffer ()
+ {
_screenBuffer = CreateConsoleScreenBuffer (
- DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
- ShareMode.FileShareRead | ShareMode.FileShareWrite,
- nint.Zero,
- 1,
- nint.Zero
- );
+ DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
+ ShareMode.FileShareRead | ShareMode.FileShareWrite,
+ nint.Zero,
+ 1,
+ nint.Zero
+ );
if (_screenBuffer == INVALID_HANDLE_VALUE)
{
@@ -99,212 +171,229 @@ public WindowsOutput ()
public void Write (ReadOnlySpan str)
{
- if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
+ if (!WriteConsole (_isVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
{
throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
}
}
- public void Write (IOutputBuffer buffer)
+ public Size ResizeBuffer (Size size)
{
- WindowsConsole.ExtendedCharInfo [] outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols];
+ Size newSize = SetConsoleWindow (
+ (short)Math.Max (size.Width, 0),
+ (short)Math.Max (size.Height, 0));
- // TODO: probably do need this right?
- /*
- if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
+ return newSize;
+ }
+
+ internal Size SetConsoleWindow (short cols, short rows)
+ {
+ var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
+ csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+ if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
{
- return;
- }*/
+ throw new Win32Exception (Marshal.GetLastWin32Error ());
+ }
- var bufferCoords = new WindowsConsole.Coord
+ WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+ short newCols = Math.Min (cols, maxWinSize.X);
+ short newRows = Math.Min (rows, maxWinSize.Y);
+ csbi.dwSize = new (newCols, Math.Max (newRows, (short)1));
+ csbi.srWindow = new (0, 0, newCols, newRows);
+ csbi.dwMaximumWindowSize = new (newCols, newRows);
+
+ if (!SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
{
- X = (short)buffer.Cols, //Clip.Width,
- Y = (short)buffer.Rows //Clip.Height
- };
+ throw new Win32Exception (Marshal.GetLastWin32Error ());
+ }
+
+ var winRect = new WindowsConsole.SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
- for (var row = 0; row < buffer.Rows; row++)
+ if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
{
- if (!buffer.DirtyLines [row])
- {
- continue;
- }
+ //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+ return new (cols, rows);
+ }
- buffer.DirtyLines [row] = false;
+ SetConsoleOutputWindow (csbi);
- for (var col = 0; col < buffer.Cols; col++)
- {
- int position = row * buffer.Cols + col;
- outputBuffer [position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault ();
+ return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
+ }
- if (buffer.Contents [row, col].IsDirty == false)
- {
- outputBuffer [position].Empty = true;
- outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+ private void SetConsoleOutputWindow (WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi)
+ {
+ if ((_isVirtualTerminal
+ ? _outputHandle
+ : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
+ {
+ throw new Win32Exception (Marshal.GetLastWin32Error ());
+ }
+ }
- continue;
- }
+ public override void Write (IOutputBuffer outputBuffer)
+ {
+ _force16Colors = Application.Driver!.Force16Colors;
+ _everythingStringBuilder = new StringBuilder ();
+
+ // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter.
+ _consoleBuffer = 0;
+ if (_force16Colors)
+ {
+ if (_isVirtualTerminal)
+ {
+ _consoleBuffer = _outputHandle;
+ }
+ else
+ {
+ _consoleBuffer = _screenBuffer;
+ }
+ }
+ else
+ {
+ _consoleBuffer = _outputHandle;
+ }
- outputBuffer [position].Empty = false;
+ base.Write (outputBuffer);
- if (buffer.Contents [row, col].Rune.IsBmp)
- {
- outputBuffer [position].Char = (char)buffer.Contents [row, col].Rune.Value;
- }
- else
+ try
+ {
+ if (_force16Colors && !_isVirtualTerminal)
+ {
+ SetConsoleActiveScreenBuffer (_consoleBuffer);
+ }
+ else
+ {
+ var span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string
+
+ var result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero);
+ if (!result)
{
- //outputBuffer [position].Empty = true;
- outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+ int err = Marshal.GetLastWin32Error ();
- if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols)
+ if (err != 0)
{
- // TODO: This is a hack to deal with non-BMP and wide characters.
- col++;
- position = row * buffer.Cols + col;
- outputBuffer [position].Empty = false;
- outputBuffer [position].Char = ' ';
+ throw new Win32Exception (err);
}
}
}
}
-
- var damageRegion = new WindowsConsole.SmallRect
+ catch (Exception e)
{
- Top = 0,
- Left = 0,
- Bottom = (short)buffer.Rows,
- Right = (short)buffer.Cols
- };
+ Logging.Logger.LogError ($"Error: {e.Message} in {nameof (WindowsOutput)}");
- //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
- if (!ConsoleDriver.RunningUnitTests
- && !WriteToConsole (
- new (buffer.Cols, buffer.Rows),
- outputBuffer,
- bufferCoords,
- damageRegion,
- Application.Driver!.Force16Colors))
- {
- int err = Marshal.GetLastWin32Error ();
-
- if (err != 0)
+ if (!ConsoleDriver.RunningUnitTests)
{
- throw new Win32Exception (err);
+ throw;
}
}
-
- WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
}
-
- public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors)
+ ///
+ protected override void Write (StringBuilder output)
{
+ if (output.Length == 0)
+ {
+ return;
+ }
- //Debug.WriteLine ("WriteToConsole");
+ var str = output.ToString ();
- //if (_screenBuffer == nint.Zero)
- //{
- // ReadFromConsoleOutput (size, bufferSize, ref window);
- //}
+ if (_force16Colors && !_isVirtualTerminal)
+ {
+ var a = str.ToCharArray ();
+ WriteConsole (_screenBuffer,a ,(uint)a.Length, out _, nint.Zero);
+ }
+ else
+ {
+ _everythingStringBuilder.Append (str);
+ }
+ }
- var result = false;
+ ///
+ protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
+ {
+ var force16Colors = Application.Force16Colors;
if (force16Colors)
{
- var i = 0;
- WindowsConsole.CharInfo [] ci = new WindowsConsole.CharInfo [charInfoBuffer.Length];
-
- foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
+ if (_isVirtualTerminal)
{
- ci [i++] = new ()
- {
- Char = new () { UnicodeChar = info.Char },
- Attributes =
- (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
- };
+ output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
+ output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
+ EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+ }
+ else
+ {
+ var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4));
+ SetConsoleTextAttribute (_screenBuffer, as16ColorInt);
}
-
- result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new () { X = window.Left, Y = window.Top }, ref window);
}
else
{
- StringBuilder stringBuilder = new();
-
- stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
- EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0);
+ EscSeqUtils.CSI_AppendForegroundColorRGB (output, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
+ EscSeqUtils.CSI_AppendBackgroundColorRGB (output, attr.Background.R, attr.Background.G, attr.Background.B);
+ EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
+ }
+ }
- Attribute? prev = null;
- foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
- {
- Attribute attr = info.Attribute;
+ private Size? _lastSize;
+ private Size? _lastWindowSizeBeforeMaximized;
+ private bool _lockResize;
- if (attr != prev)
- {
- prev = attr;
- EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B);
- EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B);
- EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style);
- _redrawTextStyle = attr.Style;
- }
-
- if (info.Char != '\x1b')
- {
- if (!info.Empty)
- {
- stringBuilder.Append (info.Char);
- }
- }
- else
- {
- stringBuilder.Append (' ');
- }
- }
-
- stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
- stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
+ public Size GetWindowSize ()
+ {
+ if (_lockResize)
+ {
+ return _lastSize!.Value;
+ }
- // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed.
- char [] rentedWriteArray = ArrayPool.Shared.Rent (minimumLength: stringBuilder.Length);
- try
- {
- Span writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length);
- stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
+ var newSize = GetWindowSize (out _);
+ Size largestWindowSize = GetLargestConsoleWindowSize ();
- // Supply console with the new content.
- result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
- }
- finally
+ if (_lastWindowSizeBeforeMaximized is null && newSize == largestWindowSize)
+ {
+ _lastWindowSizeBeforeMaximized = _lastSize;
+ }
+ else if (_lastWindowSizeBeforeMaximized is { } && newSize != largestWindowSize)
+ {
+ if (newSize != _lastWindowSizeBeforeMaximized)
{
- ArrayPool.Shared.Return (rentedWriteArray);
+ newSize = _lastWindowSizeBeforeMaximized.Value;
}
- foreach (SixelToRender sixel in Application.Sixel)
- {
- SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
- WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
- }
+ _lastWindowSizeBeforeMaximized = null;
}
- if (!result)
+ if (_lastSize == null || _lastSize != newSize)
{
- int err = Marshal.GetLastWin32Error ();
-
- if (err != 0)
+ // User is resizing the screen, they can only ever resize the active
+ // buffer since. We now however have issue because background offscreen
+ // buffer will be wrong size, recreate it to ensure it doesn't result in
+ // differing active and back buffer sizes (which causes flickering of window size)
+ Size? bufSize = null;
+ while (bufSize != newSize)
{
- throw new Win32Exception (err);
+ _lockResize = true;
+ bufSize = ResizeBuffer (newSize);
}
+
+ _lockResize = false;
+ _lastSize = newSize;
}
- return result;
+ return newSize;
}
- public Size GetWindowSize ()
+ public Size GetWindowSize (out WindowsConsole.Coord cursorPosition)
{
var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
csbi.cbSize = (uint)Marshal.SizeOf (csbi);
- if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+ if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
{
//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+ cursorPosition = default;
return Size.Empty;
}
@@ -312,18 +401,45 @@ public Size GetWindowSize ()
csbi.srWindow.Right - csbi.srWindow.Left + 1,
csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
+ cursorPosition = csbi.dwCursorPosition;
return sz;
}
+ private Size GetLargestConsoleWindowSize ()
+ {
+ WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
+
+ return new (maxWinSize.X, maxWinSize.Y);
+ }
+
+ ///
+ protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
+ {
+ if (_force16Colors && !_isVirtualTerminal)
+ {
+ SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY));
+ }
+ else
+ {
+ // CSI codes are 1 indexed
+ _everythingStringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
+ EscSeqUtils.CSI_AppendCursorPosition (_everythingStringBuilder, screenPositionY + 1, screenPositionX + 1);
+ }
+
+ _lastCursorPosition = new (screenPositionX, screenPositionY);
+
+ return true;
+ }
+
///
- public void SetCursorVisibility (CursorVisibility visibility)
+ public override void SetCursorVisibility (CursorVisibility visibility)
{
if (ConsoleDriver.RunningUnitTests)
{
return;
}
- if (Application.Driver!.Force16Colors)
+ if (!_isVirtualTerminal)
{
var info = new WindowsConsole.ConsoleCursorInfo
{
@@ -342,22 +458,34 @@ public void SetCursorVisibility (CursorVisibility visibility)
}
}
- private Point _lastCursorPosition;
+ private Point? _lastCursorPosition;
///
public void SetCursorPosition (int col, int row)
{
- if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+ if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
{
return;
}
_lastCursorPosition = new (col, row);
- SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+ if (_isVirtualTerminal)
+ {
+ var sb = new StringBuilder ();
+ EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1);
+ Write (sb.ToString ());
+ }
+ else
+ {
+ SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+ }
}
private bool _isDisposed;
+ private bool _force16Colors;
+ private nint _consoleBuffer;
+ private StringBuilder _everythingStringBuilder;
///
public void Dispose ()
@@ -367,16 +495,19 @@ public void Dispose ()
return;
}
- if (_screenBuffer != nint.Zero)
+ if (_isVirtualTerminal)
+ {
+ //Disable alternative screen buffer.
+ Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+ }
+ else
{
- try
+ if (_screenBuffer != nint.Zero)
{
CloseHandle (_screenBuffer);
}
- catch (Exception e)
- {
- Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method");
- }
+
+ _screenBuffer = nint.Zero;
}
_isDisposed = true;