mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Fixes 4191 - Rewrite WindowsOutput (#4193)
* Consider width2 chars that are not IsBmp * Apply same fix in WindowsDriver * Explicitly use type of local variable * Revert changes to WindowsDriver * Assume we are running in a terminal that supports true color by default unless user explicitly forces 16 * Switch to SetAttribute and WriteConsole instead of WriteConsoleOutput for 16 color mode * Fix some cursor issues (WIP) * Remove concept of 'dirty rows' from v2 as its never actually used * Remove damageRegion as it does nothing * Make string builder to console writing simpler * Radically simplify Write method * Simplify conditional logic * Simplify restoring cursor position * Reference local variable for console buffer * Reduce calls to ConsoleWrite by accumulating till attribute changes * When resizing v2 16 color mode on windows, recreate the back buffer to match its size * Fixes for VTS enabled * Fix _lastSize never being assigned * Fixes VTS for Force16Colors * Fixes force16Colors in VTS * Fixes escape sequences always echoing in non-VTS * Force Force16Colors in non-VTS. It have a bug in adding a newline in the last line * WIP Add base class for NetOutput * Abstract away how we change attribute * WIP - Make WindowsOutput use base class * WIP working to fix set cursor position * Remove commented out code * Fixes legacy output mode * Fixes size with no alt buffer supported on VTS and size restore after maximized. * Fix set cursor which also fixes the broken surrogate pairs * Add force parameter * Fixes an issue that only happens with Windows Terminal when paste surrogate pairs by press Ctrl+V * In Windows escape sequences must be sent during the lifetime of the console which is created in input handle * Ensure flush the input buffer before reset the console * Flush input buffer before reset console in v2win * Fixes issue in v2net not being refreshing the menu bar at start * Only force layout and draw on size changed. * Fix v2net issue not draw first line by forcing set cursor position * Set _lastCursorPosition nullable and remove bool force from set cursor position * Remove force parameter * Cleanup code --------- Co-authored-by: BDisp <bd.bdisp@gmail.com>
This commit is contained in:
@@ -96,6 +96,11 @@ internal class NetWinVTConsole
|
|||||||
|
|
||||||
public void Cleanup ()
|
public void Cleanup ()
|
||||||
{
|
{
|
||||||
|
if (!FlushConsoleInputBuffer (_inputHandle))
|
||||||
|
{
|
||||||
|
throw new ApplicationException ($"Failed to flush input buffer, error code: {GetLastError ()}.");
|
||||||
|
}
|
||||||
|
|
||||||
if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
|
if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
|
||||||
{
|
{
|
||||||
throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
|
throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
|
||||||
@@ -123,4 +128,7 @@ internal class NetWinVTConsole
|
|||||||
|
|
||||||
[DllImport ("kernel32.dll")]
|
[DllImport ("kernel32.dll")]
|
||||||
private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
|
private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
|
||||||
|
|
||||||
|
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,6 @@ namespace Terminal.Gui.Drivers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IOutputBuffer
|
public interface IOutputBuffer
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// As performance is a concern, we keep track of the dirty lines and only refresh those.
|
|
||||||
/// This is in addition to the dirty flag on each cell.
|
|
||||||
/// </summary>
|
|
||||||
public bool [] DirtyLines { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
|
/// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -162,4 +162,43 @@ public abstract class InputProcessor<T> : IInputProcessor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
protected abstract void ProcessAfterParsing (T input);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
|
|||||||
private ConsoleDriverFacade<T> _facade;
|
private ConsoleDriverFacade<T> _facade;
|
||||||
private Task _inputTask;
|
private Task _inputTask;
|
||||||
private readonly ITimedEvents _timedEvents;
|
private readonly ITimedEvents _timedEvents;
|
||||||
private readonly bool _isWindowsTerminal;
|
|
||||||
|
|
||||||
private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
|
private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
|
||||||
|
|
||||||
@@ -61,7 +60,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
|
|||||||
_inputProcessor = inputProcessor;
|
_inputProcessor = inputProcessor;
|
||||||
_outputFactory = outputFactory;
|
_outputFactory = outputFactory;
|
||||||
_loop = loop;
|
_loop = loop;
|
||||||
_isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -162,11 +160,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
|
|||||||
_loop.AnsiRequestScheduler,
|
_loop.AnsiRequestScheduler,
|
||||||
_loop.WindowSizeMonitor);
|
_loop.WindowSizeMonitor);
|
||||||
|
|
||||||
if (!_isWindowsTerminal)
|
|
||||||
{
|
|
||||||
Application.Force16Colors = _facade.Force16Colors = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Application.Driver = _facade;
|
Application.Driver = _facade;
|
||||||
|
|
||||||
_startupSemaphore.Release ();
|
_startupSemaphore.Release ();
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Enable alternative screen buffer.
|
||||||
|
Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
|
||||||
|
|
||||||
|
//Set cursor key to application.
|
||||||
|
Console.Out.Write (EscSeqUtils.CSI_HideCursor);
|
||||||
|
|
||||||
Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
|
Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
|
||||||
Console.TreatControlCAsInput = true;
|
Console.TreatControlCAsInput = true;
|
||||||
}
|
}
|
||||||
@@ -68,8 +74,14 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
|||||||
public override void Dispose ()
|
public override void Dispose ()
|
||||||
{
|
{
|
||||||
base.Dispose ();
|
base.Dispose ();
|
||||||
_adjustConsole?.Cleanup ();
|
|
||||||
|
|
||||||
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
|
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 ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,13 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
|
|||||||
protected override void ProcessAfterParsing (ConsoleKeyInfo input)
|
protected override void ProcessAfterParsing (ConsoleKeyInfo input)
|
||||||
{
|
{
|
||||||
var key = KeyConverter.ToKey (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 */
|
/* For building test cases */
|
||||||
|
|||||||
@@ -6,15 +6,10 @@ namespace Terminal.Gui.Drivers;
|
|||||||
/// Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
|
/// Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
|
||||||
/// methods e.g. <see cref="System.Console"/>
|
/// methods e.g. <see cref="System.Console"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NetOutput : IConsoleOutput
|
public class NetOutput : OutputBase, IConsoleOutput
|
||||||
{
|
{
|
||||||
private readonly bool _isWinPlatform;
|
private readonly bool _isWinPlatform;
|
||||||
|
|
||||||
private CursorVisibility? _cachedCursorVisibility;
|
|
||||||
|
|
||||||
// Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
|
|
||||||
private TextStyle _redrawTextStyle = TextStyle.None;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the <see cref="NetOutput"/> class.
|
/// Creates a new instance of the <see cref="NetOutput"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -30,176 +25,10 @@ public class NetOutput : IConsoleOutput
|
|||||||
{
|
{
|
||||||
_isWinPlatform = true;
|
_isWinPlatform = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Enable alternative screen buffer.
|
|
||||||
Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
|
|
||||||
|
|
||||||
//Set cursor key to application.
|
|
||||||
Console.Out.Write (EscSeqUtils.CSI_HideCursor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Write (ReadOnlySpan<char> text)
|
public void Write (ReadOnlySpan<char> text) { Console.Out.Write (text); }
|
||||||
{
|
|
||||||
Console.Out.Write (text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Write (IOutputBuffer buffer)
|
|
||||||
{
|
|
||||||
if (ConsoleDriver.RunningUnitTests)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Console.WindowHeight < 1
|
|
||||||
|| buffer.Contents.Length != buffer.Rows * buffer.Cols
|
|
||||||
|| buffer.Rows != Console.WindowHeight)
|
|
||||||
{
|
|
||||||
// return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var top = 0;
|
|
||||||
var left = 0;
|
|
||||||
int rows = buffer.Rows;
|
|
||||||
int cols = buffer.Cols;
|
|
||||||
var output = new StringBuilder ();
|
|
||||||
Attribute? redrawAttr = null;
|
|
||||||
int lastCol = -1;
|
|
||||||
|
|
||||||
CursorVisibility? savedVisibility = _cachedCursorVisibility;
|
|
||||||
SetCursorVisibility (CursorVisibility.Invisible);
|
|
||||||
|
|
||||||
const int maxCharsPerRune = 2;
|
|
||||||
Span<char> runeBuffer = stackalloc char[maxCharsPerRune];
|
|
||||||
|
|
||||||
for (int row = top; row < rows; row++)
|
|
||||||
{
|
|
||||||
if (Console.WindowHeight < 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!buffer.DirtyLines [row])
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SetCursorPositionImpl (0, row))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.DirtyLines [row] = false;
|
|
||||||
output.Clear ();
|
|
||||||
|
|
||||||
for (int col = left; col < cols; col++)
|
|
||||||
{
|
|
||||||
lastCol = -1;
|
|
||||||
var outputWidth = 0;
|
|
||||||
|
|
||||||
for (; col < cols; col++)
|
|
||||||
{
|
|
||||||
if (!buffer.Contents [row, col].IsDirty)
|
|
||||||
{
|
|
||||||
if (output.Length > 0)
|
|
||||||
{
|
|
||||||
WriteToConsole (output, ref lastCol, row, ref outputWidth);
|
|
||||||
}
|
|
||||||
else if (lastCol == -1)
|
|
||||||
{
|
|
||||||
lastCol = col;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastCol + 1 < cols)
|
|
||||||
{
|
|
||||||
lastCol++;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastCol == -1)
|
|
||||||
{
|
|
||||||
lastCol = col;
|
|
||||||
}
|
|
||||||
|
|
||||||
Attribute attr = buffer.Contents [row, col].Attribute.Value;
|
|
||||||
|
|
||||||
// Performance: Only send the escape sequence if the attribute has changed.
|
|
||||||
if (attr != redrawAttr)
|
|
||||||
{
|
|
||||||
redrawAttr = attr;
|
|
||||||
|
|
||||||
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
|
||||||
output,
|
|
||||||
attr.Foreground.R,
|
|
||||||
attr.Foreground.G,
|
|
||||||
attr.Foreground.B
|
|
||||||
);
|
|
||||||
|
|
||||||
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
|
||||||
output,
|
|
||||||
attr.Background.R,
|
|
||||||
attr.Background.G,
|
|
||||||
attr.Background.B
|
|
||||||
);
|
|
||||||
|
|
||||||
EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style);
|
|
||||||
|
|
||||||
_redrawTextStyle = attr.Style;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputWidth++;
|
|
||||||
|
|
||||||
// Avoid Rune.ToString() by appending the rune chars.
|
|
||||||
Rune rune = buffer.Contents [row, col].Rune;
|
|
||||||
int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
|
|
||||||
ReadOnlySpan<char> runeChars = runeBuffer[..runeCharsWritten];
|
|
||||||
output.Append (runeChars);
|
|
||||||
|
|
||||||
if (buffer.Contents [row, col].CombiningMarks.Count > 0)
|
|
||||||
{
|
|
||||||
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
|
|
||||||
// compatible with the driver architecture. Any CMs (except in the first col)
|
|
||||||
// are correctly combined with the base char, but are ALSO treated as 1 column
|
|
||||||
// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
|
|
||||||
//
|
|
||||||
// For now, we just ignore the list of CMs.
|
|
||||||
//foreach (var combMark in Contents [row, col].CombiningMarks) {
|
|
||||||
// output.Append (combMark);
|
|
||||||
//}
|
|
||||||
// WriteToConsole (output, ref lastCol, row, ref outputWidth);
|
|
||||||
}
|
|
||||||
else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
|
|
||||||
{
|
|
||||||
WriteToConsole (output, ref lastCol, row, ref outputWidth);
|
|
||||||
SetCursorPositionImpl (col - 1, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Contents [row, col].IsDirty = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output.Length > 0)
|
|
||||||
{
|
|
||||||
SetCursorPositionImpl (lastCol, row);
|
|
||||||
Console.Out.Write (output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (SixelToRender s in Application.Sixel)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace (s.SixelData))
|
|
||||||
{
|
|
||||||
SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
|
|
||||||
Console.Out.Write (s.SixelData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
|
|
||||||
_cachedCursorVisibility = savedVisibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public Size GetWindowSize ()
|
public Size GetWindowSize ()
|
||||||
@@ -213,23 +42,37 @@ public class NetOutput : IConsoleOutput
|
|||||||
return new (Console.WindowWidth, Console.WindowHeight);
|
return new (Console.WindowWidth, Console.WindowHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
|
|
||||||
{
|
|
||||||
SetCursorPositionImpl (lastCol, row);
|
|
||||||
Console.Out.Write (output);
|
|
||||||
output.Clear ();
|
|
||||||
lastCol += outputWidth;
|
|
||||||
outputWidth = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
|
public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
|
||||||
|
|
||||||
private Point _lastCursorPosition;
|
private Point? _lastCursorPosition;
|
||||||
|
|
||||||
private bool SetCursorPositionImpl (int col, int row)
|
/// <inheritdoc/>
|
||||||
|
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
||||||
{
|
{
|
||||||
if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
|
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
||||||
|
output,
|
||||||
|
attr.Foreground.R,
|
||||||
|
attr.Foreground.G,
|
||||||
|
attr.Foreground.B
|
||||||
|
);
|
||||||
|
|
||||||
|
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
||||||
|
output,
|
||||||
|
attr.Background.R,
|
||||||
|
attr.Background.G,
|
||||||
|
attr.Background.B
|
||||||
|
);
|
||||||
|
|
||||||
|
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override void Write (StringBuilder output) { Console.Out.Write (output); }
|
||||||
|
|
||||||
|
protected override bool SetCursorPositionImpl (int col, int row)
|
||||||
|
{
|
||||||
|
if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -259,21 +102,10 @@ public class NetOutput : IConsoleOutput
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose ()
|
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 ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetCursorVisibility (CursorVisibility visibility)
|
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||||
{
|
{
|
||||||
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
|
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
|
||||||
}
|
}
|
||||||
|
|||||||
163
Terminal.Gui/Drivers/V2/OutputBase.cs
Normal file
163
Terminal.Gui/Drivers/V2/OutputBase.cs
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
|
public abstract class OutputBase
|
||||||
|
{
|
||||||
|
private CursorVisibility? _cachedCursorVisibility;
|
||||||
|
|
||||||
|
// Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
|
||||||
|
private TextStyle _redrawTextStyle = TextStyle.None;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public virtual void Write (IOutputBuffer buffer)
|
||||||
|
{
|
||||||
|
if (ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Console.WindowHeight < 1
|
||||||
|
|| buffer.Contents.Length != buffer.Rows * buffer.Cols
|
||||||
|
|| buffer.Rows != Console.WindowHeight)
|
||||||
|
{
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var top = 0;
|
||||||
|
var left = 0;
|
||||||
|
int rows = buffer.Rows;
|
||||||
|
int cols = buffer.Cols;
|
||||||
|
var output = new StringBuilder ();
|
||||||
|
Attribute? redrawAttr = null;
|
||||||
|
int lastCol = -1;
|
||||||
|
|
||||||
|
CursorVisibility? savedVisibility = _cachedCursorVisibility;
|
||||||
|
SetCursorVisibility (CursorVisibility.Invisible);
|
||||||
|
|
||||||
|
const int maxCharsPerRune = 2;
|
||||||
|
Span<char> runeBuffer = stackalloc char [maxCharsPerRune];
|
||||||
|
|
||||||
|
for (int row = top; row < rows; row++)
|
||||||
|
{
|
||||||
|
if (Console.WindowHeight < 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetCursorPositionImpl (0, row))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Clear ();
|
||||||
|
|
||||||
|
for (int col = left; col < cols; col++)
|
||||||
|
{
|
||||||
|
lastCol = -1;
|
||||||
|
var outputWidth = 0;
|
||||||
|
|
||||||
|
for (; col < cols; col++)
|
||||||
|
{
|
||||||
|
if (!buffer.Contents [row, col].IsDirty)
|
||||||
|
{
|
||||||
|
if (output.Length > 0)
|
||||||
|
{
|
||||||
|
WriteToConsole (output, ref lastCol, row, ref outputWidth);
|
||||||
|
}
|
||||||
|
else if (lastCol == -1)
|
||||||
|
{
|
||||||
|
lastCol = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCol + 1 < cols)
|
||||||
|
{
|
||||||
|
lastCol++;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCol == -1)
|
||||||
|
{
|
||||||
|
lastCol = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
Attribute attr = buffer.Contents [row, col].Attribute.Value;
|
||||||
|
|
||||||
|
// Performance: Only send the escape sequence if the attribute has changed.
|
||||||
|
if (attr != redrawAttr)
|
||||||
|
{
|
||||||
|
redrawAttr = attr;
|
||||||
|
|
||||||
|
AppendOrWriteAttribute (output, attr, _redrawTextStyle);
|
||||||
|
|
||||||
|
_redrawTextStyle = attr.Style;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputWidth++;
|
||||||
|
|
||||||
|
// Avoid Rune.ToString() by appending the rune chars.
|
||||||
|
Rune rune = buffer.Contents [row, col].Rune;
|
||||||
|
int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer);
|
||||||
|
ReadOnlySpan<char> runeChars = runeBuffer [..runeCharsWritten];
|
||||||
|
output.Append (runeChars);
|
||||||
|
|
||||||
|
if (buffer.Contents [row, col].CombiningMarks.Count > 0)
|
||||||
|
{
|
||||||
|
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
|
||||||
|
// compatible with the driver architecture. Any CMs (except in the first col)
|
||||||
|
// are correctly combined with the base char, but are ALSO treated as 1 column
|
||||||
|
// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
|
||||||
|
//
|
||||||
|
// For now, we just ignore the list of CMs.
|
||||||
|
//foreach (var combMark in Contents [row, col].CombiningMarks) {
|
||||||
|
// output.Append (combMark);
|
||||||
|
//}
|
||||||
|
// WriteToConsole (output, ref lastCol, row, ref outputWidth);
|
||||||
|
}
|
||||||
|
else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
|
||||||
|
{
|
||||||
|
WriteToConsole (output, ref lastCol, row, ref outputWidth);
|
||||||
|
SetCursorPositionImpl (col - 1, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.Contents [row, col].IsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.Length > 0)
|
||||||
|
{
|
||||||
|
SetCursorPositionImpl (lastCol, row);
|
||||||
|
Write (output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SixelToRender s in Application.Sixel)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace (s.SixelData))
|
||||||
|
{
|
||||||
|
SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
|
||||||
|
Console.Out.Write (s.SixelData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
|
||||||
|
_cachedCursorVisibility = savedVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle);
|
||||||
|
|
||||||
|
private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
|
||||||
|
{
|
||||||
|
SetCursorPositionImpl (lastCol, row);
|
||||||
|
Write (output);
|
||||||
|
output.Clear ();
|
||||||
|
lastCol += outputWidth;
|
||||||
|
outputWidth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void Write (StringBuilder output);
|
||||||
|
|
||||||
|
protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY);
|
||||||
|
|
||||||
|
public abstract void SetCursorVisibility (CursorVisibility visibility);
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ using static Terminal.Gui.Drivers.WindowsConsole;
|
|||||||
|
|
||||||
namespace Terminal.Gui.Drivers;
|
namespace Terminal.Gui.Drivers;
|
||||||
|
|
||||||
internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindowsInput
|
internal class WindowsInput : ConsoleInput<InputRecord>, IWindowsInput
|
||||||
{
|
{
|
||||||
private readonly nint _inputHandle;
|
private readonly nint _inputHandle;
|
||||||
|
|
||||||
@@ -35,6 +35,9 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
|||||||
|
|
||||||
private readonly uint _originalConsoleMode;
|
private readonly uint _originalConsoleMode;
|
||||||
|
|
||||||
|
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||||
|
private static extern bool FlushConsoleInputBuffer (nint hConsoleInput);
|
||||||
|
|
||||||
public WindowsInput ()
|
public WindowsInput ()
|
||||||
{
|
{
|
||||||
Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
|
Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
|
||||||
@@ -50,16 +53,16 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
|||||||
_originalConsoleMode = v;
|
_originalConsoleMode = v;
|
||||||
|
|
||||||
uint newConsoleMode = _originalConsoleMode;
|
uint newConsoleMode = _originalConsoleMode;
|
||||||
newConsoleMode |= (uint)(WindowsConsole.ConsoleModes.EnableMouseInput | WindowsConsole.ConsoleModes.EnableExtendedFlags);
|
newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
|
||||||
newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableQuickEditMode;
|
newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
|
||||||
newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableProcessedInput;
|
newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
|
||||||
SetConsoleMode (_inputHandle, newConsoleMode);
|
SetConsoleMode (_inputHandle, newConsoleMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool Peek ()
|
protected override bool Peek ()
|
||||||
{
|
{
|
||||||
const int bufferSize = 1; // We only need to check if there's at least one event
|
const int bufferSize = 1; // We only need to check if there's at least one event
|
||||||
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
|
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -89,10 +92,10 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<WindowsConsole.InputRecord> Read ()
|
protected override IEnumerable<InputRecord> Read ()
|
||||||
{
|
{
|
||||||
const int bufferSize = 1;
|
const int bufferSize = 1;
|
||||||
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
|
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -104,7 +107,7 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
|||||||
|
|
||||||
return numberEventsRead == 0
|
return numberEventsRead == 0
|
||||||
? []
|
? []
|
||||||
: new [] { Marshal.PtrToStructure<WindowsConsole.InputRecord> (pRecord) };
|
: new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -123,6 +126,11 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!FlushConsoleInputBuffer (_inputHandle))
|
||||||
|
{
|
||||||
|
throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
|
||||||
|
}
|
||||||
|
|
||||||
SetConsoleMode (_inputHandle, _originalConsoleMode);
|
SetConsoleMode (_inputHandle, _originalConsoleMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
|
|||||||
{
|
{
|
||||||
var key = KeyConverter.ToKey (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!);
|
OnKeyDown (key!);
|
||||||
OnKeyUp (key!);
|
OnKeyUp (key!);
|
||||||
@@ -82,10 +83,29 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
|
|||||||
{
|
{
|
||||||
var mouseFlags = MouseFlags.None;
|
var mouseFlags = MouseFlags.None;
|
||||||
|
|
||||||
mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button1Pressed, MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
|
mouseFlags = UpdateMouseFlags (
|
||||||
mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
|
mouseFlags,
|
||||||
mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
|
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'
|
// 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))
|
if (e.ButtonState.HasFlag (WindowsConsole.ButtonState.Button3Pressed) || e.ButtonState.HasFlag (WindowsConsole.ButtonState.RightmostButtonPressed))
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System.Buffers;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Terminal.Gui.Drivers.WindowsConsole;
|
|
||||||
|
|
||||||
namespace Terminal.Gui.Drivers;
|
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)]
|
[LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||||
[return: MarshalAs (UnmanagedType.Bool)]
|
[return: MarshalAs (UnmanagedType.Bool)]
|
||||||
@@ -19,11 +18,15 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
nint lpReserved
|
nint lpReserved
|
||||||
);
|
);
|
||||||
|
|
||||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
[LibraryImport ("kernel32.dll", SetLastError = true)]
|
||||||
private static extern bool CloseHandle (nint handle);
|
private static partial nint GetStdHandle (int nStdHandle);
|
||||||
|
|
||||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
[LibraryImport ("kernel32.dll", SetLastError = true)]
|
||||||
private static extern nint CreateConsoleScreenBuffer (
|
[return: MarshalAs (UnmanagedType.Bool)]
|
||||||
|
private static partial bool CloseHandle (nint handle);
|
||||||
|
|
||||||
|
[LibraryImport ("kernel32.dll", SetLastError = true)]
|
||||||
|
private static partial nint CreateConsoleScreenBuffer (
|
||||||
DesiredAccess dwDesiredAccess,
|
DesiredAccess dwDesiredAccess,
|
||||||
ShareMode dwShareMode,
|
ShareMode dwShareMode,
|
||||||
nint secutiryAttributes,
|
nint secutiryAttributes,
|
||||||
@@ -32,6 +35,7 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
);
|
);
|
||||||
|
|
||||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs (UnmanagedType.Bool)]
|
||||||
private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi);
|
private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi);
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
@@ -50,19 +54,52 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
|
|
||||||
internal static nint INVALID_HANDLE_VALUE = new (-1);
|
internal static nint INVALID_HANDLE_VALUE = new (-1);
|
||||||
|
|
||||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
[LibraryImport ("kernel32.dll", SetLastError = true)]
|
||||||
private static extern bool SetConsoleActiveScreenBuffer (nint handle);
|
[return: MarshalAs (UnmanagedType.Bool)]
|
||||||
|
private static partial bool SetConsoleActiveScreenBuffer (nint handle);
|
||||||
|
|
||||||
[DllImport ("kernel32.dll")]
|
[LibraryImport ("kernel32.dll")]
|
||||||
private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
|
[return: MarshalAs (UnmanagedType.Bool)]
|
||||||
|
private static partial bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition);
|
||||||
|
|
||||||
[DllImport ("kernel32.dll", SetLastError = true)]
|
[DllImport ("kernel32.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs (UnmanagedType.Bool)]
|
||||||
private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo);
|
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);
|
||||||
|
|
||||||
// Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
|
[LibraryImport ("kernel32.dll")]
|
||||||
private TextStyle _redrawTextStyle = TextStyle.None;
|
[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
|
||||||
|
);
|
||||||
|
|
||||||
|
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 ()
|
public WindowsOutput ()
|
||||||
{
|
{
|
||||||
@@ -73,13 +110,48 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
return;
|
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 (
|
_screenBuffer = CreateConsoleScreenBuffer (
|
||||||
DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
|
DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
|
||||||
ShareMode.FileShareRead | ShareMode.FileShareWrite,
|
ShareMode.FileShareRead | ShareMode.FileShareWrite,
|
||||||
nint.Zero,
|
nint.Zero,
|
||||||
1,
|
1,
|
||||||
nint.Zero
|
nint.Zero
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_screenBuffer == INVALID_HANDLE_VALUE)
|
if (_screenBuffer == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
@@ -99,212 +171,229 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
|
|
||||||
public void Write (ReadOnlySpan<char> str)
|
public void Write (ReadOnlySpan<char> str)
|
||||||
{
|
{
|
||||||
if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
|
if (!WriteConsole (_isVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
|
||||||
{
|
{
|
||||||
throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
|
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?
|
return newSize;
|
||||||
/*
|
|
||||||
if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var bufferCoords = new WindowsConsole.Coord
|
|
||||||
{
|
|
||||||
X = (short)buffer.Cols, //Clip.Width,
|
|
||||||
Y = (short)buffer.Rows //Clip.Height
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var row = 0; row < buffer.Rows; row++)
|
|
||||||
{
|
|
||||||
if (!buffer.DirtyLines [row])
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.DirtyLines [row] = false;
|
|
||||||
|
|
||||||
for (var col = 0; col < buffer.Cols; col++)
|
|
||||||
{
|
|
||||||
int position = row * buffer.Cols + col;
|
|
||||||
outputBuffer [position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault ();
|
|
||||||
|
|
||||||
if (buffer.Contents [row, col].IsDirty == false)
|
|
||||||
{
|
|
||||||
outputBuffer [position].Empty = true;
|
|
||||||
outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
outputBuffer [position].Empty = false;
|
|
||||||
|
|
||||||
if (buffer.Contents [row, col].Rune.IsBmp)
|
|
||||||
{
|
|
||||||
outputBuffer [position].Char = (char)buffer.Contents [row, col].Rune.Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//outputBuffer [position].Empty = true;
|
|
||||||
outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
|
|
||||||
|
|
||||||
if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols)
|
|
||||||
{
|
|
||||||
// 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 = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var damageRegion = new WindowsConsole.SmallRect
|
|
||||||
{
|
|
||||||
Top = 0,
|
|
||||||
Left = 0,
|
|
||||||
Bottom = (short)buffer.Rows,
|
|
||||||
Right = (short)buffer.Cols
|
|
||||||
};
|
|
||||||
|
|
||||||
//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)
|
|
||||||
{
|
|
||||||
throw new Win32Exception (err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors)
|
internal Size SetConsoleWindow (short cols, short rows)
|
||||||
{
|
|
||||||
|
|
||||||
//Debug.WriteLine ("WriteToConsole");
|
|
||||||
|
|
||||||
//if (_screenBuffer == nint.Zero)
|
|
||||||
//{
|
|
||||||
// ReadFromConsoleOutput (size, bufferSize, ref window);
|
|
||||||
//}
|
|
||||||
|
|
||||||
var result = false;
|
|
||||||
|
|
||||||
if (force16Colors)
|
|
||||||
{
|
|
||||||
var i = 0;
|
|
||||||
WindowsConsole.CharInfo [] ci = new WindowsConsole.CharInfo [charInfoBuffer.Length];
|
|
||||||
|
|
||||||
foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
|
|
||||||
{
|
|
||||||
ci [i++] = new ()
|
|
||||||
{
|
|
||||||
Char = new () { UnicodeChar = info.Char },
|
|
||||||
Attributes =
|
|
||||||
(ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Attribute? prev = null;
|
|
||||||
|
|
||||||
foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
|
|
||||||
{
|
|
||||||
Attribute attr = info.Attribute;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed.
|
|
||||||
char [] rentedWriteArray = ArrayPool<char>.Shared.Rent (minimumLength: stringBuilder.Length);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Span<char> writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length);
|
|
||||||
stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
|
|
||||||
|
|
||||||
// Supply console with the new content.
|
|
||||||
result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ArrayPool<char>.Shared.Return (rentedWriteArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result)
|
|
||||||
{
|
|
||||||
int err = Marshal.GetLastWin32Error ();
|
|
||||||
|
|
||||||
if (err != 0)
|
|
||||||
{
|
|
||||||
throw new Win32Exception (err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Size GetWindowSize ()
|
|
||||||
{
|
{
|
||||||
var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
|
var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
|
||||||
csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
||||||
|
|
||||||
if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
|
if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
||||||
|
{
|
||||||
|
throw new Win32Exception (Marshal.GetLastWin32Error ());
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
throw new Win32Exception (Marshal.GetLastWin32Error ());
|
||||||
|
}
|
||||||
|
|
||||||
|
var winRect = new WindowsConsole.SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
|
||||||
|
|
||||||
|
if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
|
||||||
{
|
{
|
||||||
//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
|
//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
|
||||||
|
return new (cols, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetConsoleOutputWindow (csbi);
|
||||||
|
|
||||||
|
return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Write (outputBuffer);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
int err = Marshal.GetLastWin32Error ();
|
||||||
|
|
||||||
|
if (err != 0)
|
||||||
|
{
|
||||||
|
throw new Win32Exception (err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logging.Logger.LogError ($"Error: {e.Message} in {nameof (WindowsOutput)}");
|
||||||
|
|
||||||
|
if (!ConsoleDriver.RunningUnitTests)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Write (StringBuilder output)
|
||||||
|
{
|
||||||
|
if (output.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var str = output.ToString ();
|
||||||
|
|
||||||
|
if (_force16Colors && !_isVirtualTerminal)
|
||||||
|
{
|
||||||
|
var a = str.ToCharArray ();
|
||||||
|
WriteConsole (_screenBuffer,a ,(uint)a.Length, out _, nint.Zero);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_everythingStringBuilder.Append (str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
||||||
|
{
|
||||||
|
var force16Colors = Application.Force16Colors;
|
||||||
|
|
||||||
|
if (force16Colors)
|
||||||
|
{
|
||||||
|
if (_isVirtualTerminal)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Size? _lastSize;
|
||||||
|
private Size? _lastWindowSizeBeforeMaximized;
|
||||||
|
private bool _lockResize;
|
||||||
|
|
||||||
|
public Size GetWindowSize ()
|
||||||
|
{
|
||||||
|
if (_lockResize)
|
||||||
|
{
|
||||||
|
return _lastSize!.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newSize = GetWindowSize (out _);
|
||||||
|
Size largestWindowSize = GetLargestConsoleWindowSize ();
|
||||||
|
|
||||||
|
if (_lastWindowSizeBeforeMaximized is null && newSize == largestWindowSize)
|
||||||
|
{
|
||||||
|
_lastWindowSizeBeforeMaximized = _lastSize;
|
||||||
|
}
|
||||||
|
else if (_lastWindowSizeBeforeMaximized is { } && newSize != largestWindowSize)
|
||||||
|
{
|
||||||
|
if (newSize != _lastWindowSizeBeforeMaximized)
|
||||||
|
{
|
||||||
|
newSize = _lastWindowSizeBeforeMaximized.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastWindowSizeBeforeMaximized = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lastSize == null || _lastSize != newSize)
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
_lockResize = true;
|
||||||
|
bufSize = ResizeBuffer (newSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lockResize = false;
|
||||||
|
_lastSize = newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Size GetWindowSize (out WindowsConsole.Coord cursorPosition)
|
||||||
|
{
|
||||||
|
var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
|
||||||
|
csbi.cbSize = (uint)Marshal.SizeOf (csbi);
|
||||||
|
|
||||||
|
if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
|
||||||
|
{
|
||||||
|
//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
|
||||||
|
cursorPosition = default;
|
||||||
return Size.Empty;
|
return Size.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,18 +401,45 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
csbi.srWindow.Right - csbi.srWindow.Left + 1,
|
csbi.srWindow.Right - csbi.srWindow.Left + 1,
|
||||||
csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
|
csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
|
||||||
|
|
||||||
|
cursorPosition = csbi.dwCursorPosition;
|
||||||
return sz;
|
return sz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Size GetLargestConsoleWindowSize ()
|
||||||
|
{
|
||||||
|
WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer);
|
||||||
|
|
||||||
|
return new (maxWinSize.X, maxWinSize.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
|
||||||
|
{
|
||||||
|
if (_force16Colors && !_isVirtualTerminal)
|
||||||
|
{
|
||||||
|
SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// CSI codes are 1 indexed
|
||||||
|
_everythingStringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
|
||||||
|
EscSeqUtils.CSI_AppendCursorPosition (_everythingStringBuilder, screenPositionY + 1, screenPositionX + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastCursorPosition = new (screenPositionX, screenPositionY);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetCursorVisibility (CursorVisibility visibility)
|
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||||
{
|
{
|
||||||
if (ConsoleDriver.RunningUnitTests)
|
if (ConsoleDriver.RunningUnitTests)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Application.Driver!.Force16Colors)
|
if (!_isVirtualTerminal)
|
||||||
{
|
{
|
||||||
var info = new WindowsConsole.ConsoleCursorInfo
|
var info = new WindowsConsole.ConsoleCursorInfo
|
||||||
{
|
{
|
||||||
@@ -342,22 +458,34 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Point _lastCursorPosition;
|
private Point? _lastCursorPosition;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void SetCursorPosition (int col, int row)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastCursorPosition = new (col, row);
|
_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 _isDisposed;
|
||||||
|
private bool _force16Colors;
|
||||||
|
private nint _consoleBuffer;
|
||||||
|
private StringBuilder _everythingStringBuilder;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose ()
|
public void Dispose ()
|
||||||
@@ -367,16 +495,19 @@ internal partial class WindowsOutput : IConsoleOutput
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_screenBuffer != nint.Zero)
|
if (_isVirtualTerminal)
|
||||||
{
|
{
|
||||||
try
|
//Disable alternative screen buffer.
|
||||||
|
Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_screenBuffer != nint.Zero)
|
||||||
{
|
{
|
||||||
CloseHandle (_screenBuffer);
|
CloseHandle (_screenBuffer);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
_screenBuffer = nint.Zero;
|
||||||
Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user