mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-31 10:17:55 +01:00
Improves drivers responses to being more reliable.
This commit is contained in:
@@ -176,6 +176,13 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!_inputHandlerTokenSource.IsCancellationRequested)
|
||||
{
|
||||
_waitForInput.Reset ();
|
||||
}
|
||||
}
|
||||
|
||||
if (_pollDataQueue?.Count == 0 || _forceRead)
|
||||
{
|
||||
@@ -235,12 +242,15 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
{
|
||||
if (_retries > 1)
|
||||
{
|
||||
EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
|
||||
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
|
||||
{
|
||||
seqReqStatus.AnsiRequest.Response = string.Empty;
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
{
|
||||
EscSeqRequests.Statuses.TryDequeue (out _);
|
||||
|
||||
seqReqStatus.AnsiRequest.Response = string.Empty;
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
_retries = 0;
|
||||
@@ -257,7 +267,10 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
|
||||
try
|
||||
{
|
||||
Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
|
||||
if (!_forceRead)
|
||||
{
|
||||
Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -266,7 +279,6 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
}
|
||||
}
|
||||
|
||||
_waitForInput.Reset ();
|
||||
_eventReady.Set ();
|
||||
}
|
||||
}
|
||||
@@ -327,6 +339,22 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator))
|
||||
{
|
||||
if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result))
|
||||
{
|
||||
lock (result.AnsiRequest._responseLock)
|
||||
{
|
||||
result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator;
|
||||
result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator);
|
||||
|
||||
EscSeqUtils.InvalidRequestTerminator = null;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (newConsoleKeyInfo != default)
|
||||
{
|
||||
_pollDataQueue!.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo));
|
||||
@@ -420,8 +448,7 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
// Write to stdout (fd 1)
|
||||
write (STDOUT_FILENO, ansiRequest, ansiRequest.Length);
|
||||
|
||||
// Flush the stdout buffer immediately using fsync
|
||||
fsync (STDOUT_FILENO);
|
||||
Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
|
||||
}
|
||||
|
||||
[DllImport ("libc")]
|
||||
@@ -436,9 +463,6 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
[DllImport ("libc")]
|
||||
private static extern int write (int fd, string buf, int n);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int fsync (int fd);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int ioctl (int fd, int request, ref Winsize ws);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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;
|
||||
@@ -140,7 +141,7 @@ internal class NetEvents : IDisposable
|
||||
|
||||
//CancellationTokenSource _waitForStartCancellationTokenSource;
|
||||
private readonly ManualResetEventSlim _winChange = new (false);
|
||||
private readonly Queue<InputResult?> _inputQueue = new ();
|
||||
private readonly ConcurrentQueue<InputResult?> _inputQueue = new ();
|
||||
private readonly ConsoleDriver _consoleDriver;
|
||||
private ConsoleKeyInfo [] _cki;
|
||||
private bool _isEscSeq;
|
||||
@@ -191,7 +192,10 @@ internal class NetEvents : IDisposable
|
||||
#endif
|
||||
if (_inputQueue.Count > 0)
|
||||
{
|
||||
return _inputQueue.Dequeue ();
|
||||
if (_inputQueue.TryDequeue (out InputResult? result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,17 +204,10 @@ internal class NetEvents : IDisposable
|
||||
|
||||
private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
|
||||
{
|
||||
// if there is a key available, return it without waiting
|
||||
// (or dispatching work to the thread queue)
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
return Console.ReadKey (intercept);
|
||||
}
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Task.Delay (100, cancellationToken).Wait (cancellationToken);
|
||||
|
||||
// if there is a key available, return it without waiting
|
||||
// (or dispatching work to the thread queue)
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
return Console.ReadKey (intercept);
|
||||
@@ -220,12 +217,15 @@ internal class NetEvents : IDisposable
|
||||
{
|
||||
if (_retries > 1)
|
||||
{
|
||||
EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
|
||||
|
||||
lock (seqReqStatus.AnsiRequest._responseLock)
|
||||
if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
|
||||
{
|
||||
seqReqStatus.AnsiRequest.Response = string.Empty;
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
{
|
||||
EscSeqRequests.Statuses.TryDequeue (out _);
|
||||
|
||||
seqReqStatus.AnsiRequest.Response = string.Empty;
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
_retries = 0;
|
||||
@@ -239,6 +239,11 @@ internal class NetEvents : IDisposable
|
||||
{
|
||||
_retries = 0;
|
||||
}
|
||||
|
||||
if (!_forceRead)
|
||||
{
|
||||
Task.Delay (100, cancellationToken).Wait (cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested ();
|
||||
@@ -310,7 +315,10 @@ internal class NetEvents : IDisposable
|
||||
|
||||
_isEscSeq = true;
|
||||
|
||||
if (_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
|
||||
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.IsLetter (consoleKeyInfo.KeyChar)))
|
||||
{
|
||||
ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
|
||||
_cki = null;
|
||||
@@ -510,7 +518,26 @@ internal class NetEvents : IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
HandleKeyboardEvent (newConsoleKeyInfo);
|
||||
if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator))
|
||||
{
|
||||
if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result))
|
||||
{
|
||||
lock (result.AnsiRequest._responseLock)
|
||||
{
|
||||
result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator;
|
||||
result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator);
|
||||
|
||||
EscSeqUtils.InvalidRequestTerminator = null;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (newConsoleKeyInfo != default)
|
||||
{
|
||||
HandleKeyboardEvent (newConsoleKeyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
[UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
|
||||
@@ -1822,7 +1849,7 @@ internal class NetMainLoop : IMainLoopDriver
|
||||
|
||||
private readonly ManualResetEventSlim _eventReady = new (false);
|
||||
private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
|
||||
private readonly Queue<InputResult?> _resultQueue = new ();
|
||||
private readonly ConcurrentQueue<InputResult?> _resultQueue = new ();
|
||||
internal readonly ManualResetEventSlim _waitForProbe = new (false);
|
||||
private readonly CancellationTokenSource _eventReadyTokenSource = new ();
|
||||
private MainLoop _mainLoop;
|
||||
@@ -1897,11 +1924,12 @@ internal class NetMainLoop : IMainLoopDriver
|
||||
while (_resultQueue.Count > 0)
|
||||
{
|
||||
// Always dequeue even if it's null and invoke if isn't null
|
||||
InputResult? dequeueResult = _resultQueue.Dequeue ();
|
||||
|
||||
if (dequeueResult is { })
|
||||
if (_resultQueue.TryDequeue (out InputResult? dequeueResult))
|
||||
{
|
||||
ProcessInput?.Invoke (dequeueResult.Value);
|
||||
if (dequeueResult is { })
|
||||
{
|
||||
ProcessInput?.Invoke (dequeueResult.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1960,10 +1988,10 @@ internal class NetMainLoop : IMainLoopDriver
|
||||
|
||||
try
|
||||
{
|
||||
while (_resultQueue.Count > 0 && _resultQueue.Peek () is null)
|
||||
while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out InputResult? result) && result is null)
|
||||
{
|
||||
// Dequeue null values
|
||||
_resultQueue.Dequeue ();
|
||||
_resultQueue.TryDequeue (out _);
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException) // Peek can raise an exception
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
// 2) The values provided during Init (and the first WindowsConsole.EventType.WindowBufferSize) are not correct.
|
||||
//
|
||||
// If HACK_CHECK_WINCHANGED is defined then we ignore WindowsConsole.EventType.WindowBufferSize events
|
||||
// and instead check the console size every every 500ms in a thread in WidowsMainLoop.
|
||||
// and instead check the console size every 500ms in a thread in WidowsMainLoop.
|
||||
// As of Windows 11 23H2 25947.1000 and/or WT 1.19.2682 tearing no longer occurs when using
|
||||
// the WindowsConsole.EventType.WindowBufferSize event. However, on Init the window size is
|
||||
// still incorrect so we still need this hack.
|
||||
|
||||
#define HACK_CHECK_WINCHANGED
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -920,6 +921,7 @@ internal class WindowsConsole
|
||||
uint numberEventsRead = 0;
|
||||
StringBuilder ansiSequence = new StringBuilder ();
|
||||
bool readingSequence = false;
|
||||
bool raisedResponse = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -972,6 +974,7 @@ internal class WindowsConsole
|
||||
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
{
|
||||
raisedResponse = true;
|
||||
seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
|
||||
// Clear the terminator for not be enqueued
|
||||
@@ -985,16 +988,31 @@ internal class WindowsConsole
|
||||
}
|
||||
}
|
||||
|
||||
if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
|
||||
if (readingSequence && !raisedResponse && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
|
||||
{
|
||||
_mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
|
||||
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
{
|
||||
seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
|
||||
}
|
||||
|
||||
_retries = 0;
|
||||
}
|
||||
else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
|
||||
{
|
||||
if (_retries > 1)
|
||||
{
|
||||
_mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
|
||||
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
|
||||
{
|
||||
seqReqStatus.AnsiRequest.Response = string.Empty;
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
|
||||
lock (seqReqStatus!.AnsiRequest._responseLock)
|
||||
{
|
||||
_mainLoop.EscSeqRequests.Statuses.TryDequeue (out _);
|
||||
|
||||
seqReqStatus.AnsiRequest.Response = string.Empty;
|
||||
seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
_retries = 0;
|
||||
@@ -2337,7 +2355,7 @@ internal class WindowsMainLoop : IMainLoopDriver
|
||||
private readonly ManualResetEventSlim _eventReady = new (false);
|
||||
|
||||
// The records that we keep fetching
|
||||
private readonly Queue<WindowsConsole.InputRecord []> _resultQueue = new ();
|
||||
private readonly ConcurrentQueue<WindowsConsole.InputRecord []> _resultQueue = new ();
|
||||
internal readonly ManualResetEventSlim _waitForProbe = new (false);
|
||||
private readonly WindowsConsole _winConsole;
|
||||
private CancellationTokenSource _eventReadyTokenSource = new ();
|
||||
@@ -2422,11 +2440,12 @@ internal class WindowsMainLoop : IMainLoopDriver
|
||||
{
|
||||
while (_resultQueue.Count > 0)
|
||||
{
|
||||
WindowsConsole.InputRecord [] inputRecords = _resultQueue.Dequeue ();
|
||||
|
||||
if (inputRecords is { Length: > 0 })
|
||||
if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord [] inputRecords))
|
||||
{
|
||||
((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]);
|
||||
if (inputRecords is { Length: > 0 })
|
||||
{
|
||||
((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if HACK_CHECK_WINCHANGED
|
||||
@@ -2524,15 +2543,7 @@ internal class WindowsMainLoop : IMainLoopDriver
|
||||
}
|
||||
}
|
||||
|
||||
if (_eventReady.IsSet)
|
||||
{
|
||||
// it's already in an iteration and ensures set to iterate again
|
||||
Application.Invoke (() => _eventReady.Set ());
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventReady.Set ();
|
||||
}
|
||||
_eventReady.Set ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user