Try to fix NetDriver compilation

This commit is contained in:
tznind
2024-11-27 12:46:01 +00:00
parent 49f45bb7a4
commit 42ff86f68a
4 changed files with 222 additions and 289 deletions

View File

@@ -1804,7 +1804,7 @@ public static class EscSeqUtils
/// <summary>
/// The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) R
/// </summary>
public static readonly string CSI_RequestCursorPositionReport_Terminator = "R";
public const string CSI_RequestCursorPositionReport_Terminator = "R";
/// <summary>
/// ESC [ 0 c - Send Device Attributes (Primary DA)
@@ -1855,13 +1855,13 @@ public static class EscSeqUtils
/// <summary>
/// The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t
/// </summary>
public static readonly string CSI_ReportTerminalSizeInChars_Terminator = "t";
public const string CSI_ReportTerminalSizeInChars_Terminator = "t";
/// <summary>
/// The value of the response to <see cref="CSI_ReportTerminalSizeInChars"/> indicating value 1 and 2 are the terminal
/// size in chars.
/// </summary>
public static readonly string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
#endregion
}

View File

@@ -40,6 +40,9 @@ public class FakeDriver : ConsoleDriver
public static Behaviors FakeBehaviors = new ();
public override bool SupportsTrueColor => false;
/// <inheritdoc />
public override void WriteRaw (string ansi) { throw new NotImplementedException (); }
public FakeDriver ()
{
Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;

View File

@@ -3,7 +3,9 @@
// NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
//
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static Terminal.Gui.NetEvents;
@@ -11,9 +13,11 @@ namespace Terminal.Gui;
internal class NetDriver : ConsoleDriver
{
public bool IsWinPlatform { get; private set; }
public NetWinVTConsole? NetWinConsole { get; private set; }
public override void Suspend ()
{
if (Environment.OSVersion.Platform != PlatformID.Unix)
@@ -132,33 +136,30 @@ internal class NetDriver : ConsoleDriver
{
output.Append (
EscSeqUtils.CSI_SetGraphicsRendition (
MapColors (
(ConsoleColor)attr.Background
.GetClosestNamedColor16 (),
false
),
MapColors (
(ConsoleColor)attr.Foreground
.GetClosestNamedColor16 ())
)
MapColors (
(ConsoleColor)attr.Background.GetClosestNamedColor16 (),
false
),
MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ())
)
);
}
else
{
output.Append (
EscSeqUtils.CSI_SetForegroundColorRGB (
attr.Foreground.R,
attr.Foreground.G,
attr.Foreground.B
)
attr.Foreground.R,
attr.Foreground.G,
attr.Foreground.B
)
);
output.Append (
EscSeqUtils.CSI_SetBackgroundColorRGB (
attr.Background.R,
attr.Background.G,
attr.Background.B
)
attr.Background.R,
attr.Background.G,
attr.Background.B
)
);
}
}
@@ -221,10 +222,17 @@ internal class NetDriver : ConsoleDriver
return updated;
}
#region Init/End/MainLoop
/// <inheritdoc />
internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
internal NetMainLoop? _mainLoopDriver;
/// <inheritdoc />
internal override void RawWrite (string str)
{
Console.Write (str);
}
public override MainLoop Init ()
{
@@ -236,7 +244,7 @@ internal class NetDriver : ConsoleDriver
try
{
NetWinConsole = new ();
NetWinConsole = new NetWinVTConsole ();
}
catch (ApplicationException)
{
@@ -295,6 +303,7 @@ internal class NetDriver : ConsoleDriver
_mainLoopDriver.ProcessInput = ProcessInput;
return new (_mainLoopDriver);
return new MainLoop (_mainLoopDriver);
}
private void ProcessInput (InputResult inputEvent)
@@ -323,7 +332,6 @@ internal class NetDriver : ConsoleDriver
break;
case EventType.Mouse:
MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
//Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
OnMouseEvent (me);
@@ -334,7 +342,7 @@ internal class NetDriver : ConsoleDriver
Left = 0;
Cols = inputEvent.WindowSizeEvent.Size.Width;
Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0);
;
ResizeScreen ();
ClearContents ();
_winSizeChanging = false;
@@ -349,7 +357,6 @@ internal class NetDriver : ConsoleDriver
throw new ArgumentOutOfRangeException ();
}
}
public override void End ()
{
if (IsWinPlatform)
@@ -374,6 +381,9 @@ internal class NetDriver : ConsoleDriver
#endregion Init/End/MainLoop
#region Color Handling
public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix
@@ -787,4 +797,4 @@ internal class NetDriver : ConsoleDriver
}
#endregion Low-Level DotNet tuff
}
}

View File

@@ -1,238 +1,150 @@
#nullable enable
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
namespace Terminal.Gui;
internal class NetEvents : IDisposable
{
private readonly ManualResetEventSlim _inputReady = new (false);
private CancellationTokenSource? _inputReadyCancellationTokenSource;
private readonly Queue<InputResult> _inputQueue = new ();
private readonly IConsoleDriver _consoleDriver;
private ConsoleKeyInfo []? _cki;
private bool _isEscSeq;
#if PROCESS_REQUEST
bool _neededProcessRequest;
#endif
public NetEvents (IConsoleDriver consoleDriver)
private readonly CancellationTokenSource _netEventsDisposed = new CancellationTokenSource ();
//CancellationTokenSource _waitForStartCancellationTokenSource;
private readonly ManualResetEventSlim _winChange = new (false);
private readonly BlockingCollection<InputResult?> _inputQueue = new (new ConcurrentQueue<InputResult?> ());
private readonly ConsoleDriver _consoleDriver;
public AnsiResponseParser<ConsoleKeyInfo> Parser { get; private set; } = new ();
public NetEvents (ConsoleDriver consoleDriver)
{
_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
_inputReadyCancellationTokenSource = new ();
Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
}
public InputResult? DequeueInput ()
{
while (_inputReadyCancellationTokenSource is { Token.IsCancellationRequested: false })
Task.Run (() =>
{
try
{
if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
{
if (_inputQueue.Count == 0)
{
_inputReady.Wait (_inputReadyCancellationTokenSource.Token);
}
}
ProcessInputQueue ();
}
catch (OperationCanceledException)
{ }
}, _netEventsDisposed.Token);
if (_inputQueue.Count > 0)
Task.Run (() => {
try
{
CheckWindowSizeChange ();
}
catch (OperationCanceledException)
{ }
}, _netEventsDisposed.Token);
Parser.UnexpectedResponseHandler = ProcessRequestResponse;
}
public InputResult? DequeueInput ()
{
while (!_netEventsDisposed.Token.IsCancellationRequested)
{
_winChange.Set ();
try
{
if (_inputQueue.TryTake (out var item, -1, _netEventsDisposed.Token))
{
return _inputQueue.Dequeue ();
return item;
}
}
catch (OperationCanceledException)
{
return null;
}
finally
{
if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
{
_inputReady.Reset ();
}
}
#if PROCESS_REQUEST
_neededProcessRequest = false;
#endif
}
return null;
}
private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
private ConsoleKeyInfo ReadConsoleKeyInfo (bool intercept = true)
{
while (!cancellationToken.IsCancellationRequested)
// if there is a key available, return it without waiting
// (or dispatching work to the thread queue)
if (Console.KeyAvailable)
{
// if there is a key available, return it without waiting
// (or dispatching work to the thread queue)
return Console.ReadKey (intercept);
}
while (!_netEventsDisposed.IsCancellationRequested)
{
Task.Delay (100, _netEventsDisposed.Token).Wait (_netEventsDisposed.Token);
foreach (var k in ShouldRelease ())
{
ProcessMapConsoleKeyInfo (k);
}
if (Console.KeyAvailable)
{
return Console.ReadKey (intercept);
}
// The delay must be here because it may have a request response after a while
// In WSL it takes longer for keys to be available.
Task.Delay (100, cancellationToken).Wait (cancellationToken);
}
cancellationToken.ThrowIfCancellationRequested ();
_netEventsDisposed.Token.ThrowIfCancellationRequested ();
return default (ConsoleKeyInfo);
}
public IEnumerable<ConsoleKeyInfo> ShouldRelease ()
{
if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
DateTime.Now - Parser.StateChangedAt > _consoleDriver.EscTimeout)
{
return Parser.Release ().Select (o => o.Item2);
}
return [];
}
private void ProcessInputQueue ()
{
while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
while (!_netEventsDisposed.IsCancellationRequested)
{
try
if (_inputQueue.Count == 0)
{
ConsoleKey key = 0;
ConsoleModifiers mod = 0;
ConsoleKeyInfo newConsoleKeyInfo = default;
while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
while (!_netEventsDisposed.IsCancellationRequested)
{
ConsoleKeyInfo consoleKeyInfo;
try
consoleKeyInfo = ReadConsoleKeyInfo ();
// Parse
foreach (var k in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
{
consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token);
ProcessMapConsoleKeyInfo (k.Item2);
}
catch (OperationCanceledException)
{
return;
}
var ckiAlreadyResized = false;
if (EscSeqUtils.IncompleteCkInfos is { })
{
ckiAlreadyResized = true;
_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
_cki = EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki);
EscSeqUtils.IncompleteCkInfos = null;
if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B')
{
_isEscSeq = true;
}
}
if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
|| (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq))
{
if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
{
_cki = EscSeqUtils.ResizeArray (
new (
(char)KeyCode.Esc,
0,
false,
false,
false
),
_cki
);
}
_isEscSeq = true;
if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
|| (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127)
|| (_cki is { }
&& char.IsLetter (_cki [^1].KeyChar)
&& char.IsLower (consoleKeyInfo.KeyChar)
&& char.IsLetter (consoleKeyInfo.KeyChar))
|| (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar))
|| (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar))
|| (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar)))
{
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
_cki = null;
_isEscSeq = false;
ProcessMapConsoleKeyInfo (consoleKeyInfo);
}
else
{
newConsoleKeyInfo = consoleKeyInfo;
if (!ckiAlreadyResized)
{
_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
}
if (Console.KeyAvailable)
{
continue;
}
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki!, ref mod);
_cki = null;
_isEscSeq = false;
}
break;
}
if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { })
{
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
_cki = null;
if (Console.KeyAvailable)
{
_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
}
else
{
ProcessMapConsoleKeyInfo (consoleKeyInfo);
}
break;
}
ProcessMapConsoleKeyInfo (consoleKeyInfo);
break;
}
if (_inputQueue.Count > 0)
{
_inputReady.Set ();
}
}
catch (OperationCanceledException)
{
return;
}
}
}
void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
{
_inputQueue.Enqueue (
new ()
{
EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
}
);
_isEscSeq = false;
}
void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
{
_inputQueue.Add (
new InputResult
{
EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
}
);
}
private void CheckWindowSizeChange ()
{
void RequestWindowSize (CancellationToken cancellationToken)
void RequestWindowSize ()
{
while (!cancellationToken.IsCancellationRequested)
while (!_netEventsDisposed.IsCancellationRequested)
{
// Wait for a while then check if screen has changed sizes
Task.Delay (500, cancellationToken).Wait (cancellationToken);
Task.Delay (500, _netEventsDisposed.Token).Wait (_netEventsDisposed.Token);
int buffHeight, buffWidth;
@@ -258,19 +170,17 @@ internal class NetEvents : IDisposable
}
}
cancellationToken.ThrowIfCancellationRequested ();
_netEventsDisposed.Token.ThrowIfCancellationRequested ();
}
while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
while (!_netEventsDisposed.IsCancellationRequested)
{
try
{
RequestWindowSize (_inputReadyCancellationTokenSource.Token);
_winChange.Wait (_netEventsDisposed.Token);
_winChange.Reset ();
if (_inputQueue.Count > 0)
{
_inputReady.Set ();
}
RequestWindowSize ();
}
catch (OperationCanceledException)
{
@@ -295,16 +205,29 @@ internal class NetEvents : IDisposable
int w = Math.Max (winWidth, 0);
int h = Math.Max (winHeight, 0);
_inputQueue.Enqueue (
new ()
_inputQueue.Add (
new InputResult
{
EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (w, h) }
EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) }
}
);
return true;
}
private bool ProcessRequestResponse (IEnumerable<Tuple<char, ConsoleKeyInfo>> obj)
{
// Added for signature compatibility with existing method, not sure what they are even for.
ConsoleKeyInfo newConsoleKeyInfo = default;
ConsoleKey key = default;
ConsoleModifiers mod = default;
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, obj.Select (v => v.Item2).ToArray (), ref mod);
// Handled
return true;
}
// Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
private void ProcessRequestResponse (
ref ConsoleKeyInfo newConsoleKeyInfo,
@@ -313,22 +236,23 @@ internal class NetEvents : IDisposable
ref ConsoleModifiers mod
)
{
// isMouse is true if it's CSI<, false otherwise
EscSeqUtils.DecodeEscSeq (
ref newConsoleKeyInfo,
ref key,
cki,
ref mod,
out string c1Control,
out string code,
out string [] values,
out string terminating,
out bool isMouse,
out List<MouseFlags> mouseFlags,
out Point pos,
out bool isReq,
(f, p) => HandleMouseEvent (MapMouseFlags (f), p)
);
ref newConsoleKeyInfo,
ref key,
cki,
ref mod,
out string c1Control,
out string code,
out string [] values,
out string terminating,
out bool isMouse,
out List<MouseFlags> mouseFlags,
out Point pos,
out bool isReq,
(f, p) => HandleMouseEvent (MapMouseFlags (f), p)
);
if (isMouse)
{
@@ -347,10 +271,7 @@ internal class NetEvents : IDisposable
return;
}
if (newConsoleKeyInfo != default)
{
HandleKeyboardEvent (newConsoleKeyInfo);
}
HandleKeyboardEvent (newConsoleKeyInfo);
}
[UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
@@ -491,51 +412,53 @@ internal class NetEvents : IDisposable
private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
{
if (terminating ==
switch (terminating)
{
// BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
// The observation is correct because the response isn't immediate and this is useless
EscSeqUtils.CSI_RequestCursorPositionReport_Terminator)
{
var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
if (_lastCursorPosition.Y != point.Y)
{
_lastCursorPosition = point;
var eventType = EventType.WindowPosition;
var winPositionEv = new WindowPositionEvent { CursorPosition = point };
if (_lastCursorPosition.Y != point.Y)
{
_lastCursorPosition = point;
var eventType = EventType.WindowPosition;
var winPositionEv = new WindowPositionEvent { CursorPosition = point };
_inputQueue.Enqueue (
new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
);
}
else
{
return;
}
}
else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator)
{
if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue)
{
EnqueueWindowSizeEvent (
Math.Max (int.Parse (values [1]), 0),
Math.Max (int.Parse (values [2]), 0),
Math.Max (int.Parse (values [1]), 0),
Math.Max (int.Parse (values [2]), 0)
);
}
else
{
_inputQueue.Add (
new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
);
}
else
{
return;
}
break;
case EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator:
switch (values [0])
{
case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue:
EnqueueWindowSizeEvent (
Math.Max (int.Parse (values [1]), 0),
Math.Max (int.Parse (values [2]), 0),
Math.Max (int.Parse (values [1]), 0),
Math.Max (int.Parse (values [2]), 0)
);
break;
default:
EnqueueRequestResponseEvent (c1Control, code, values, terminating);
break;
}
break;
default:
EnqueueRequestResponseEvent (c1Control, code, values, terminating);
}
}
else
{
EnqueueRequestResponseEvent (c1Control, code, values, terminating);
}
_inputReady.Set ();
break;
}
}
private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
@@ -543,7 +466,7 @@ internal class NetEvents : IDisposable
var eventType = EventType.RequestResponse;
var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
_inputQueue.Enqueue (
_inputQueue.Add (
new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
);
}
@@ -552,8 +475,8 @@ internal class NetEvents : IDisposable
{
var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
_inputQueue.Enqueue (
new () { EventType = EventType.Mouse, MouseEvent = mouseEvent }
_inputQueue.Add (
new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent }
);
}
@@ -634,15 +557,15 @@ internal class NetEvents : IDisposable
public readonly override string ToString ()
{
return (EventType switch
{
EventType.Key => ToString (ConsoleKeyInfo),
EventType.Mouse => MouseEvent.ToString (),
return EventType switch
{
EventType.Key => ToString (ConsoleKeyInfo),
EventType.Mouse => MouseEvent.ToString (),
//EventType.WindowSize => WindowSize.ToString (),
//EventType.RequestResponse => RequestResponse.ToString (),
_ => "Unknown event type: " + EventType
})!;
//EventType.WindowSize => WindowSize.ToString (),
//EventType.RequestResponse => RequestResponse.ToString (),
_ => "Unknown event type: " + EventType
};
}
/// <summary>Prints a ConsoleKeyInfoEx structure</summary>
@@ -667,16 +590,13 @@ internal class NetEvents : IDisposable
{
var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
_inputQueue.Enqueue (inputResult);
_inputQueue.Add (inputResult);
}
public void Dispose ()
{
_inputReadyCancellationTokenSource?.Cancel ();
_inputReadyCancellationTokenSource?.Dispose ();
_inputReadyCancellationTokenSource = null;
_inputReady.Dispose ();
_netEventsDisposed?.Cancel ();
_netEventsDisposed?.Dispose ();
try
{
@@ -692,4 +612,4 @@ internal class NetEvents : IDisposable
// Ignore - Console input has already been closed
}
}
}
}