mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 07:47:54 +01:00
Merge branch 'gui-cs:v2_develop' into v2_develop
This commit is contained in:
@@ -212,14 +212,15 @@ public static partial class Application // Initialization (Init/Shutdown)
|
||||
// use reflection to get the list of drivers
|
||||
List<Type?> driverTypes = new ();
|
||||
|
||||
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
|
||||
// Only inspect the IConsoleDriver assembly
|
||||
var asm = typeof (IConsoleDriver).Assembly;
|
||||
|
||||
foreach (Type? type in asm.GetTypes ())
|
||||
{
|
||||
foreach (Type? type in asm.GetTypes ())
|
||||
if (typeof (IConsoleDriver).IsAssignableFrom (type) &&
|
||||
type is { IsAbstract: false, IsClass: true })
|
||||
{
|
||||
if (typeof (IConsoleDriver).IsAssignableFrom (type) && !type.IsAbstract && type.IsClass)
|
||||
{
|
||||
driverTypes.Add (type);
|
||||
}
|
||||
driverTypes.Add (type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,11 @@ internal class 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 @@ internal class NetWinVTConsole
|
||||
|
||||
[DllImport ("kernel32.dll")]
|
||||
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>
|
||||
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>
|
||||
/// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
|
||||
/// </summary>
|
||||
|
||||
@@ -162,4 +162,43 @@ public abstract class InputProcessor<T> : IInputProcessor
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
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 Task _inputTask;
|
||||
private readonly ITimedEvents _timedEvents;
|
||||
private readonly bool _isWindowsTerminal;
|
||||
|
||||
private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
|
||||
|
||||
@@ -61,7 +60,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
|
||||
_inputProcessor = inputProcessor;
|
||||
_outputFactory = outputFactory;
|
||||
_loop = loop;
|
||||
_isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,11 +160,6 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
|
||||
_loop.AnsiRequestScheduler,
|
||||
_loop.WindowSizeMonitor);
|
||||
|
||||
if (!_isWindowsTerminal)
|
||||
{
|
||||
Application.Force16Colors = _facade.Force16Colors = true;
|
||||
}
|
||||
|
||||
Application.Driver = _facade;
|
||||
|
||||
_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.TreatControlCAsInput = true;
|
||||
}
|
||||
@@ -68,8 +74,14 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,13 @@ public class NetInputProcessor : InputProcessor<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 */
|
||||
|
||||
@@ -6,15 +6,10 @@ namespace Terminal.Gui.Drivers;
|
||||
/// Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
|
||||
/// methods e.g. <see cref="System.Console"/>
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="NetOutput"/> class.
|
||||
/// </summary>
|
||||
@@ -30,176 +25,10 @@ public class NetOutput : IConsoleOutput
|
||||
{
|
||||
_isWinPlatform = true;
|
||||
}
|
||||
|
||||
//Enable alternative screen buffer.
|
||||
Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
|
||||
|
||||
//Set cursor key to application.
|
||||
Console.Out.Write (EscSeqUtils.CSI_HideCursor);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Write (ReadOnlySpan<char> 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;
|
||||
}
|
||||
public void Write (ReadOnlySpan<char> text) { Console.Out.Write (text); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Size GetWindowSize ()
|
||||
@@ -213,23 +42,37 @@ public class NetOutput : IConsoleOutput
|
||||
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/>
|
||||
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;
|
||||
}
|
||||
@@ -259,21 +102,10 @@ public class NetOutput : IConsoleOutput
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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 () { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetCursorVisibility (CursorVisibility visibility)
|
||||
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||
{
|
||||
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;
|
||||
|
||||
internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindowsInput
|
||||
internal class WindowsInput : ConsoleInput<InputRecord>, IWindowsInput
|
||||
{
|
||||
private readonly nint _inputHandle;
|
||||
|
||||
@@ -35,6 +35,9 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
||||
|
||||
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 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
||||
_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<WindowsConsole.InputRecord> () * bufferSize);
|
||||
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
|
||||
|
||||
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;
|
||||
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<WindowsConsole.InputRecord> () * bufferSize);
|
||||
nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -104,7 +107,7 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
||||
|
||||
return numberEventsRead == 0
|
||||
? []
|
||||
: new [] { Marshal.PtrToStructure<WindowsConsole.InputRecord> (pRecord) };
|
||||
: new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -123,6 +126,11 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FlushConsoleInputBuffer (_inputHandle))
|
||||
{
|
||||
throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}.");
|
||||
}
|
||||
|
||||
SetConsoleMode (_inputHandle, _originalConsoleMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,8 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
|
||||
{
|
||||
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 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
|
||||
{
|
||||
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))
|
||||
|
||||
@@ -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 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
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 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
);
|
||||
|
||||
[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 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
|
||||
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);
|
||||
|
||||
// Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
|
||||
private TextStyle _redrawTextStyle = TextStyle.None;
|
||||
[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
|
||||
);
|
||||
|
||||
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 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
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 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}*/
|
||||
|
||||
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);
|
||||
return newSize;
|
||||
}
|
||||
|
||||
public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors)
|
||||
{
|
||||
|
||||
//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 ()
|
||||
internal Size SetConsoleWindow (short cols, short rows)
|
||||
{
|
||||
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 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 ());
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -312,18 +401,45 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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/>
|
||||
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 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
}
|
||||
}
|
||||
|
||||
private Point _lastCursorPosition;
|
||||
private Point? _lastCursorPosition;
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose ()
|
||||
@@ -367,16 +495,19 @@ internal partial class WindowsOutput : IConsoleOutput
|
||||
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);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method");
|
||||
}
|
||||
|
||||
_screenBuffer = nint.Zero;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
@@ -397,10 +397,10 @@ public class MenuBarv2 : Menuv2, IDesignable
|
||||
|
||||
// If the active Application Popover is part of this MenuBar, hide it.
|
||||
if (Application.Popover?.GetActivePopover () is PopoverMenu popoverMenu
|
||||
&& popoverMenu?.Root?.SuperMenuItem?.SuperView == this)
|
||||
&& popoverMenu.Root?.SuperMenuItem?.SuperView == this)
|
||||
{
|
||||
// Logging.Debug ($"{Title} - Calling Application.Popover?.Hide ({popoverMenu.Title})");
|
||||
Application.Popover?.Hide (popoverMenu);
|
||||
Application.Popover.Hide (popoverMenu);
|
||||
}
|
||||
|
||||
if (menuBarItem is null)
|
||||
@@ -416,6 +416,7 @@ public class MenuBarv2 : Menuv2, IDesignable
|
||||
if (menuBarItem.PopoverMenu?.Root is { })
|
||||
{
|
||||
menuBarItem.PopoverMenu.Root.SuperMenuItem = menuBarItem;
|
||||
menuBarItem.PopoverMenu.Root.SchemeName = SchemeName;
|
||||
}
|
||||
|
||||
// Logging.Debug ($"{Title} - \"{menuBarItem.PopoverMenu?.Title}\".MakeVisible");
|
||||
@@ -423,14 +424,15 @@ public class MenuBarv2 : Menuv2, IDesignable
|
||||
|
||||
menuBarItem.Accepting += OnMenuItemAccepted;
|
||||
|
||||
menuBarItem.PopoverMenu!.Root.SchemeName = SchemeName;
|
||||
|
||||
return;
|
||||
|
||||
void OnMenuItemAccepted (object? sender, EventArgs args)
|
||||
{
|
||||
// Logging.Debug ($"{Title} - OnMenuItemAccepted");
|
||||
menuBarItem.PopoverMenu!.VisibleChanged -= OnMenuItemAccepted;
|
||||
if (menuBarItem.PopoverMenu is { })
|
||||
{
|
||||
menuBarItem.PopoverMenu.VisibleChanged -= OnMenuItemAccepted;
|
||||
}
|
||||
|
||||
if (Active && menuBarItem.PopoverMenu is { Visible: false })
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user