Browse Source

Restoring drivers with the sctructural changes.

BDisp 8 months ago
parent
commit
bea966978a
32 changed files with 1118 additions and 1959 deletions
  1. 1 1
      Terminal.Gui/Application/Application.Run.cs
  2. 10 20
      Terminal.Gui/Application/MainLoop.cs
  3. 0 126
      Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs
  4. 0 17
      Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs
  5. 0 80
      Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs
  6. 0 64
      Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs
  7. 0 125
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  8. 403 35
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  9. 0 12
      Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c
  10. 0 17
      Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh
  11. 111 374
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  12. 0 13
      Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
  13. 2 0
      Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs
  14. 28 0
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs
  15. 114 0
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs
  16. 31 26
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  17. 0 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  18. 16 23
      Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
  19. 72 115
      Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
  20. 20 16
      Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
  21. 13 128
      Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
  22. 5 7
      Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
  23. 17 12
      Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
  24. 14 31
      Terminal.Gui/Terminal.Gui.csproj
  25. BIN
      Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib
  26. BIN
      Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.so
  27. 0 440
      UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs
  28. 0 4
      UnitTests/Application/MainLoopTests.cs
  29. 0 79
      UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs
  30. 72 0
      UnitTests/Input/EscSeqRequestsTests.cs
  31. 189 190
      UnitTests/Input/EscSeqUtilsTests.cs
  32. 0 2
      UnitTests/View/Adornment/PaddingTests.cs

+ 1 - 1
Terminal.Gui/Application/Application.Run.cs

@@ -521,7 +521,7 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
     /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
     /// <value>The main loop.</value>
-    public static MainLoop? MainLoop { get; private set; }
+    internal static MainLoop? MainLoop { get; private set; }
 
     /// <summary>
     ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to

+ 10 - 20
Terminal.Gui/Application/MainLoop.cs

@@ -1,4 +1,5 @@
-//
+#nullable enable
+//
 // MainLoop.cs: IMainLoopDriver and MainLoop for Terminal.Gui
 //
 // Authors:
@@ -10,7 +11,7 @@ using System.Collections.ObjectModel;
 namespace Terminal.Gui;
 
 /// <summary>Interface to create a platform specific <see cref="MainLoop"/> driver.</summary>
-public interface IMainLoopDriver
+internal interface IMainLoopDriver
 {
     /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
     /// <returns><c>true</c>, if there were pending events, <c>false</c> otherwise.</returns>
@@ -29,16 +30,6 @@ public interface IMainLoopDriver
 
     /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.</summary>
     void Wakeup ();
-
-    /// <summary>
-    ///     Flag <see cref="WaitForInput"/> to force reading input instead of call <see cref="ManualResetEventSlim.Wait ()"/>.
-    /// </summary>
-    bool ForceRead { get; set; }
-
-    /// <summary>
-    ///     Switch for waiting for input or signaled to resume.
-    /// </summary>
-    ManualResetEventSlim WaitForInput { get; set; }
 }
 
 /// <summary>The MainLoop monitors timers and idle handlers.</summary>
@@ -46,7 +37,7 @@ public interface IMainLoopDriver
 ///     Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
 ///     on Windows.
 /// </remarks>
-public class MainLoop : IDisposable
+internal class MainLoop : IDisposable
 {
     internal List<Func<bool>> _idleHandlers = new ();
     internal SortedList<long, Timeout> _timeouts = new ();
@@ -82,7 +73,7 @@ public class MainLoop : IDisposable
 
     /// <summary>The current <see cref="IMainLoopDriver"/> in use.</summary>
     /// <value>The main loop driver.</value>
-    public IMainLoopDriver MainLoopDriver { get; private set; }
+    internal IMainLoopDriver? MainLoopDriver { get; private set; }
 
     /// <summary>Used for unit tests.</summary>
     internal bool Running { get; set; }
@@ -127,7 +118,7 @@ public class MainLoop : IDisposable
             _idleHandlers.Add (idleHandler);
         }
 
-        MainLoopDriver.Wakeup ();
+        MainLoopDriver?.Wakeup ();
 
         return idleHandler;
     }
@@ -198,7 +189,7 @@ public class MainLoop : IDisposable
     ///     You can use this method if you want to probe if events are pending. Typically used if you need to flush the
     ///     input queue while still running some of your own code in your main thread.
     /// </remarks>
-    internal bool EventsPending () { return MainLoopDriver.EventsPending (); }
+    internal bool EventsPending () { return MainLoopDriver!.EventsPending (); }
 
     /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
     /// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
@@ -232,7 +223,7 @@ public class MainLoop : IDisposable
     {
         lock (_timeoutsLockToken)
         {
-            int idx = _timeouts.IndexOfValue (token as Timeout);
+            int idx = _timeouts.IndexOfValue ((token as Timeout)!);
 
             if (idx == -1)
             {
@@ -277,7 +268,7 @@ public class MainLoop : IDisposable
             }
         }
 
-        MainLoopDriver.Iteration ();
+        MainLoopDriver?.Iteration ();
 
         bool runIdle;
 
@@ -303,8 +294,7 @@ public class MainLoop : IDisposable
     ///     Invoked when a new timeout is added. To be used in the case when
     ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
     /// </summary>
-    [CanBeNull]
-    internal event EventHandler<TimeoutEventArgs> TimeoutAdded;
+    internal event EventHandler<TimeoutEventArgs>? TimeoutAdded;
 
     /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input.</summary>
     internal void Wakeup () { MainLoopDriver?.Wakeup (); }

+ 0 - 126
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs

@@ -1,126 +0,0 @@
-#nullable enable
-namespace Terminal.Gui;
-
-/// <summary>
-///     Describes an ongoing ANSI request sent to the console.
-///     Send a request using <see cref="ConsoleDriver.TryWriteAnsiRequest"/> which will return the response.
-/// </summary>
-public class AnsiEscapeSequenceRequest
-{
-    internal readonly object _responseLock = new (); // Per-instance lock
-
-    /// <summary>
-    ///     Gets the response received from the request.
-    /// </summary>
-    public AnsiEscapeSequenceResponse? AnsiEscapeSequenceResponse { get; internal set; }
-
-    /// <summary>
-    ///     The value expected in the response after the CSI e.g.
-    ///     <see>
-    ///         <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
-    ///     </see>
-    ///     should result in a response of the form <c>ESC [ 8 ; height ; width t</c>. In this case,
-    ///     <see cref="ExpectedResponseValue"/>
-    ///     will be <c>"8"</c>.
-    /// </summary>
-    public string? ExpectedResponseValue { get; init; }
-
-    /// <summary>
-    ///     Gets the request string to send e.g. see
-    ///     <see>
-    ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
-    ///     </see>
-    /// </summary>
-    public required string Request { get; init; }
-
-    /// <summary>
-    ///     <para>
-    ///         Gets the terminator that uniquely identifies the response received from
-    ///         the console. e.g. for
-    ///         <see>
-    ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
-    ///         </see>
-    ///         the terminator is
-    ///         <see>
-    ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Terminator</cref>
-    ///         </see>
-    ///         .
-    ///     </para>
-    ///     <para>
-    ///         After sending a request, the first response with matching terminator will be matched
-    ///         to the oldest outstanding request.
-    ///     </para>
-    /// </summary>
-    public required string Terminator { get; init; }
-
-    internal void RaiseResponseFromInput (string? response, AnsiEscapeSequenceRequest? request)
-    {
-        ProcessResponse (response, request);
-
-        ResponseFromInput?.Invoke (this, AnsiEscapeSequenceResponse);
-    }
-
-    /// <summary>
-    ///     Raised with the response object and validation.
-    /// </summary>
-    internal event EventHandler<AnsiEscapeSequenceResponse?>? ResponseFromInput;
-
-    /// <summary>
-    ///     Process the <see cref="AnsiEscapeSequenceResponse"/> of an ANSI escape sequence request.
-    /// </summary>
-    /// <param name="response">The response.</param>
-    private void ProcessResponse (string? response, AnsiEscapeSequenceRequest? request)
-    {
-        var error = new StringBuilder ();
-        var values = new string? [] { null };
-
-        try
-        {
-            if (!string.IsNullOrEmpty (response) && !response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc))
-            {
-                throw new InvalidOperationException ($"Invalid Response: {response}");
-            }
-
-            if (string.IsNullOrEmpty (Terminator))
-            {
-                throw new InvalidOperationException ("Terminator request is empty.");
-            }
-
-            if (string.IsNullOrEmpty (response))
-            {
-                throw new InvalidOperationException ("Response request is null.");
-            }
-
-            if (!string.IsNullOrEmpty (response) && !response.EndsWith (Terminator [^1]))
-            {
-                string resp = string.IsNullOrEmpty (response) ? "" : response.Last ().ToString ();
-
-                throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{Terminator [^1]}'");
-            }
-        }
-        catch (Exception ex)
-        {
-            error.AppendLine ($"Error executing ANSI request:\n{ex.Message}");
-        }
-        finally
-        {
-            if (string.IsNullOrEmpty (error.ToString ()))
-            {
-                (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (response?.ToCharArray ());
-            }
-
-            if (request is { } && !string.IsNullOrEmpty (request.ExpectedResponseValue) && request.ExpectedResponseValue != values [0])
-            {
-                error.AppendLine ($"Error executing ANSI request:\nValue ends with '{values [0]}'\nand doesn't end with: '{ExpectedResponseValue! [^1]}'");
-            }
-        }
-
-        AnsiEscapeSequenceResponse = new ()
-        {
-            Response = response, Error = error.ToString (),
-            Terminator = string.IsNullOrEmpty (response) ? "" : response [^1].ToString (),
-            ExpectedResponseValue = values [0],
-            Valid = string.IsNullOrWhiteSpace (error.ToString ()) && !string.IsNullOrWhiteSpace (response)
-        };
-    }
-}

+ 0 - 17
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestStatus.cs

@@ -1,17 +0,0 @@
-#nullable enable
-namespace Terminal.Gui;
-
-/// <summary>
-///     Represents the status of an ANSI escape sequence request made to the terminal using
-///     <see cref="AnsiEscapeSequenceRequests"/>.
-/// </summary>
-/// <remarks></remarks>
-public class AnsiEscapeSequenceRequestStatus
-{
-    /// <summary>Creates a new state of escape sequence request.</summary>
-    /// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
-    public AnsiEscapeSequenceRequestStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; }
-
-    /// <summary>Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator).</summary>
-    public AnsiEscapeSequenceRequest AnsiRequest { get; }
-}

+ 0 - 80
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequests.cs

@@ -1,80 +0,0 @@
-#nullable enable
-
-using System.Collections.Concurrent;
-
-namespace Terminal.Gui;
-
-/// <summary>
-///     Manages ANSI Escape Sequence requests and responses. The list of <see cref="AnsiEscapeSequenceRequestStatus"/>
-///     contains the
-///     status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator).
-/// </summary>
-public static class AnsiEscapeSequenceRequests
-{
-    /// <summary>
-    ///     Adds a new request for the ANSI Escape Sequence defined by <paramref name="ansiRequest"/>. Adds a
-    ///     <see cref="AnsiEscapeSequenceRequestStatus"/> instance to <see cref="Statuses"/> list.
-    /// </summary>
-    /// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
-    public static void Add (AnsiEscapeSequenceRequest ansiRequest)
-    {
-        lock (ansiRequest._responseLock)
-        {
-            Statuses.Enqueue (new (ansiRequest));
-        }
-
-        System.Diagnostics.Debug.Assert (Statuses.Count > 0);
-    }
-
-    /// <summary>
-    ///     Clear the <see cref="Statuses"/> property.
-    /// </summary>
-    public static void Clear ()
-    {
-        lock (Statuses)
-        {
-            Statuses.Clear ();
-        }
-    }
-
-    /// <summary>
-    ///     Indicates if a <see cref="AnsiEscapeSequenceRequestStatus"/> with the <paramref name="terminator"/> exists in the
-    ///     <see cref="Statuses"/> list.
-    /// </summary>
-    /// <param name="terminator"></param>
-    /// <param name="seqReqStatus"></param>
-    /// <returns><see langword="true"/> if exist, <see langword="false"/> otherwise.</returns>
-    public static bool HasResponse (string terminator, out AnsiEscapeSequenceRequestStatus? seqReqStatus)
-    {
-        lock (Statuses)
-        {
-            Statuses.TryPeek (out seqReqStatus);
-
-            return seqReqStatus?.AnsiRequest.Terminator == terminator;
-        }
-    }
-
-    /// <summary>
-    ///     Removes a request defined by <paramref name="seqReqStatus"/>. If a matching
-    ///     <see cref="AnsiEscapeSequenceRequestStatus"/> is
-    ///     found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented.
-    ///     If the number of outstanding requests is 0, the <see cref="AnsiEscapeSequenceRequestStatus"/> is removed from
-    ///     <see cref="Statuses"/>.
-    /// </summary>
-    /// <param name="seqReqStatus">The <see cref="AnsiEscapeSequenceRequestStatus"/> object.</param>
-    public static void Remove (AnsiEscapeSequenceRequestStatus? seqReqStatus)
-    {
-        lock (Statuses)
-        {
-            Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? request);
-
-            if (request != seqReqStatus)
-            {
-                throw new InvalidOperationException ("Both EscSeqReqStatus objects aren't equals.");
-            }
-        }
-    }
-
-    /// <summary>Gets the <see cref="AnsiEscapeSequenceRequestStatus"/> list.</summary>
-    public static ConcurrentQueue<AnsiEscapeSequenceRequestStatus> Statuses { get; } = new ();
-}

+ 0 - 64
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs

@@ -1,64 +0,0 @@
-#nullable enable
-namespace Terminal.Gui;
-
-/// <summary>
-///     Describes a response received from the console as a result of a request being sent via
-///     <see cref="AnsiEscapeSequenceRequest"/>.
-/// </summary>
-public class AnsiEscapeSequenceResponse
-{
-    /// <summary>
-    ///     Gets the error string received from e.g. see
-    ///     <see>
-    ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
-    ///     </see>
-    ///     .
-    /// </summary>
-    public required string Error { get; init; }
-
-    /// <summary>
-    ///     The value expected in the response after the CSI e.g.
-    ///     <see>
-    ///         <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
-    ///     </see>
-    ///     should result in a response of the form <c>ESC [ 8 ; height ; width t</c>. In this case,
-    ///     <see cref="ExpectedResponseValue"/>
-    ///     will be <c>"8"</c>.
-    /// </summary>
-
-    public string? ExpectedResponseValue { get; init; }
-
-    /// <summary>
-    ///     Gets the Response string received from e.g. see
-    ///     <see>
-    ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
-    ///     </see>
-    ///     .
-    /// </summary>
-    public required string? Response { get; init; }
-
-    /// <summary>
-    ///     <para>
-    ///         Gets the terminator that uniquely identifies the response received from
-    ///         the console. e.g. for
-    ///         <see>
-    ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
-    ///         </see>
-    ///         the terminator is
-    ///         <see>
-    ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Terminator</cref>
-    ///         </see>
-    ///         .
-    ///     </para>
-    ///     <para>
-    ///         After sending a request, the first response with matching terminator will be matched
-    ///         to the oldest outstanding request.
-    ///     </para>
-    /// </summary>
-    public required string Terminator { get; init; }
-
-    /// <summary>
-    ///     Gets if the request has a valid response.
-    /// </summary>
-    public bool Valid { get; internal set; }
-}

+ 0 - 125
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -1,6 +1,5 @@
 #nullable enable
 
-using System.Collections.Concurrent;
 using System.Diagnostics;
 
 namespace Terminal.Gui;
@@ -13,77 +12,6 @@ namespace Terminal.Gui;
 /// </remarks>
 public abstract class ConsoleDriver
 {
-    private readonly ManualResetEventSlim _waitAnsiRequest = new (false);
-    private readonly ManualResetEventSlim _ansiResponseReady = new (false);
-    private readonly CancellationTokenSource? _ansiRequestTokenSource = new ();
-    private readonly ConcurrentQueue<AnsiEscapeSequenceRequest> _requestQueue = new ();
-    private readonly ConcurrentQueue<AnsiEscapeSequenceRequest> _responseQueue = new ();
-    private IMainLoopDriver? _mainLoopDriver;
-
-    internal void ProcessAnsiRequestHandler ()
-    {
-        while (_ansiRequestTokenSource is { IsCancellationRequested: false})
-        {
-            try
-            {
-                if (_requestQueue.Count == 0)
-                {
-                    try
-                    {
-                        _waitAnsiRequest.Wait (_ansiRequestTokenSource.Token);
-                    }
-                    catch (OperationCanceledException)
-                    {
-                        return;
-                    }
-
-                    _waitAnsiRequest.Reset ();
-                }
-
-                while (_requestQueue.TryDequeue (out AnsiEscapeSequenceRequest? ansiRequest))
-                {
-                    try
-                    {
-                        lock (ansiRequest._responseLock)
-                        {
-                            AnsiEscapeSequenceRequest request = ansiRequest;
-
-                            ansiRequest.ResponseFromInput += (s, e) =>
-                                                             {
-                                                                 Debug.Assert (s == request);
-                                                                 Debug.Assert (e == request.AnsiEscapeSequenceResponse);
-
-                                                                 _responseQueue.Enqueue (request);
-
-                                                                 _ansiResponseReady.Set ();
-                                                             };
-
-                            AnsiEscapeSequenceRequests.Add (ansiRequest);
-
-                            WriteRaw (ansiRequest.Request);
-
-                            _mainLoopDriver!.ForceRead = true;
-                        }
-
-                        if (!_ansiRequestTokenSource.IsCancellationRequested)
-                        {
-                            _mainLoopDriver.WaitForInput.Set ();
-                        }
-                    }
-                    catch (OperationCanceledException)
-                    {
-                        return;
-                    }
-
-                }
-            }
-            catch (OperationCanceledException)
-            {
-                return;
-            }
-        }
-    }
-
     /// <summary>
     ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
     ///     <code>
@@ -104,59 +32,6 @@ public abstract class ConsoleDriver
 
     #region ANSI Esc Sequence Handling
 
-    /// <summary>
-    ///     Provide unique handling for all the terminal write ANSI escape sequence request.
-    /// </summary>
-    /// <param name="mainLoopDriver">The <see cref="IMainLoopDriver"/> object.</param>
-    /// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
-    /// <returns><see languard="true"/> if the request response is valid, <see languard="false"/> otherwise.</returns>
-    public bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest)
-    {
-        ArgumentNullException.ThrowIfNull (mainLoopDriver, nameof (mainLoopDriver));
-        ArgumentNullException.ThrowIfNull (ansiRequest, nameof (ansiRequest));
-
-        lock (ansiRequest._responseLock)
-        {
-            _mainLoopDriver = mainLoopDriver;
-            _requestQueue.Enqueue (ansiRequest);
-
-            _waitAnsiRequest.Set ();
-        }
-
-        try
-        {
-            _ansiResponseReady.Wait (_ansiRequestTokenSource!.Token);
-
-            _ansiResponseReady.Reset ();
-
-            _responseQueue.TryDequeue (out _);
-
-            lock (ansiRequest._responseLock)
-            {
-                _mainLoopDriver.ForceRead = false;
-
-                if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request))
-                {
-                    if (AnsiEscapeSequenceRequests.Statuses.Count > 0
-                        && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
-                    {
-                        lock (request.AnsiRequest._responseLock)
-                        {
-                            // Bad request or no response at all
-                            AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
-                        }
-                    }
-                }
-
-                return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true };
-            }
-        }
-        catch (OperationCanceledException)
-        {
-            return false;
-        }
-    }
-
     // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver.
     // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API?
     /// <summary>

+ 403 - 35
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -104,7 +104,7 @@ internal class CursesDriver : ConsoleDriver
     {
         if (!RunningUnitTests)
         {
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents);
+            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
         }
     }
 
@@ -112,7 +112,7 @@ internal class CursesDriver : ConsoleDriver
     {
         if (!RunningUnitTests)
         {
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents);
+            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
         }
     }
 
@@ -150,7 +150,7 @@ internal class CursesDriver : ConsoleDriver
             }
             else
             {
-                _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1));
+                _mainLoopDriver?.WriteRaw (EscSeqUtils.CSI_SetCursorPosition (Row + 1, Col + 1));
             }
         }
     }
@@ -299,7 +299,7 @@ internal class CursesDriver : ConsoleDriver
                             redrawAttr = attr;
 
                             output.Append (
-                                           AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB (
+                                           EscSeqUtils.CSI_SetForegroundColorRGB (
                                                                                   attr.Foreground.R,
                                                                                   attr.Foreground.G,
                                                                                   attr.Foreground.B
@@ -307,7 +307,7 @@ internal class CursesDriver : ConsoleDriver
                                           );
 
                             output.Append (
-                                           AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB (
+                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
                                                                                   attr.Background.R,
                                                                                   attr.Background.G,
                                                                                   attr.Background.B
@@ -554,11 +554,11 @@ internal class CursesDriver : ConsoleDriver
         if (visibility != CursorVisibility.Invisible)
         {
             _mainLoopDriver?.WriteRaw (
-                                       AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle (
-                                                                                          (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)
-                                                                                          (((int)visibility >> 24)
-                                                                                           & 0xFF)
-                                                                                         )
+                                       EscSeqUtils.CSI_SetCursorStyle (
+                                                                       (EscSeqUtils.DECSCUSR_Style)
+                                                                       (((int)visibility >> 24)
+                                                                        & 0xFF)
+                                                                      )
                                       );
         }
 
@@ -571,7 +571,7 @@ internal class CursesDriver : ConsoleDriver
     {
         // + 1 is needed because non-Windows is based on 1 instead of 0 and
         // Console.CursorTop/CursorLeft isn't reliable.
-        Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (row + 1, col + 1));
+        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
 
         return true;
     }
@@ -580,6 +580,7 @@ internal class CursesDriver : ConsoleDriver
 
     private Curses.Window? _window;
     private UnixMainLoop? _mainLoopDriver;
+    private object _processInputToken;
 
     internal override MainLoop Init ()
     {
@@ -638,6 +639,17 @@ internal class CursesDriver : ConsoleDriver
             {
                 Curses.timeout (0);
             }
+
+            _processInputToken = _mainLoopDriver.AddWatch (
+                                                           0,
+                                                           UnixMainLoop.Condition.PollIn,
+                                                           x =>
+                                                           {
+                                                               ProcessInput ();
+
+                                                               return true;
+                                                           }
+                                                          );
         }
 
         CurrentAttribute = new (ColorName16.White, ColorName16.Black);
@@ -678,59 +690,415 @@ internal class CursesDriver : ConsoleDriver
                 Curses.refresh ();
             }
 
-            Task.Run (ProcessAnsiRequestHandler);
+            EscSeqUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed;
         }
 
         return new (_mainLoopDriver);
     }
 
-    internal void ProcessInput (UnixMainLoop.PollData inputEvent)
+    internal void ProcessInput ()
     {
-        switch (inputEvent.EventType)
+        int wch;
+        int code = Curses.get_wch (out wch);
+
+        //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
+        if (code == Curses.ERR)
         {
-            case UnixMainLoop.EventType.Key:
-                ConsoleKeyInfo consoleKeyInfo = inputEvent.KeyEvent;
+            return;
+        }
 
-                KeyCode map = AnsiEscapeSequenceRequestUtils.MapKey (consoleKeyInfo);
+        var k = KeyCode.Null;
 
-                if (map == KeyCode.Null)
+        if (code == Curses.KEY_CODE_YES)
+        {
+            while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize)
+            {
+                ProcessWinChange ();
+                code = Curses.get_wch (out wch);
+            }
+
+            if (wch == 0)
+            {
+                return;
+            }
+
+            if (wch == Curses.KeyMouse)
+            {
+                int wch2 = wch;
+
+                while (wch2 == Curses.KeyMouse)
                 {
-                    break;
+                    Key kea = null;
+
+                    ConsoleKeyInfo [] cki =
+                    {
+                        new ((char)KeyCode.Esc, 0, false, false, false),
+                        new ('[', 0, false, false, false),
+                        new ('<', 0, false, false, false)
+                    };
+                    code = 0;
+                    HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
                 }
 
-                OnKeyDown (new (map));
-                OnKeyUp (new (map));
+                return;
+            }
+
+            k = MapCursesKey (wch);
+
+            if (wch >= 277 && wch <= 288)
+            {
+                // Shift+(F1 - F12)
+                wch -= 12;
+                k = KeyCode.ShiftMask | MapCursesKey (wch);
+            }
+            else if (wch >= 289 && wch <= 300)
+            {
+                // Ctrl+(F1 - F12)
+                wch -= 24;
+                k = KeyCode.CtrlMask | MapCursesKey (wch);
+            }
+            else if (wch >= 301 && wch <= 312)
+            {
+                // Ctrl+Shift+(F1 - F12)
+                wch -= 36;
+                k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
+            }
+            else if (wch >= 313 && wch <= 324)
+            {
+                // Alt+(F1 - F12)
+                wch -= 48;
+                k = KeyCode.AltMask | MapCursesKey (wch);
+            }
+            else if (wch >= 325 && wch <= 327)
+            {
+                // Shift+Alt+(F1 - F3)
+                wch -= 60;
+                k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
+            }
+
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
+
+            return;
+        }
 
-                break;
-            case UnixMainLoop.EventType.Mouse:
-                var me = new MouseEventArgs { Position = inputEvent.MouseEvent.Position, Flags = inputEvent.MouseEvent.MouseFlags };
-                OnMouseEvent (me);
+        // Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
+        if (wch == 27)
+        {
+            Curses.timeout (10);
 
-                break;
-            case UnixMainLoop.EventType.WindowSize:
-                Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height);
-                ProcessWinChange (size);
+            code = Curses.get_wch (out int wch2);
 
-                break;
-            default:
-                throw new ArgumentOutOfRangeException ();
+            if (code == Curses.KEY_CODE_YES)
+            {
+                k = KeyCode.AltMask | MapCursesKey (wch);
+            }
+
+            Key key = null;
+
+            if (code == 0)
+            {
+                // The ESC-number handling, debatable.
+                // Simulates the AltMask itself by pressing Alt + Space.
+                // Needed for macOS
+                if (wch2 == (int)KeyCode.Space)
+                {
+                    k = KeyCode.AltMask | KeyCode.Space;
+                }
+                else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A
+                         && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z)
+                {
+                    k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space));
+                }
+                else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64)
+                {
+                    k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64));
+                }
+                else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9)
+                {
+                    k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
+                }
+                else
+                {
+                    ConsoleKeyInfo [] cki =
+                    [
+                        new ((char)KeyCode.Esc, 0, false, false, false), new ((char)wch2, 0, false, false, false)
+                    ];
+                    HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
+
+                    return;
+                }
+                //else if (wch2 == Curses.KeyCSI)
+                //{
+                //    ConsoleKeyInfo [] cki =
+                //    {
+                //        new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false)
+                //    };
+                //    HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
+
+                //    return;
+                //}
+                //else
+                //{
+                //    // Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
+                //    if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0)
+                //    {
+                //        k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
+                //    }
+
+                //    if (wch2 == 0)
+                //    {
+                //        k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
+                //    }
+                //    //else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
+                //    //{
+                //    //    k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
+                //    //}
+                //    else if (wch2 < 256)
+                //    {
+                //        k = (KeyCode)wch2; // | KeyCode.AltMask;
+                //    }
+                //    else
+                //    {
+                //        k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
+                //    }
+                //}
+
+                key = new Key (k);
+            }
+            else
+            {
+                key = Key.Esc;
+            }
+
+            OnKeyDown (key);
+            OnKeyUp (key);
+        }
+        else if (wch == Curses.KeyTab)
+        {
+            k = MapCursesKey (wch);
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
+        }
+        else if (wch == 127)
+        {
+            // Backspace needed for macOS
+            k = KeyCode.Backspace;
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
+        }
+        else
+        {
+            // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
+            k = (KeyCode)wch;
+
+            if (wch == 0)
+            {
+                k = KeyCode.CtrlMask | KeyCode.Space;
+            }
+            else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64)
+            {
+                if ((KeyCode)(wch + 64) != KeyCode.J)
+                {
+                    k = KeyCode.CtrlMask | (KeyCode)(wch + 64);
+                }
+            }
+            else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
+            {
+                k = (KeyCode)wch | KeyCode.ShiftMask;
+            }
+
+            if (wch == '\n' || wch == '\r')
+            {
+                k = KeyCode.Enter;
+            }
+
+            // Strip the KeyCode.Space flag off if it's set
+            //if (k != KeyCode.Space && k.HasFlag (KeyCode.Space))
+            if (Key.GetIsKeyCodeAtoZ (k) && (k & KeyCode.Space) != 0)
+            {
+                k &= ~KeyCode.Space;
+            }
+
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
         }
     }
-    private void ProcessWinChange (Size size)
+
+    internal void ProcessWinChange ()
     {
-        if (!RunningUnitTests && Curses.ChangeWindowSize (size.Height, size.Width))
+        if (!RunningUnitTests && Curses.CheckWinChange ())
         {
             ClearContents ();
-            OnSizeChanged (new (new (Cols, Rows)));
+            OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows)));
+        }
+    }
+
+    private void HandleEscSeqResponse (
+        ref int code,
+        ref KeyCode k,
+        ref int wch2,
+        ref Key keyEventArgs,
+        ref ConsoleKeyInfo [] cki
+    )
+    {
+        ConsoleKey ck = 0;
+        ConsoleModifiers mod = 0;
+
+        while (code == 0)
+        {
+            code = Curses.get_wch (out wch2);
+            var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
+
+            if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse)
+            {
+                EscSeqUtils.DecodeEscSeq (
+                                          ref consoleKeyInfo,
+                                          ref ck,
+                                          cki,
+                                          ref mod,
+                                          out _,
+                                          out _,
+                                          out _,
+                                          out _,
+                                          out bool isKeyMouse,
+                                          out List<MouseFlags> mouseFlags,
+                                          out Point pos,
+                                          out _,
+                                          EscSeqUtils.ProcessMouseEvent
+                                         );
+
+                if (isKeyMouse)
+                {
+                    foreach (MouseFlags mf in mouseFlags)
+                    {
+                        OnMouseEvent (new () { Flags = mf, Position = pos });
+                    }
+
+                    cki = null;
+
+                    if (wch2 == 27)
+                    {
+                        cki = EscSeqUtils.ResizeArray (
+                                                       new ConsoleKeyInfo (
+                                                                           (char)KeyCode.Esc,
+                                                                           0,
+                                                                           false,
+                                                                           false,
+                                                                           false
+                                                                          ),
+                                                       cki
+                                                      );
+                    }
+                }
+                else
+                {
+                    k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+                    keyEventArgs = new Key (k);
+                    OnKeyDown (keyEventArgs);
+                }
+            }
+            else
+            {
+                cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
+            }
+        }
+    }
+
+    private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e)
+    {
+        OnMouseEvent (e);
+    }
+
+    private static KeyCode MapCursesKey (int cursesKey)
+    {
+        switch (cursesKey)
+        {
+            case Curses.KeyF1: return KeyCode.F1;
+            case Curses.KeyF2: return KeyCode.F2;
+            case Curses.KeyF3: return KeyCode.F3;
+            case Curses.KeyF4: return KeyCode.F4;
+            case Curses.KeyF5: return KeyCode.F5;
+            case Curses.KeyF6: return KeyCode.F6;
+            case Curses.KeyF7: return KeyCode.F7;
+            case Curses.KeyF8: return KeyCode.F8;
+            case Curses.KeyF9: return KeyCode.F9;
+            case Curses.KeyF10: return KeyCode.F10;
+            case Curses.KeyF11: return KeyCode.F11;
+            case Curses.KeyF12: return KeyCode.F12;
+            case Curses.KeyUp: return KeyCode.CursorUp;
+            case Curses.KeyDown: return KeyCode.CursorDown;
+            case Curses.KeyLeft: return KeyCode.CursorLeft;
+            case Curses.KeyRight: return KeyCode.CursorRight;
+            case Curses.KeyHome: return KeyCode.Home;
+            case Curses.KeyEnd: return KeyCode.End;
+            case Curses.KeyNPage: return KeyCode.PageDown;
+            case Curses.KeyPPage: return KeyCode.PageUp;
+            case Curses.KeyDeleteChar: return KeyCode.Delete;
+            case Curses.KeyInsertChar: return KeyCode.Insert;
+            case Curses.KeyTab: return KeyCode.Tab;
+            case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
+            case Curses.KeyBackspace: return KeyCode.Backspace;
+            case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask;
+            case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask;
+            case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask;
+            case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask;
+            case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask;
+            case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask;
+            case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask;
+            case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask;
+            case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask;
+            case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask;
+            case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask;
+            case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask;
+            case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask;
+            case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask;
+            case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask;
+            case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask;
+            case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask;
+            case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask;
+            case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask;
+            case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask;
+            case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask;
+            case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask;
+            case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask;
+            case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask;
+            case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask;
+            case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask;
+            case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask;
+            default: return KeyCode.Null;
         }
     }
 
     internal override void End ()
     {
+        EscSeqUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed;
 
         StopReportingMouseMoves ();
         SetCursorVisibility (CursorVisibility.Default);
 
+        if (_mainLoopDriver is { })
+        {
+            _mainLoopDriver.RemoveWatch (_processInputToken);
+        }
+
         if (RunningUnitTests)
         {
             return;

+ 0 - 12
Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c

@@ -1,12 +0,0 @@
-#include <stdio.h>
-#include <sys/ioctl.h>
-
-// Used to get the value of the TIOCGWINSZ variable,
-// which may have different values ​​on different Unix operating systems.
-//   Linux=0x005413
-//   Darwin and OpenBSD=0x40087468,
-//   Solaris=0x005468
-// See https://stackoverflow.com/questions/16237137/what-is-termios-tiocgwinsz
-int get_tiocgwinsz_value() {
-    return TIOCGWINSZ;
-}

+ 0 - 17
Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.sh

@@ -1,17 +0,0 @@
-#!/bin/bash
-
-# Create output directory if it doesn't exist
-mkdir -p ../../compiled-binaries
-
-# Determine the output file extension based on the OS
-if [[ "$OSTYPE" == "linux-gnu"* ]]; then
-    OUTPUT_FILE="../../compiled-binaries/libGetTIOCGWINSZ.so"
-elif [[ "$OSTYPE" == "darwin"* ]]; then
-    OUTPUT_FILE="../../compiled-binaries/libGetTIOCGWINSZ.dylib"
-else
-    echo "Unsupported OS: $OSTYPE"
-    exit 1
-fi
-
-# Compile the C file
-gcc -shared -fPIC -o "$OUTPUT_FILE" GetTIOCGWINSZ.c

+ 111 - 374
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -3,7 +3,6 @@
 // mainloop.cs: Linux/Curses MainLoop implementation.
 //
 
-using System.Collections.Concurrent;
 using System.Runtime.InteropServices;
 
 namespace Terminal.Gui;
@@ -13,7 +12,7 @@ namespace Terminal.Gui;
 ///     In addition to the general functions of the MainLoop, the Unix version can watch file descriptors using the
 ///     AddWatch methods.
 /// </remarks>
-internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
+internal class UnixMainLoop : IMainLoopDriver
 {
     /// <summary>Condition on which to wake up from file descriptor activity.  These match the Linux/BSD poll definitions.</summary>
     [Flags]
@@ -38,17 +37,31 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
         PollNval = 32
     }
 
-    private readonly CursesDriver _cursesDriver = (CursesDriver)consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
+    public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff);
+    private static readonly nint _ignore = Marshal.AllocHGlobal (1);
+
+    private readonly CursesDriver _cursesDriver;
+    private readonly Dictionary<int, Watch> _descriptorWatchers = new ();
+    private readonly int [] _wakeUpPipes = new int [2];
     private MainLoop? _mainLoop;
+    private bool _pollDirty = true;
     private Pollfd []? _pollMap;
-    private readonly ConcurrentQueue<PollData> _pollDataQueue = new ();
-    private readonly ManualResetEventSlim _eventReady = new (false);
-    ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false);
-    private readonly ManualResetEventSlim _windowSizeChange = new (false);
-    private readonly CancellationTokenSource _eventReadyTokenSource = new ();
-    private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
+    private bool _winChanged;
+
+    public UnixMainLoop (ConsoleDriver consoleDriver)
+    {
+        ArgumentNullException.ThrowIfNull (consoleDriver);
+
+        _cursesDriver = (CursesDriver)consoleDriver;
+    }
 
-    void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
+    void IMainLoopDriver.Wakeup ()
+    {
+        if (!ConsoleDriver.RunningUnitTests)
+        {
+            write (_wakeUpPipes [1], _ignore, 1);
+        }
+    }
 
     void IMainLoopDriver.Setup (MainLoop mainLoop)
     {
@@ -61,358 +74,143 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
 
         try
         {
-            // Setup poll for stdin (fd 0)
-            _pollMap = new Pollfd [1];
-            _pollMap [0].fd = 0;         // stdin (file descriptor 0)
-            _pollMap [0].events = (short)Condition.PollIn; // Monitor input for reading
+            pipe (_wakeUpPipes);
+
+            AddWatch (
+                      _wakeUpPipes [0],
+                      Condition.PollIn,
+                      _ =>
+                      {
+                          read (_wakeUpPipes [0], _ignore, 1);
+
+                          return true;
+                      }
+                     );
         }
         catch (DllNotFoundException e)
         {
             throw new NotSupportedException ("libc not found", e);
         }
-
-        AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed += EscSeqUtils_ContinuousButtonPressed;
-
-        Task.Run (CursesInputHandler, _inputHandlerTokenSource.Token);
-        Task.Run (WindowSizeHandler, _inputHandlerTokenSource.Token);
     }
 
-    private static readonly int TIOCGWINSZ = GetTIOCGWINSZValue ();
-
-    private const string PlaceholderLibrary = "compiled-binaries/libGetTIOCGWINSZ"; // Placeholder, won't directly load
-
-    [DllImport (PlaceholderLibrary, EntryPoint = "get_tiocgwinsz_value")]
-    private static extern int GetTIOCGWINSZValueInternal ();
-
-    public static int GetTIOCGWINSZValue ()
+    bool IMainLoopDriver.EventsPending ()
     {
-        // Determine the correct library path based on the OS
-        string libraryPath = Path.Combine (
-                                           AppContext.BaseDirectory,
-                                           "compiled-binaries",
-                                           RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ? "libGetTIOCGWINSZ.dylib" : "libGetTIOCGWINSZ.so");
-
-        // Load the native library manually
-        nint handle = NativeLibrary.Load (libraryPath);
-
-        // Ensure the handle is valid
-        if (handle == nint.Zero)
+        if (ConsoleDriver.RunningUnitTests)
         {
-            throw new DllNotFoundException ($"Unable to load library: {libraryPath}");
+            return true;
         }
 
-        return GetTIOCGWINSZValueInternal ();
-    }
+        UpdatePollMap ();
 
-    private void EscSeqUtils_ContinuousButtonPressed (object? sender, MouseEventArgs e)
-    {
-        _pollDataQueue.Enqueue (EnqueueMouseEvent (e.Flags, e.Position));
-    }
+        bool checkTimersResult = _mainLoop!.CheckTimersAndIdleHandlers (out int pollTimeout);
 
-    private void WindowSizeHandler ()
-    {
-        var ws = new Winsize ();
-        ioctl (0, TIOCGWINSZ, ref ws);
-
-        // Store initial window size
-        int rows = ws.ws_row;
-        int cols = ws.ws_col;
+        int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout);
 
-        while (_inputHandlerTokenSource is { IsCancellationRequested: false })
+        if (n == KEY_RESIZE)
         {
-            try
-            {
-                _windowSizeChange.Wait (_inputHandlerTokenSource.Token);
-                _windowSizeChange.Reset ();
-
-                while (!_inputHandlerTokenSource.IsCancellationRequested)
-                {
-                    // Wait for a while then check if screen has changed sizes
-                    Task.Delay (500, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
-
-                    ioctl (0, TIOCGWINSZ, ref ws);
-
-                    if (rows != ws.ws_row || cols != ws.ws_col)
-                    {
-                        rows = ws.ws_row;
-                        cols = ws.ws_col;
-
-                        _pollDataQueue.Enqueue (EnqueueWindowSizeEvent (rows, cols));
-
-                        break;
-                    }
-                }
-            }
-            catch (OperationCanceledException)
-            {
-                return;
-            }
-
-            _eventReady.Set ();
+            _winChanged = true;
         }
-    }
-
-    bool IMainLoopDriver.ForceRead { get; set; }
-    private int _retries;
 
-    private void CursesInputHandler ()
-    {
-        while (_mainLoop is { })
-        {
-            try
-            {
-                if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead)
-                {
-                    try
-                    {
-                        ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token);
-                    }
-                    catch (Exception ex)
-                    {
-                        if (ex is OperationCanceledException or ObjectDisposedException)
-                        {
-                            return;
-                        }
-
-                        throw;
-                    }
-
-                    ((IMainLoopDriver)this).WaitForInput.Reset ();
-                }
-
-                ProcessInputQueue ();
-            }
-            catch (OperationCanceledException)
-            {
-                return;
-            }
-        }
+        return checkTimersResult || n >= KEY_RESIZE;
     }
 
-    private void ProcessInputQueue ()
+    void IMainLoopDriver.Iteration ()
     {
-        if (_pollDataQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead)
+        if (ConsoleDriver.RunningUnitTests)
         {
-            while (!_inputHandlerTokenSource.IsCancellationRequested)
-            {
-                try
-                {
-                    Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
-                }
-                catch (OperationCanceledException)
-                {
-                    return;
-                }
-
-                int n = poll (_pollMap!, (uint)_pollMap!.Length, 0);
-
-                if (n > 0)
-                {
-                    // Check if stdin has data
-                    if ((_pollMap [0].revents & (int)Condition.PollIn) != 0)
-                    {
-                        // Allocate memory for the buffer
-                        var buf = new byte [2048];
-                        nint bufPtr = Marshal.AllocHGlobal (buf.Length);
-
-                        try
-                        {
-                            // Read from the stdin
-                            int bytesRead = read (_pollMap [0].fd, bufPtr, buf.Length);
-
-                            if (bytesRead > 0)
-                            {
-                                // Copy the data from unmanaged memory to a byte array
-                                var buffer = new byte [bytesRead];
-                                Marshal.Copy (bufPtr, buffer, 0, bytesRead);
-
-                                // Convert the byte array to a string (assuming UTF-8 encoding)
-                                string data = Encoding.UTF8.GetString (buffer);
-
-                                if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { })
-                                {
-                                    data = data.Insert (0, AnsiEscapeSequenceRequestUtils.ToString (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos));
-                                    AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null;
-                                }
-
-                                // Enqueue the data
-                                ProcessEnqueuePollData (data);
-                            }
-                        }
-                        finally
-                        {
-                            // Free the allocated memory
-                            Marshal.FreeHGlobal (bufPtr);
-                        }
-                    }
-
-                    if (_retries > 0)
-                    {
-                        _retries = 0;
-                    }
-
-                    break;
-                }
-
-                if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
-                {
-                    if (_retries > 1)
-                    {
-                        if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus))
-                        {
-                            lock (seqReqStatus.AnsiRequest._responseLock)
-                            {
-                                AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
-
-                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest);
-                            }
-                        }
-
-                        _retries = 0;
-                    }
-                    else
-                    {
-                        _retries++;
-                    }
-                }
-                else
-                {
-                    _retries = 0;
-                }
-            }
+            return;
         }
 
-        if (_pollDataQueue.Count > 0)
+        if (_winChanged)
         {
-            _eventReady.Set ();
+            _winChanged = false;
+            _cursesDriver.ProcessInput ();
+
+            // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426
+            _cursesDriver.ProcessWinChange ();
         }
-    }
 
-    private void ProcessEnqueuePollData (string pollData)
-    {
-        foreach (string split in AnsiEscapeSequenceRequestUtils.SplitEscapeRawString (pollData))
+        if (_pollMap is null)
         {
-            EnqueuePollData (split);
+            return;
         }
-    }
 
-    private void EnqueuePollData (string pollDataPart)
-    {
-        ConsoleKeyInfo [] cki = AnsiEscapeSequenceRequestUtils.ToConsoleKeyInfoArray (pollDataPart);
-
-        ConsoleKey key = 0;
-        ConsoleModifiers mod = 0;
-        ConsoleKeyInfo newConsoleKeyInfo = default;
-
-        AnsiEscapeSequenceRequestUtils.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 AnsiEscapeSequenceRequestStatus? seqReqStatus,
-                                                     AnsiEscapeSequenceRequestUtils.ProcessMouseEvent
-                                                    );
-
-        if (isMouse)
+        foreach (Pollfd p in _pollMap)
         {
-            foreach (MouseFlags mf in mouseFlags)
+            if (p.revents == 0)
             {
-                _pollDataQueue.Enqueue (EnqueueMouseEvent (mf, pos));
+                continue;
             }
 
-            return;
-        }
+            if (!_descriptorWatchers.TryGetValue (p.fd, out Watch? watch))
+            {
+                continue;
+            }
 
-        if (newConsoleKeyInfo != default)
-        {
-            _pollDataQueue.Enqueue (EnqueueKeyboardEvent (newConsoleKeyInfo));
+            if (!watch.Callback (_mainLoop!))
+            {
+                _descriptorWatchers.Remove (p.fd);
+            }
         }
     }
 
-    private PollData EnqueueMouseEvent (MouseFlags mouseFlags, Point pos)
+    void IMainLoopDriver.TearDown ()
     {
-        var mouseEvent = new MouseEvent { Position = pos, MouseFlags = mouseFlags };
+        _descriptorWatchers.Clear ();
 
-        return new () { EventType = EventType.Mouse, MouseEvent = mouseEvent };
+        _mainLoop = null;
     }
 
-    private PollData EnqueueKeyboardEvent (ConsoleKeyInfo keyInfo)
+    /// <summary>Watches a file descriptor for activity.</summary>
+    /// <remarks>
+    ///     When the condition is met, the provided callback is invoked.  If the callback returns false, the watch is
+    ///     automatically removed. The return value is a token that represents this watch, you can use this token to remove the
+    ///     watch by calling RemoveWatch.
+    /// </remarks>
+    internal object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop, bool> callback)
     {
-        return new () { EventType = EventType.Key, KeyEvent = keyInfo };
-    }
+        ArgumentNullException.ThrowIfNull (callback);
 
-    private PollData EnqueueWindowSizeEvent (int rows, int cols)
-    {
-        return new () { EventType = EventType.WindowSize, WindowSizeEvent = new () { Size = new (cols, rows) } };
+        var watch = new Watch { Condition = condition, Callback = callback, File = fileDescriptor };
+        _descriptorWatchers [fileDescriptor] = watch;
+        _pollDirty = true;
+
+        return watch;
     }
 
-    bool IMainLoopDriver.EventsPending ()
+    /// <summary>Removes an active watch from the mainloop.</summary>
+    /// <remarks>The token parameter is the value returned from AddWatch</remarks>
+    internal void RemoveWatch (object token)
     {
-        ((IMainLoopDriver)this).WaitForInput.Set ();
-        _windowSizeChange.Set ();
-
-        if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
-        {
-            return true;
-        }
-
-        try
+        if (!ConsoleDriver.RunningUnitTests)
         {
-            if (!_eventReadyTokenSource.IsCancellationRequested)
+            if (token is not Watch watch)
             {
-                _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
+                return;
             }
-        }
-        catch (OperationCanceledException)
-        {
-            return true;
-        }
-        finally
-        {
-            _eventReady.Reset ();
-        }
 
-        if (!_eventReadyTokenSource.IsCancellationRequested)
-        {
-            return _pollDataQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
+            _descriptorWatchers.Remove (watch.File);
         }
-
-        return true;
     }
 
-    void IMainLoopDriver.Iteration ()
+    private void UpdatePollMap ()
     {
-        // Dequeue and process the data
-        while (_pollDataQueue.TryDequeue (out PollData inputRecords))
+        if (!_pollDirty)
         {
-            _cursesDriver.ProcessInput (inputRecords);
+            return;
         }
-    }
-
-    void IMainLoopDriver.TearDown ()
-    {
-        AnsiEscapeSequenceRequestUtils.ContinuousButtonPressed -= EscSeqUtils_ContinuousButtonPressed;
-
-        _inputHandlerTokenSource.Cancel ();
-        _inputHandlerTokenSource.Dispose ();
-        ((IMainLoopDriver)this).WaitForInput?.Dispose ();
 
-        _windowSizeChange.Dispose();
+        _pollDirty = false;
 
-        _pollDataQueue.Clear ();
+        _pollMap = new Pollfd [_descriptorWatchers.Count];
+        var i = 0;
 
-        _eventReadyTokenSource.Cancel ();
-        _eventReadyTokenSource.Dispose ();
-        _eventReady.Dispose ();
-
-        _mainLoop = null;
+        foreach (int fd in _descriptorWatchers.Keys)
+        {
+            _pollMap [i].fd = fd;
+            _pollMap [i].events = (short)_descriptorWatchers [fd].Condition;
+            i++;
+        }
     }
 
     internal void WriteRaw (string ansiRequest)
@@ -421,21 +219,24 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
         write (STDOUT_FILENO, ansiRequest, ansiRequest.Length);
     }
 
+    [DllImport ("libc")]
+    private static extern int pipe ([In][Out] int [] pipes);
+
     [DllImport ("libc")]
     private static extern int poll ([In] [Out] Pollfd [] ufds, uint nfds, int timeout);
 
     [DllImport ("libc")]
     private static extern int read (int fd, nint buf, nint n);
 
+    [DllImport ("libc")]
+    private static extern int write (int fd, nint buf, nint n);
+
     // File descriptor for stdout
     private const int STDOUT_FILENO = 1;
 
     [DllImport ("libc")]
     private static extern int write (int fd, string buf, int n);
 
-    [DllImport ("libc", SetLastError = true)]
-    private static extern int ioctl (int fd, int request, ref Winsize ws);
-
     [StructLayout (LayoutKind.Sequential)]
     private struct Pollfd
     {
@@ -444,74 +245,10 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver
         public readonly short revents;
     }
 
-    /// <summary>
-    ///     Window or terminal size structure. This information is stored by the kernel in order to provide a consistent
-    ///     interface, but is not used by the kernel.
-    /// </summary>
-    [StructLayout (LayoutKind.Sequential)]
-    public struct Winsize
-    {
-        public ushort ws_row;    // Number of rows
-        public ushort ws_col;    // Number of columns
-        public ushort ws_xpixel; // Width in pixels (unused)
-        public ushort ws_ypixel; // Height in pixels (unused)
-    }
-
-    #region Events
-
-    public enum EventType
-    {
-        Key = 1,
-        Mouse = 2,
-        WindowSize = 3
-    }
-
-    public struct MouseEvent
-    {
-        public Point Position;
-        public MouseFlags MouseFlags;
-    }
-
-    public struct WindowSizeEvent
+    private class Watch
     {
-        public Size Size;
+        public Func<MainLoop, bool> Callback;
+        public Condition Condition;
+        public int File;
     }
-
-    public struct PollData
-    {
-        public EventType EventType;
-        public ConsoleKeyInfo KeyEvent;
-        public MouseEvent MouseEvent;
-        public WindowSizeEvent WindowSizeEvent;
-
-        public readonly override string ToString ()
-        {
-            return (EventType switch
-                    {
-                        EventType.Key => ToString (KeyEvent),
-                        EventType.Mouse => MouseEvent.ToString (),
-                        EventType.WindowSize => WindowSizeEvent.ToString (),
-                        _ => "Unknown event type: " + EventType
-                    })!;
-        }
-
-        /// <summary>Prints a ConsoleKeyInfoEx structure</summary>
-        /// <param name="cki"></param>
-        /// <returns></returns>
-        public readonly string ToString (ConsoleKeyInfo cki)
-        {
-            var ke = new Key ((KeyCode)cki.KeyChar);
-            var sb = new StringBuilder ();
-            sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
-            sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
-            sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
-            sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
-            sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
-            string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
-
-            return $"[ConsoleKeyInfo({s})]";
-        }
-    }
-
-    #endregion
 }

+ 0 - 13
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -143,19 +143,6 @@ public partial class Curses
         return false;
     }
 
-    public static bool ChangeWindowSize (int l, int c)
-    {
-        if (l != lines || c != cols)
-        {
-            lines = l;
-            cols = c;
-
-            return true;
-        }
-
-        return false;
-    }
-
     public static int clearok (nint win, bool bf) { return methods.clearok (win, bf); }
     public static int COLOR_PAIRS () { return methods.COLOR_PAIRS (); }
     public static int curs_set (int visibility) { return methods.curs_set (visibility); }

+ 2 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs

@@ -55,6 +55,8 @@ public partial class Curses
     public const int COLOR_GRAY = 0x8;
     public const int KEY_CODE_YES = 0x100;
     public const int ERR = unchecked ((int)0xffffffff);
+    public const int TIOCGWINSZ = 0x5413;
+    public const int TIOCGWINSZ_MAC = 0x40087468;
     [Flags]
     public enum Event : long
     {

+ 28 - 0
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReqStatus.cs

@@ -0,0 +1,28 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     Represents the status of an ANSI escape sequence request made to the terminal using
+///     <see cref="EscSeqRequests"/>.
+/// </summary>
+/// <remarks></remarks>
+public class EscSeqReqStatus
+{
+    /// <summary>Creates a new state of escape sequence request.</summary>
+    /// <param name="terminator">The terminator.</param>
+    /// <param name="numReq">The number of requests.</param>
+    public EscSeqReqStatus (string terminator, int numReq)
+    {
+        Terminator = terminator;
+        NumRequests = NumOutstanding = numReq;
+    }
+
+    /// <summary>Gets the number of unfinished requests.</summary>
+    public int NumOutstanding { get; set; }
+
+    /// <summary>Gets the number of requests.</summary>
+    public int NumRequests { get; }
+
+    /// <summary>Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator).</summary>
+    public string Terminator { get; }
+}

+ 114 - 0
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqRequests.cs

@@ -0,0 +1,114 @@
+#nullable enable
+
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Manages ANSI Escape Sequence requests and responses. The list of <see cref="EscSeqReqStatus"/>
+///     contains the
+///     status of the request. Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator).
+/// </summary>
+public static class EscSeqRequests
+{
+    /// <summary>Gets the <see cref="EscSeqReqStatus"/> list.</summary>
+    public static List<EscSeqReqStatus> Statuses { get; } = new ();
+
+    /// <summary>
+    ///     Adds a new request for the ANSI Escape Sequence defined by <paramref name="terminator"/>. Adds a
+    ///     <see cref="EscSeqReqStatus"/> instance to <see cref="Statuses"/> list.
+    /// </summary>
+    /// <param name="terminator">The terminator.</param>
+    /// <param name="numReq">The number of requests.</param>
+    public static void Add (string terminator, int numReq = 1)
+    {
+        lock (Statuses)
+        {
+            EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator);
+
+            if (found is null)
+            {
+                Statuses.Add (new EscSeqReqStatus (terminator, numReq));
+            }
+            else if (found.NumOutstanding < found.NumRequests)
+            {
+                found.NumOutstanding = Math.Min (found.NumOutstanding + numReq, found.NumRequests);
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Clear the <see cref="Statuses"/> property.
+    /// </summary>
+    public static void Clear ()
+    {
+        lock (Statuses)
+        {
+            Statuses.Clear ();
+        }
+    }
+
+    /// <summary>
+    ///     Indicates if a <see cref="EscSeqReqStatus"/> with the <paramref name="terminator"/> exists in the
+    ///     <see cref="Statuses"/> list.
+    /// </summary>
+    /// <param name="terminator"></param>
+    /// <returns><see langword="true"/> if exist, <see langword="false"/> otherwise.</returns>
+    public static bool HasResponse (string terminator)
+    {
+        lock (Statuses)
+        {
+            EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator);
+
+            if (found is null)
+            {
+                return false;
+            }
+
+            if (found is { NumOutstanding: > 0 })
+            {
+                return true;
+            }
+
+            // BUGBUG: Why does an API that returns a bool remove the entry from the list?
+            // NetDriver and Unit tests never exercise this line of code. Maybe Curses does?
+            Statuses.Remove (found);
+
+            return false;
+        }
+    }
+
+    /// <summary>
+    ///     Removes a request defined by <paramref name="terminator"/>. If a matching <see cref="EscSeqReqStatus"/> is
+    ///     found and the number of outstanding requests is greater than 0, the number of outstanding requests is decremented.
+    ///     If the number of outstanding requests is 0, the <see cref="EscSeqReqStatus"/> is removed from
+    ///     <see cref="Statuses"/>.
+    /// </summary>
+    /// <param name="terminator">The terminating string.</param>
+    public static void Remove (string terminator)
+    {
+        lock (Statuses)
+        {
+            EscSeqReqStatus? found = Statuses.Find (x => x.Terminator == terminator);
+
+            if (found is null)
+            {
+                return;
+            }
+
+            if (found.NumOutstanding == 0)
+            {
+                Statuses.Remove (found);
+            }
+            else if (found.NumOutstanding > 0)
+            {
+                found.NumOutstanding--;
+
+                if (found.NumOutstanding == 0)
+                {
+                    Statuses.Remove (found);
+                }
+            }
+        }
+    }
+}

+ 31 - 26
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs → Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -20,7 +20,7 @@ namespace Terminal.Gui;
 ///     * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 ///     * https://vt100.net/
 /// </remarks>
-public static class AnsiEscapeSequenceRequestUtils
+public static class EscSeqUtils
 {
     // TODO: One type per file - Move this enum to a separate file.
     /// <summary>
@@ -189,7 +189,7 @@ public static class AnsiEscapeSequenceRequestUtils
     /// <param name="isMouse">Indicates if the escape sequence is a mouse event.</param>
     /// <param name="buttonState">The <see cref="MouseFlags"/> button state.</param>
     /// <param name="pos">The <see cref="MouseFlags"/> position.</param>
-    /// <param name="seqReqStatus">The <see cref="AnsiEscapeSequenceRequestStatus"/> object.</param>
+    /// <param name="isResponse">Indicates if the escape sequence is a response to a request.</param>
     /// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
     public static void DecodeEscSeq (
         ref ConsoleKeyInfo newConsoleKeyInfo,
@@ -203,7 +203,7 @@ public static class AnsiEscapeSequenceRequestUtils
         out bool isMouse,
         out List<MouseFlags> buttonState,
         out Point pos,
-        out AnsiEscapeSequenceRequestStatus? seqReqStatus,
+        out bool isResponse,
         Action<MouseFlags, Point> continuousButtonPressedHandler
     )
     {
@@ -212,7 +212,7 @@ public static class AnsiEscapeSequenceRequestUtils
         isMouse = false;
         buttonState = [0];
         pos = default (Point);
-        seqReqStatus = null;
+        isResponse = false;
         var keyChar = '\0';
 
         switch (c1Control)
@@ -339,16 +339,10 @@ public static class AnsiEscapeSequenceRequestUtils
                     return;
                 }
 
-                if (AnsiEscapeSequenceRequests.HasResponse (terminator, out seqReqStatus))
+                if (EscSeqRequests.HasResponse (terminator))
                 {
-                    AnsiEscapeSequenceRequests.Remove (seqReqStatus);
-
-                    var ckiString = ToString (cki);
-
-                    lock (seqReqStatus?.AnsiRequest._responseLock!)
-                    {
-                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString, seqReqStatus.AnsiRequest);
-                    }
+                    isResponse = true;
+                    EscSeqRequests.Remove (terminator);
 
                     return;
                 }
@@ -376,15 +370,10 @@ public static class AnsiEscapeSequenceRequestUtils
                     else
                     {
                         // It's request response that wasn't handled by a valid request terminator
-                        System.Diagnostics.Debug.Assert (AnsiEscapeSequenceRequests.Statuses.Count > 0);
-
-                        if (AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? result))
-                        {
-                            lock (result.AnsiRequest._responseLock)
-                            {
-                                result.AnsiRequest.RaiseResponseFromInput (ToString (cki), result.AnsiRequest);
-                            }
-                        }
+                        System.Diagnostics.Debug.Assert (EscSeqRequests.Statuses.Count > 0);
+
+                        isResponse = true;
+                        EscSeqRequests.Remove (terminator);
                     }
                 }
                 else
@@ -1804,7 +1793,12 @@ public static class AnsiEscapeSequenceRequestUtils
     ///     https://terminalguide.namepad.de/seq/csi_sn__p-6/
     ///     The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) ; 1 R
     /// </summary>
-    public static readonly AnsiEscapeSequenceRequest CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" };
+    public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n";
+
+    /// <summary>
+    ///     The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) R
+    /// </summary>
+    public static readonly string CSI_RequestCursorPositionReport_Terminator = "R";
 
     /// <summary>
     ///     ESC [ 0 c - Send Device Attributes (Primary DA)
@@ -1826,7 +1820,7 @@ public static class AnsiEscapeSequenceRequestUtils
     ///     The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
     ///     <see cref="CSI_SendDeviceAttributes2"/>
     /// </summary>
-    public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" };
+    public static readonly string CSI_SendDeviceAttributes = CSI + "0c";
 
     /// <summary>
     ///     ESC [ > 0 c - Send Device Attributes (Secondary DA)
@@ -1834,7 +1828,7 @@ public static class AnsiEscapeSequenceRequestUtils
     ///     The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
     ///     <see cref="CSI_SendDeviceAttributes2"/>
     /// </summary>
-    public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" };
+    public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c";
 
     /*
      TODO: depends on https://github.com/gui-cs/Terminal.Gui/pull/3768
@@ -1854,7 +1848,18 @@ public static class AnsiEscapeSequenceRequestUtils
     ///     https://terminalguide.namepad.de/seq/csi_st-18/
     ///     The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t
     /// </summary>
-    public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", ExpectedResponseValue = "8" };
+    public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t";
+
+    /// <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";
+
+    /// <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";
 
     #endregion
 }

+ 0 - 2
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -3,8 +3,6 @@
 internal class FakeMainLoop : IMainLoopDriver
 {
     public Action<ConsoleKeyInfo> MockKeyPressed;
-    public bool ForceRead { get; set; }
-    public ManualResetEventSlim WaitForInput { get; set; } = new ();
 
     public FakeMainLoop (ConsoleDriver consoleDriver = null)
     {

+ 16 - 23
Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

@@ -29,15 +29,15 @@ internal class NetDriver : ConsoleDriver
             Console.Clear ();
 
             //Disable alternative screen buffer.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
 
             //Set cursor key to cursor.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_ShowCursor);
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
 
             Platform.Suspend ();
 
             //Enable alternative screen buffer.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
 
             SetContentsAsDirty ();
             Refresh ();
@@ -131,7 +131,7 @@ internal class NetDriver : ConsoleDriver
                         if (Force16Colors)
                         {
                             output.Append (
-                                           AnsiEscapeSequenceRequestUtils.CSI_SetGraphicsRendition (
+                                           EscSeqUtils.CSI_SetGraphicsRendition (
                                                                                                     MapColors (
                                                                                                          (ConsoleColor)attr.Background
                                                                                                              .GetClosestNamedColor16 (),
@@ -146,7 +146,7 @@ internal class NetDriver : ConsoleDriver
                         else
                         {
                             output.Append (
-                                           AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB (
+                                           EscSeqUtils.CSI_SetForegroundColorRGB (
                                                                                                      attr.Foreground.R,
                                                                                                      attr.Foreground.G,
                                                                                                      attr.Foreground.B
@@ -154,7 +154,7 @@ internal class NetDriver : ConsoleDriver
                                           );
 
                             output.Append (
-                                           AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB (
+                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
                                                                                                      attr.Background.R,
                                                                                                      attr.Background.G,
                                                                                                      attr.Background.B
@@ -272,10 +272,10 @@ internal class NetDriver : ConsoleDriver
             Rows = Console.WindowHeight;
 
             //Enable alternative screen buffer.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
 
             //Set cursor key to application.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_HideCursor);
+            Console.Out.Write (EscSeqUtils.CSI_HideCursor);
         }
         else
         {
@@ -294,11 +294,6 @@ internal class NetDriver : ConsoleDriver
         _mainLoopDriver = new (this);
         _mainLoopDriver.ProcessInput = ProcessInput;
 
-        if (!RunningUnitTests)
-        {
-            Task.Run (ProcessAnsiRequestHandler);
-        }
-
         return new (_mainLoopDriver);
     }
 
@@ -315,7 +310,7 @@ internal class NetDriver : ConsoleDriver
 
                 //Debug.WriteLine ($"event: {inputEvent}");
 
-                KeyCode map = AnsiEscapeSequenceRequestUtils.MapKey (consoleKeyInfo);
+                KeyCode map = EscSeqUtils.MapKey (consoleKeyInfo);
 
                 if (map == KeyCode.Null)
                 {
@@ -364,17 +359,15 @@ internal class NetDriver : ConsoleDriver
 
         StopReportingMouseMoves ();
 
-
-
         if (!RunningUnitTests)
         {
             Console.ResetColor ();
 
             //Disable alternative screen buffer.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
 
             //Set cursor key to cursor.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_ShowCursor);
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
             Console.Out.Close ();
         }
     }
@@ -464,7 +457,7 @@ internal class NetDriver : ConsoleDriver
 
         // + 1 is needed because non-Windows is based on 1 instead of 0 and
         // Console.CursorTop/CursorLeft isn't reliable.
-        Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (row + 1, col + 1));
+        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
 
         return true;
     }
@@ -493,7 +486,7 @@ internal class NetDriver : ConsoleDriver
     {
         _cachedCursorVisibility = visibility;
 
-        Console.Out.Write (visibility == CursorVisibility.Default ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor);
+        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
 
         return visibility == CursorVisibility.Default;
     }
@@ -522,7 +515,7 @@ internal class NetDriver : ConsoleDriver
     {
         if (!RunningUnitTests)
         {
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents);
+            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
         }
     }
 
@@ -530,7 +523,7 @@ internal class NetDriver : ConsoleDriver
     {
         if (!RunningUnitTests)
         {
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents);
+            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
         }
     }
 
@@ -786,7 +779,7 @@ internal class NetDriver : ConsoleDriver
         }
         else
         {
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SetTerminalWindowSize (Rows, Cols));
+            Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
         }
 
         // CONCURRENCY: Unsynchronized access to Clip is not safe.

+ 72 - 115
Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs

@@ -77,8 +77,6 @@ internal class NetEvents : IDisposable
             // 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);
-
-            ProcessResponse ();
         }
 
         cancellationToken.ThrowIfCancellationRequested ();
@@ -86,39 +84,6 @@ internal class NetEvents : IDisposable
         return default (ConsoleKeyInfo);
     }
 
-    //internal bool _forceRead;
-    private int _retries;
-
-    private void ProcessResponse ()
-    {
-        if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
-        {
-            if (_retries > 1)
-            {
-                if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus)
-                    && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
-                {
-                    lock (seqReqStatus.AnsiRequest._responseLock)
-                    {
-                        AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
-
-                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest);
-                    }
-                }
-
-                _retries = 0;
-            }
-            else
-            {
-                _retries++;
-            }
-        }
-        else
-        {
-            _retries = 0;
-        }
-    }
-
     private void ProcessInputQueue ()
     {
         while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
@@ -144,13 +109,13 @@ internal class NetEvents : IDisposable
 
                     var ckiAlreadyResized = false;
 
-                    if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { })
+                    if (EscSeqUtils.IncompleteCkInfos is { })
                     {
                         ckiAlreadyResized = true;
 
-                        _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki);
-                        _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki);
-                        AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null;
+                        _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
+                        _cki = EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki);
+                        EscSeqUtils.IncompleteCkInfos = null;
 
                         if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B')
                         {
@@ -163,7 +128,7 @@ internal class NetEvents : IDisposable
                     {
                         if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
                         {
-                            _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (
+                            _cki = EscSeqUtils.ResizeArray (
                                                                                new (
                                                                                     (char)KeyCode.Esc,
                                                                                     0,
@@ -190,7 +155,6 @@ internal class NetEvents : IDisposable
                             ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
                             _cki = null;
                             _isEscSeq = false;
-                            ProcessResponse ();
 
                             ProcessMapConsoleKeyInfo (consoleKeyInfo);
                         }
@@ -200,7 +164,7 @@ internal class NetEvents : IDisposable
 
                             if (!ckiAlreadyResized)
                             {
-                                _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki);
+                                _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
                             }
 
                             if (Console.KeyAvailable)
@@ -211,7 +175,6 @@ internal class NetEvents : IDisposable
                             ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki!, ref mod);
                             _cki = null;
                             _isEscSeq = false;
-                            ProcessResponse ();
                         }
 
                         break;
@@ -221,11 +184,10 @@ internal class NetEvents : IDisposable
                     {
                         ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
                         _cki = null;
-                        ProcessResponse ();
 
                         if (Console.KeyAvailable)
                         {
-                            _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki);
+                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
                         }
                         else
                         {
@@ -237,11 +199,6 @@ internal class NetEvents : IDisposable
 
                     ProcessMapConsoleKeyInfo (consoleKeyInfo);
 
-                    if (_retries > 0)
-                    {
-                        _retries = 0;
-                    }
-
                     break;
                 }
 
@@ -261,7 +218,7 @@ internal class NetEvents : IDisposable
             _inputQueue.Enqueue (
                                  new ()
                                  {
-                                     EventType = EventType.Key, ConsoleKeyInfo = AnsiEscapeSequenceRequestUtils.MapConsoleKeyInfo (consoleKeyInfo)
+                                     EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
                                  }
                                 );
             _isEscSeq = false;
@@ -357,7 +314,7 @@ internal class NetEvents : IDisposable
     )
     {
         // isMouse is true if it's CSI<, false otherwise
-        AnsiEscapeSequenceRequestUtils.DecodeEscSeq (
+        EscSeqUtils.DecodeEscSeq (
                                                      ref newConsoleKeyInfo,
                                                      ref key,
                                                      cki,
@@ -369,7 +326,7 @@ internal class NetEvents : IDisposable
                                                      out bool isMouse,
                                                      out List<MouseFlags> mouseFlags,
                                                      out Point pos,
-                                                     out AnsiEscapeSequenceRequestStatus? seqReqStatus,
+                                                     out bool isReq,
                                                      (f, p) => HandleMouseEvent (MapMouseFlags (f), p)
                                                     );
 
@@ -383,9 +340,9 @@ internal class NetEvents : IDisposable
             return;
         }
 
-        if (seqReqStatus is { })
+        if (isReq)
         {
-            //HandleRequestResponseEvent (c1Control, code, values, terminating);
+            HandleRequestResponseEvent (c1Control, code, values, terminating);
 
             return;
         }
@@ -530,66 +487,66 @@ internal class NetEvents : IDisposable
         return mbs;
     }
 
-    //private Point _lastCursorPosition;
-
-    //private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
-    //{
-    //    if (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 };
-
-    //        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.Value)
-    //        {
-    //            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
-    //        {
-    //            EnqueueRequestResponseEvent (c1Control, code, values, terminating);
-    //        }
-    //    }
-    //    else
-    //    {
-    //        EnqueueRequestResponseEvent (c1Control, code, values, terminating);
-    //    }
-
-    //    _inputReady.Set ();
-    //}
-
-    //private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
-    //{
-    //    var eventType = EventType.RequestResponse;
-    //    var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
-
-    //    _inputQueue.Enqueue (
-    //                         new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
-    //                        );
-    //}
+    private Point _lastCursorPosition;
+
+    private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
+    {
+        if (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 };
+
+            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
+            {
+                EnqueueRequestResponseEvent (c1Control, code, values, terminating);
+            }
+        }
+        else
+        {
+            EnqueueRequestResponseEvent (c1Control, code, values, terminating);
+        }
+
+        _inputReady.Set ();
+    }
+
+    private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
+    {
+        var eventType = EventType.RequestResponse;
+        var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
+
+        _inputQueue.Enqueue (
+                             new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
+                            );
+    }
 
     private void HandleMouseEvent (MouseButtonState buttonState, Point pos)
     {

+ 20 - 16
Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs

@@ -19,10 +19,9 @@ internal class NetMainLoop : IMainLoopDriver
     private readonly ManualResetEventSlim _eventReady = new (false);
     private readonly CancellationTokenSource _eventReadyTokenSource = new ();
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
+    private readonly ManualResetEventSlim _waitForProbe = new (false);
     private readonly ConcurrentQueue<NetEvents.InputResult> _resultQueue = new ();
     private MainLoop? _mainLoop;
-    bool IMainLoopDriver.ForceRead { get; set; }
-    ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false);
 
     /// <summary>Initializes the class with the console driver.</summary>
     /// <remarks>Passing a consoleDriver is provided to capture windows resizing.</remarks>
@@ -52,7 +51,12 @@ internal class NetMainLoop : IMainLoopDriver
 
     bool IMainLoopDriver.EventsPending ()
     {
-        ((IMainLoopDriver)this).WaitForInput.Set ();
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return true;
+        }
+
+        _waitForProbe.Set ();
 
         if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
         {
@@ -66,13 +70,6 @@ internal class NetMainLoop : IMainLoopDriver
                 // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
                 // are no timers, but there IS an idle handler waiting.
                 _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
-
-                _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
-
-                if (!_eventReadyTokenSource.IsCancellationRequested)
-                {
-                    return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
-                }
             }
         }
         catch (OperationCanceledException)
@@ -84,13 +81,20 @@ internal class NetMainLoop : IMainLoopDriver
             _eventReady.Reset ();
         }
 
+        _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
+
+        if (!_eventReadyTokenSource.IsCancellationRequested)
+        {
+            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
+        }
+
         // If cancellation was requested then always return true
         return true;
     }
 
     void IMainLoopDriver.Iteration ()
     {
-        while (_resultQueue.TryDequeue (out NetEvents.InputResult inputRecords))
+        while (!ConsoleDriver.RunningUnitTests && _resultQueue.TryDequeue (out NetEvents.InputResult inputRecords))
         {
             ProcessInput?.Invoke (inputRecords);
         }
@@ -104,7 +108,7 @@ internal class NetMainLoop : IMainLoopDriver
         _eventReadyTokenSource.Dispose ();
 
         _eventReady.Dispose ();
-        ((IMainLoopDriver)this).WaitForInput.Dispose ();
+        _waitForProbe.Dispose ();
 
         _resultQueue.Clear ();
         _netEvents?.Dispose ();
@@ -119,11 +123,11 @@ internal class NetMainLoop : IMainLoopDriver
         {
             try
             {
-                if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this).ForceRead)
+                if (!_inputHandlerTokenSource.IsCancellationRequested)
                 {
                     try
                     {
-                        ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token);
+                        _waitForProbe.Wait (_inputHandlerTokenSource.Token);
                     }
                     catch (Exception ex)
                     {
@@ -135,7 +139,7 @@ internal class NetMainLoop : IMainLoopDriver
                         throw;
                     }
 
-                    ((IMainLoopDriver)this).WaitForInput.Reset ();
+                    _waitForProbe.Reset ();
                 }
 
                 ProcessInputQueue ();
@@ -149,7 +153,7 @@ internal class NetMainLoop : IMainLoopDriver
 
     private void ProcessInputQueue ()
     {
-        if (_resultQueue.Count == 0 || ((IMainLoopDriver)this).ForceRead)
+        if (_resultQueue.Count == 0)
         {
             NetEvents.InputResult? result = _netEvents!.DequeueInput ();
 

+ 13 - 128
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs

@@ -62,9 +62,6 @@ internal class WindowsConsole
         const int BUFFER_SIZE = 1;
         InputRecord inputRecord = default;
         uint numberEventsRead = 0;
-        StringBuilder ansiSequence = new StringBuilder ();
-        bool readingSequence = false;
-        bool raisedResponse = false;
 
         while (!_inputReadyCancellationTokenSource!.IsCancellationRequested)
         {
@@ -79,101 +76,6 @@ internal class WindowsConsole
                                       out inputRecord,
                                       BUFFER_SIZE,
                                       out numberEventsRead);
-
-                    if (inputRecord.EventType == EventType.Key)
-                    {
-                        KeyEventRecord keyEvent = inputRecord.KeyEvent;
-
-                        if (keyEvent.bKeyDown)
-                        {
-                            char inputChar = keyEvent.UnicodeChar;
-
-                            // Check if input is part of an ANSI escape sequence
-                            if (inputChar == '\u001B') // Escape character
-                            {
-                                // Peek to check if there is any input available with key event and bKeyDown and not Escape character
-                                if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0)
-                                {
-                                    if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true, KeyEvent.UnicodeChar: not '\u001B' })
-                                    {
-                                        // It's really an ANSI request response
-                                        readingSequence = true;
-                                        // Start a new sequence ensuring in the cases where wasn't clear by reading the terminator
-                                        ansiSequence.Clear ();
-                                        ansiSequence.Append (inputChar);
-
-                                        continue;
-                                    }
-                                }
-                            }
-                            else if (readingSequence)
-                            {
-                                ansiSequence.Append (inputChar);
-
-                                // Check if the sequence has ended with an expected command terminator
-                                if (AnsiEscapeSequenceRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus))
-                                {
-                                    // Finished reading the sequence and remove the enqueued request
-                                    AnsiEscapeSequenceRequests.Remove (seqReqStatus);
-
-                                    lock (seqReqStatus!.AnsiRequest._responseLock)
-                                    {
-                                        readingSequence = false;
-                                        raisedResponse = true;
-                                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest);
-                                        ClearInputRecord ();
-
-                                        // Clear the ansiSequence to avoid insert another Esc character
-                                        ansiSequence.Clear ();
-                                    }
-                                }
-
-                                if (readingSequence)
-                                {
-                                    continue;
-                                }
-                            }
-                        }
-                    }
-                }
-
-                if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
-                {
-                    AnsiEscapeSequenceRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus);
-
-                    lock (seqReqStatus!.AnsiRequest._responseLock)
-                    {
-                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString (), seqReqStatus.AnsiRequest);
-                        ClearInputRecord();
-                    }
-
-                    _retries = 0;
-                }
-                else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0)
-                {
-                    if (_retries > 1)
-                    {
-                        if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
-                        {
-                            lock (seqReqStatus.AnsiRequest._responseLock)
-                            {
-                                AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _);
-
-                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (null, seqReqStatus.AnsiRequest);
-                                ClearInputRecord();
-                            }
-                        }
-
-                        _retries = 0;
-                    }
-                    else
-                    {
-                        _retries++;
-                    }
-                }
-                else
-                {
-                    _retries = 0;
                 }
 
                 if (numberEventsRead > 0)
@@ -181,16 +83,13 @@ internal class WindowsConsole
                     return inputRecord;
                 }
 
-                if (!_forceRead)
+                try
                 {
-                    try
-                    {
-                        Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token);
-                    }
-                    catch (OperationCanceledException)
-                    {
-                        return null;
-                    }
+                    Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token);
+                }
+                catch (OperationCanceledException)
+                {
+                    return null;
                 }
             }
             catch (Exception ex)
@@ -205,26 +104,15 @@ internal class WindowsConsole
         }
 
         return null;
-
-        void ClearInputRecord ()
-        {
-            // Clear the terminator for not be enqueued
-            inputRecord = default (InputRecord);
-
-            // Clear numberEventsRead to not exit
-            numberEventsRead = 0;
-        }
     }
 
-    internal bool _forceRead;
-
     private void ProcessInputQueue ()
     {
         while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
         {
             try
             {
-                if (_inputQueue.Count == 0 || _forceRead)
+                if (_inputQueue.Count == 0)
                 {
                     while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
                     {
@@ -253,7 +141,6 @@ internal class WindowsConsole
         }
     }
 
-
     private CharInfo []? _originalStdOutChars;
 
     public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
@@ -288,8 +175,8 @@ internal class WindowsConsole
         {
             _stringBuilder.Clear ();
 
-            _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorPosition);
-            _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (0, 0));
+            _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
+            _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0));
 
             Attribute? prev = null;
 
@@ -300,8 +187,8 @@ internal class WindowsConsole
                 if (attr != prev)
                 {
                     prev = attr;
-                    _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B));
-                    _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B));
+                    _stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B));
+                    _stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B));
                 }
 
                 if (info.Char != '\x1b')
@@ -317,8 +204,8 @@ internal class WindowsConsole
                 }
             }
 
-            _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorPosition);
-            _stringBuilder.Append (AnsiEscapeSequenceRequestUtils.CSI_HideCursor);
+            _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
+            _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
 
             var s = _stringBuilder.ToString ();
 
@@ -1118,8 +1005,6 @@ internal class WindowsConsole
         }
     }
 
-    private int _retries;
-
 #if false // Not needed on the constructor. Perhaps could be used on resizing. To study.
 		[DllImport ("kernel32.dll", ExactSpelling = true)]
 		static extern IntPtr GetConsoleWindow ();

+ 5 - 7
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs

@@ -244,7 +244,7 @@ internal class WindowsDriver : ConsoleDriver
         else
         {
             var sb = new StringBuilder ();
-            sb.Append (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1));
+            sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1));
             WinConsole?.WriteANSI (sb.ToString ());
         }
 
@@ -280,7 +280,7 @@ internal class WindowsDriver : ConsoleDriver
         else
         {
             var sb = new StringBuilder ();
-            sb.Append (visibility != CursorVisibility.Invisible ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor);
+            sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
             return WinConsole?.WriteANSI (sb.ToString ()) ?? false;
         }
     }
@@ -295,7 +295,7 @@ internal class WindowsDriver : ConsoleDriver
         else
         {
             var sb = new StringBuilder ();
-            sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? AnsiEscapeSequenceRequestUtils.CSI_ShowCursor : AnsiEscapeSequenceRequestUtils.CSI_HideCursor);
+            sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
             return WinConsole?.WriteANSI (sb.ToString ()) ?? false;
         }
 
@@ -420,7 +420,7 @@ internal class WindowsDriver : ConsoleDriver
         if (!RunningUnitTests && _isWindowsTerminal)
         {
             // Disable alternative screen buffer.
-            Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
         }
     }
 
@@ -446,7 +446,7 @@ internal class WindowsDriver : ConsoleDriver
 
                 if (_isWindowsTerminal)
                 {
-                    Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+                    Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
                 }
             }
             catch (Win32Exception e)
@@ -481,8 +481,6 @@ internal class WindowsDriver : ConsoleDriver
         if (!RunningUnitTests)
         {
             WinConsole?.SetInitialCursorVisibility ();
-
-            Task.Run (ProcessAnsiRequestHandler);
         }
 
         return new MainLoop (_mainLoopDriver);

+ 17 - 12
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs

@@ -23,7 +23,7 @@ internal class WindowsMainLoop : IMainLoopDriver
 
     // The records that we keep fetching
     private readonly ConcurrentQueue<WindowsConsole.InputRecord> _resultQueue = new ();
-    ManualResetEventSlim IMainLoopDriver.WaitForInput { get; set; } = new (false);
+    private readonly ManualResetEventSlim _waitForProbe = new (false);
     private readonly WindowsConsole? _winConsole;
     private CancellationTokenSource _eventReadyTokenSource = new ();
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
@@ -59,7 +59,12 @@ internal class WindowsMainLoop : IMainLoopDriver
 
     bool IMainLoopDriver.EventsPending ()
     {
-        ((IMainLoopDriver)this).WaitForInput.Set ();
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return true;
+        }
+
+        _waitForProbe.Set ();
 #if HACK_CHECK_WINCHANGED
         _winChange.Set ();
 #endif
@@ -83,7 +88,10 @@ internal class WindowsMainLoop : IMainLoopDriver
         }
         finally
         {
-            _eventReady.Reset ();
+            if (!_eventReadyTokenSource.IsCancellationRequested)
+            {
+                _eventReady.Reset ();
+            }
         }
 
         if (!_eventReadyTokenSource.IsCancellationRequested)
@@ -104,7 +112,7 @@ internal class WindowsMainLoop : IMainLoopDriver
 
     void IMainLoopDriver.Iteration ()
     {
-        while (_resultQueue.TryDequeue (out WindowsConsole.InputRecord inputRecords))
+        while (!ConsoleDriver.RunningUnitTests && _resultQueue.TryDequeue (out WindowsConsole.InputRecord inputRecords))
         {
             ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords);
         }
@@ -133,7 +141,7 @@ internal class WindowsMainLoop : IMainLoopDriver
             }
         }
 
-        ((IMainLoopDriver)this).WaitForInput?.Dispose ();
+        _waitForProbe.Dispose ();
 
         _resultQueue.Clear ();
 
@@ -148,19 +156,17 @@ internal class WindowsMainLoop : IMainLoopDriver
         _mainLoop = null;
     }
 
-    public bool ForceRead { get; set; }
-
     private void WindowsInputHandler ()
     {
         while (_mainLoop is { })
         {
             try
             {
-                if (_inputHandlerTokenSource.IsCancellationRequested && !ForceRead)
+                if (_inputHandlerTokenSource.IsCancellationRequested)
                 {
                     try
                     {
-                        ((IMainLoopDriver)this).WaitForInput.Wait (_inputHandlerTokenSource.Token);
+                        _waitForProbe.Wait (_inputHandlerTokenSource.Token);
                     }
                     catch (Exception ex)
                     {
@@ -172,7 +178,7 @@ internal class WindowsMainLoop : IMainLoopDriver
                         throw;
                     }
 
-                    ((IMainLoopDriver)this).WaitForInput.Reset ();
+                    _waitForProbe.Reset ();
                 }
 
                 ProcessInputQueue ();
@@ -187,7 +193,7 @@ internal class WindowsMainLoop : IMainLoopDriver
 
     private void ProcessInputQueue ()
     {
-        if (_resultQueue?.Count == 0 || ForceRead)
+        if (_resultQueue?.Count == 0)
         {
             WindowsConsole.InputRecord? result = _winConsole!.DequeueInput ();
 
@@ -232,4 +238,3 @@ internal class WindowsMainLoop : IMainLoopDriver
     }
 #endif
 }
-

+ 14 - 31
Terminal.Gui/Terminal.Gui.csproj

@@ -11,9 +11,9 @@
   <!-- Assembly name. -->
   <!-- Referenced throughout this file for consistency. -->
   <!-- =================================================================== -->
-  <PropertyGroup>
-    <AssemblyName>Terminal.Gui</AssemblyName>
-  </PropertyGroup>
+<PropertyGroup>
+  <AssemblyName>Terminal.Gui</AssemblyName>
+</PropertyGroup>
 
   <!-- =================================================================== -->
   <!-- .NET Build Settings -->
@@ -151,39 +151,22 @@
           <PackageOutputPath>$(MSBuildThisFileDirectory)bin\$(Configuration)\</PackageOutputPath>
       </PropertyGroup>
 
-	  <!-- Ensure the local_packages folder exists -->
-	  <Message Text="Checking if $(LocalPackagesPath) exists, creating if necessary." Importance="high" />
-	  <MakeDir Directories="$(LocalPackagesPath)" />
+      <!-- Ensure the local_packages folder exists -->
+      <Message Text="Checking if $(LocalPackagesPath) exists, creating if necessary." Importance="high" />
+      <MakeDir Directories="$(LocalPackagesPath)" />
 
-	  <!-- Collect .nupkg and .snupkg files into an item group -->
-	  <ItemGroup>
-		  <NuGetPackages Include="$(PackageOutputPath)*.nupkg;$(PackageOutputPath)*.snupkg" />
-	  </ItemGroup>
+      <!-- Collect .nupkg and .snupkg files into an item group -->
+      <ItemGroup>
+          <NuGetPackages Include="$(PackageOutputPath)*.nupkg;$(PackageOutputPath)*.snupkg" />
+      </ItemGroup>
 
-	  <!-- Check if any packages were found -->
-	  <Message Text="Found packages: @(NuGetPackages)" Importance="high" />
+      <!-- Check if any packages were found -->
+      <Message Text="Found packages: @(NuGetPackages)" Importance="high" />
 
       <!-- Copy files only if found -->
       <Copy SourceFiles="@(NuGetPackages)" DestinationFolder="$(LocalPackagesPath)" SkipUnchangedFiles="false" Condition="@(NuGetPackages) != ''" />
 
-	  <!-- Log success -->
-	  <Message Text="Copy completed successfully." Importance="high" />
+      <!-- Log success -->
+      <Message Text="Copy completed successfully." Importance="high" />
   </Target>
-
-  <ItemGroup>
-	   <!--Include the executable in the NuGet package and set it to copy to output directories-->
-	   <!-- Include the Linux shared library -->
-	  <None Include="$(ProjectDir)compiled-binaries\libGetTIOCGWINSZ.so">
-		  <Pack>true</Pack>
-		  <PackagePath>compiled-binaries/</PackagePath>
-		  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-	  </None>
-
-	   <!-- Include the macOS shared library -->
-	   <None Include="$(ProjectDir)compiled-binaries/libGetTIOCGWINSZ.dylib">
-		   <Pack>true</Pack>
-		   <PackagePath>compiled-binaries/</PackagePath>
-		   <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-	   </None>
-  </ItemGroup>
 </Project>

BIN
Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.dylib


BIN
Terminal.Gui/compiled-binaries/libGetTIOCGWINSZ.so


+ 0 - 440
UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs

@@ -1,440 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Terminal.Gui;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")]
-[ScenarioCategory ("Tests")]
-public sealed class AnsiEscapeSequenceRequests : Scenario
-{
-    private readonly Dictionary<DateTime, AnsiEscapeSequenceRequest> _sends = new ();
-
-    private readonly object _lockAnswers = new ();
-    private readonly Dictionary<DateTime, AnsiEscapeSequenceRequest> _answers = new ();
-    private readonly Dictionary<DateTime, AnsiEscapeSequenceRequest> _errors = new ();
-
-    private GraphView _graphView;
-
-    private ScatterSeries _sentSeries;
-    private ScatterSeries _answeredSeries;
-    private Label _lblSummary;
-    private Label _lblErrorSummary;
-
-    public override void Main ()
-    {
-        // Init
-        Application.Init ();
-
-        var tv = new TabView
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill ()
-        };
-
-        var single = new Tab ();
-        single.DisplayText = "_Single";
-        single.View = BuildSingleTab ();
-
-        Tab bulk = new ();
-        bulk.DisplayText = "_Multi";
-        bulk.View = BuildBulkTab ();
-
-        tv.AddTab (single, true);
-        tv.AddTab (bulk, false);
-
-        // Setup - Create a top-level application window and configure it.
-        Window appWindow = new ()
-        {
-            Title = GetQuitKeyAndName ()
-        };
-
-        appWindow.Add (tv);
-
-        // Run - Start the application.
-        Application.Run (appWindow);
-        bulk.View.Dispose ();
-        single.View.Dispose ();
-        appWindow.Dispose ();
-
-        // Shutdown - Calling Application.Shutdown is required.
-        Application.Shutdown ();
-    }
-
-    private View BuildBulkTab ()
-    {
-        var w = new View
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-            CanFocus = true
-        };
-
-        var lbl = new Label
-        {
-            Text =
-                "_This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends. Responses are in red, queued messages are in green.",
-            Height = 2,
-            Width = Dim.Fill ()
-        };
-
-        Application.AddTimeout (
-                                TimeSpan.FromMilliseconds (1000),
-                                () =>
-                                {
-                                    lock (_lockAnswers)
-                                    {
-                                        UpdateGraph ();
-
-                                        UpdateResponses ();
-                                    }
-
-                                    return true;
-                                });
-
-        var tv = new TextView
-        {
-            Y = Pos.Bottom (lbl),
-            Width = Dim.Percent (50),
-            Height = Dim.Fill ()
-        };
-
-        var lblDar = new Label
-        {
-            Y = Pos.Bottom (lbl),
-            X = Pos.Right (tv) + 1,
-            Text = "_DAR per second: "
-        };
-
-        var cbDar = new NumericUpDown
-        {
-            X = Pos.Right (lblDar),
-            Y = Pos.Bottom (lbl),
-            Value = 0
-        };
-
-        cbDar.ValueChanging += (s, e) =>
-                               {
-                                   if (e.NewValue is < 0 or > 20)
-                                   {
-                                       e.Cancel = true;
-                                   }
-                               };
-        w.Add (cbDar);
-
-        int lastSendTime = Environment.TickCount;
-        var lockObj = new object ();
-        int interval = 50;
-
-        Application.AddTimeout (
-                                TimeSpan.FromMilliseconds ((double)interval / (cbDar.Value > 0 ? cbDar.Value : 1)),
-                                () =>
-                                {
-                                    lock (lockObj)
-                                    {
-                                        if (cbDar.Value > 0)
-                                        {
-                                            interval = 1000 / cbDar.Value; // Calculate the desired interval in milliseconds
-                                            int currentTime = Environment.TickCount; // Current system time in milliseconds
-
-                                            // Check if the time elapsed since the last send is greater than the interval
-                                            if (currentTime - lastSendTime >= interval)
-                                            {
-                                                SendDar (); // Send the request
-                                                lastSendTime = currentTime; // Update the last send time
-                                            }
-                                        }
-                                    }
-
-                                    return true;
-                                });
-
-        _graphView = new ()
-        {
-            Y = Pos.Bottom (cbDar),
-            X = Pos.Right (tv),
-            Width = Dim.Fill (),
-            Height = Dim.Fill (2)
-        };
-
-        _lblSummary = new ()
-        {
-            Y = Pos.Bottom (_graphView),
-            X = Pos.Right (tv),
-            Width = Dim.Fill ()
-        };
-
-        _lblErrorSummary = new ()
-        {
-            Y = Pos.Bottom (_lblSummary),
-            X = Pos.Right (tv),
-            Width = Dim.Fill ()
-        };
-
-        SetupGraph ();
-
-        w.Add (lbl);
-        w.Add (lblDar);
-        w.Add (cbDar);
-        w.Add (tv);
-        w.Add (_graphView);
-        w.Add (_lblSummary);
-        w.Add (_lblErrorSummary);
-
-        return w;
-    }
-
-    private View BuildSingleTab ()
-    {
-        var w = new View
-        {
-            Width = Dim.Fill (),
-            Height = Dim.Fill (),
-            CanFocus = true
-        };
-
-        w.Padding.Thickness = new (1);
-
-        // TODO: This hackery is why I think the EscSeqUtils class should be refactored and the CSI's made type safe.
-        List<string> scrRequests = new ()
-        {
-            "CSI_SendDeviceAttributes",
-            "CSI_ReportTerminalSizeInChars",
-            "CSI_RequestCursorPositionReport",
-            "CSI_SendDeviceAttributes2"
-        };
-
-        var cbRequests = new ComboBox { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper<string> (new (scrRequests)) };
-        w.Add (cbRequests);
-
-        // TODO: Use Pos.Align and Dim.Func so these hardcoded widths aren't needed.
-        var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "_Request:" };
-        var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 };
-        w.Add (label, tfRequest);
-
-        label = new () { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "E_xpectedResponseValue:" };
-        var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 };
-        w.Add (label, tfValue);
-
-        label = new () { X = Pos.Left (tfValue) + label.Text.Length, Y = Pos.Top (tfValue) - 1, Text = "_Terminator:" };
-        var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 };
-        w.Add (label, tfTerminator);
-
-        cbRequests.SelectedItemChanged += (s, e) =>
-                                          {
-                                              if (cbRequests.SelectedItem == -1)
-                                              {
-                                                  return;
-                                              }
-
-                                              string selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem];
-                                              AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null;
-
-                                              switch (selAnsiEscapeSequenceRequestName)
-                                              {
-                                                  case "CSI_SendDeviceAttributes":
-                                                      selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes;
-
-                                                      break;
-                                                  case "CSI_ReportTerminalSizeInChars":
-                                                      selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_ReportTerminalSizeInChars;
-
-                                                      break;
-                                                  case "CSI_RequestCursorPositionReport":
-                                                      selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_RequestCursorPositionReport;
-
-                                                      break;
-                                                  case "CSI_SendDeviceAttributes2":
-                                                      selAnsiEscapeSequenceRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes2;
-
-                                                      break;
-                                              }
-
-                                              tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : "";
-
-                                              tfValue.Text = selAnsiEscapeSequenceRequest is { }
-                                                                 ? selAnsiEscapeSequenceRequest.ExpectedResponseValue ?? ""
-                                                                 : "";
-                                              tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : "";
-                                          };
-
-        // Forces raise cbRequests.SelectedItemChanged to update TextFields
-        cbRequests.SelectedItem = 0;
-
-        label = new () { Y = Pos.Bottom (tfRequest) + 2, Text = "_Response:" };
-        var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true };
-        w.Add (label, tvResponse);
-
-        label = new () { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "_Error:" };
-        var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true };
-        w.Add (label, tvError);
-
-        label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "_Value:" };
-        var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true };
-        w.Add (label, tvValue);
-
-        label = new () { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "_Terminator:" };
-        var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true };
-        w.Add (label, tvTerminator);
-
-        var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "_Send Request", IsDefault = true };
-
-        var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 };
-        w.Add (lblSuccess);
-
-        btnResponse.Accepting += (s, e) =>
-                                 {
-                                     var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest
-                                     {
-                                         Request = tfRequest.Text,
-                                         Terminator = tfTerminator.Text,
-                                         ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
-                                     };
-
-                                     bool success = Application.Driver!.TryWriteAnsiRequest (
-                                                                                             Application.MainLoop!.MainLoopDriver,
-                                                                                             ref ansiEscapeSequenceRequest
-                                                                                            );
-
-                                     tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? "";
-                                     tvError.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Error ?? "";
-                                     tvValue.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.ExpectedResponseValue ?? "";
-                                     tvTerminator.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Terminator ?? "";
-
-                                     if (success)
-                                     {
-                                         lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"];
-                                         lblSuccess.Text = "Success";
-                                     }
-                                     else
-                                     {
-                                         lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"];
-                                         lblSuccess.Text = "Error";
-                                     }
-                                 };
-        w.Add (btnResponse);
-
-        w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "Send other requests by editing the TextFields." });
-
-        return w;
-    }
-
-    private string GetSummary ()
-    {
-        if (_answers.Count == 0)
-        {
-            return "No requests sent yet";
-        }
-
-        string last = _answers.Last ().Value.AnsiEscapeSequenceResponse!.Response;
-
-        int unique = _answers.Values.Distinct ().Count ();
-        int total = _answers.Count;
-
-        return $"Last:{last} U:{unique} T:{total}";
-    }
-
-    private string GetSummaryErrors ()
-    {
-        if (_errors.Count == 0)
-        {
-            return "No errors received yet";
-        }
-
-        string last = _errors.Last ().Value.AnsiEscapeSequenceResponse!.Error;
-
-        int unique = _errors.Values.Distinct ().Count ();
-        int total = _errors.Count;
-
-        return $"Last:{last} U:{unique} T:{total}";
-    }
-
-    private void HandleResponse (AnsiEscapeSequenceRequest ansiRequest)
-    {
-        lock (_lockAnswers)
-        {
-            _answers.Add (DateTime.Now, ansiRequest);
-            KeyValuePair<DateTime, AnsiEscapeSequenceRequest> found = _sends.First (r => r.Value == ansiRequest);
-            _sends.Remove (found.Key);
-        }
-    }
-
-    private void HandleResponseError (AnsiEscapeSequenceRequest ansiRequest)
-    {
-        lock (_lockAnswers)
-        {
-            _errors.Add (DateTime.Now, ansiRequest);
-            KeyValuePair<DateTime, AnsiEscapeSequenceRequest> found = _sends.First (r => r.Value == ansiRequest);
-            _sends.Remove (found.Key);
-        }
-    }
-
-    private void SendDar ()
-    {
-        AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes;
-        _sends.Add (DateTime.Now, ansiRequest);
-
-        if (Application.Driver!.TryWriteAnsiRequest (Application.MainLoop!.MainLoopDriver, ref ansiRequest))
-        {
-            HandleResponse (ansiRequest);
-        }
-        else
-        {
-            HandleResponseError (ansiRequest);
-        }
-    }
-
-    private void SetupGraph ()
-    {
-        _graphView.Series.Add (_sentSeries = new ());
-        _graphView.Series.Add (_answeredSeries = new ());
-
-        _sentSeries.Fill = new (new ('.'), new (ColorName16.BrightGreen, ColorName16.Black));
-        _answeredSeries.Fill = new (new ('.'), new (ColorName16.BrightRed, ColorName16.Black));
-
-        // Todo:
-        // _graphView.Annotations.Add (_sentSeries new PathAnnotation {});
-
-        _graphView.CellSize = new (1, 1);
-        _graphView.MarginBottom = 2;
-        _graphView.AxisX.Increment = 1;
-        _graphView.AxisX.Text = "Seconds";
-        _graphView.GraphColor = new Attribute (Color.Green, Color.Black);
-    }
-
-    private static Func<KeyValuePair<DateTime, AnsiEscapeSequenceRequest>, int> ToSeconds () { return t => (int)(DateTime.Now - t.Key).TotalSeconds; }
-
-    private void UpdateGraph ()
-    {
-        System.Diagnostics.Debug.Assert (_sends.Count == 0);
-
-        _sentSeries.Points = _sends
-                             .GroupBy (ToSeconds ())
-                             .Select (g => new PointF (g.Key, g.Count ()))
-                             .ToList ();
-
-        _answeredSeries.Points = _answers
-                                 .Where (
-                                         r => r.Value.AnsiEscapeSequenceResponse is { }
-                                              && !string.IsNullOrEmpty (r.Value?.AnsiEscapeSequenceResponse.Response))
-                                 .GroupBy (ToSeconds ())
-                                 .Select (g => new PointF (g.Key, g.Count ()))
-                                 .ToList ();
-
-        //  _graphView.ScrollOffset  = new PointF(,0);
-        if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0)
-        {
-            _graphView.SetNeedsDraw ();
-        }
-    }
-
-    private void UpdateResponses ()
-    {
-        _lblSummary.Text = GetSummary ();
-        _lblSummary.SetNeedsDraw ();
-
-        _lblErrorSummary.Text = GetSummaryErrors ();
-        _lblErrorSummary.SetNeedsDraw ();
-    }
-}

+ 0 - 4
UnitTests/Application/MainLoopTests.cs

@@ -946,14 +946,10 @@ public class MainLoopTests
     private class TestMainloop : IMainLoopDriver
     {
         private MainLoop mainLoop;
-        private bool _forceRead1;
-        private ManualResetEventSlim _waitForInput1;
         public bool EventsPending () { throw new NotImplementedException (); }
         public void Iteration () { throw new NotImplementedException (); }
         public void TearDown () { throw new NotImplementedException (); }
         public void Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; }
         public void Wakeup () { throw new NotImplementedException (); }
-        public bool ForceRead { get; set; }
-        public ManualResetEventSlim WaitForInput { get; set; }
     }
 }

+ 0 - 79
UnitTests/Input/AnsiEscapeSequenceRequestsTests.cs

@@ -1,79 +0,0 @@
-namespace Terminal.Gui.InputTests;
-
-public class AnsiEscapeSequenceRequestsTests
-{
-    [Fact]
-    public void Add_Tests ()
-    {
-        AnsiEscapeSequenceRequests.Clear ();
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        Assert.Single (AnsiEscapeSequenceRequests.Statuses);
-        Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator);
-
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        Assert.Equal (2, AnsiEscapeSequenceRequests.Statuses.Count);
-        Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator);
-
-        AnsiEscapeSequenceRequests.Clear ();
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        Assert.Equal (2, AnsiEscapeSequenceRequests.Statuses.Count);
-        Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator);
-
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        Assert.Equal (3, AnsiEscapeSequenceRequests.Statuses.Count);
-        Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator);
-    }
-
-    [Fact]
-    public void Constructor_Defaults ()
-    {
-        AnsiEscapeSequenceRequests.Clear ();
-        Assert.NotNull (AnsiEscapeSequenceRequests.Statuses);
-        Assert.Empty (AnsiEscapeSequenceRequests.Statuses);
-    }
-
-    [Fact]
-    public void Remove_Tests ()
-    {
-        AnsiEscapeSequenceRequests.Clear ();
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        AnsiEscapeSequenceRequests.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus);
-        AnsiEscapeSequenceRequests.Remove (seqReqStatus);
-        Assert.Empty (AnsiEscapeSequenceRequests.Statuses);
-
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus);
-        AnsiEscapeSequenceRequests.Remove (seqReqStatus);
-        Assert.Single (AnsiEscapeSequenceRequests.Statuses);
-        Assert.Equal ("t", AnsiEscapeSequenceRequests.Statuses.ToArray () [^1].AnsiRequest.Terminator);
-
-        AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus);
-        AnsiEscapeSequenceRequests.Remove (seqReqStatus);
-        Assert.Empty (AnsiEscapeSequenceRequests.Statuses);
-    }
-
-    [Fact]
-    public void Requested_Tests ()
-    {
-        AnsiEscapeSequenceRequests.Clear ();
-        Assert.False (AnsiEscapeSequenceRequests.HasResponse ("t", out AnsiEscapeSequenceRequestStatus seqReqStatus));
-        Assert.Null (seqReqStatus);
-
-        AnsiEscapeSequenceRequests.Add (new () { Request = "", Terminator = "t" });
-        Assert.False (AnsiEscapeSequenceRequests.HasResponse ("r", out seqReqStatus));
-        Assert.NotNull (seqReqStatus);
-        Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator);
-        Assert.True (AnsiEscapeSequenceRequests.HasResponse ("t", out seqReqStatus));
-        Assert.NotNull (seqReqStatus);
-        Assert.Equal ("t", seqReqStatus.AnsiRequest.Terminator);
-    }
-
-    [Fact]
-    public void Request_Initialization_AnsiEscapeSequenceResponse_Is_Null ()
-    {
-        AnsiEscapeSequenceRequest ansiRequest = new () { Request = "\u001b[0c", Terminator = "c"};
-        Assert.Null (ansiRequest.AnsiEscapeSequenceResponse);
-    }
-}

+ 72 - 0
UnitTests/Input/EscSeqRequestsTests.cs

@@ -0,0 +1,72 @@
+namespace Terminal.Gui.InputTests;
+
+public class EscSeqRequestsTests
+{
+    [Fact]
+    public void Add_Tests ()
+    {
+        EscSeqRequests.Clear ();
+        EscSeqRequests.Add ("t");
+        Assert.Single (EscSeqRequests.Statuses);
+        Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator);
+        Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests);
+        Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding);
+
+        EscSeqRequests.Add ("t", 2);
+        Assert.Single (EscSeqRequests.Statuses);
+        Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator);
+        Assert.Equal (1, EscSeqRequests.Statuses [^1].NumRequests);
+        Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding);
+
+        EscSeqRequests.Clear ();
+        EscSeqRequests.Add ("t", 2);
+        Assert.Single (EscSeqRequests.Statuses);
+        Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator);
+        Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests);
+        Assert.Equal (2, EscSeqRequests.Statuses [^1].NumOutstanding);
+
+        EscSeqRequests.Add ("t", 3);
+        Assert.Single (EscSeqRequests.Statuses);
+        Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator);
+        Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests);
+        Assert.Equal (2, EscSeqRequests.Statuses [^1].NumOutstanding);
+    }
+
+    [Fact]
+    public void Constructor_Defaults ()
+    {
+        EscSeqRequests.Clear ();
+        Assert.NotNull (EscSeqRequests.Statuses);
+        Assert.Empty (EscSeqRequests.Statuses);
+    }
+
+    [Fact]
+    public void Remove_Tests ()
+    {
+        EscSeqRequests.Clear ();
+        EscSeqRequests.Add ("t");
+        EscSeqRequests.Remove ("t");
+        Assert.Empty (EscSeqRequests.Statuses);
+
+        EscSeqRequests.Add ("t", 2);
+        EscSeqRequests.Remove ("t");
+        Assert.Single (EscSeqRequests.Statuses);
+        Assert.Equal ("t", EscSeqRequests.Statuses [^1].Terminator);
+        Assert.Equal (2, EscSeqRequests.Statuses [^1].NumRequests);
+        Assert.Equal (1, EscSeqRequests.Statuses [^1].NumOutstanding);
+
+        EscSeqRequests.Remove ("t");
+        Assert.Empty (EscSeqRequests.Statuses);
+    }
+
+    [Fact]
+    public void Requested_Tests ()
+    {
+        EscSeqRequests.Clear ();
+        Assert.False (EscSeqRequests.HasResponse ("t"));
+
+        EscSeqRequests.Add ("t");
+        Assert.False (EscSeqRequests.HasResponse ("r"));
+        Assert.True (EscSeqRequests.HasResponse ("t"));
+    }
+}

File diff suppressed because it is too large
+ 189 - 190
UnitTests/Input/EscSeqUtilsTests.cs


+ 0 - 2
UnitTests/View/Adornment/PaddingTests.cs

@@ -33,7 +33,5 @@ PPP",
                                              output
                                             );
         TestHelpers.AssertDriverAttributesAre ("0", output, null, view.GetNormalColor ());
-
-        ((FakeDriver)Application.Driver!).End ();
     }
 }

Some files were not shown because too many files changed in this diff