Browse Source

An huge improvements on drivers and bug fixes.

BDisp 8 months ago
parent
commit
a6af3aadb7

+ 43 - 59
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs

@@ -11,26 +11,28 @@ public class AnsiEscapeSequenceRequest
     internal readonly object _responseLock = new (); // Per-instance lock
 
     /// <summary>
-    ///     Gets the request string to send e.g. see
-    ///     <see>
-    ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
-    ///     </see>
+    ///     Gets the response received from the request.
     /// </summary>
-    public required string Request { get; init; }
+    public AnsiEscapeSequenceResponse? AnsiEscapeSequenceResponse { get; internal set; }
 
-    // QUESTION: Could the type of this propperty be AnsiEscapeSequenceResponse? This would remove the
-    // QUESTION: removal of the redundant Rresponse, Terminator, and ExpectedRespnseValue properties from this class?
-    // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable?
     /// <summary>
-    ///     Gets the response received from the request.
+    ///     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? Response { get; internal set; }
+    public string? ExpectedResponseValue { get; init; }
 
     /// <summary>
-    ///     Raised when the console responds with an ANSI response code that matches the
-    ///     <see cref="Terminator"/>
+    ///     Gets the request string to send e.g. see
+    ///     <see>
+    ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
+    ///     </see>
     /// </summary>
-    public event EventHandler<AnsiEscapeSequenceResponse>? ResponseReceived;
+    public required string Request { get; init; }
 
     /// <summary>
     ///     <para>
@@ -52,47 +54,49 @@ public class AnsiEscapeSequenceRequest
     /// </summary>
     public required string Terminator { get; init; }
 
+    internal void RaiseResponseFromInput (string? response)
+    {
+        ProcessResponse (response);
+
+        ResponseFromInput?.Invoke (this, AnsiEscapeSequenceResponse);
+    }
+
     /// <summary>
-    ///     Attempt an ANSI escape sequence request which may return a response or error.
+    ///     Raised with the response object and validation.
     /// </summary>
-    /// <param name="ansiRequest">The ANSI escape sequence to request.</param>
-    /// <param name="result">
-    ///     When this method returns <see langword="true"/>, the response. <see cref="AnsiEscapeSequenceResponse.Error"/> will
-    ///     be <see cref="string.Empty"/>.
-    /// </param>
-    /// <returns>A <see cref="AnsiEscapeSequenceResponse"/> with the response, error, terminator, and value.</returns>
-    public static bool TryRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result)
+    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)
     {
         var error = new StringBuilder ();
         var values = new string? [] { null };
 
         try
         {
-            ConsoleDriver? driver = Application.Driver;
-
-            // Send the ANSI escape sequence
-            ansiRequest.Response = driver?.WriteAnsiRequest (ansiRequest)!;
-
-            if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc))
+            if (!string.IsNullOrEmpty (response) && !response.StartsWith (AnsiEscapeSequenceRequestUtils.KeyEsc))
             {
-                throw new InvalidOperationException ($"Invalid Response: {ansiRequest.Response}");
+                throw new InvalidOperationException ($"Invalid Response: {response}");
             }
 
-            if (string.IsNullOrEmpty (ansiRequest.Terminator))
+            if (string.IsNullOrEmpty (Terminator))
             {
                 throw new InvalidOperationException ("Terminator request is empty.");
             }
 
-            if (string.IsNullOrEmpty (ansiRequest.Response))
+            if (string.IsNullOrEmpty (response))
             {
                 throw new InvalidOperationException ("Response request is null.");
             }
 
-            if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.EndsWith (ansiRequest.Terminator [^1]))
+            if (!string.IsNullOrEmpty (response) && !response.EndsWith (Terminator [^1]))
             {
-                string resp = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response.Last ().ToString ();
+                string resp = string.IsNullOrEmpty (response) ? "" : response.Last ().ToString ();
 
-                throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{ansiRequest.Terminator [^1]}'");
+                throw new InvalidOperationException ($"Terminator ends with '{resp}'\nand doesn't end with: '{Terminator [^1]}'");
             }
         }
         catch (Exception ex)
@@ -103,36 +107,16 @@ public class AnsiEscapeSequenceRequest
         {
             if (string.IsNullOrEmpty (error.ToString ()))
             {
-                (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (ansiRequest.Response.ToCharArray ());
+                (string? _, string? _, values, string? _) = AnsiEscapeSequenceRequestUtils.GetEscapeResult (response?.ToCharArray ());
             }
         }
 
-        AnsiEscapeSequenceResponse ansiResponse = new ()
+        AnsiEscapeSequenceResponse = new ()
         {
-            Response = ansiRequest.Response, Error = error.ToString (),
-            Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), ExpectedResponseValue = values [0]
+            Response = response, Error = error.ToString (),
+            Terminator = string.IsNullOrEmpty (response) ? "" : response [^1].ToString (),
+            ExpectedResponseValue = values [0],
+            Valid = string.IsNullOrWhiteSpace (error.ToString ()) && !string.IsNullOrWhiteSpace (response)
         };
-
-        // Invoke the event if it's subscribed
-        ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse);
-
-        result = ansiResponse;
-
-        return string.IsNullOrWhiteSpace (result.Error) && !string.IsNullOrWhiteSpace (result.Response);
     }
-
-    /// <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; }
-
-    internal void RaiseResponseFromInput (AnsiEscapeSequenceRequest ansiRequest, string? response) { ResponseFromInput?.Invoke (ansiRequest, response); }
-
-    // QUESTION: What is this for? Please provide a descriptive comment.
-    internal event EventHandler<string?>? ResponseFromInput;
 }

+ 2 - 2
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequestUtils.cs

@@ -183,7 +183,7 @@ public static class AnsiEscapeSequenceRequestUtils
     /// <summary>
     ///     Decodes an ANSI escape sequence.
     /// </summary>
-    /// <param name="escSeqRequests">The <see cref="EscSeqRequests"/> which may contain a request.</param>
+    /// <param name="escSeqRequests">The <see cref="AnsiEscapeSequenceRequests"/> which may contain a request.</param>
     /// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may change.</param>
     /// <param name="key">The <see cref="ConsoleKey"/> which may change.</param>
     /// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
@@ -195,7 +195,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="EscSeqReqStatus"/> object.</param>
+    /// <param name="seqReqStatus">The <see cref="AnsiEscapeSequenceRequestStatus"/> object.</param>
     /// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
     public static void DecodeEscSeq (
         AnsiEscapeSequenceRequests? escSeqRequests,

+ 16 - 12
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs

@@ -2,11 +2,11 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     Describes a response received from the console as a result of a request being sent via <see cref="AnsiEscapeSequenceRequest"/>.
+///     Describes a response received from the console as a result of a request being sent via
+///     <see cref="AnsiEscapeSequenceRequest"/>.
 /// </summary>
 public class AnsiEscapeSequenceResponse
 {
-    // QUESTION: Should this be nullable to indicate there was no error, or is string.Empty sufficient?
     /// <summary>
     ///     Gets the error string received from e.g. see
     ///     <see>
@@ -16,7 +16,18 @@ public class AnsiEscapeSequenceResponse
     /// </summary>
     public required string Error { get; init; }
 
-    // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable?
+    /// <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>
@@ -26,7 +37,6 @@ public class AnsiEscapeSequenceResponse
     /// </summary>
     public required string? Response { get; init; }
 
-    // QUESTION: Does string.Empty indicate no terminator expected? If not, perhaps make this property nullable?
     /// <summary>
     ///     <para>
     ///         Gets the terminator that uniquely identifies the response received from
@@ -48,13 +58,7 @@ public class AnsiEscapeSequenceResponse
     public required string Terminator { 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>.
+    ///     Gets if the request has a valid response.
     /// </summary>
-
-    public string? ExpectedResponseValue { get; init; }
+    public bool Valid { get; internal set; }
 }

+ 2 - 2
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -45,7 +45,7 @@ public abstract class ConsoleDriver
     /// </summary>
     /// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
     /// <returns>The request response.</returns>
-    public abstract string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest);
+    public abstract bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest);
 
     // 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?
@@ -53,7 +53,7 @@ public abstract class ConsoleDriver
     ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
     /// </summary>
     /// <param name="ansi"></param>
-    public abstract void WriteRaw (string ansi);
+    internal abstract void WriteRaw (string ansi);
 
     #endregion ANSI Esc Sequence Handling
 

+ 42 - 118
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -1,4 +1,4 @@
-// TODO: #nullable enable
+#nullable enable
 //
 // Driver.cs: Curses-based Driver
 //
@@ -10,7 +10,7 @@ using Unix.Terminal;
 
 namespace Terminal.Gui;
 
-/// <summary>A Linux/Mac driver based on the Curses libary.</summary>
+/// <summary>A Linux/Mac driver based on the Curses library.</summary>
 internal class CursesDriver : ConsoleDriver
 {
     public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; }
@@ -94,7 +94,7 @@ internal class CursesDriver : ConsoleDriver
         {
             for (var row = 0; row < Rows; row++)
             {
-                if (!_dirtyLines [row])
+                if (!_dirtyLines! [row])
                 {
                     continue;
                 }
@@ -103,7 +103,7 @@ internal class CursesDriver : ConsoleDriver
 
                 for (var col = 0; col < Cols; col++)
                 {
-                    if (Contents [row, col].IsDirty == false)
+                    if (Contents! [row, col].IsDirty == false)
                     {
                         continue;
                     }
@@ -147,14 +147,14 @@ internal class CursesDriver : ConsoleDriver
             if (!RunningUnitTests)
             {
                 Curses.move (Row, Col);
-                _window.wrefresh ();
+                _window?.wrefresh ();
             }
         }
         else
         {
             if (RunningUnitTests
                 || Console.WindowHeight < 1
-                || Contents.Length != Rows * Cols
+                || Contents!.Length != Rows * Cols
                 || Rows != Console.WindowHeight)
             {
                 return;
@@ -178,7 +178,7 @@ internal class CursesDriver : ConsoleDriver
                     return;
                 }
 
-                if (!_dirtyLines [row])
+                if (!_dirtyLines! [row])
                 {
                     continue;
                 }
@@ -222,7 +222,7 @@ internal class CursesDriver : ConsoleDriver
                             lastCol = col;
                         }
 
-                        Attribute attr = Contents [row, col].Attribute.Value;
+                        Attribute attr = Contents [row, col].Attribute!.Value;
 
                         // Performance: Only send the escape sequence if the attribute has changed.
                         if (attr != redrawAttr)
@@ -515,7 +515,7 @@ internal class CursesDriver : ConsoleDriver
             }
             else
             {
-                _mainLoopDriver.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1));
+                _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1));
             }
         }
     }
@@ -530,22 +530,22 @@ internal class CursesDriver : ConsoleDriver
 
         if (consoleKey == ConsoleKey.Packet)
         {
-            var mod = new ConsoleModifiers ();
+            //var mod = new ConsoleModifiers ();
 
-            if (shift)
-            {
-                mod |= ConsoleModifiers.Shift;
-            }
+            //if (shift)
+            //{
+            //    mod |= ConsoleModifiers.Shift;
+            //}
 
-            if (alt)
-            {
-                mod |= ConsoleModifiers.Alt;
-            }
+            //if (alt)
+            //{
+            //    mod |= ConsoleModifiers.Alt;
+            //}
 
-            if (control)
-            {
-                mod |= ConsoleModifiers.Control;
-            }
+            //if (control)
+            //{
+            //    mod |= ConsoleModifiers.Control;
+            //}
 
             var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
             cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
@@ -562,84 +562,6 @@ internal class CursesDriver : ConsoleDriver
         //OnKeyPressed (new KeyEventArgsEventArgs (key));
     }
 
-    // TODO: Unused- Remove
-    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;
-        }
-    }
-
     #endregion Keyboard Support
 
     #region Mouse Support
@@ -672,8 +594,8 @@ internal class CursesDriver : ConsoleDriver
 
     #region Init/End/MainLoop
 
-    public Curses.Window _window;
-    private UnixMainLoop _mainLoopDriver;
+    public Curses.Window? _window;
+    private UnixMainLoop? _mainLoopDriver;
 
     internal override MainLoop Init ()
     {
@@ -801,7 +723,7 @@ internal class CursesDriver : ConsoleDriver
                 break;
             case UnixMainLoop.EventType.WindowSize:
                 Size size = new (inputEvent.WindowSizeEvent.Size.Width, inputEvent.WindowSizeEvent.Size.Height);
-                ProcessWinChange (inputEvent.WindowSizeEvent.Size);
+                ProcessWinChange (size);
 
                 break;
             default:
@@ -821,7 +743,7 @@ internal class CursesDriver : ConsoleDriver
     {
         _ansiResponseTokenSource?.Cancel ();
         _ansiResponseTokenSource?.Dispose ();
-        _waitAnsiResponse?.Dispose ();
+        _waitAnsiResponse.Dispose ();
 
         StopReportingMouseMoves ();
         SetCursorVisibility (CursorVisibility.Default);
@@ -860,27 +782,29 @@ internal class CursesDriver : ConsoleDriver
 
 
     private readonly ManualResetEventSlim _waitAnsiResponse = new (false);
-    private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
+    private CancellationTokenSource? _ansiResponseTokenSource;
 
     /// <inheritdoc/>
-    public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
+    public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
     {
         if (_mainLoopDriver is null)
         {
-            return string.Empty;
+            return false;
         }
 
+        _ansiResponseTokenSource ??= new ();
+
         try
         {
             lock (ansiRequest._responseLock)
             {
                 ansiRequest.ResponseFromInput += (s, e) =>
-                {
-                    Debug.Assert (s == ansiRequest);
-                    Debug.Assert (e == ansiRequest.Response);
+                                                 {
+                                                     Debug.Assert (s == ansiRequest);
+                                                     Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse);
 
-                    _waitAnsiResponse.Set ();
-                };
+                                                     _waitAnsiResponse.Set ();
+                                                 };
 
                 _mainLoopDriver.EscSeqRequests.Add (ansiRequest, this);
 
@@ -896,19 +820,19 @@ internal class CursesDriver : ConsoleDriver
         }
         catch (OperationCanceledException)
         {
-            return string.Empty;
+            return false;
         }
 
         lock (ansiRequest._responseLock)
         {
             _mainLoopDriver._forceRead = false;
 
-            if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus request))
+            if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request))
             {
                 if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0
-                    && string.IsNullOrEmpty (request.AnsiRequest.Response))
+                    && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
                 {
-                    lock (request!.AnsiRequest._responseLock)
+                    lock (request.AnsiRequest._responseLock)
                     {
                         // Bad request or no response at all
                         _mainLoopDriver.EscSeqRequests.Statuses.TryDequeue (out _);
@@ -918,12 +842,12 @@ internal class CursesDriver : ConsoleDriver
 
             _waitAnsiResponse.Reset ();
 
-            return ansiRequest.Response;
+            return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true };
         }
     }
 
     /// <inheritdoc/>
-    public override void WriteRaw (string ansi) { _mainLoopDriver.WriteRaw (ansi); }
+    internal override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); }
 
 }
 

+ 4 - 6
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -242,13 +242,13 @@ internal class UnixMainLoop : IMainLoopDriver
                     {
                         if (_retries > 1)
                         {
-                            if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
+                            if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus seqReqStatus) && seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse is { } && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse.Response))
                             {
                                 lock (seqReqStatus!.AnsiRequest._responseLock)
                                 {
                                     EscSeqRequests.Statuses.TryDequeue (out _);
 
-                                    seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null);
+                                    seqReqStatus.AnsiRequest.RaiseResponseFromInput (null);
                                 }
                             }
 
@@ -331,8 +331,7 @@ internal class UnixMainLoop : IMainLoopDriver
 
             lock (seqReqStatus.AnsiRequest._responseLock)
             {
-                seqReqStatus.AnsiRequest.Response = ckiString;
-                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString);
+                seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString);
             }
 
             return;
@@ -344,8 +343,7 @@ internal class UnixMainLoop : IMainLoopDriver
             {
                 lock (result.AnsiRequest._responseLock)
                 {
-                    result.AnsiRequest.Response = AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator;
-                    result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator);
+                    result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator);
 
                     AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null;
                 }

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

@@ -393,10 +393,10 @@ public class FakeDriver : ConsoleDriver
     }
 
     /// <inheritdoc />
-    public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); }
+    public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); }
 
     /// <inheritdoc />
-    public override void WriteRaw (string ansi) { throw new NotImplementedException (); }
+    internal override void WriteRaw (string ansi) { throw new NotImplementedException (); }
 
     public void SetBufferSize (int width, int height)
     {

+ 34 - 37
Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

@@ -4,9 +4,7 @@
 //
 
 using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 using System.Runtime.InteropServices;
-using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.NetEvents;
 
 namespace Terminal.Gui;
@@ -367,7 +365,7 @@ internal class NetDriver : ConsoleDriver
         _ansiResponseTokenSource?.Cancel ();
         _ansiResponseTokenSource?.Dispose ();
 
-        _waitAnsiResponse?.Dispose ();
+        _waitAnsiResponse.Dispose ();
 
         if (!RunningUnitTests)
         {
@@ -406,19 +404,19 @@ internal class NetDriver : ConsoleDriver
     private const int COLOR_WHITE = 37;
     private const int COLOR_YELLOW = 33;
 
-    // Cache the list of ConsoleColor values.
-    [UnconditionalSuppressMessage (
-                                      "AOT",
-                                      "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
-                                      Justification = "<Pending>")]
-    private static readonly HashSet<int> ConsoleColorValues = new (
-                                                                   Enum.GetValues (typeof (ConsoleColor))
-                                                                       .OfType<ConsoleColor> ()
-                                                                       .Select (c => (int)c)
-                                                                  );
+    //// Cache the list of ConsoleColor values.
+    //[UnconditionalSuppressMessage (
+    //                                  "AOT",
+    //                                  "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
+    //                                  Justification = "<Pending>")]
+    //private static readonly HashSet<int> ConsoleColorValues = new (
+    //                                                               Enum.GetValues (typeof (ConsoleColor))
+    //                                                                   .OfType<ConsoleColor> ()
+    //                                                                   .Select (c => (int)c)
+    //                                                              );
 
     // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
-    private static readonly Dictionary<ConsoleColor, int> colorMap = new ()
+    private static readonly Dictionary<ConsoleColor, int> _colorMap = new ()
     {
         { ConsoleColor.Black, COLOR_BLACK },
         { ConsoleColor.DarkBlue, COLOR_BLUE },
@@ -441,7 +439,7 @@ internal class NetDriver : ConsoleDriver
     // Map a ConsoleColor to a platform dependent value.
     private int MapColors (ConsoleColor color, bool isForeground = true)
     {
-        return colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
+        return _colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
     }
 
     #endregion
@@ -705,22 +703,22 @@ internal class NetDriver : ConsoleDriver
         { }
     }
 
-    private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-    {
-        if (consoleKeyInfo.Key != ConsoleKey.Packet)
-        {
-            return consoleKeyInfo;
-        }
+    //private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+    //{
+    //    if (consoleKeyInfo.Key != ConsoleKey.Packet)
+    //    {
+    //        return consoleKeyInfo;
+    //    }
 
-        ConsoleModifiers mod = consoleKeyInfo.Modifiers;
-        bool shift = (mod & ConsoleModifiers.Shift) != 0;
-        bool alt = (mod & ConsoleModifiers.Alt) != 0;
-        bool control = (mod & ConsoleModifiers.Control) != 0;
+    //    ConsoleModifiers mod = consoleKeyInfo.Modifiers;
+    //    bool shift = (mod & ConsoleModifiers.Shift) != 0;
+    //    bool alt = (mod & ConsoleModifiers.Alt) != 0;
+    //    bool control = (mod & ConsoleModifiers.Control) != 0;
 
-        ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+    //    ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 
-        return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
-    }
+    //    return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
+    //}
 
     #endregion Keyboard Handling
 
@@ -730,13 +728,13 @@ internal class NetDriver : ConsoleDriver
     private CancellationTokenSource? _ansiResponseTokenSource;
 
     /// <inheritdoc/>
-    public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
+    public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
     {
         lock (ansiRequest._responseLock)
         {
             if (_mainLoopDriver is null)
             {
-                return string.Empty;
+                return false;
             }
         }
 
@@ -749,21 +747,20 @@ internal class NetDriver : ConsoleDriver
                 ansiRequest.ResponseFromInput += (s, e) =>
                                                  {
                                                      Debug.Assert (s == ansiRequest);
-                                                     Debug.Assert (e == ansiRequest.Response);
+                                                     Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse);
 
                                                      _waitAnsiResponse.Set ();
                                                  };
 
-                _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest);
+                _mainLoopDriver._netEvents!.EscSeqRequests.Add (ansiRequest);
 
                 _mainLoopDriver._netEvents._forceRead = true;
             }
-
             _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
         }
         catch (OperationCanceledException)
         {
-            return string.Empty;
+            return false;
         }
 
         lock (ansiRequest._responseLock)
@@ -773,7 +770,7 @@ internal class NetDriver : ConsoleDriver
             if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request))
             {
                 if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0
-                    && string.IsNullOrEmpty (request.AnsiRequest.Response))
+                    && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
                 {
                     lock (request.AnsiRequest._responseLock)
                     {
@@ -785,12 +782,12 @@ internal class NetDriver : ConsoleDriver
 
             _waitAnsiResponse.Reset ();
 
-            return ansiRequest.Response;
+            return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true };
         }
     }
 
     /// <inheritdoc/>
-    public override void WriteRaw (string ansi) { throw new NotImplementedException (); }
+    internal override void WriteRaw (string ansi) { throw new NotImplementedException (); }
 
     private volatile bool _winSizeChanging;
 

+ 7 - 7
Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs

@@ -62,13 +62,13 @@ internal class NetEvents : IDisposable
             {
                 if (_retries > 1)
                 {
-                    if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
+                    if (EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
                     {
                         lock (seqReqStatus.AnsiRequest._responseLock)
                         {
                             EscSeqRequests.Statuses.TryDequeue (out _);
 
-                            seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null);
+                            seqReqStatus.AnsiRequest.RaiseResponseFromInput (null);
                         }
                     }
 
@@ -150,7 +150,9 @@ internal class NetEvents : IDisposable
                             if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
                                 || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127)
                                 || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))
-                                || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)))
+                                || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar))
+                                || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar))
+                                || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar)))
                             {
                                 ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
                                 _cki = null;
@@ -341,8 +343,7 @@ internal class NetEvents : IDisposable
 
             lock (seqReqStatus.AnsiRequest._responseLock)
             {
-                seqReqStatus.AnsiRequest.Response = ckiString;
-                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString);
+                seqReqStatus.AnsiRequest.RaiseResponseFromInput (ckiString);
             }
 
             return;
@@ -354,8 +355,7 @@ internal class NetEvents : IDisposable
             {
                 lock (result.AnsiRequest._responseLock)
                 {
-                    result.AnsiRequest.Response = AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator;
-                    result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator);
+                    result.AnsiRequest.RaiseResponseFromInput (AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator);
 
                     AnsiEscapeSequenceRequestUtils.InvalidRequestTerminator = null;
                 }

+ 205 - 127
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs

@@ -1,4 +1,5 @@
 #nullable enable
+using System.Collections.Concurrent;
 using System.ComponentModel;
 using System.Runtime.InteropServices;
 using Terminal.Gui.ConsoleDrivers;
@@ -7,6 +8,8 @@ namespace Terminal.Gui;
 
 internal class WindowsConsole
 {
+    private CancellationTokenSource? _inputReadyCancellationTokenSource;
+    private readonly BlockingCollection<InputRecord> _inputQueue = new (new ConcurrentQueue<InputRecord> ());
     internal WindowsMainLoop? _mainLoop;
 
     public const int STD_OUTPUT_HANDLE = -11;
@@ -32,8 +35,206 @@ internal class WindowsConsole
         newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
         newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
         ConsoleMode = newConsoleMode;
+
+        _inputReadyCancellationTokenSource = new ();
+        Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
+    }
+
+    public InputRecord? DequeueInput ()
+    {
+        while (_inputReadyCancellationTokenSource is { })
+        {
+            try
+            {
+                return _inputQueue.Take (_inputReadyCancellationTokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            {
+                return null;
+            }
+        }
+
+        return null;
     }
 
+    public InputRecord? ReadConsoleInput ()
+    {
+        const int BUFFER_SIZE = 1;
+        InputRecord inputRecord = default;
+        uint numberEventsRead = 0;
+        StringBuilder ansiSequence = new StringBuilder ();
+        bool readingSequence = false;
+        bool raisedResponse = false;
+
+        while (!_inputReadyCancellationTokenSource!.IsCancellationRequested)
+        {
+            try
+            {
+                // Peek to check if there is any input available
+                if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0)
+                {
+                    // Read the input since it is available
+                    ReadConsoleInput (
+                                      _inputHandle,
+                                      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
+                                if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0)
+                                {
+                                    if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true })
+                                    {
+                                        // It's really an ANSI request response
+                                        readingSequence = true;
+                                        ansiSequence.Clear (); // Start a new sequence
+                                        ansiSequence.Append (inputChar);
+
+                                        continue;
+                                    }
+                                }
+                            }
+                            else if (readingSequence)
+                            {
+                                ansiSequence.Append (inputChar);
+
+                                // Check if the sequence has ended with an expected command terminator
+                                if (_mainLoop?.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus))
+                                {
+                                    // Finished reading the sequence and remove the enqueued request
+                                    _mainLoop.EscSeqRequests.Remove (seqReqStatus);
+
+                                    lock (seqReqStatus!.AnsiRequest._responseLock)
+                                    {
+                                        raisedResponse = true;
+                                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ());
+                                        // Clear the terminator for not be enqueued
+                                        inputRecord = default (InputRecord);
+                                    }
+                                }
+
+                                continue;
+                            }
+                        }
+                    }
+                }
+
+                if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 })
+                {
+                    _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus);
+
+                    lock (seqReqStatus!.AnsiRequest._responseLock)
+                    {
+                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (ansiSequence.ToString ());
+                        // Clear the terminator for not be enqueued
+                        inputRecord = default (InputRecord);
+                    }
+
+                    _retries = 0;
+                }
+                else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 })
+                {
+                    if (_retries > 1)
+                    {
+                        if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
+                        {
+                            lock (seqReqStatus.AnsiRequest._responseLock)
+                            {
+                                _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _);
+
+                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (null);
+                                // Clear the terminator for not be enqueued
+                                inputRecord = default (InputRecord);
+                            }
+                        }
+
+                        _retries = 0;
+                    }
+                    else
+                    {
+                        _retries++;
+                    }
+                }
+                else
+                {
+                    _retries = 0;
+                }
+
+                if (numberEventsRead > 0)
+                {
+                    return inputRecord;
+                }
+
+                if (!_forceRead)
+                {
+                    try
+                    {
+                        Task.Delay (100, _inputReadyCancellationTokenSource.Token).Wait (_inputReadyCancellationTokenSource.Token);
+                    }
+                    catch (OperationCanceledException)
+                    {
+                        return null;
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+    internal bool _forceRead;
+
+    private void ProcessInputQueue ()
+    {
+        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
+        {
+            try
+            {
+                if (_inputQueue.Count == 0 || _forceRead)
+                {
+                    while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
+                    {
+                        try
+                        {
+                            InputRecord? inpRec = ReadConsoleInput ();
+
+                            if (inpRec is { })
+                            {
+                                _inputQueue.Add (inpRec.Value);
+
+                                break;
+                            }
+                        }
+                        catch (OperationCanceledException)
+                        {
+                            return;
+                        }
+                    }
+                }
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+        }
+    }
+
+
     private CharInfo []? _originalStdOutChars;
 
     public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
@@ -310,6 +511,10 @@ internal class WindowsConsole
         //}
 
         //_screenBuffer = nint.Zero;
+
+        _inputReadyCancellationTokenSource?.Cancel ();
+        _inputReadyCancellationTokenSource?.Dispose ();
+        _inputReadyCancellationTokenSource = null;
     }
 
     //internal Size GetConsoleBufferWindow (out Point position)
@@ -896,133 +1101,6 @@ internal class WindowsConsole
 
     private int _retries;
 
-    public InputRecord []? ReadConsoleInput ()
-    {
-        const int BUFFER_SIZE = 1;
-        InputRecord inputRecord = default;
-        uint numberEventsRead = 0;
-        StringBuilder ansiSequence = new StringBuilder ();
-        bool readingSequence = false;
-        bool raisedResponse = false;
-
-        while (true)
-        {
-            try
-            {
-                // Peek to check if there is any input available
-                if (PeekConsoleInput (_inputHandle, out _, BUFFER_SIZE, out uint eventsRead) && eventsRead > 0)
-                {
-                    // Read the input since it is available
-                    ReadConsoleInput (
-                                      _inputHandle,
-                                      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
-                                if (PeekConsoleInput (_inputHandle, out InputRecord peekRecord, BUFFER_SIZE, out eventsRead) && eventsRead > 0)
-                                {
-                                    if (peekRecord is { EventType: EventType.Key, KeyEvent.bKeyDown: true })
-                                    {
-                                        // It's really an ANSI request response
-                                        readingSequence = true;
-                                        ansiSequence.Clear (); // Start a new sequence
-                                        ansiSequence.Append (inputChar);
-
-                                        continue;
-                                    }
-                                }
-                            }
-                            else if (readingSequence)
-                            {
-                                ansiSequence.Append (inputChar);
-
-                                // Check if the sequence has ended with an expected command terminator
-                                if (_mainLoop?.EscSeqRequests is { } && _mainLoop.EscSeqRequests.HasResponse (inputChar.ToString (), out AnsiEscapeSequenceRequestStatus? seqReqStatus))
-                                {
-                                    // Finished reading the sequence and remove the enqueued request
-                                    _mainLoop.EscSeqRequests.Remove (seqReqStatus);
-
-                                    lock (seqReqStatus!.AnsiRequest._responseLock)
-                                    {
-                                        raisedResponse = true;
-                                        seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
-                                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
-                                        // Clear the terminator for not be enqueued
-                                        inputRecord = default (InputRecord);
-                                    }
-                                }
-
-                                continue;
-                            }
-                        }
-                    }
-                }
-
-                if (readingSequence && !raisedResponse && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 })
-                {
-                    _mainLoop.EscSeqRequests.Statuses.TryDequeue (out AnsiEscapeSequenceRequestStatus? seqReqStatus);
-
-                    lock (seqReqStatus!.AnsiRequest._responseLock)
-                    {
-                        seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
-                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
-                        // Clear the terminator for not be enqueued
-                        inputRecord = default (InputRecord);
-                    }
-
-                    _retries = 0;
-                }
-                else if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && _mainLoop?.EscSeqRequests is { Statuses.Count: > 0 })
-                {
-                    if (_retries > 1)
-                    {
-                        if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
-                        {
-                            lock (seqReqStatus.AnsiRequest._responseLock)
-                            {
-                                _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _);
-
-                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, null);
-                                // Clear the terminator for not be enqueued
-                                inputRecord = default (InputRecord);
-                            }
-                        }
-
-                        _retries = 0;
-                    }
-                    else
-                    {
-                        _retries++;
-                    }
-                }
-                else
-                {
-                    _retries = 0;
-                }
-
-                return (numberEventsRead == 0
-                            ? null
-                            : [inputRecord])!;
-            }
-            catch (Exception)
-            {
-                return null;
-            }
-        }
-    }
-
 #if false // Not needed on the constructor. Perhaps could be used on resizing. To study.
 		[DllImport ("kernel32.dll", ExactSpelling = true)]
 		static extern IntPtr GetConsoleWindow ();

+ 11 - 14
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs

@@ -198,16 +198,18 @@ internal class WindowsDriver : ConsoleDriver
     }
 
     private readonly ManualResetEventSlim _waitAnsiResponse = new (false);
-    private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
+    private CancellationTokenSource? _ansiResponseTokenSource;
 
     /// <inheritdoc/>
-    public override string? WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
+    public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
     {
         if (_mainLoopDriver is null)
         {
-            return string.Empty;
+            return false;
         }
 
+        _ansiResponseTokenSource ??= new ();
+
         try
         {
             lock (ansiRequest._responseLock)
@@ -215,7 +217,7 @@ internal class WindowsDriver : ConsoleDriver
                 ansiRequest.ResponseFromInput += (s, e) =>
                                                  {
                                                      Debug.Assert (s == ansiRequest);
-                                                     Debug.Assert (e == ansiRequest.Response);
+                                                     Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse);
 
                                                      _waitAnsiResponse.Set ();
                                                  };
@@ -225,16 +227,11 @@ internal class WindowsDriver : ConsoleDriver
                 _mainLoopDriver._forceRead = true;
             }
 
-            if (!_ansiResponseTokenSource.IsCancellationRequested)
-            {
-                _mainLoopDriver._waitForProbe.Set ();
-
-                _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
-            }
+            _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
         }
         catch (OperationCanceledException)
         {
-            return string.Empty;
+            return false;
         }
 
         lock (ansiRequest._responseLock)
@@ -244,7 +241,7 @@ internal class WindowsDriver : ConsoleDriver
             if (_mainLoopDriver.EscSeqRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request))
             {
                 if (_mainLoopDriver.EscSeqRequests.Statuses.Count > 0
-                    && string.IsNullOrEmpty (request.AnsiRequest.Response))
+                    && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response))
                 {
                     lock (request.AnsiRequest._responseLock)
                     {
@@ -256,11 +253,11 @@ internal class WindowsDriver : ConsoleDriver
 
             _waitAnsiResponse.Reset ();
 
-            return ansiRequest.Response;
+            return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true };
         }
     }
 
-    public override void WriteRaw (string ansi)
+    internal override void WriteRaw (string ansi)
     {
         WinConsole?.WriteANSI (ansi);
     }

+ 20 - 50
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs

@@ -21,8 +21,7 @@ internal class WindowsMainLoop : IMainLoopDriver
     private readonly ManualResetEventSlim _eventReady = new (false);
 
     // The records that we keep fetching
-    private readonly ConcurrentQueue<WindowsConsole.InputRecord []> _resultQueue = new ();
-    internal readonly ManualResetEventSlim _waitForProbe = new (false);
+    private readonly BlockingCollection<WindowsConsole.InputRecord> _resultQueue = new (new ConcurrentQueue<WindowsConsole.InputRecord> ());
     private readonly WindowsConsole? _winConsole;
     private CancellationTokenSource _eventReadyTokenSource = new ();
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
@@ -60,11 +59,10 @@ internal class WindowsMainLoop : IMainLoopDriver
 
     bool IMainLoopDriver.EventsPending ()
     {
-        _waitForProbe.Set ();
 #if HACK_CHECK_WINCHANGED
         _winChange.Set ();
 #endif
-        if (_mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
+        if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
         {
             return true;
         }
@@ -99,6 +97,7 @@ internal class WindowsMainLoop : IMainLoopDriver
         _eventReadyTokenSource.Dispose ();
         _eventReadyTokenSource = new CancellationTokenSource ();
 
+        // If cancellation was requested then always return true
         return true;
     }
 
@@ -106,12 +105,9 @@ internal class WindowsMainLoop : IMainLoopDriver
     {
         while (_resultQueue.Count > 0)
         {
-            if (_resultQueue.TryDequeue (out WindowsConsole.InputRecord []? inputRecords))
+            if (_resultQueue.TryTake (out WindowsConsole.InputRecord dequeueResult))
             {
-                if (inputRecords is { Length: > 0 })
-                {
-                    ((WindowsDriver)_consoleDriver).ProcessInput (inputRecords [0]);
-                }
+                ((WindowsDriver)_consoleDriver).ProcessInput (dequeueResult);
             }
         }
 #if HACK_CHECK_WINCHANGED
@@ -139,9 +135,7 @@ internal class WindowsMainLoop : IMainLoopDriver
             }
         }
 
-        _waitForProbe.Dispose ();
-
-        _resultQueue.Clear ();
+        _resultQueue.Dispose ();
 
         _eventReadyTokenSource.Cancel ();
         _eventReadyTokenSource.Dispose ();
@@ -162,54 +156,30 @@ internal class WindowsMainLoop : IMainLoopDriver
         {
             try
             {
-                if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead)
+                if (_inputHandlerTokenSource.IsCancellationRequested)
                 {
-                    _waitForProbe.Wait (_inputHandlerTokenSource.Token);
+                    return;
                 }
-            }
-            catch (OperationCanceledException)
-            {
-                // Wakes the _waitForProbe if it's waiting
-                _waitForProbe.Set ();
 
-                return;
-            }
-            finally
-            {
-                // If IsCancellationRequested is true the code after
-                // the `finally` block will not be executed.
-                if (!_inputHandlerTokenSource.IsCancellationRequested)
+                if (_resultQueue?.Count == 0 || _forceRead)
                 {
-                    _waitForProbe.Reset ();
-                }
-            }
+                    WindowsConsole.InputRecord? result = _winConsole!.DequeueInput ();
 
-            if (_resultQueue?.Count == 0 || _forceRead)
-            {
-                while (!_inputHandlerTokenSource.IsCancellationRequested)
-                {
-                    WindowsConsole.InputRecord [] inpRec = _winConsole.ReadConsoleInput ();
-
-                    if (inpRec is { })
+                    if (result.HasValue)
                     {
-                        _resultQueue!.Enqueue (inpRec);
-
-                        break;
+                        _resultQueue!.Add (result.Value);
                     }
+                }
 
-                    if (!_forceRead)
-                    {
-                        try
-                        {
-                            Task.Delay (100, _inputHandlerTokenSource.Token).Wait (_inputHandlerTokenSource.Token);
-                        }
-                        catch (OperationCanceledException)
-                        { }
-                    }
+                if (!_inputHandlerTokenSource.IsCancellationRequested && _resultQueue?.Count > 0)
+                {
+                    _eventReady.Set ();
                 }
             }
-
-            _eventReady.Set ();
+            catch (OperationCanceledException)
+            {
+                return;
+            }
         }
     }
 

+ 12 - 10
UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs

@@ -280,15 +280,14 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
                                          ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
                                      };
 
-                                     bool success = AnsiEscapeSequenceRequest.TryRequest (
-                                                                                          ansiEscapeSequenceRequest,
-                                                                                          out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse
-                                                                                         );
+                                     bool success = Application.Driver!.TryWriteAnsiRequest (
+                                                                                             ansiEscapeSequenceRequest
+                                                                                            );
 
-                                     tvResponse.Text = ansiEscapeSequenceResponse.Response ?? "";
-                                     tvError.Text = ansiEscapeSequenceResponse.Error;
-                                     tvValue.Text = ansiEscapeSequenceResponse.ExpectedResponseValue ?? "";
-                                     tvTerminator.Text = ansiEscapeSequenceResponse.Terminator;
+                                     tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? "";
+                                     tvError.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Error ?? "";
+                                     tvValue.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.ExpectedResponseValue ?? "";
+                                     tvTerminator.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Terminator ?? "";
 
                                      if (success)
                                      {
@@ -334,8 +333,11 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
     private void SendDar ()
     {
         _sends.Add (DateTime.Now);
-        string result = Application.Driver.WriteAnsiRequest (AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes);
-        HandleResponse (result);
+        AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes;
+        if (Application.Driver!.TryWriteAnsiRequest (ansiRequest))
+        {
+            HandleResponse (ansiRequest.AnsiEscapeSequenceResponse.Response);
+        }
     }
 
     private void SetupGraph ()