Răsfoiți Sursa

Code Review

Tig 8 luni în urmă
părinte
comite
2919d55817

+ 21 - 17
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs

@@ -4,35 +4,38 @@ namespace Terminal.Gui;
 /// <summary>
 ///     Describes an ongoing ANSI request sent to the console.
 ///     Use <see cref="ResponseReceived"/> to handle the response
-///     when console answers the request.
+///     when the console answers the request.
 /// </summary>
 public class AnsiEscapeSequenceRequest
 {
     internal readonly object _responseLock = new (); // Per-instance lock
 
     /// <summary>
-    ///     Request to send e.g. see
+    ///     Gets the request string to send e.g. see
     ///     <see>
     ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
     ///     </see>
     /// </summary>
     public required string Request { get; init; }
 
+    // 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>
-    ///     Response received from the request.
+    ///     Gets the response received from the request.
     /// </summary>
     public string Response { get; internal set; } = string.Empty;
 
     /// <summary>
-    ///     Invoked when the console responds with an ANSI response code that matches the
+    ///     Raised when the console responds with an ANSI response code that matches the
     ///     <see cref="Terminator"/>
     /// </summary>
     public event EventHandler<AnsiEscapeSequenceResponse>? ResponseReceived;
 
     /// <summary>
     ///     <para>
-    ///         The terminator that uniquely identifies the type of response as responded
-    ///         by the console. e.g. for
+    ///         Gets the terminator that uniquely identifies the response received from
+    ///         the console. e.g. for
     ///         <see>
     ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
     ///         </see>
@@ -50,15 +53,15 @@ public class AnsiEscapeSequenceRequest
     public required string Terminator { get; init; }
 
     /// <summary>
-    ///     Execute an ANSI escape sequence escape which may return a response or error.
+    ///     Attempt an ANSI escape sequence request which may return a response or error.
     /// </summary>
     /// <param name="ansiRequest">The ANSI escape sequence to request.</param>
     /// <param name="result">
-    ///     When this method returns <see langword="true"/>, an object containing the response with an empty
-    ///     error.
+    ///     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 TryExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result)
+    /// <returns>A <see cref="AnsiEscapeSequenceResponse"/> with the response, error, terminator, and value.</returns>
+    public static bool TryRequest (AnsiEscapeSequenceRequest ansiRequest, out AnsiEscapeSequenceResponse result)
     {
         var error = new StringBuilder ();
         var values = new string? [] { null };
@@ -72,7 +75,7 @@ public class AnsiEscapeSequenceRequest
 
             if (!string.IsNullOrEmpty (ansiRequest.Response) && !ansiRequest.Response.StartsWith (EscSeqUtils.KeyEsc))
             {
-                throw new InvalidOperationException ("Invalid escape character!");
+                throw new InvalidOperationException ($"Invalid Response: {ansiRequest.Response}");
             }
 
             if (string.IsNullOrEmpty (ansiRequest.Terminator))
@@ -102,7 +105,7 @@ public class AnsiEscapeSequenceRequest
         AnsiEscapeSequenceResponse ansiResponse = new ()
         {
             Response = ansiRequest.Response, Error = error.ToString (),
-            Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), Value = values [0]
+            Terminator = string.IsNullOrEmpty (ansiRequest.Response) ? "" : ansiRequest.Response [^1].ToString (), ExpectedResponseValue = values [0]
         };
 
         // Invoke the event if it's subscribed
@@ -114,16 +117,17 @@ public class AnsiEscapeSequenceRequest
     }
 
     /// <summary>
-    ///     The value expected in the response e.g.
+    ///     The value expected in the response after the CSI e.g.
     ///     <see>
     ///         <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
     ///     </see>
-    ///     which will have a 't' as terminator but also other different request may return the same terminator with a
-    ///     different value.
+    ///     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? Value { get; init; }
+    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;
 }

+ 17 - 10
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs

@@ -2,20 +2,23 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///     Describes a finished ANSI received from the console.
+///     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>
-    ///     Error received from e.g. see
+    ///     Gets the error string received from e.g. see
     ///     <see>
     ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
     ///     </see>
+    ///     .
     /// </summary>
     public required string Error { get; init; }
 
+    // QUESTION: Does string.Empty indicate no response recevied? If not, perhaps make this property nullable?
     /// <summary>
-    ///     Response received from e.g. see
+    ///     Gets the Response string received from e.g. see
     ///     <see>
     ///         <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
     ///     </see>
@@ -23,10 +26,11 @@ 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>
-    ///         The terminator that uniquely identifies the type of response as responded
-    ///         by the console. e.g. for
+    ///         Gets the terminator that uniquely identifies the response received from
+    ///         the console. e.g. for
     ///         <see>
     ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Request</cref>
     ///         </see>
@@ -34,20 +38,23 @@ public class AnsiEscapeSequenceResponse
     ///         <see>
     ///             <cref>EscSeqUtils.CSI_SendDeviceAttributes.Terminator</cref>
     ///         </see>
+    ///         .
     ///     </para>
     ///     <para>
-    ///         The received terminator must match to the terminator sent by the request.
+    ///         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>
-    ///     The value expected in the response e.g.
+    ///     The value expected in the response after the CSI e.g.
     ///     <see>
     ///         <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
     ///     </see>
-    ///     which will have a 't' as terminator but also other different request may return the same terminator with a
-    ///     different value.
+    ///     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? Value { get; init; }
+
+    public string? ExpectedResponseValue { get; init; }
 }

+ 184 - 481
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -1,7 +1,4 @@
 #nullable enable
-//
-// ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
-//
 
 using System.Diagnostics;
 
@@ -15,6 +12,53 @@ namespace Terminal.Gui;
 /// </remarks>
 public abstract class ConsoleDriver
 {
+    /// <summary>
+    ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
+    ///     <code>
+    ///  public ColorTests ()
+    ///  {
+    ///    ConsoleDriver.RunningUnitTests = true;
+    ///  }
+    /// </code>
+    /// </summary>
+    internal static bool RunningUnitTests { get; set; }
+
+    /// <summary>Get the operating system clipboard.</summary>
+    public IClipboard? Clipboard { get; internal set; }
+
+    /// <summary>Returns the name of the driver and relevant library version information.</summary>
+    /// <returns></returns>
+    public virtual string GetVersionInfo () { return GetType ().Name; }
+
+    /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
+    /// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
+    public abstract void Suspend ();
+
+    #region ANSI Esc Sequence Handling
+
+    // QUESTION: Should this be virtual with a default implementation that does the common stuff?
+    // QUESTION: Looking at the implementations of this method, there is TONs of duplicated code.
+    // QUESTION: We should figure out how to find just the things that are unique to each driver and
+    // QUESTION: create more fine-grained APIs to handle those.
+    /// <summary>
+    ///     Provide handling for the terminal write ANSI escape sequence request.
+    /// </summary>
+    /// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
+    /// <returns>The request response.</returns>
+    public abstract string WriteAnsiRequest (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?
+    /// <summary>
+    ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
+    /// </summary>
+    /// <param name="ansi"></param>
+    public abstract void WriteRaw (string ansi);
+
+    #endregion ANSI Esc Sequence Handling
+
+    #region Screen and Contents
+
     // As performance is a concern, we keep track of the dirty lines and only refresh those.
     // This is in addition to the dirty flag on each cell.
     internal bool []? _dirtyLines;
@@ -23,30 +67,18 @@ public abstract class ConsoleDriver
     /// <summary>Gets the location and size of the terminal screen.</summary>
     internal Rectangle Screen => new (0, 0, Cols, Rows);
 
-    private Rectangle _clip;
+    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
+    public abstract void UpdateScreen ();
 
-    /// <summary>
-    ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
-    ///     to.
-    /// </summary>
-    /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
-    public Rectangle Clip
-    {
-        get => _clip;
-        set
-        {
-            if (_clip == value)
-            {
-                return;
-            }
+    /// <summary>Called when the terminal size changes. Fires the <see cref="SizeChanged"/> event.</summary>
+    /// <param name="args"></param>
+    public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); }
 
-            // Don't ever let Clip be bigger than Screen
-            _clip = Rectangle.Intersect (Screen, value);
-        }
-    }
+    /// <summary>The event fired when the terminal is resized.</summary>
+    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
 
-    /// <summary>Get the operating system clipboard.</summary>
-    public IClipboard? Clipboard { get; internal set; }
+    /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
+    public abstract void Refresh ();
 
     /// <summary>
     ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
@@ -75,6 +107,43 @@ public abstract class ConsoleDriver
     /// <summary>The leftmost column in the terminal.</summary>
     public virtual int Left { get; internal set; } = 0;
 
+    /// <summary>Tests if the specified rune is supported by the driver.</summary>
+    /// <param name="rune"></param>
+    /// <returns>
+    ///     <see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver does not
+    ///     support displaying this rune.
+    /// </returns>
+    public virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); }
+
+    /// <summary>Tests whether the specified coordinate are valid for drawing.</summary>
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    /// <returns>
+    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
+    ///     <see langword="true"/> otherwise.
+    /// </returns>
+    public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); }
+
+    /// <summary>
+    ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
+    ///     Used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+    /// </summary>
+    /// <remarks>
+    ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
+    ///     <para>
+    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="Cols"/> and
+    ///         <see cref="Rows"/>, the method still sets those properties.
+    ///     </para>
+    /// </remarks>
+    /// <param name="col">Column to move to.</param>
+    /// <param name="row">Row to move to.</param>
+    public virtual void Move (int col, int row)
+    {
+        //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0));
+        Col = col;
+        Row = row;
+    }
+
     /// <summary>
     ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
     ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
@@ -95,16 +164,27 @@ public abstract class ConsoleDriver
     /// <summary>The topmost row in the terminal.</summary>
     public virtual int Top { get; internal set; } = 0;
 
+    private Rectangle _clip;
+
     /// <summary>
-    ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
-    ///     <code>
-    ///  public ColorTests ()
-    ///  {
-    ///    ConsoleDriver.RunningUnitTests = true;
-    ///  }
-    /// </code>
+    ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
+    ///     to.
     /// </summary>
-    internal static bool RunningUnitTests { get; set; }
+    /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
+    public Rectangle Clip
+    {
+        get => _clip;
+        set
+        {
+            if (_clip == value)
+            {
+                return;
+            }
+
+            // Don't ever let Clip be bigger than Screen
+            _clip = Rectangle.Intersect (Screen, value);
+        }
+    }
 
     /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
     /// <remarks>
@@ -310,10 +390,38 @@ public abstract class ConsoleDriver
         }
     }
 
+    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="CurrentAttribute"/></summary>
+    /// <remarks>
+    ///     The value of <see cref="Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
+    /// </remarks>
+    /// <param name="rect">The Screen-relative rectangle.</param>
+    /// <param name="rune">The Rune used to fill the rectangle</param>
+    public void FillRect (Rectangle rect, Rune rune = default)
+    {
+        rect = Rectangle.Intersect (rect, Clip);
+
+        lock (Contents!)
+        {
+            for (int r = rect.Y; r < rect.Y + rect.Height; r++)
+            {
+                for (int c = rect.X; c < rect.X + rect.Width; c++)
+                {
+                    Contents [r, c] = new ()
+                    {
+                        Rune = rune != default (Rune) ? rune : (Rune)' ',
+                        Attribute = CurrentAttribute, IsDirty = true
+                    };
+                    _dirtyLines! [r] = true;
+                }
+            }
+        }
+    }
+
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
     public void ClearContents ()
     {
         Contents = new Cell [Rows, Cols];
+
         //CONCURRENCY: Unsynchronized access to Clip isn't safe.
         // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere.
         Clip = Screen;
@@ -325,21 +433,22 @@ public abstract class ConsoleDriver
             {
                 for (var c = 0; c < Cols; c++)
                 {
-                    Contents [row, c] = new Cell
+                    Contents [row, c] = new ()
                     {
                         Rune = (Rune)' ',
                         Attribute = new Attribute (Color.White, Color.Black),
                         IsDirty = true
                     };
                 }
+
                 _dirtyLines [row] = true;
             }
         }
     }
 
     /// <summary>
-    /// Sets <see cref="Contents"/> as dirty for situations where views
-    /// don't need layout and redrawing, but just refresh the screen.
+    ///     Sets <see cref="Contents"/> as dirty for situations where views
+    ///     don't need layout and redrawing, but just refresh the screen.
     /// </summary>
     public void SetContentsAsDirty ()
     {
@@ -351,37 +460,8 @@ public abstract class ConsoleDriver
                 {
                     Contents [row, c].IsDirty = true;
                 }
-                _dirtyLines! [row] = true;
-            }
-        }
-    }
 
-    /// <summary>Determines if the terminal cursor should be visible or not and sets it accordingly.</summary>
-    /// <returns><see langword="true"/> upon success</returns>
-    public abstract bool EnsureCursorVisibility ();
-
-    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="CurrentAttribute"/></summary>
-    /// <remarks>
-    /// The value of <see cref="Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
-    /// </remarks>
-    /// <param name="rect">The Screen-relative rectangle.</param>
-    /// <param name="rune">The Rune used to fill the rectangle</param>
-    public void FillRect (Rectangle rect, Rune rune = default)
-    {
-        rect = Rectangle.Intersect (rect, Clip);
-        lock (Contents!)
-        {
-            for (int r = rect.Y; r < rect.Y + rect.Height; r++)
-            {
-                for (int c = rect.X; c < rect.X + rect.Width; c++)
-                {
-                    Contents [r, c] = new Cell
-                    {
-                        Rune = rune != default ? rune : (Rune)' ',
-                        Attribute = CurrentAttribute, IsDirty = true
-                    };
-                    _dirtyLines! [r] = true;
-                }
+                _dirtyLines! [row] = true;
             }
         }
     }
@@ -394,79 +474,28 @@ public abstract class ConsoleDriver
     /// <param name="c"></param>
     public void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); }
 
+    #endregion Screen and Contents
+
+    #region Cursor Handling
+
+    /// <summary>Determines if the terminal cursor should be visible or not and sets it accordingly.</summary>
+    /// <returns><see langword="true"/> upon success</returns>
+    public abstract bool EnsureCursorVisibility ();
+
     /// <summary>Gets the terminal cursor visibility.</summary>
     /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
     /// <returns><see langword="true"/> upon success</returns>
     public abstract bool GetCursorVisibility (out CursorVisibility visibility);
 
-    /// <summary>Returns the name of the driver and relevant library version information.</summary>
-    /// <returns></returns>
-    public virtual string GetVersionInfo () { return GetType ().Name; }
-
-    /// <summary>Tests if the specified rune is supported by the driver.</summary>
-    /// <param name="rune"></param>
-    /// <returns>
-    ///     <see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver does not
-    ///     support displaying this rune.
-    /// </returns>
-    public virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); }
-
-    /// <summary>Tests whether the specified coordinate are valid for drawing.</summary>
-    /// <param name="col">The column.</param>
-    /// <param name="row">The row.</param>
-    /// <returns>
-    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
-    ///     <see langword="true"/> otherwise.
-    /// </returns>
-    public bool IsValidLocation (int col, int row)
-    {
-        return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row);
-    }
-
-    /// <summary>
-    ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
-    ///     Used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
-    /// </summary>
-    /// <remarks>
-    ///     <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
-    ///     <para>
-    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="Cols"/> and
-    ///         <see cref="Rows"/>, the method still sets those properties.
-    ///     </para>
-    /// </remarks>
-    /// <param name="col">Column to move to.</param>
-    /// <param name="row">Row to move to.</param>
-    public virtual void Move (int col, int row)
-    {
-        //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0));
-        Col = col;
-        Row = row;
-    }
-
-    /// <summary>Called when the terminal size changes. Fires the <see cref="SizeChanged"/> event.</summary>
-    /// <param name="args"></param>
-    public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); }
-
-    /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
-    public abstract void Refresh ();
+    /// <summary>Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.</summary>
+    public abstract void UpdateCursor ();
 
     /// <summary>Sets the terminal cursor visibility.</summary>
     /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
     /// <returns><see langword="true"/> upon success</returns>
     public abstract bool SetCursorVisibility (CursorVisibility visibility);
 
-    /// <summary>The event fired when the terminal is resized.</summary>
-    public event EventHandler<SizeChangedEventArgs>? SizeChanged;
-
-    /// <summary>Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.</summary>
-    /// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
-    public abstract void Suspend ();
-
-    /// <summary>Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.</summary>
-    public abstract void UpdateCursor ();
-
-    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
-    public abstract void UpdateScreen ();
+    #endregion Cursor Handling
 
     #region Setup & Teardown
 
@@ -518,7 +547,7 @@ public abstract class ConsoleDriver
             // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed.
             if (Application.Driver is { })
             {
-                _currentAttribute = new Attribute (value.Foreground, value.Background);
+                _currentAttribute = new (value.Foreground, value.Background);
 
                 return;
             }
@@ -551,16 +580,33 @@ public abstract class ConsoleDriver
     public virtual Attribute MakeColor (in Color foreground, in Color background)
     {
         // Encode the colors into the int value.
-        return new Attribute (
-                              -1, // only used by cursesdriver!
-                              foreground,
-                              background
-                             );
+        return new (
+                    -1, // only used by cursesdriver!
+                    foreground,
+                    background
+                   );
     }
 
-    #endregion
+    #endregion Color Handling
 
-    #region Mouse and Keyboard
+    #region Mouse Handling
+
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    public event EventHandler<MouseEventArgs>? MouseEvent;
+
+    /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
+    /// <param name="a"></param>
+    public void OnMouseEvent (MouseEventArgs a)
+    {
+        // Ensure ScreenPosition is set
+        a.ScreenPosition = a.Position;
+
+        MouseEvent?.Invoke (this, a);
+    }
+
+    #endregion Mouse Handling
+
+    #region Keyboard Handling
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
@@ -587,19 +633,8 @@ public abstract class ConsoleDriver
     /// <param name="a"></param>
     public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
 
-    /// <summary>Event fired when a mouse event occurs.</summary>
-    public event EventHandler<MouseEventArgs>? MouseEvent;
-
-    /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
-    /// <param name="a"></param>
-    public void OnMouseEvent (MouseEventArgs a)
-    {
-        // Ensure ScreenPosition is set
-        a.ScreenPosition = a.Position;
-
-        MouseEvent?.Invoke (this, a);
-    }
-
+    // TODO: Remove this API - it was needed when we didn't have a reliable way to simulate key presses.
+    // TODO: We now do: Applicaiton.RaiseKeyDown and Application.RaiseKeyUp
     /// <summary>Simulates a key press.</summary>
     /// <param name="keyChar">The key character.</param>
     /// <param name="key">The key.</param>
@@ -608,337 +643,5 @@ public abstract class ConsoleDriver
     /// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
     public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
 
-    /// <summary>
-    ///     Provide handling for the terminal write ANSI escape sequence request.
-    /// </summary>
-    /// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
-    /// <returns>The request response.</returns>
-    public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest);
-
-    /// <summary>
-    ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
-    /// </summary>
-    /// <param name="ansi"></param>
-    public abstract void WriteRaw (string ansi);
-
-    #endregion
-}
-
-/// <summary>
-///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a
-///     consistent way for application code to specify keys and receive key events.
-///     <para>
-///         The <see cref="Key"/> class provides a higher-level abstraction, with helper methods and properties for
-///         common operations. For example, <see cref="Key.IsAlt"/> and <see cref="Key.IsCtrl"/> provide a convenient way
-///         to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed.
-///     </para>
-/// </summary>
-/// <remarks>
-///     <para>
-///         Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a
-///         keyboard. Enum values are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.).
-///         Even though the values are the same as the ASCII values for uppercase characters, these enum values represent
-///         *lowercase*, un-shifted characters.
-///     </para>
-///     <para>
-///         Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. <see cref="KeyCode.D0"/>,
-///         <see cref="KeyCode.D1"/>, etc.).
-///     </para>
-///     <para>
-///         The shift modifiers (<see cref="KeyCode.ShiftMask"/>, <see cref="KeyCode.CtrlMask"/>, and
-///         <see cref="KeyCode.AltMask"/>) can be combined (with logical or) with the other key codes to represent shifted
-///         keys. For example, the <see cref="KeyCode.A"/> enum value represents the un-shifted 'a' key, while
-///         <see cref="KeyCode.ShiftMask"/> | <see cref="KeyCode.A"/> represents the 'A' key (shifted 'a' key). Likewise,
-///         <see cref="KeyCode.AltMask"/> | <see cref="KeyCode.A"/> represents the 'Alt+A' key combination.
-///     </para>
-///     <para>
-///         All other keys that produce a printable character are encoded as the Unicode value of the character. For
-///         example, the <see cref="KeyCode"/> for the '!' character is 33, which is the Unicode value for '!'. Likewise,
-///         `â` is 226, `Â` is 194, etc.
-///     </para>
-///     <para>
-///         If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
-///         the one of the lower bits (as extracted by <see cref="CharMask"/>).
-///     </para>
-/// </remarks>
-[Flags]
-public enum KeyCode : uint
-{
-    /// <summary>
-    ///     Mask that indicates that the key is a unicode codepoint. Values outside this range indicate the key has shift
-    ///     modifiers or is a special key like function keys, arrows keys and so on.
-    /// </summary>
-    CharMask = 0x_f_ffff,
-
-    /// <summary>
-    ///     If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
-    ///     in the lower bits (as extracted by <see cref="CharMask"/>).
-    /// </summary>
-    SpecialMask = 0x_fff0_0000,
-
-    /// <summary>
-    ///     When this value is set, the Key encodes the sequence Shift-KeyValue. The actual value must be extracted by
-    ///     removing the ShiftMask.
-    /// </summary>
-    ShiftMask = 0x_1000_0000,
-
-    /// <summary>
-    ///     When this value is set, the Key encodes the sequence Alt-KeyValue. The actual value must be extracted by
-    ///     removing the AltMask.
-    /// </summary>
-    AltMask = 0x_8000_0000,
-
-    /// <summary>
-    ///     When this value is set, the Key encodes the sequence Ctrl-KeyValue. The actual value must be extracted by
-    ///     removing the CtrlMask.
-    /// </summary>
-    CtrlMask = 0x_4000_0000,
-
-    /// <summary>The key code representing an invalid or empty key.</summary>
-    Null = 0,
-
-    /// <summary>Backspace key.</summary>
-    Backspace = 8,
-
-    /// <summary>The key code for the tab key (forwards tab key).</summary>
-    Tab = 9,
-
-    /// <summary>The key code for the return key.</summary>
-    Enter = ConsoleKey.Enter,
-
-    /// <summary>The key code for the clear key.</summary>
-    Clear = 12,
-
-    /// <summary>The key code for the escape key.</summary>
-    Esc = 27,
-
-    /// <summary>The key code for the space bar key.</summary>
-    Space = 32,
-
-    /// <summary>Digit 0.</summary>
-    D0 = 48,
-
-    /// <summary>Digit 1.</summary>
-    D1,
-
-    /// <summary>Digit 2.</summary>
-    D2,
-
-    /// <summary>Digit 3.</summary>
-    D3,
-
-    /// <summary>Digit 4.</summary>
-    D4,
-
-    /// <summary>Digit 5.</summary>
-    D5,
-
-    /// <summary>Digit 6.</summary>
-    D6,
-
-    /// <summary>Digit 7.</summary>
-    D7,
-
-    /// <summary>Digit 8.</summary>
-    D8,
-
-    /// <summary>Digit 9.</summary>
-    D9,
-
-    /// <summary>The key code for the A key</summary>
-    A = 65,
-
-    /// <summary>The key code for the B key</summary>
-    B,
-
-    /// <summary>The key code for the C key</summary>
-    C,
-
-    /// <summary>The key code for the D key</summary>
-    D,
-
-    /// <summary>The key code for the E key</summary>
-    E,
-
-    /// <summary>The key code for the F key</summary>
-    F,
-
-    /// <summary>The key code for the G key</summary>
-    G,
-
-    /// <summary>The key code for the H key</summary>
-    H,
-
-    /// <summary>The key code for the I key</summary>
-    I,
-
-    /// <summary>The key code for the J key</summary>
-    J,
-
-    /// <summary>The key code for the K key</summary>
-    K,
-
-    /// <summary>The key code for the L key</summary>
-    L,
-
-    /// <summary>The key code for the M key</summary>
-    M,
-
-    /// <summary>The key code for the N key</summary>
-    N,
-
-    /// <summary>The key code for the O key</summary>
-    O,
-
-    /// <summary>The key code for the P key</summary>
-    P,
-
-    /// <summary>The key code for the Q key</summary>
-    Q,
-
-    /// <summary>The key code for the R key</summary>
-    R,
-
-    /// <summary>The key code for the S key</summary>
-    S,
-
-    /// <summary>The key code for the T key</summary>
-    T,
-
-    /// <summary>The key code for the U key</summary>
-    U,
-
-    /// <summary>The key code for the V key</summary>
-    V,
-
-    /// <summary>The key code for the W key</summary>
-    W,
-
-    /// <summary>The key code for the X key</summary>
-    X,
-
-    /// <summary>The key code for the Y key</summary>
-    Y,
-
-    /// <summary>The key code for the Z key</summary>
-    Z,
-
-    ///// <summary>
-    ///// The key code for the Delete key.
-    ///// </summary>
-    //Delete = 127,
-
-    // --- Special keys ---
-    // The values below are common non-alphanum keys. Their values are
-    // based on the .NET ConsoleKey values, which, in-turn are based on the
-    // VK_ values from the Windows API.
-    // We add MaxCodePoint to avoid conflicts with the Unicode values.
-
-    /// <summary>The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys.</summary>
-    MaxCodePoint = 0x10FFFF,
-
-    /// <summary>Cursor up key</summary>
-    CursorUp = MaxCodePoint + ConsoleKey.UpArrow,
-
-    /// <summary>Cursor down key.</summary>
-    CursorDown = MaxCodePoint + ConsoleKey.DownArrow,
-
-    /// <summary>Cursor left key.</summary>
-    CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow,
-
-    /// <summary>Cursor right key.</summary>
-    CursorRight = MaxCodePoint + ConsoleKey.RightArrow,
-
-    /// <summary>Page Up key.</summary>
-    PageUp = MaxCodePoint + ConsoleKey.PageUp,
-
-    /// <summary>Page Down key.</summary>
-    PageDown = MaxCodePoint + ConsoleKey.PageDown,
-
-    /// <summary>Home key.</summary>
-    Home = MaxCodePoint + ConsoleKey.Home,
-
-    /// <summary>End key.</summary>
-    End = MaxCodePoint + ConsoleKey.End,
-
-    /// <summary>Insert (INS) key.</summary>
-    Insert = MaxCodePoint + ConsoleKey.Insert,
-
-    /// <summary>Delete (DEL) key.</summary>
-    Delete = MaxCodePoint + ConsoleKey.Delete,
-
-    /// <summary>Print screen character key.</summary>
-    PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen,
-
-    /// <summary>F1 key.</summary>
-    F1 = MaxCodePoint + ConsoleKey.F1,
-
-    /// <summary>F2 key.</summary>
-    F2 = MaxCodePoint + ConsoleKey.F2,
-
-    /// <summary>F3 key.</summary>
-    F3 = MaxCodePoint + ConsoleKey.F3,
-
-    /// <summary>F4 key.</summary>
-    F4 = MaxCodePoint + ConsoleKey.F4,
-
-    /// <summary>F5 key.</summary>
-    F5 = MaxCodePoint + ConsoleKey.F5,
-
-    /// <summary>F6 key.</summary>
-    F6 = MaxCodePoint + ConsoleKey.F6,
-
-    /// <summary>F7 key.</summary>
-    F7 = MaxCodePoint + ConsoleKey.F7,
-
-    /// <summary>F8 key.</summary>
-    F8 = MaxCodePoint + ConsoleKey.F8,
-
-    /// <summary>F9 key.</summary>
-    F9 = MaxCodePoint + ConsoleKey.F9,
-
-    /// <summary>F10 key.</summary>
-    F10 = MaxCodePoint + ConsoleKey.F10,
-
-    /// <summary>F11 key.</summary>
-    F11 = MaxCodePoint + ConsoleKey.F11,
-
-    /// <summary>F12 key.</summary>
-    F12 = MaxCodePoint + ConsoleKey.F12,
-
-    /// <summary>F13 key.</summary>
-    F13 = MaxCodePoint + ConsoleKey.F13,
-
-    /// <summary>F14 key.</summary>
-    F14 = MaxCodePoint + ConsoleKey.F14,
-
-    /// <summary>F15 key.</summary>
-    F15 = MaxCodePoint + ConsoleKey.F15,
-
-    /// <summary>F16 key.</summary>
-    F16 = MaxCodePoint + ConsoleKey.F16,
-
-    /// <summary>F17 key.</summary>
-    F17 = MaxCodePoint + ConsoleKey.F17,
-
-    /// <summary>F18 key.</summary>
-    F18 = MaxCodePoint + ConsoleKey.F18,
-
-    /// <summary>F19 key.</summary>
-    F19 = MaxCodePoint + ConsoleKey.F19,
-
-    /// <summary>F20 key.</summary>
-    F20 = MaxCodePoint + ConsoleKey.F20,
-
-    /// <summary>F21 key.</summary>
-    F21 = MaxCodePoint + ConsoleKey.F21,
-
-    /// <summary>F22 key.</summary>
-    F22 = MaxCodePoint + ConsoleKey.F22,
-
-    /// <summary>F23 key.</summary>
-    F23 = MaxCodePoint + ConsoleKey.F23,
-
-    /// <summary>F24 key.</summary>
-    F24 = MaxCodePoint + ConsoleKey.F24
+    #endregion Keyboard Handling
 }

+ 1 - 0
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
 
 namespace Terminal.Gui.ConsoleDrivers;
 
+// QUESTION: This class combines Windows specific code with cross-platform code. Should this be split into two classes?
 /// <summary>Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.</summary>
 public static class ConsoleKeyMapping
 {

Fișier diff suprimat deoarece este prea mare
+ 507 - 390
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs


+ 5 - 4
Terminal.Gui/ConsoleDrivers/CursesDriver/GetTIOCGWINSZ.c

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

+ 1 - 14
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs

@@ -4,20 +4,7 @@ using System.Collections.Concurrent;
 
 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="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
-    public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; }
-
-    /// <summary>Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator).</summary>
-    public AnsiEscapeSequenceRequest AnsiRequest { get; }
-}
+// QUESTION: Can this class be moved/refactored/combined with the new AnsiEscapeSequenceRequest/Response class?
 
 // TODO: This class is a singleton. It should use the singleton pattern.
 /// <summary>

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

@@ -0,0 +1,17 @@
+#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="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param>
+    public EscSeqReqStatus (AnsiEscapeSequenceRequest ansiRequest) { AnsiRequest = ansiRequest; }
+
+    /// <summary>Gets the Escape Sequence Terminator (e.g. ESC[8t ... t is the terminator).</summary>
+    public AnsiEscapeSequenceRequest AnsiRequest { get; }
+}

+ 13 - 1
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -3,6 +3,13 @@ using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 
+// QUESTION: Should this class be refactored into separate classes for:
+// QUESTION:   CSI definitions
+// QUESTION:   Primitives like DecodeEsqReq
+// QUESTION:   Screen/Color/Cursor handling
+// QUESTION:   Mouse handling
+// QUESTION:   Keyboard handling
+
 /// <summary>
 ///     Provides a platform-independent API for managing ANSI escape sequences.
 /// </summary>
@@ -14,6 +21,7 @@ namespace Terminal.Gui;
 /// </remarks>
 public static class EscSeqUtils
 {
+    // TODO: One type per file - Move this enum to a separate file.
     /// <summary>
     ///     Options for ANSI ESC "[xJ" - Clears part of the screen.
     /// </summary>
@@ -40,6 +48,9 @@ public static class EscSeqUtils
         EntireScreenAndScrollbackBuffer = 3
     }
 
+    // QUESTION: I wonder if EscSeqUtils.CSI_... should be more strongly typed such that this (and Terminator could be
+    // QUESTION: public required CSIRequests Request { get; init; }
+    // QUESTION: public required CSITerminators Terminator { get; init; }
     /// <summary>
     ///     Escape key code (ASCII 27/0x1B).
     /// </summary>
@@ -419,6 +430,7 @@ public static class EscSeqUtils
                };
     }
 
+
     /// <summary>
     ///     Gets the <see cref="ConsoleKey"/> depending on terminating and value.
     /// </summary>
@@ -1721,7 +1733,7 @@ public static class EscSeqUtils
     ///     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", Value = "8" };
+    public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", ExpectedResponseValue = "8" };
 
     #endregion
 }

+ 321 - 0
Terminal.Gui/ConsoleDrivers/KeyCode.cs

@@ -0,0 +1,321 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a
+///     consistent way for application code to specify keys and receive key events.
+///     <para>
+///         The <see cref="Key"/> class provides a higher-level abstraction, with helper methods and properties for
+///         common operations. For example, <see cref="Key.IsAlt"/> and <see cref="Key.IsCtrl"/> provide a convenient way
+///         to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed.
+///     </para>
+/// </summary>
+/// <remarks>
+///     <para>
+///         Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a
+///         keyboard. Enum values are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.).
+///         Even though the values are the same as the ASCII values for uppercase characters, these enum values represent
+///         *lowercase*, un-shifted characters.
+///     </para>
+///     <para>
+///         Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. <see cref="KeyCode.D0"/>,
+///         <see cref="KeyCode.D1"/>, etc.).
+///     </para>
+///     <para>
+///         The shift modifiers (<see cref="KeyCode.ShiftMask"/>, <see cref="KeyCode.CtrlMask"/>, and
+///         <see cref="KeyCode.AltMask"/>) can be combined (with logical or) with the other key codes to represent shifted
+///         keys. For example, the <see cref="KeyCode.A"/> enum value represents the un-shifted 'a' key, while
+///         <see cref="KeyCode.ShiftMask"/> | <see cref="KeyCode.A"/> represents the 'A' key (shifted 'a' key). Likewise,
+///         <see cref="KeyCode.AltMask"/> | <see cref="KeyCode.A"/> represents the 'Alt+A' key combination.
+///     </para>
+///     <para>
+///         All other keys that produce a printable character are encoded as the Unicode value of the character. For
+///         example, the <see cref="KeyCode"/> for the '!' character is 33, which is the Unicode value for '!'. Likewise,
+///         `â` is 226, `Â` is 194, etc.
+///     </para>
+///     <para>
+///         If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
+///         the one of the lower bits (as extracted by <see cref="CharMask"/>).
+///     </para>
+/// </remarks>
+[Flags]
+public enum KeyCode : uint
+{
+    /// <summary>
+    ///     Mask that indicates that the key is a unicode codepoint. Values outside this range indicate the key has shift
+    ///     modifiers or is a special key like function keys, arrows keys and so on.
+    /// </summary>
+    CharMask = 0x_f_ffff,
+
+    /// <summary>
+    ///     If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
+    ///     in the lower bits (as extracted by <see cref="CharMask"/>).
+    /// </summary>
+    SpecialMask = 0x_fff0_0000,
+
+    /// <summary>
+    ///     When this value is set, the Key encodes the sequence Shift-KeyValue. The actual value must be extracted by
+    ///     removing the ShiftMask.
+    /// </summary>
+    ShiftMask = 0x_1000_0000,
+
+    /// <summary>
+    ///     When this value is set, the Key encodes the sequence Alt-KeyValue. The actual value must be extracted by
+    ///     removing the AltMask.
+    /// </summary>
+    AltMask = 0x_8000_0000,
+
+    /// <summary>
+    ///     When this value is set, the Key encodes the sequence Ctrl-KeyValue. The actual value must be extracted by
+    ///     removing the CtrlMask.
+    /// </summary>
+    CtrlMask = 0x_4000_0000,
+
+    /// <summary>The key code representing an invalid or empty key.</summary>
+    Null = 0,
+
+    /// <summary>Backspace key.</summary>
+    Backspace = 8,
+
+    /// <summary>The key code for the tab key (forwards tab key).</summary>
+    Tab = 9,
+
+    /// <summary>The key code for the return key.</summary>
+    Enter = ConsoleKey.Enter,
+
+    /// <summary>The key code for the clear key.</summary>
+    Clear = 12,
+
+    /// <summary>The key code for the escape key.</summary>
+    Esc = 27,
+
+    /// <summary>The key code for the space bar key.</summary>
+    Space = 32,
+
+    /// <summary>Digit 0.</summary>
+    D0 = 48,
+
+    /// <summary>Digit 1.</summary>
+    D1,
+
+    /// <summary>Digit 2.</summary>
+    D2,
+
+    /// <summary>Digit 3.</summary>
+    D3,
+
+    /// <summary>Digit 4.</summary>
+    D4,
+
+    /// <summary>Digit 5.</summary>
+    D5,
+
+    /// <summary>Digit 6.</summary>
+    D6,
+
+    /// <summary>Digit 7.</summary>
+    D7,
+
+    /// <summary>Digit 8.</summary>
+    D8,
+
+    /// <summary>Digit 9.</summary>
+    D9,
+
+    /// <summary>The key code for the A key</summary>
+    A = 65,
+
+    /// <summary>The key code for the B key</summary>
+    B,
+
+    /// <summary>The key code for the C key</summary>
+    C,
+
+    /// <summary>The key code for the D key</summary>
+    D,
+
+    /// <summary>The key code for the E key</summary>
+    E,
+
+    /// <summary>The key code for the F key</summary>
+    F,
+
+    /// <summary>The key code for the G key</summary>
+    G,
+
+    /// <summary>The key code for the H key</summary>
+    H,
+
+    /// <summary>The key code for the I key</summary>
+    I,
+
+    /// <summary>The key code for the J key</summary>
+    J,
+
+    /// <summary>The key code for the K key</summary>
+    K,
+
+    /// <summary>The key code for the L key</summary>
+    L,
+
+    /// <summary>The key code for the M key</summary>
+    M,
+
+    /// <summary>The key code for the N key</summary>
+    N,
+
+    /// <summary>The key code for the O key</summary>
+    O,
+
+    /// <summary>The key code for the P key</summary>
+    P,
+
+    /// <summary>The key code for the Q key</summary>
+    Q,
+
+    /// <summary>The key code for the R key</summary>
+    R,
+
+    /// <summary>The key code for the S key</summary>
+    S,
+
+    /// <summary>The key code for the T key</summary>
+    T,
+
+    /// <summary>The key code for the U key</summary>
+    U,
+
+    /// <summary>The key code for the V key</summary>
+    V,
+
+    /// <summary>The key code for the W key</summary>
+    W,
+
+    /// <summary>The key code for the X key</summary>
+    X,
+
+    /// <summary>The key code for the Y key</summary>
+    Y,
+
+    /// <summary>The key code for the Z key</summary>
+    Z,
+
+    ///// <summary>
+    ///// The key code for the Delete key.
+    ///// </summary>
+    //Delete = 127,
+
+    // --- Special keys ---
+    // The values below are common non-alphanum keys. Their values are
+    // based on the .NET ConsoleKey values, which, in-turn are based on the
+    // VK_ values from the Windows API.
+    // We add MaxCodePoint to avoid conflicts with the Unicode values.
+
+    /// <summary>The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys.</summary>
+    MaxCodePoint = 0x10FFFF,
+
+    /// <summary>Cursor up key</summary>
+    CursorUp = MaxCodePoint + ConsoleKey.UpArrow,
+
+    /// <summary>Cursor down key.</summary>
+    CursorDown = MaxCodePoint + ConsoleKey.DownArrow,
+
+    /// <summary>Cursor left key.</summary>
+    CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow,
+
+    /// <summary>Cursor right key.</summary>
+    CursorRight = MaxCodePoint + ConsoleKey.RightArrow,
+
+    /// <summary>Page Up key.</summary>
+    PageUp = MaxCodePoint + ConsoleKey.PageUp,
+
+    /// <summary>Page Down key.</summary>
+    PageDown = MaxCodePoint + ConsoleKey.PageDown,
+
+    /// <summary>Home key.</summary>
+    Home = MaxCodePoint + ConsoleKey.Home,
+
+    /// <summary>End key.</summary>
+    End = MaxCodePoint + ConsoleKey.End,
+
+    /// <summary>Insert (INS) key.</summary>
+    Insert = MaxCodePoint + ConsoleKey.Insert,
+
+    /// <summary>Delete (DEL) key.</summary>
+    Delete = MaxCodePoint + ConsoleKey.Delete,
+
+    /// <summary>Print screen character key.</summary>
+    PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen,
+
+    /// <summary>F1 key.</summary>
+    F1 = MaxCodePoint + ConsoleKey.F1,
+
+    /// <summary>F2 key.</summary>
+    F2 = MaxCodePoint + ConsoleKey.F2,
+
+    /// <summary>F3 key.</summary>
+    F3 = MaxCodePoint + ConsoleKey.F3,
+
+    /// <summary>F4 key.</summary>
+    F4 = MaxCodePoint + ConsoleKey.F4,
+
+    /// <summary>F5 key.</summary>
+    F5 = MaxCodePoint + ConsoleKey.F5,
+
+    /// <summary>F6 key.</summary>
+    F6 = MaxCodePoint + ConsoleKey.F6,
+
+    /// <summary>F7 key.</summary>
+    F7 = MaxCodePoint + ConsoleKey.F7,
+
+    /// <summary>F8 key.</summary>
+    F8 = MaxCodePoint + ConsoleKey.F8,
+
+    /// <summary>F9 key.</summary>
+    F9 = MaxCodePoint + ConsoleKey.F9,
+
+    /// <summary>F10 key.</summary>
+    F10 = MaxCodePoint + ConsoleKey.F10,
+
+    /// <summary>F11 key.</summary>
+    F11 = MaxCodePoint + ConsoleKey.F11,
+
+    /// <summary>F12 key.</summary>
+    F12 = MaxCodePoint + ConsoleKey.F12,
+
+    /// <summary>F13 key.</summary>
+    F13 = MaxCodePoint + ConsoleKey.F13,
+
+    /// <summary>F14 key.</summary>
+    F14 = MaxCodePoint + ConsoleKey.F14,
+
+    /// <summary>F15 key.</summary>
+    F15 = MaxCodePoint + ConsoleKey.F15,
+
+    /// <summary>F16 key.</summary>
+    F16 = MaxCodePoint + ConsoleKey.F16,
+
+    /// <summary>F17 key.</summary>
+    F17 = MaxCodePoint + ConsoleKey.F17,
+
+    /// <summary>F18 key.</summary>
+    F18 = MaxCodePoint + ConsoleKey.F18,
+
+    /// <summary>F19 key.</summary>
+    F19 = MaxCodePoint + ConsoleKey.F19,
+
+    /// <summary>F20 key.</summary>
+    F20 = MaxCodePoint + ConsoleKey.F20,
+
+    /// <summary>F21 key.</summary>
+    F21 = MaxCodePoint + ConsoleKey.F21,
+
+    /// <summary>F22 key.</summary>
+    F22 = MaxCodePoint + ConsoleKey.F22,
+
+    /// <summary>F23 key.</summary>
+    F23 = MaxCodePoint + ConsoleKey.F23,
+
+    /// <summary>F24 key.</summary>
+    F24 = MaxCodePoint + ConsoleKey.F24
+}

+ 0 - 2007
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1,2007 +0,0 @@
-//
-// NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
-//
-
-using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.InteropServices;
-using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
-using static Terminal.Gui.NetEvents;
-
-namespace Terminal.Gui;
-
-internal class NetWinVTConsole
-{
-    private const uint DISABLE_NEWLINE_AUTO_RETURN = 8;
-    private const uint ENABLE_ECHO_INPUT = 4;
-    private const uint ENABLE_EXTENDED_FLAGS = 128;
-    private const uint ENABLE_INSERT_MODE = 32;
-    private const uint ENABLE_LINE_INPUT = 2;
-    private const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
-    private const uint ENABLE_MOUSE_INPUT = 16;
-
-    // Input modes.
-    private const uint ENABLE_PROCESSED_INPUT = 1;
-
-    // Output modes.
-    private const uint ENABLE_PROCESSED_OUTPUT = 1;
-    private const uint ENABLE_QUICK_EDIT_MODE = 64;
-    private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
-    private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
-    private const uint ENABLE_WINDOW_INPUT = 8;
-    private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2;
-    private const int STD_ERROR_HANDLE = -12;
-    private const int STD_INPUT_HANDLE = -10;
-    private const int STD_OUTPUT_HANDLE = -11;
-
-    private readonly nint _errorHandle;
-    private readonly nint _inputHandle;
-    private readonly uint _originalErrorConsoleMode;
-    private readonly uint _originalInputConsoleMode;
-    private readonly uint _originalOutputConsoleMode;
-    private readonly nint _outputHandle;
-
-    public NetWinVTConsole ()
-    {
-        _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
-
-        if (!GetConsoleMode (_inputHandle, out uint mode))
-        {
-            throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}.");
-        }
-
-        _originalInputConsoleMode = mode;
-
-        if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT)
-        {
-            mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
-
-            if (!SetConsoleMode (_inputHandle, mode))
-            {
-                throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}.");
-            }
-        }
-
-        _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
-
-        if (!GetConsoleMode (_outputHandle, out mode))
-        {
-            throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}.");
-        }
-
-        _originalOutputConsoleMode = mode;
-
-        if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN)
-        {
-            mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
-
-            if (!SetConsoleMode (_outputHandle, mode))
-            {
-                throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}.");
-            }
-        }
-
-        _errorHandle = GetStdHandle (STD_ERROR_HANDLE);
-
-        if (!GetConsoleMode (_errorHandle, out mode))
-        {
-            throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
-        }
-
-        _originalErrorConsoleMode = mode;
-
-        if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN)
-        {
-            mode |= DISABLE_NEWLINE_AUTO_RETURN;
-
-            if (!SetConsoleMode (_errorHandle, mode))
-            {
-                throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
-            }
-        }
-    }
-
-    public void Cleanup ()
-    {
-        if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
-        {
-            throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
-        }
-
-        if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode))
-        {
-            throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}.");
-        }
-
-        if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode))
-        {
-            throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}.");
-        }
-    }
-
-    [DllImport ("kernel32.dll")]
-    private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
-
-    [DllImport ("kernel32.dll")]
-    private static extern uint GetLastError ();
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern nint GetStdHandle (int nStdHandle);
-
-    [DllImport ("kernel32.dll")]
-    private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
-}
-
-internal class NetEvents : IDisposable
-{
-    private readonly ManualResetEventSlim _inputReady = new (false);
-    private CancellationTokenSource _inputReadyCancellationTokenSource;
-    internal readonly ManualResetEventSlim _waitForStart = new (false);
-
-    //CancellationTokenSource _waitForStartCancellationTokenSource;
-    private readonly ManualResetEventSlim _winChange = new (false);
-    private readonly ConcurrentQueue<InputResult?> _inputQueue = new ();
-    private readonly ConsoleDriver _consoleDriver;
-    private ConsoleKeyInfo [] _cki;
-    private bool _isEscSeq;
-#if PROCESS_REQUEST
-    bool _neededProcessRequest;
-#endif
-    public EscSeqRequests EscSeqRequests { get; } = new ();
-
-    public NetEvents (ConsoleDriver consoleDriver)
-    {
-        _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
-        _inputReadyCancellationTokenSource = new CancellationTokenSource ();
-
-        Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
-
-        Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
-    }
-
-    public InputResult? DequeueInput ()
-    {
-        while (_inputReadyCancellationTokenSource != null
-               && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
-        {
-            _waitForStart.Set ();
-            _winChange.Set ();
-
-            try
-            {
-                if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
-                {
-                    if (_inputQueue.Count == 0)
-                    {
-                        _inputReady.Wait (_inputReadyCancellationTokenSource.Token);
-                    }
-                }
-            }
-            catch (OperationCanceledException)
-            {
-                return null;
-            }
-            finally
-            {
-                _inputReady.Reset ();
-            }
-
-#if PROCESS_REQUEST
-            _neededProcessRequest = false;
-#endif
-            if (_inputQueue.Count > 0)
-            {
-                if (_inputQueue.TryDequeue (out InputResult? result))
-                {
-                    return result;
-                }
-            }
-        }
-
-        return null;
-    }
-
-    private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
-    {
-        while (!cancellationToken.IsCancellationRequested)
-        {
-            // if there is a key available, return it without waiting
-            //  (or dispatching work to the thread queue)
-            if (Console.KeyAvailable)
-            {
-                return Console.ReadKey (intercept);
-            }
-
-            if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 })
-            {
-                if (_retries > 1)
-                {
-                    if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
-                    {
-                        lock (seqReqStatus!.AnsiRequest._responseLock)
-                        {
-                            EscSeqRequests.Statuses.TryDequeue (out _);
-
-                            seqReqStatus.AnsiRequest.Response = string.Empty;
-                            seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
-                        }
-                    }
-
-                    _retries = 0;
-                }
-                else
-                {
-                    _retries++;
-                }
-            }
-            else
-            {
-                _retries = 0;
-            }
-
-            if (!_forceRead)
-            {
-                Task.Delay (100, cancellationToken).Wait (cancellationToken);
-            }
-        }
-
-        cancellationToken.ThrowIfCancellationRequested ();
-
-        return default (ConsoleKeyInfo);
-    }
-
-    internal bool _forceRead;
-    private int _retries;
-
-    private void ProcessInputQueue ()
-    {
-        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
-        {
-            try
-            {
-                if (!_forceRead)
-                {
-                    _waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
-                }
-            }
-            catch (OperationCanceledException)
-            {
-                return;
-            }
-
-            _waitForStart.Reset ();
-
-            if (_inputQueue.Count == 0 || _forceRead)
-            {
-                ConsoleKey key = 0;
-                ConsoleModifiers mod = 0;
-                ConsoleKeyInfo newConsoleKeyInfo = default;
-
-                while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
-                {
-                    ConsoleKeyInfo consoleKeyInfo;
-
-                    try
-                    {
-                        consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token);
-                    }
-                    catch (OperationCanceledException)
-                    {
-                        return;
-                    }
-
-                    if (EscSeqUtils.IncompleteCkInfos is { })
-                    {
-                        EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki);
-                    }
-
-                    if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
-                        || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq))
-                    {
-                        if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
-                        {
-                            _cki = EscSeqUtils.ResizeArray (
-                                                            new ConsoleKeyInfo (
-                                                                                (char)KeyCode.Esc,
-                                                                                0,
-                                                                                false,
-                                                                                false,
-                                                                                false
-                                                                               ),
-                                                            _cki
-                                                           );
-                        }
-
-                        _isEscSeq = true;
-
-                        if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
-                            || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127)
-                            || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))
-                            || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)))
-                        {
-                            ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
-                            _cki = null;
-                            _isEscSeq = false;
-
-                            ProcessMapConsoleKeyInfo (consoleKeyInfo);
-                        }
-                        else
-                        {
-                            newConsoleKeyInfo = consoleKeyInfo;
-                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
-
-                            if (Console.KeyAvailable)
-                            {
-                                continue;
-                            }
-
-                            ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
-                            _cki = null;
-                            _isEscSeq = false;
-                        }
-
-                        break;
-                    }
-
-                    if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { })
-                    {
-                        ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
-                        _cki = null;
-
-                        if (Console.KeyAvailable)
-                        {
-                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
-                        }
-                        else
-                        {
-                            ProcessMapConsoleKeyInfo (consoleKeyInfo);
-                        }
-
-                        break;
-                    }
-
-                    ProcessMapConsoleKeyInfo (consoleKeyInfo);
-
-                    if (_retries > 0)
-                    {
-                        _retries = 0;
-                    }
-
-                    break;
-                }
-            }
-
-            _inputReady.Set ();
-        }
-
-        void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-        {
-            _inputQueue.Enqueue (
-                                 new InputResult
-                                 {
-                                     EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
-                                 }
-                                );
-            _isEscSeq = false;
-        }
-    }
-
-    private void CheckWindowSizeChange ()
-    {
-        void RequestWindowSize (CancellationToken cancellationToken)
-        {
-            while (!cancellationToken.IsCancellationRequested)
-            {
-                // Wait for a while then check if screen has changed sizes
-                Task.Delay (500, cancellationToken).Wait (cancellationToken);
-
-                int buffHeight, buffWidth;
-
-                if (((NetDriver)_consoleDriver).IsWinPlatform)
-                {
-                    buffHeight = Math.Max (Console.BufferHeight, 0);
-                    buffWidth = Math.Max (Console.BufferWidth, 0);
-                }
-                else
-                {
-                    buffHeight = _consoleDriver.Rows;
-                    buffWidth = _consoleDriver.Cols;
-                }
-
-                if (EnqueueWindowSizeEvent (
-                                            Math.Max (Console.WindowHeight, 0),
-                                            Math.Max (Console.WindowWidth, 0),
-                                            buffHeight,
-                                            buffWidth
-                                           ))
-                {
-                    return;
-                }
-            }
-
-            cancellationToken.ThrowIfCancellationRequested ();
-        }
-
-        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
-        {
-            try
-            {
-                _winChange.Wait (_inputReadyCancellationTokenSource.Token);
-                _winChange.Reset ();
-
-                RequestWindowSize (_inputReadyCancellationTokenSource.Token);
-            }
-            catch (OperationCanceledException)
-            {
-                return;
-            }
-
-            _inputReady.Set ();
-        }
-    }
-
-    /// <summary>Enqueue a window size event if the window size has changed.</summary>
-    /// <param name="winHeight"></param>
-    /// <param name="winWidth"></param>
-    /// <param name="buffHeight"></param>
-    /// <param name="buffWidth"></param>
-    /// <returns></returns>
-    private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
-    {
-        if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows)
-        {
-            return false;
-        }
-
-        int w = Math.Max (winWidth, 0);
-        int h = Math.Max (winHeight, 0);
-
-        _inputQueue.Enqueue (
-                             new InputResult
-                             {
-                                 EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) }
-                             }
-                            );
-
-        return true;
-    }
-
-    // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
-    private void ProcessRequestResponse (
-        ref ConsoleKeyInfo newConsoleKeyInfo,
-        ref ConsoleKey key,
-        ConsoleKeyInfo [] cki,
-        ref ConsoleModifiers mod
-    )
-    {
-        // isMouse is true if it's CSI<, false otherwise
-        EscSeqUtils.DecodeEscSeq (
-                                  EscSeqRequests,
-                                  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 EscSeqReqStatus seqReqStatus,
-                                  (f, p) => HandleMouseEvent (MapMouseFlags (f), p)
-                                 );
-
-        if (isMouse)
-        {
-            foreach (MouseFlags mf in mouseFlags)
-            {
-                HandleMouseEvent (MapMouseFlags (mf), pos);
-            }
-
-            return;
-        }
-
-        if (seqReqStatus is { })
-        {
-            //HandleRequestResponseEvent (c1Control, code, values, terminating);
-
-            var ckiString = EscSeqUtils.ToString (cki);
-
-            lock (seqReqStatus.AnsiRequest._responseLock)
-            {
-                seqReqStatus.AnsiRequest.Response = ckiString;
-                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString);
-            }
-
-            return;
-        }
-
-        if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator))
-        {
-            if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result))
-            {
-                lock (result.AnsiRequest._responseLock)
-                {
-                    result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator;
-                    result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator);
-
-                    EscSeqUtils.InvalidRequestTerminator = null;
-                }
-            }
-
-            return;
-        }
-
-        if (newConsoleKeyInfo != default)
-        {
-            HandleKeyboardEvent (newConsoleKeyInfo);
-        }
-    }
-
-    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
-    private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
-    {
-        MouseButtonState mbs = default;
-
-        foreach (object flag in Enum.GetValues (mouseFlags.GetType ()))
-        {
-            if (mouseFlags.HasFlag ((MouseFlags)flag))
-            {
-                switch (flag)
-                {
-                    case MouseFlags.Button1Pressed:
-                        mbs |= MouseButtonState.Button1Pressed;
-
-                        break;
-                    case MouseFlags.Button1Released:
-                        mbs |= MouseButtonState.Button1Released;
-
-                        break;
-                    case MouseFlags.Button1Clicked:
-                        mbs |= MouseButtonState.Button1Clicked;
-
-                        break;
-                    case MouseFlags.Button1DoubleClicked:
-                        mbs |= MouseButtonState.Button1DoubleClicked;
-
-                        break;
-                    case MouseFlags.Button1TripleClicked:
-                        mbs |= MouseButtonState.Button1TripleClicked;
-
-                        break;
-                    case MouseFlags.Button2Pressed:
-                        mbs |= MouseButtonState.Button2Pressed;
-
-                        break;
-                    case MouseFlags.Button2Released:
-                        mbs |= MouseButtonState.Button2Released;
-
-                        break;
-                    case MouseFlags.Button2Clicked:
-                        mbs |= MouseButtonState.Button2Clicked;
-
-                        break;
-                    case MouseFlags.Button2DoubleClicked:
-                        mbs |= MouseButtonState.Button2DoubleClicked;
-
-                        break;
-                    case MouseFlags.Button2TripleClicked:
-                        mbs |= MouseButtonState.Button2TripleClicked;
-
-                        break;
-                    case MouseFlags.Button3Pressed:
-                        mbs |= MouseButtonState.Button3Pressed;
-
-                        break;
-                    case MouseFlags.Button3Released:
-                        mbs |= MouseButtonState.Button3Released;
-
-                        break;
-                    case MouseFlags.Button3Clicked:
-                        mbs |= MouseButtonState.Button3Clicked;
-
-                        break;
-                    case MouseFlags.Button3DoubleClicked:
-                        mbs |= MouseButtonState.Button3DoubleClicked;
-
-                        break;
-                    case MouseFlags.Button3TripleClicked:
-                        mbs |= MouseButtonState.Button3TripleClicked;
-
-                        break;
-                    case MouseFlags.WheeledUp:
-                        mbs |= MouseButtonState.ButtonWheeledUp;
-
-                        break;
-                    case MouseFlags.WheeledDown:
-                        mbs |= MouseButtonState.ButtonWheeledDown;
-
-                        break;
-                    case MouseFlags.WheeledLeft:
-                        mbs |= MouseButtonState.ButtonWheeledLeft;
-
-                        break;
-                    case MouseFlags.WheeledRight:
-                        mbs |= MouseButtonState.ButtonWheeledRight;
-
-                        break;
-                    case MouseFlags.Button4Pressed:
-                        mbs |= MouseButtonState.Button4Pressed;
-
-                        break;
-                    case MouseFlags.Button4Released:
-                        mbs |= MouseButtonState.Button4Released;
-
-                        break;
-                    case MouseFlags.Button4Clicked:
-                        mbs |= MouseButtonState.Button4Clicked;
-
-                        break;
-                    case MouseFlags.Button4DoubleClicked:
-                        mbs |= MouseButtonState.Button4DoubleClicked;
-
-                        break;
-                    case MouseFlags.Button4TripleClicked:
-                        mbs |= MouseButtonState.Button4TripleClicked;
-
-                        break;
-                    case MouseFlags.ButtonShift:
-                        mbs |= MouseButtonState.ButtonShift;
-
-                        break;
-                    case MouseFlags.ButtonCtrl:
-                        mbs |= MouseButtonState.ButtonCtrl;
-
-                        break;
-                    case MouseFlags.ButtonAlt:
-                        mbs |= MouseButtonState.ButtonAlt;
-
-                        break;
-                    case MouseFlags.ReportMousePosition:
-                        mbs |= MouseButtonState.ReportMousePosition;
-
-                        break;
-                    case MouseFlags.AllEvents:
-                        mbs |= MouseButtonState.AllEvents;
-
-                        break;
-                }
-            }
-        }
-
-        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 void HandleMouseEvent (MouseButtonState buttonState, Point pos)
-    {
-        var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
-
-        _inputQueue.Enqueue (
-                             new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent }
-                            );
-
-        _inputReady.Set ();
-    }
-
-    public enum EventType
-    {
-        Key = 1,
-        Mouse = 2,
-        WindowSize = 3,
-        WindowPosition = 4,
-        RequestResponse = 5
-    }
-
-    [Flags]
-    public enum MouseButtonState
-    {
-        Button1Pressed = 0x1,
-        Button1Released = 0x2,
-        Button1Clicked = 0x4,
-        Button1DoubleClicked = 0x8,
-        Button1TripleClicked = 0x10,
-        Button2Pressed = 0x20,
-        Button2Released = 0x40,
-        Button2Clicked = 0x80,
-        Button2DoubleClicked = 0x100,
-        Button2TripleClicked = 0x200,
-        Button3Pressed = 0x400,
-        Button3Released = 0x800,
-        Button3Clicked = 0x1000,
-        Button3DoubleClicked = 0x2000,
-        Button3TripleClicked = 0x4000,
-        ButtonWheeledUp = 0x8000,
-        ButtonWheeledDown = 0x10000,
-        ButtonWheeledLeft = 0x20000,
-        ButtonWheeledRight = 0x40000,
-        Button4Pressed = 0x80000,
-        Button4Released = 0x100000,
-        Button4Clicked = 0x200000,
-        Button4DoubleClicked = 0x400000,
-        Button4TripleClicked = 0x800000,
-        ButtonShift = 0x1000000,
-        ButtonCtrl = 0x2000000,
-        ButtonAlt = 0x4000000,
-        ReportMousePosition = 0x8000000,
-        AllEvents = -1
-    }
-
-    public struct MouseEvent
-    {
-        public Point Position;
-        public MouseButtonState ButtonState;
-    }
-
-    public struct WindowSizeEvent
-    {
-        public Size Size;
-    }
-
-    public struct WindowPositionEvent
-    {
-        public int Top;
-        public int Left;
-        public Point CursorPosition;
-    }
-
-    public struct RequestResponseEvent
-    {
-        public (string c1Control, string code, string [] values, string terminating) ResultTuple;
-    }
-
-    public struct InputResult
-    {
-        public EventType EventType;
-        public ConsoleKeyInfo ConsoleKeyInfo;
-        public MouseEvent MouseEvent;
-        public WindowSizeEvent WindowSizeEvent;
-        public WindowPositionEvent WindowPositionEvent;
-        public RequestResponseEvent RequestResponseEvent;
-
-        public readonly override string ToString ()
-        {
-            return EventType switch
-                   {
-                       EventType.Key => ToString (ConsoleKeyInfo),
-                       EventType.Mouse => MouseEvent.ToString (),
-
-                       //EventType.WindowSize => WindowSize.ToString (),
-                       //EventType.RequestResponse => RequestResponse.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})]";
-        }
-    }
-
-    private void HandleKeyboardEvent (ConsoleKeyInfo cki)
-    {
-        var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
-
-        _inputQueue.Enqueue (inputResult);
-    }
-
-    public void Dispose ()
-    {
-        _inputReadyCancellationTokenSource?.Cancel ();
-        _inputReadyCancellationTokenSource?.Dispose ();
-        _inputReadyCancellationTokenSource = null;
-
-        try
-        {
-            // throws away any typeahead that has been typed by
-            // the user and has not yet been read by the program.
-            while (Console.KeyAvailable)
-            {
-                Console.ReadKey (true);
-            }
-        }
-        catch (InvalidOperationException)
-        {
-            // Ignore - Console input has already been closed
-        }
-    }
-}
-
-internal class NetDriver : ConsoleDriver
-{
-    private const int COLOR_BLACK = 30;
-    private const int COLOR_BLUE = 34;
-    private const int COLOR_BRIGHT_BLACK = 90;
-    private const int COLOR_BRIGHT_BLUE = 94;
-    private const int COLOR_BRIGHT_CYAN = 96;
-    private const int COLOR_BRIGHT_GREEN = 92;
-    private const int COLOR_BRIGHT_MAGENTA = 95;
-    private const int COLOR_BRIGHT_RED = 91;
-    private const int COLOR_BRIGHT_WHITE = 97;
-    private const int COLOR_BRIGHT_YELLOW = 93;
-    private const int COLOR_CYAN = 36;
-    private const int COLOR_GREEN = 32;
-    private const int COLOR_MAGENTA = 35;
-    private const int COLOR_RED = 31;
-    private const int COLOR_WHITE = 37;
-    private const int COLOR_YELLOW = 33;
-    internal NetMainLoop _mainLoopDriver;
-    public bool IsWinPlatform { get; private set; }
-    public NetWinVTConsole NetWinConsole { get; private set; }
-
-    public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix
-                                              || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
-
-    public override void Refresh ()
-    {
-        UpdateScreen ();
-        UpdateCursor ();
-    }
-
-    public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
-    {
-        var input = new InputResult
-        {
-            EventType = EventType.Key, ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
-        };
-
-        try
-        {
-            ProcessInput (input);
-        }
-        catch (OverflowException)
-        { }
-    }
-
-    public override void Suspend ()
-    {
-        if (Environment.OSVersion.Platform != PlatformID.Unix)
-        {
-            return;
-        }
-
-        StopReportingMouseMoves ();
-
-        if (!RunningUnitTests)
-        {
-            Console.ResetColor ();
-            Console.Clear ();
-
-            //Disable alternative screen buffer.
-            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
-            //Set cursor key to cursor.
-            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-
-            Platform.Suspend ();
-
-            //Enable alternative screen buffer.
-            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
-            SetContentsAsDirty ();
-            Refresh ();
-        }
-
-        StartReportingMouseMoves ();
-    }
-
-    public override void UpdateScreen ()
-    {
-        if (RunningUnitTests
-            || _winSizeChanging
-            || Console.WindowHeight < 1
-            || Contents.Length != Rows * Cols
-            || Rows != Console.WindowHeight)
-        {
-            return;
-        }
-
-        var top = 0;
-        var left = 0;
-        int rows = Rows;
-        int cols = Cols;
-        var output = new StringBuilder ();
-        Attribute? redrawAttr = null;
-        int lastCol = -1;
-
-        CursorVisibility? savedVisibility = _cachedCursorVisibility;
-        SetCursorVisibility (CursorVisibility.Invisible);
-
-        for (int row = top; row < rows; row++)
-        {
-            if (Console.WindowHeight < 1)
-            {
-                return;
-            }
-
-            if (!_dirtyLines [row])
-            {
-                continue;
-            }
-
-            if (!SetCursorPosition (0, row))
-            {
-                return;
-            }
-
-            _dirtyLines [row] = false;
-            output.Clear ();
-
-            for (int col = left; col < cols; col++)
-            {
-                lastCol = -1;
-                var outputWidth = 0;
-
-                for (; col < cols; col++)
-                {
-                    if (!Contents [row, col].IsDirty)
-                    {
-                        if (output.Length > 0)
-                        {
-                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        }
-                        else if (lastCol == -1)
-                        {
-                            lastCol = col;
-                        }
-
-                        if (lastCol + 1 < cols)
-                        {
-                            lastCol++;
-                        }
-
-                        continue;
-                    }
-
-                    if (lastCol == -1)
-                    {
-                        lastCol = col;
-                    }
-
-                    Attribute attr = Contents [row, col].Attribute.Value;
-
-                    // Performance: Only send the escape sequence if the attribute has changed.
-                    if (attr != redrawAttr)
-                    {
-                        redrawAttr = attr;
-
-                        if (Force16Colors)
-                        {
-                            output.Append (
-                                           EscSeqUtils.CSI_SetGraphicsRendition (
-                                                                                 MapColors (
-                                                                                            (ConsoleColor)attr.Background.GetClosestNamedColor16 (),
-                                                                                            false
-                                                                                           ),
-                                                                                 MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ())
-                                                                                )
-                                          );
-                        }
-                        else
-                        {
-                            output.Append (
-                                           EscSeqUtils.CSI_SetForegroundColorRGB (
-                                                                                  attr.Foreground.R,
-                                                                                  attr.Foreground.G,
-                                                                                  attr.Foreground.B
-                                                                                 )
-                                          );
-
-                            output.Append (
-                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
-                                                                                  attr.Background.R,
-                                                                                  attr.Background.G,
-                                                                                  attr.Background.B
-                                                                                 )
-                                          );
-                        }
-                    }
-
-                    outputWidth++;
-                    Rune rune = Contents [row, col].Rune;
-                    output.Append (rune);
-
-                    if (Contents [row, col].CombiningMarks.Count > 0)
-                    {
-                        // AtlasEngine does not support NON-NORMALIZED combining marks in a way
-                        // compatible with the driver architecture. Any CMs (except in the first col)
-                        // are correctly combined with the base char, but are ALSO treated as 1 column
-                        // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
-                        // 
-                        // For now, we just ignore the list of CMs.
-                        //foreach (var combMark in Contents [row, col].CombiningMarks) {
-                        //	output.Append (combMark);
-                        //}
-                        // WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                    }
-                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
-                    {
-                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        SetCursorPosition (col - 1, row);
-                    }
-
-                    Contents [row, col].IsDirty = false;
-                }
-            }
-
-            if (output.Length > 0)
-            {
-                SetCursorPosition (lastCol, row);
-                Console.Write (output);
-            }
-
-            foreach (var s in Application.Sixel)
-            {
-                if (!string.IsNullOrWhiteSpace (s.SixelData))
-                {
-                    SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y);
-                    Console.Write (s.SixelData);
-                }
-            }
-        }
-
-        SetCursorPosition (0, 0);
-
-        _cachedCursorVisibility = savedVisibility;
-
-        void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
-        {
-            SetCursorPosition (lastCol, row);
-            Console.Write (output);
-            output.Clear ();
-            lastCol += outputWidth;
-            outputWidth = 0;
-        }
-    }
-
-    internal override void End ()
-    {
-        if (IsWinPlatform)
-        {
-            NetWinConsole?.Cleanup ();
-        }
-
-        StopReportingMouseMoves ();
-
-        _ansiResponseTokenSource?.Cancel ();
-        _ansiResponseTokenSource?.Dispose ();
-
-        _waitAnsiResponse?.Dispose ();
-
-        if (!RunningUnitTests)
-        {
-            Console.ResetColor ();
-
-            //Disable alternative screen buffer.
-            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
-            //Set cursor key to cursor.
-            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-            Console.Out.Close ();
-        }
-    }
-
-    internal override MainLoop Init ()
-    {
-        PlatformID p = Environment.OSVersion.Platform;
-
-        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
-        {
-            IsWinPlatform = true;
-
-            try
-            {
-                NetWinConsole = new NetWinVTConsole ();
-            }
-            catch (ApplicationException)
-            {
-                // Likely running as a unit test, or in a non-interactive session.
-            }
-        }
-
-        if (IsWinPlatform)
-        {
-            Clipboard = new WindowsClipboard ();
-        }
-        else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
-        {
-            Clipboard = new MacOSXClipboard ();
-        }
-        else
-        {
-            if (CursesDriver.Is_WSL_Platform ())
-            {
-                Clipboard = new WSLClipboard ();
-            }
-            else
-            {
-                Clipboard = new CursesClipboard ();
-            }
-        }
-
-        if (!RunningUnitTests)
-        {
-            Console.TreatControlCAsInput = true;
-
-            Cols = Console.WindowWidth;
-            Rows = Console.WindowHeight;
-
-            //Enable alternative screen buffer.
-            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
-            //Set cursor key to application.
-            Console.Out.Write (EscSeqUtils.CSI_HideCursor);
-        }
-        else
-        {
-            // We are being run in an environment that does not support a console
-            // such as a unit test, or a pipe.
-            Cols = 80;
-            Rows = 24;
-        }
-
-        ResizeScreen ();
-        ClearContents ();
-        CurrentAttribute = new Attribute (Color.White, Color.Black);
-
-        StartReportingMouseMoves ();
-
-        _mainLoopDriver = new NetMainLoop (this);
-        _mainLoopDriver.ProcessInput = ProcessInput;
-
-
-        return new MainLoop (_mainLoopDriver);
-    }
-
-    private void ProcessInput (InputResult inputEvent)
-    {
-        switch (inputEvent.EventType)
-        {
-            case EventType.Key:
-                ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
-
-                //if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-                //	consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-                //}
-
-                //Debug.WriteLine ($"event: {inputEvent}");
-
-                KeyCode map = MapKey (consoleKeyInfo);
-
-                if (map == KeyCode.Null)
-                {
-                    break;
-                }
-
-                OnKeyDown (new Key (map));
-                OnKeyUp (new Key (map));
-
-                break;
-            case EventType.Mouse:
-                MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
-                //Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
-                OnMouseEvent (me);
-
-                break;
-            case EventType.WindowSize:
-                _winSizeChanging = true;
-                Top = 0;
-                Left = 0;
-                Cols = inputEvent.WindowSizeEvent.Size.Width;
-                Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0);
-                ;
-                ResizeScreen ();
-                ClearContents ();
-                _winSizeChanging = false;
-                OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows)));
-
-                break;
-            case EventType.RequestResponse:
-                break;
-            case EventType.WindowPosition:
-                break;
-            default:
-                throw new ArgumentOutOfRangeException ();
-        }
-    }
-
-    #region Size and Position Handling
-
-    private volatile bool _winSizeChanging;
-
-    private void SetWindowPosition (int col, int row)
-    {
-        if (!RunningUnitTests)
-        {
-            Top = Console.WindowTop;
-            Left = Console.WindowLeft;
-        }
-        else
-        {
-            Top = row;
-            Left = col;
-        }
-    }
-
-    public virtual void ResizeScreen ()
-    {
-        // Not supported on Unix.
-        if (IsWinPlatform)
-        {
-            // Can raise an exception while is still resizing.
-            try
-            {
-#pragma warning disable CA1416
-                if (Console.WindowHeight > 0)
-                {
-                    Console.CursorTop = 0;
-                    Console.CursorLeft = 0;
-                    Console.WindowTop = 0;
-                    Console.WindowLeft = 0;
-
-                    if (Console.WindowHeight > Rows)
-                    {
-                        Console.SetWindowSize (Cols, Rows);
-                    }
-
-                    Console.SetBufferSize (Cols, Rows);
-                }
-#pragma warning restore CA1416
-            }
-            // INTENT: Why are these eating the exceptions?
-            // Comments would be good here.
-            catch (IOException)
-            {
-                // CONCURRENCY: Unsynchronized access to Clip is not safe.
-                Clip = new (0, 0, Cols, Rows);
-            }
-            catch (ArgumentOutOfRangeException)
-            {
-                // CONCURRENCY: Unsynchronized access to Clip is not safe.
-                Clip = new (0, 0, Cols, Rows);
-            }
-        }
-        else
-        {
-            Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
-        }
-
-        // CONCURRENCY: Unsynchronized access to Clip is not safe.
-        Clip = new (0, 0, Cols, Rows);
-    }
-
-    #endregion
-
-    #region Color Handling
-
-    // 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 ()
-    {
-        { ConsoleColor.Black, COLOR_BLACK },
-        { ConsoleColor.DarkBlue, COLOR_BLUE },
-        { ConsoleColor.DarkGreen, COLOR_GREEN },
-        { ConsoleColor.DarkCyan, COLOR_CYAN },
-        { ConsoleColor.DarkRed, COLOR_RED },
-        { ConsoleColor.DarkMagenta, COLOR_MAGENTA },
-        { ConsoleColor.DarkYellow, COLOR_YELLOW },
-        { ConsoleColor.Gray, COLOR_WHITE },
-        { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
-        { ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
-        { ConsoleColor.Green, COLOR_BRIGHT_GREEN },
-        { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
-        { ConsoleColor.Red, COLOR_BRIGHT_RED },
-        { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
-        { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
-        { ConsoleColor.White, COLOR_BRIGHT_WHITE }
-    };
-
-    // 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;
-    }
-
-    ///// <remarks>
-    ///// In the NetDriver, colors are encoded as an int. 
-    ///// However, the foreground color is stored in the most significant 16 bits, 
-    ///// and the background color is stored in the least significant 16 bits.
-    ///// </remarks>
-    //public override Attribute MakeColor (Color foreground, Color background)
-    //{
-    //	// Encode the colors into the int value.
-    //	return new Attribute (
-    //		platformColor: ((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
-    //		foreground: foreground,
-    //		background: background
-    //	);
-    //}
-
-    #endregion
-
-    #region Cursor Handling
-
-    private bool SetCursorPosition (int col, int row)
-    {
-        if (IsWinPlatform)
-        {
-            // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
-            try
-            {
-                Console.SetCursorPosition (col, row);
-
-                return true;
-            }
-            catch (Exception)
-            {
-                return false;
-            }
-        }
-
-        // + 1 is needed because non-Windows is based on 1 instead of 0 and
-        // Console.CursorTop/CursorLeft isn't reliable.
-        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
-
-        return true;
-    }
-
-    private CursorVisibility? _cachedCursorVisibility;
-
-    public override void UpdateCursor ()
-    {
-        EnsureCursorVisibility ();
-
-        if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows)
-        {
-            SetCursorPosition (Col, Row);
-            SetWindowPosition (0, Row);
-        }
-    }
-
-    public override bool GetCursorVisibility (out CursorVisibility visibility)
-    {
-        visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
-
-        return visibility == CursorVisibility.Default;
-    }
-
-    public override bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        _cachedCursorVisibility = visibility;
-
-        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
-
-        return visibility == CursorVisibility.Default;
-    }
-
-    public override bool EnsureCursorVisibility ()
-    {
-        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
-        {
-            GetCursorVisibility (out CursorVisibility cursorVisibility);
-            _cachedCursorVisibility = cursorVisibility;
-            SetCursorVisibility (CursorVisibility.Invisible);
-
-            return false;
-        }
-
-        SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
-
-        return _cachedCursorVisibility == CursorVisibility.Default;
-    }
-
-    #endregion
-
-    #region Mouse Handling
-
-    public void StartReportingMouseMoves ()
-    {
-        if (!RunningUnitTests)
-        {
-            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
-        }
-    }
-
-    public void StopReportingMouseMoves ()
-    {
-        if (!RunningUnitTests)
-        {
-            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
-        }
-    }
-
-    private readonly ManualResetEventSlim _waitAnsiResponse = new (false);
-    private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
-
-    /// <inheritdoc/>
-    public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
-    {
-        if (_mainLoopDriver is null)
-        {
-            return string.Empty;
-        }
-
-        try
-        {
-            lock (ansiRequest._responseLock)
-            {
-                ansiRequest.ResponseFromInput += (s, e) =>
-                                                 {
-                                                     Debug.Assert (s == ansiRequest);
-                                                     Debug.Assert (e == ansiRequest.Response);
-
-                                                     _waitAnsiResponse.Set ();
-                                                 };
-
-                _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest);
-
-                _mainLoopDriver._netEvents._forceRead = true;
-            }
-
-            if (!_ansiResponseTokenSource.IsCancellationRequested)
-            {
-                _mainLoopDriver._netEvents._waitForStart.Set ();
-
-                if (!_mainLoopDriver._waitForProbe.IsSet)
-                {
-                    _mainLoopDriver._waitForProbe.Set ();
-                }
-
-                _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
-            }
-        }
-        catch (OperationCanceledException)
-        {
-            return string.Empty;
-        }
-
-        lock (ansiRequest._responseLock)
-        {
-            _mainLoopDriver._netEvents._forceRead = false;
-
-            if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request))
-            {
-                if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0
-                    && string.IsNullOrEmpty (request.AnsiRequest.Response))
-                {
-                    lock (request!.AnsiRequest._responseLock)
-                    {
-                        // Bad request or no response at all
-                        _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _);
-                    }
-                }
-            }
-
-            _waitAnsiResponse.Reset ();
-
-            return ansiRequest.Response;
-        }
-    }
-
-    /// <inheritdoc />
-    public override void WriteRaw (string ansi) { throw new NotImplementedException (); }
-
-    private MouseEventArgs ToDriverMouse (NetEvents.MouseEvent me)
-    {
-       //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
-
-        MouseFlags mouseFlag = 0;
-
-        if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0)
-        {
-            mouseFlag |= MouseFlags.Button1Pressed;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button1Released) != 0)
-        {
-            mouseFlag |= MouseFlags.Button1Released;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button1Clicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button1DoubleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button1TripleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0)
-        {
-            mouseFlag |= MouseFlags.Button2Pressed;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button2Released) != 0)
-        {
-            mouseFlag |= MouseFlags.Button2Released;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button2Clicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button2DoubleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button2TripleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0)
-        {
-            mouseFlag |= MouseFlags.Button3Pressed;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button3Released) != 0)
-        {
-            mouseFlag |= MouseFlags.Button3Released;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button3Clicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button3DoubleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button3TripleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0)
-        {
-            mouseFlag |= MouseFlags.WheeledUp;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0)
-        {
-            mouseFlag |= MouseFlags.WheeledDown;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0)
-        {
-            mouseFlag |= MouseFlags.WheeledLeft;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0)
-        {
-            mouseFlag |= MouseFlags.WheeledRight;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0)
-        {
-            mouseFlag |= MouseFlags.Button4Pressed;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button4Released) != 0)
-        {
-            mouseFlag |= MouseFlags.Button4Released;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button4Clicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button4DoubleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0)
-        {
-            mouseFlag |= MouseFlags.Button4TripleClicked;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0)
-        {
-            mouseFlag |= MouseFlags.ReportMousePosition;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonShift) != 0)
-        {
-            mouseFlag |= MouseFlags.ButtonShift;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0)
-        {
-            mouseFlag |= MouseFlags.ButtonCtrl;
-        }
-
-        if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0)
-        {
-            mouseFlag |= MouseFlags.ButtonAlt;
-        }
-
-        return new MouseEventArgs { Position = me.Position, Flags = mouseFlag };
-    }
-
-    #endregion Mouse Handling
-
-    #region Keyboard Handling
-
-    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;
-
-        ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-
-        return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
-    }
-
-    private KeyCode MapKey (ConsoleKeyInfo keyInfo)
-    {
-        switch (keyInfo.Key)
-        {
-            case ConsoleKey.OemPeriod:
-            case ConsoleKey.OemComma:
-            case ConsoleKey.OemPlus:
-            case ConsoleKey.OemMinus:
-            case ConsoleKey.Packet:
-            case ConsoleKey.Oem1:
-            case ConsoleKey.Oem2:
-            case ConsoleKey.Oem3:
-            case ConsoleKey.Oem4:
-            case ConsoleKey.Oem5:
-            case ConsoleKey.Oem6:
-            case ConsoleKey.Oem7:
-            case ConsoleKey.Oem8:
-            case ConsoleKey.Oem102:
-                if (keyInfo.KeyChar == 0)
-                {
-                    // If the keyChar is 0, keyInfo.Key value is not a printable character. 
-
-                    return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
-                }
-
-                if (keyInfo.Modifiers != ConsoleModifiers.Shift)
-                {
-                    // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
-                    return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
-                }
-
-                // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
-                // and passing on Shift would be redundant.
-                return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
-        }
-
-        // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
-        if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
-        {
-            if (keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I)
-            {
-                return KeyCode.Tab;
-            }
-
-            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key));
-        }
-
-        // Handle control keys (e.g. CursorUp)
-        if (keyInfo.Key != ConsoleKey.None
-            && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))
-        {
-            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
-        }
-
-        if (((ConsoleKey)keyInfo.KeyChar) is >= ConsoleKey.A and <= ConsoleKey.Z)
-        {
-            // Shifted
-            keyInfo = new ConsoleKeyInfo (
-                                          keyInfo.KeyChar,
-                                          (ConsoleKey)keyInfo.KeyChar,
-                                          true,
-                                          keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
-                                          keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
-        }
-
-        if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z)
-        {
-            // Unshifted
-            keyInfo = new ConsoleKeyInfo (
-                                          keyInfo.KeyChar,
-                                          (ConsoleKey)(keyInfo.KeyChar - 32),
-                                          false,
-                                          keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
-                                          keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
-        }
-
-        if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z )
-        {
-            if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
-                || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
-            {
-                // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos
-                return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
-            }
-
-            if (keyInfo.Modifiers == ConsoleModifiers.Shift)
-            {
-                // If ShiftMask is on  add the ShiftMask
-                if (char.IsUpper (keyInfo.KeyChar))
-                {
-                    return (KeyCode)keyInfo.Key | KeyCode.ShiftMask;
-                }
-            }
-
-            return (KeyCode)keyInfo.Key;
-        }
-
-
-        return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
-    }
-
-    #endregion Keyboard Handling
-}
-
-/// <summary>
-///     Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is
-///     cross-platform but lacks things like file descriptor monitoring.
-/// </summary>
-/// <remarks>This implementation is used for NetDriver.</remarks>
-internal class NetMainLoop : IMainLoopDriver
-{
-    internal NetEvents _netEvents;
-
-    /// <summary>Invoked when a Key is pressed.</summary>
-    internal Action<InputResult> ProcessInput;
-
-    private readonly ManualResetEventSlim _eventReady = new (false);
-    private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
-    private readonly ConcurrentQueue<InputResult?> _resultQueue = new ();
-    internal readonly ManualResetEventSlim _waitForProbe = new (false);
-    private readonly CancellationTokenSource _eventReadyTokenSource = new ();
-    private MainLoop _mainLoop;
-
-    /// <summary>Initializes the class with the console driver.</summary>
-    /// <remarks>Passing a consoleDriver is provided to capture windows resizing.</remarks>
-    /// <param name="consoleDriver">The console driver used by this Net main loop.</param>
-    /// <exception cref="ArgumentNullException"></exception>
-    public NetMainLoop (ConsoleDriver consoleDriver = null)
-    {
-        if (consoleDriver is null)
-        {
-            throw new ArgumentNullException (nameof (consoleDriver));
-        }
-
-        _netEvents = new NetEvents (consoleDriver);
-    }
-
-    void IMainLoopDriver.Setup (MainLoop mainLoop)
-    {
-        _mainLoop = mainLoop;
-
-        if (ConsoleDriver.RunningUnitTests)
-        {
-            return;
-        }
-
-        Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
-    }
-
-    void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
-
-    bool IMainLoopDriver.EventsPending ()
-    {
-        _waitForProbe.Set ();
-
-        if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout))
-        {
-            return true;
-        }
-
-        try
-        {
-            if (!_eventReadyTokenSource.IsCancellationRequested)
-            {
-                // 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);
-            }
-        }
-        catch (OperationCanceledException)
-        {
-            return true;
-        }
-        finally
-        {
-            _eventReady.Reset ();
-        }
-
-        _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
-
-        if (!_eventReadyTokenSource.IsCancellationRequested)
-        {
-            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
-        }
-
-        return true;
-    }
-
-    void IMainLoopDriver.Iteration ()
-    {
-        while (_resultQueue.Count > 0)
-        {
-            // Always dequeue even if it's null and invoke if isn't null
-            if (_resultQueue.TryDequeue (out InputResult? dequeueResult))
-            {
-                if (dequeueResult is { })
-                {
-                    ProcessInput?.Invoke (dequeueResult.Value);
-                }
-            }
-        }
-    }
-
-    void IMainLoopDriver.TearDown ()
-    {
-        _inputHandlerTokenSource?.Cancel ();
-        _inputHandlerTokenSource?.Dispose ();
-        _eventReadyTokenSource?.Cancel ();
-        _eventReadyTokenSource?.Dispose ();
-
-        _eventReady?.Dispose ();
-
-        _resultQueue?.Clear ();
-        _waitForProbe?.Dispose ();
-        _netEvents?.Dispose ();
-        _netEvents = null;
-
-        _mainLoop = null;
-    }
-
-    private void NetInputHandler ()
-    {
-        while (_mainLoop is { })
-        {
-            try
-            {
-                if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested)
-                {
-                    _waitForProbe.Wait (_inputHandlerTokenSource.Token);
-                }
-            }
-            catch (OperationCanceledException)
-            {
-                return;
-            }
-            finally
-            {
-                if (_waitForProbe.IsSet)
-                {
-                    _waitForProbe.Reset ();
-                }
-            }
-
-            if (_inputHandlerTokenSource.IsCancellationRequested)
-            {
-                return;
-            }
-
-            _inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
-
-            if (_resultQueue.Count == 0)
-            {
-                _resultQueue.Enqueue (_netEvents.DequeueInput ());
-            }
-
-            try
-            {
-                while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out InputResult? result) && result is null)
-                {
-                    // Dequeue null values
-                    _resultQueue.TryDequeue (out _);
-                }
-            }
-            catch (InvalidOperationException) // Peek can raise an exception
-            { }
-
-            if (_resultQueue.Count > 0)
-            {
-                _eventReady.Set ();
-            }
-        }
-    }
-}

+ 965 - 0
Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

@@ -0,0 +1,965 @@
+// TODO: #nullable enable
+//
+// NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
+//
+
+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;
+
+internal class NetDriver : ConsoleDriver
+{
+    public bool IsWinPlatform { get; private set; }
+    public NetWinVTConsole NetWinConsole { get; private set; }
+
+    public override void Refresh ()
+    {
+        UpdateScreen ();
+        UpdateCursor ();
+    }
+
+    public override void Suspend ()
+    {
+        if (Environment.OSVersion.Platform != PlatformID.Unix)
+        {
+            return;
+        }
+
+        StopReportingMouseMoves ();
+
+        if (!RunningUnitTests)
+        {
+            Console.ResetColor ();
+            Console.Clear ();
+
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+            //Set cursor key to cursor.
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
+            Platform.Suspend ();
+
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+            SetContentsAsDirty ();
+            Refresh ();
+        }
+
+        StartReportingMouseMoves ();
+    }
+
+    #region Screen and Contents
+
+    public override void UpdateScreen ()
+    {
+        if (RunningUnitTests
+            || _winSizeChanging
+            || Console.WindowHeight < 1
+            || Contents.Length != Rows * Cols
+            || Rows != Console.WindowHeight)
+        {
+            return;
+        }
+
+        var top = 0;
+        var left = 0;
+        int rows = Rows;
+        int cols = Cols;
+        var output = new StringBuilder ();
+        Attribute? redrawAttr = null;
+        int lastCol = -1;
+
+        CursorVisibility? savedVisibility = _cachedCursorVisibility;
+        SetCursorVisibility (CursorVisibility.Invisible);
+
+        for (int row = top; row < rows; row++)
+        {
+            if (Console.WindowHeight < 1)
+            {
+                return;
+            }
+
+            if (!_dirtyLines [row])
+            {
+                continue;
+            }
+
+            if (!SetCursorPosition (0, row))
+            {
+                return;
+            }
+
+            _dirtyLines [row] = false;
+            output.Clear ();
+
+            for (int col = left; col < cols; col++)
+            {
+                lastCol = -1;
+                var outputWidth = 0;
+
+                for (; col < cols; col++)
+                {
+                    if (!Contents [row, col].IsDirty)
+                    {
+                        if (output.Length > 0)
+                        {
+                            WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        }
+                        else if (lastCol == -1)
+                        {
+                            lastCol = col;
+                        }
+
+                        if (lastCol + 1 < cols)
+                        {
+                            lastCol++;
+                        }
+
+                        continue;
+                    }
+
+                    if (lastCol == -1)
+                    {
+                        lastCol = col;
+                    }
+
+                    Attribute attr = Contents [row, col].Attribute.Value;
+
+                    // Performance: Only send the escape sequence if the attribute has changed.
+                    if (attr != redrawAttr)
+                    {
+                        redrawAttr = attr;
+
+                        if (Force16Colors)
+                        {
+                            output.Append (
+                                           EscSeqUtils.CSI_SetGraphicsRendition (
+                                                                                 MapColors (
+                                                                                            (ConsoleColor)attr.Background.GetClosestNamedColor16 (),
+                                                                                            false
+                                                                                           ),
+                                                                                 MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ())
+                                                                                )
+                                          );
+                        }
+                        else
+                        {
+                            output.Append (
+                                           EscSeqUtils.CSI_SetForegroundColorRGB (
+                                                                                  attr.Foreground.R,
+                                                                                  attr.Foreground.G,
+                                                                                  attr.Foreground.B
+                                                                                 )
+                                          );
+
+                            output.Append (
+                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
+                                                                                  attr.Background.R,
+                                                                                  attr.Background.G,
+                                                                                  attr.Background.B
+                                                                                 )
+                                          );
+                        }
+                    }
+
+                    outputWidth++;
+                    Rune rune = Contents [row, col].Rune;
+                    output.Append (rune);
+
+                    if (Contents [row, col].CombiningMarks.Count > 0)
+                    {
+                        // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+                        // compatible with the driver architecture. Any CMs (except in the first col)
+                        // are correctly combined with the base char, but are ALSO treated as 1 column
+                        // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+                        // 
+                        // For now, we just ignore the list of CMs.
+                        //foreach (var combMark in Contents [row, col].CombiningMarks) {
+                        //	output.Append (combMark);
+                        //}
+                        // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                    }
+                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+                    {
+                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        SetCursorPosition (col - 1, row);
+                    }
+
+                    Contents [row, col].IsDirty = false;
+                }
+            }
+
+            if (output.Length > 0)
+            {
+                SetCursorPosition (lastCol, row);
+                Console.Write (output);
+            }
+
+            foreach (SixelToRender s in Application.Sixel)
+            {
+                if (!string.IsNullOrWhiteSpace (s.SixelData))
+                {
+                    SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y);
+                    Console.Write (s.SixelData);
+                }
+            }
+        }
+
+        SetCursorPosition (0, 0);
+
+        _cachedCursorVisibility = savedVisibility;
+
+        void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+        {
+            SetCursorPosition (lastCol, row);
+            Console.Write (output);
+            output.Clear ();
+            lastCol += outputWidth;
+            outputWidth = 0;
+        }
+    }
+
+    #endregion Screen and Contents
+
+    #region Init/End/MainLoop
+
+    internal NetMainLoop _mainLoopDriver;
+
+    internal override MainLoop Init ()
+    {
+        PlatformID p = Environment.OSVersion.Platform;
+
+        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            IsWinPlatform = true;
+
+            try
+            {
+                NetWinConsole = new ();
+            }
+            catch (ApplicationException)
+            {
+                // Likely running as a unit test, or in a non-interactive session.
+            }
+        }
+
+        if (IsWinPlatform)
+        {
+            Clipboard = new WindowsClipboard ();
+        }
+        else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
+        {
+            Clipboard = new MacOSXClipboard ();
+        }
+        else
+        {
+            if (CursesDriver.Is_WSL_Platform ())
+            {
+                Clipboard = new WSLClipboard ();
+            }
+            else
+            {
+                Clipboard = new CursesClipboard ();
+            }
+        }
+
+        if (!RunningUnitTests)
+        {
+            Console.TreatControlCAsInput = true;
+
+            Cols = Console.WindowWidth;
+            Rows = Console.WindowHeight;
+
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+            //Set cursor key to application.
+            Console.Out.Write (EscSeqUtils.CSI_HideCursor);
+        }
+        else
+        {
+            // We are being run in an environment that does not support a console
+            // such as a unit test, or a pipe.
+            Cols = 80;
+            Rows = 24;
+        }
+
+        ResizeScreen ();
+        ClearContents ();
+        CurrentAttribute = new (Color.White, Color.Black);
+
+        StartReportingMouseMoves ();
+
+        _mainLoopDriver = new (this);
+        _mainLoopDriver.ProcessInput = ProcessInput;
+
+        return new (_mainLoopDriver);
+    }
+
+    private void ProcessInput (InputResult inputEvent)
+    {
+        switch (inputEvent.EventType)
+        {
+            case EventType.Key:
+                ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
+
+                //if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+                //	consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+                //}
+
+                //Debug.WriteLine ($"event: {inputEvent}");
+
+                KeyCode map = MapKey (consoleKeyInfo);
+
+                if (map == KeyCode.Null)
+                {
+                    break;
+                }
+
+                OnKeyDown (new (map));
+                OnKeyUp (new (map));
+
+                break;
+            case EventType.Mouse:
+                MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
+
+                //Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
+                OnMouseEvent (me);
+
+                break;
+            case EventType.WindowSize:
+                _winSizeChanging = true;
+                Top = 0;
+                Left = 0;
+                Cols = inputEvent.WindowSizeEvent.Size.Width;
+                Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0);
+                ;
+                ResizeScreen ();
+                ClearContents ();
+                _winSizeChanging = false;
+                OnSizeChanged (new (new (Cols, Rows)));
+
+                break;
+            case EventType.RequestResponse:
+                break;
+            case EventType.WindowPosition:
+                break;
+            default:
+                throw new ArgumentOutOfRangeException ();
+        }
+    }
+
+    internal override void End ()
+    {
+        if (IsWinPlatform)
+        {
+            NetWinConsole?.Cleanup ();
+        }
+
+        StopReportingMouseMoves ();
+
+        _ansiResponseTokenSource?.Cancel ();
+        _ansiResponseTokenSource?.Dispose ();
+
+        _waitAnsiResponse?.Dispose ();
+
+        if (!RunningUnitTests)
+        {
+            Console.ResetColor ();
+
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+            //Set cursor key to cursor.
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+            Console.Out.Close ();
+        }
+    }
+
+    #endregion Init/End/MainLoop
+
+    #region Color Handling
+
+    public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix
+                                              || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
+
+    private const int COLOR_BLACK = 30;
+    private const int COLOR_BLUE = 34;
+    private const int COLOR_BRIGHT_BLACK = 90;
+    private const int COLOR_BRIGHT_BLUE = 94;
+    private const int COLOR_BRIGHT_CYAN = 96;
+    private const int COLOR_BRIGHT_GREEN = 92;
+    private const int COLOR_BRIGHT_MAGENTA = 95;
+    private const int COLOR_BRIGHT_RED = 91;
+    private const int COLOR_BRIGHT_WHITE = 97;
+    private const int COLOR_BRIGHT_YELLOW = 93;
+    private const int COLOR_CYAN = 36;
+    private const int COLOR_GREEN = 32;
+    private const int COLOR_MAGENTA = 35;
+    private const int COLOR_RED = 31;
+    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)
+                                                                  );
+
+    // Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
+    private static readonly Dictionary<ConsoleColor, int> colorMap = new ()
+    {
+        { ConsoleColor.Black, COLOR_BLACK },
+        { ConsoleColor.DarkBlue, COLOR_BLUE },
+        { ConsoleColor.DarkGreen, COLOR_GREEN },
+        { ConsoleColor.DarkCyan, COLOR_CYAN },
+        { ConsoleColor.DarkRed, COLOR_RED },
+        { ConsoleColor.DarkMagenta, COLOR_MAGENTA },
+        { ConsoleColor.DarkYellow, COLOR_YELLOW },
+        { ConsoleColor.Gray, COLOR_WHITE },
+        { ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
+        { ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
+        { ConsoleColor.Green, COLOR_BRIGHT_GREEN },
+        { ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
+        { ConsoleColor.Red, COLOR_BRIGHT_RED },
+        { ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
+        { ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
+        { ConsoleColor.White, COLOR_BRIGHT_WHITE }
+    };
+
+    // 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;
+    }
+
+    #endregion
+
+    #region Cursor Handling
+
+    private bool SetCursorPosition (int col, int row)
+    {
+        if (IsWinPlatform)
+        {
+            // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
+            try
+            {
+                Console.SetCursorPosition (col, row);
+
+                return true;
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+        }
+
+        // + 1 is needed because non-Windows is based on 1 instead of 0 and
+        // Console.CursorTop/CursorLeft isn't reliable.
+        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
+
+        return true;
+    }
+
+    private CursorVisibility? _cachedCursorVisibility;
+
+    public override void UpdateCursor ()
+    {
+        EnsureCursorVisibility ();
+
+        if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows)
+        {
+            SetCursorPosition (Col, Row);
+            SetWindowPosition (0, Row);
+        }
+    }
+
+    public override bool GetCursorVisibility (out CursorVisibility visibility)
+    {
+        visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
+
+        return visibility == CursorVisibility.Default;
+    }
+
+    public override bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _cachedCursorVisibility = visibility;
+
+        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+
+        return visibility == CursorVisibility.Default;
+    }
+
+    public override bool EnsureCursorVisibility ()
+    {
+        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
+        {
+            GetCursorVisibility (out CursorVisibility cursorVisibility);
+            _cachedCursorVisibility = cursorVisibility;
+            SetCursorVisibility (CursorVisibility.Invisible);
+
+            return false;
+        }
+
+        SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
+
+        return _cachedCursorVisibility == CursorVisibility.Default;
+    }
+
+    #endregion
+
+    #region Mouse Handling
+
+    public void StartReportingMouseMoves ()
+    {
+        if (!RunningUnitTests)
+        {
+            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+        }
+    }
+
+    public void StopReportingMouseMoves ()
+    {
+        if (!RunningUnitTests)
+        {
+            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+        }
+    }
+
+    private MouseEventArgs ToDriverMouse (MouseEvent me)
+    {
+        //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+
+        MouseFlags mouseFlag = 0;
+
+        if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledUp;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledDown;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledLeft;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledRight;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0)
+        {
+            mouseFlag |= MouseFlags.ReportMousePosition;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonShift) != 0)
+        {
+            mouseFlag |= MouseFlags.ButtonShift;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0)
+        {
+            mouseFlag |= MouseFlags.ButtonCtrl;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0)
+        {
+            mouseFlag |= MouseFlags.ButtonAlt;
+        }
+
+        return new() { Position = me.Position, Flags = mouseFlag };
+    }
+
+    #endregion Mouse Handling
+
+    #region Keyboard Handling
+
+    public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+    {
+        var input = new InputResult
+        {
+            EventType = EventType.Key, ConsoleKeyInfo = new (keyChar, key, shift, alt, control)
+        };
+
+        try
+        {
+            ProcessInput (input);
+        }
+        catch (OverflowException)
+        { }
+    }
+
+    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;
+
+        ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+
+        return new (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
+    }
+
+    private KeyCode MapKey (ConsoleKeyInfo keyInfo)
+    {
+        switch (keyInfo.Key)
+        {
+            case ConsoleKey.OemPeriod:
+            case ConsoleKey.OemComma:
+            case ConsoleKey.OemPlus:
+            case ConsoleKey.OemMinus:
+            case ConsoleKey.Packet:
+            case ConsoleKey.Oem1:
+            case ConsoleKey.Oem2:
+            case ConsoleKey.Oem3:
+            case ConsoleKey.Oem4:
+            case ConsoleKey.Oem5:
+            case ConsoleKey.Oem6:
+            case ConsoleKey.Oem7:
+            case ConsoleKey.Oem8:
+            case ConsoleKey.Oem102:
+                if (keyInfo.KeyChar == 0)
+                {
+                    // If the keyChar is 0, keyInfo.Key value is not a printable character. 
+
+                    return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
+                }
+
+                if (keyInfo.Modifiers != ConsoleModifiers.Shift)
+                {
+                    // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
+                    return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+                }
+
+                // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
+                // and passing on Shift would be redundant.
+                return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
+        }
+
+        // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+        if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
+        {
+            if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I)
+            {
+                return KeyCode.Tab;
+            }
+
+            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+        }
+
+        // Handle control keys (e.g. CursorUp)
+        if (keyInfo.Key != ConsoleKey.None
+            && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))
+        {
+            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
+        }
+
+        if ((ConsoleKey)keyInfo.KeyChar is >= ConsoleKey.A and <= ConsoleKey.Z)
+        {
+            // Shifted
+            keyInfo = new (
+                           keyInfo.KeyChar,
+                           (ConsoleKey)keyInfo.KeyChar,
+                           true,
+                           keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
+                           keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
+        }
+
+        if ((ConsoleKey)keyInfo.KeyChar - 32 is >= ConsoleKey.A and <= ConsoleKey.Z)
+        {
+            // Unshifted
+            keyInfo = new (
+                           keyInfo.KeyChar,
+                           (ConsoleKey)(keyInfo.KeyChar - 32),
+                           false,
+                           keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt),
+                           keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
+        }
+
+        if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z)
+        {
+            if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
+                || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
+            {
+                // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos
+                return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
+            }
+
+            if (keyInfo.Modifiers == ConsoleModifiers.Shift)
+            {
+                // If ShiftMask is on  add the ShiftMask
+                if (char.IsUpper (keyInfo.KeyChar))
+                {
+                    return (KeyCode)keyInfo.Key | KeyCode.ShiftMask;
+                }
+            }
+
+            return (KeyCode)keyInfo.Key;
+        }
+
+        return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+    }
+
+    #endregion Keyboard Handling
+
+    #region Low-Level DotNet tuff
+
+    private readonly ManualResetEventSlim _waitAnsiResponse = new (false);
+    private readonly CancellationTokenSource _ansiResponseTokenSource = new ();
+
+    /// <inheritdoc/>
+    public override string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
+    {
+        if (_mainLoopDriver is null)
+        {
+            return string.Empty;
+        }
+
+        try
+        {
+            lock (ansiRequest._responseLock)
+            {
+                ansiRequest.ResponseFromInput += (s, e) =>
+                                                 {
+                                                     Debug.Assert (s == ansiRequest);
+                                                     Debug.Assert (e == ansiRequest.Response);
+
+                                                     _waitAnsiResponse.Set ();
+                                                 };
+
+                _mainLoopDriver._netEvents.EscSeqRequests.Add (ansiRequest);
+
+                _mainLoopDriver._netEvents._forceRead = true;
+            }
+
+            if (!_ansiResponseTokenSource.IsCancellationRequested)
+            {
+                _mainLoopDriver._netEvents._waitForStart.Set ();
+
+                if (!_mainLoopDriver._waitForProbe.IsSet)
+                {
+                    _mainLoopDriver._waitForProbe.Set ();
+                }
+
+                _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token);
+            }
+        }
+        catch (OperationCanceledException)
+        {
+            return string.Empty;
+        }
+
+        lock (ansiRequest._responseLock)
+        {
+            _mainLoopDriver._netEvents._forceRead = false;
+
+            if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus request))
+            {
+                if (_mainLoopDriver._netEvents.EscSeqRequests.Statuses.Count > 0
+                    && string.IsNullOrEmpty (request.AnsiRequest.Response))
+                {
+                    lock (request!.AnsiRequest._responseLock)
+                    {
+                        // Bad request or no response at all
+                        _mainLoopDriver._netEvents.EscSeqRequests.Statuses.TryDequeue (out _);
+                    }
+                }
+            }
+
+            _waitAnsiResponse.Reset ();
+
+            return ansiRequest.Response;
+        }
+    }
+
+    /// <inheritdoc/>
+    public override void WriteRaw (string ansi) { throw new NotImplementedException (); }
+
+    private volatile bool _winSizeChanging;
+
+    private void SetWindowPosition (int col, int row)
+    {
+        if (!RunningUnitTests)
+        {
+            Top = Console.WindowTop;
+            Left = Console.WindowLeft;
+        }
+        else
+        {
+            Top = row;
+            Left = col;
+        }
+    }
+
+    private void ResizeScreen ()
+    {
+        // Not supported on Unix.
+        if (IsWinPlatform)
+        {
+            // Can raise an exception while is still resizing.
+            try
+            {
+#pragma warning disable CA1416
+                if (Console.WindowHeight > 0)
+                {
+                    Console.CursorTop = 0;
+                    Console.CursorLeft = 0;
+                    Console.WindowTop = 0;
+                    Console.WindowLeft = 0;
+
+                    if (Console.WindowHeight > Rows)
+                    {
+                        Console.SetWindowSize (Cols, Rows);
+                    }
+
+                    Console.SetBufferSize (Cols, Rows);
+                }
+#pragma warning restore CA1416
+            }
+
+            // INTENT: Why are these eating the exceptions?
+            // Comments would be good here.
+            catch (IOException)
+            {
+                // CONCURRENCY: Unsynchronized access to Clip is not safe.
+                Clip = new (0, 0, Cols, Rows);
+            }
+            catch (ArgumentOutOfRangeException)
+            {
+                // CONCURRENCY: Unsynchronized access to Clip is not safe.
+                Clip = new (0, 0, Cols, Rows);
+            }
+        }
+        else
+        {
+            Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
+        }
+
+        // CONCURRENCY: Unsynchronized access to Clip is not safe.
+        Clip = new (0, 0, Cols, Rows);
+    }
+
+    #endregion Low-Level DotNet tuff
+}

+ 753 - 0
Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs

@@ -0,0 +1,753 @@
+// TODO: #nullable enable
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui;
+
+internal class NetEvents : IDisposable
+{
+    private readonly ManualResetEventSlim _inputReady = new (false);
+    private CancellationTokenSource _inputReadyCancellationTokenSource;
+    internal readonly ManualResetEventSlim _waitForStart = new (false);
+
+    //CancellationTokenSource _waitForStartCancellationTokenSource;
+    private readonly ManualResetEventSlim _winChange = new (false);
+    private readonly ConcurrentQueue<InputResult?> _inputQueue = new ();
+    private readonly ConsoleDriver _consoleDriver;
+    private ConsoleKeyInfo [] _cki;
+    private bool _isEscSeq;
+#if PROCESS_REQUEST
+    bool _neededProcessRequest;
+#endif
+    public EscSeqRequests EscSeqRequests { get; } = new ();
+
+    public NetEvents (ConsoleDriver consoleDriver)
+    {
+        _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
+        _inputReadyCancellationTokenSource = new CancellationTokenSource ();
+
+        Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
+
+        Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
+    }
+
+    public InputResult? DequeueInput ()
+    {
+        while (_inputReadyCancellationTokenSource != null
+               && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+        {
+            _waitForStart.Set ();
+            _winChange.Set ();
+
+            try
+            {
+                if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    if (_inputQueue.Count == 0)
+                    {
+                        _inputReady.Wait (_inputReadyCancellationTokenSource.Token);
+                    }
+                }
+            }
+            catch (OperationCanceledException)
+            {
+                return null;
+            }
+            finally
+            {
+                _inputReady.Reset ();
+            }
+
+#if PROCESS_REQUEST
+            _neededProcessRequest = false;
+#endif
+            if (_inputQueue.Count > 0)
+            {
+                if (_inputQueue.TryDequeue (out InputResult? result))
+                {
+                    return result;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
+    {
+        while (!cancellationToken.IsCancellationRequested)
+        {
+            // if there is a key available, return it without waiting
+            //  (or dispatching work to the thread queue)
+            if (Console.KeyAvailable)
+            {
+                return Console.ReadKey (intercept);
+            }
+
+            if (EscSeqUtils.IncompleteCkInfos is null && EscSeqRequests is { Statuses.Count: > 0 })
+            {
+                if (_retries > 1)
+                {
+                    if (EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
+                    {
+                        lock (seqReqStatus!.AnsiRequest._responseLock)
+                        {
+                            EscSeqRequests.Statuses.TryDequeue (out _);
+
+                            seqReqStatus.AnsiRequest.Response = string.Empty;
+                            seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                        }
+                    }
+
+                    _retries = 0;
+                }
+                else
+                {
+                    _retries++;
+                }
+            }
+            else
+            {
+                _retries = 0;
+            }
+
+            if (!_forceRead)
+            {
+                Task.Delay (100, cancellationToken).Wait (cancellationToken);
+            }
+        }
+
+        cancellationToken.ThrowIfCancellationRequested ();
+
+        return default (ConsoleKeyInfo);
+    }
+
+    internal bool _forceRead;
+    private int _retries;
+
+    private void ProcessInputQueue ()
+    {
+        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
+        {
+            try
+            {
+                if (!_forceRead)
+                {
+                    _waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
+                }
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+
+            _waitForStart.Reset ();
+
+            if (_inputQueue.Count == 0 || _forceRead)
+            {
+                ConsoleKey key = 0;
+                ConsoleModifiers mod = 0;
+                ConsoleKeyInfo newConsoleKeyInfo = default;
+
+                while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
+                {
+                    ConsoleKeyInfo consoleKeyInfo;
+
+                    try
+                    {
+                        consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token);
+                    }
+                    catch (OperationCanceledException)
+                    {
+                        return;
+                    }
+
+                    if (EscSeqUtils.IncompleteCkInfos is { })
+                    {
+                        EscSeqUtils.InsertArray (EscSeqUtils.IncompleteCkInfos, _cki);
+                    }
+
+                    if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
+                        || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq))
+                    {
+                        if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
+                        {
+                            _cki = EscSeqUtils.ResizeArray (
+                                                            new ConsoleKeyInfo (
+                                                                                (char)KeyCode.Esc,
+                                                                                0,
+                                                                                false,
+                                                                                false,
+                                                                                false
+                                                                               ),
+                                                            _cki
+                                                           );
+                        }
+
+                        _isEscSeq = true;
+
+                        if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space)
+                            || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127)
+                            || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar))
+                            || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)))
+                        {
+                            ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+                            _cki = null;
+                            _isEscSeq = false;
+
+                            ProcessMapConsoleKeyInfo (consoleKeyInfo);
+                        }
+                        else
+                        {
+                            newConsoleKeyInfo = consoleKeyInfo;
+                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
+
+                            if (Console.KeyAvailable)
+                            {
+                                continue;
+                            }
+
+                            ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+                            _cki = null;
+                            _isEscSeq = false;
+                        }
+
+                        break;
+                    }
+
+                    if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { })
+                    {
+                        ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+                        _cki = null;
+
+                        if (Console.KeyAvailable)
+                        {
+                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
+                        }
+                        else
+                        {
+                            ProcessMapConsoleKeyInfo (consoleKeyInfo);
+                        }
+
+                        break;
+                    }
+
+                    ProcessMapConsoleKeyInfo (consoleKeyInfo);
+
+                    if (_retries > 0)
+                    {
+                        _retries = 0;
+                    }
+
+                    break;
+                }
+            }
+
+            _inputReady.Set ();
+        }
+
+        void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+        {
+            _inputQueue.Enqueue (
+                                 new InputResult
+                                 {
+                                     EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
+                                 }
+                                );
+            _isEscSeq = false;
+        }
+    }
+
+    private void CheckWindowSizeChange ()
+    {
+        void RequestWindowSize (CancellationToken cancellationToken)
+        {
+            while (!cancellationToken.IsCancellationRequested)
+            {
+                // Wait for a while then check if screen has changed sizes
+                Task.Delay (500, cancellationToken).Wait (cancellationToken);
+
+                int buffHeight, buffWidth;
+
+                if (((NetDriver)_consoleDriver).IsWinPlatform)
+                {
+                    buffHeight = Math.Max (Console.BufferHeight, 0);
+                    buffWidth = Math.Max (Console.BufferWidth, 0);
+                }
+                else
+                {
+                    buffHeight = _consoleDriver.Rows;
+                    buffWidth = _consoleDriver.Cols;
+                }
+
+                if (EnqueueWindowSizeEvent (
+                                            Math.Max (Console.WindowHeight, 0),
+                                            Math.Max (Console.WindowWidth, 0),
+                                            buffHeight,
+                                            buffWidth
+                                           ))
+                {
+                    return;
+                }
+            }
+
+            cancellationToken.ThrowIfCancellationRequested ();
+        }
+
+        while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
+        {
+            try
+            {
+                _winChange.Wait (_inputReadyCancellationTokenSource.Token);
+                _winChange.Reset ();
+
+                RequestWindowSize (_inputReadyCancellationTokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+
+            _inputReady.Set ();
+        }
+    }
+
+    /// <summary>Enqueue a window size event if the window size has changed.</summary>
+    /// <param name="winHeight"></param>
+    /// <param name="winWidth"></param>
+    /// <param name="buffHeight"></param>
+    /// <param name="buffWidth"></param>
+    /// <returns></returns>
+    private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
+    {
+        if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows)
+        {
+            return false;
+        }
+
+        int w = Math.Max (winWidth, 0);
+        int h = Math.Max (winHeight, 0);
+
+        _inputQueue.Enqueue (
+                             new InputResult
+                             {
+                                 EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new (w, h) }
+                             }
+                            );
+
+        return true;
+    }
+
+    // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
+    private void ProcessRequestResponse (
+        ref ConsoleKeyInfo newConsoleKeyInfo,
+        ref ConsoleKey key,
+        ConsoleKeyInfo [] cki,
+        ref ConsoleModifiers mod
+    )
+    {
+        // isMouse is true if it's CSI<, false otherwise
+        EscSeqUtils.DecodeEscSeq (
+                                  EscSeqRequests,
+                                  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 EscSeqReqStatus seqReqStatus,
+                                  (f, p) => HandleMouseEvent (MapMouseFlags (f), p)
+                                 );
+
+        if (isMouse)
+        {
+            foreach (MouseFlags mf in mouseFlags)
+            {
+                HandleMouseEvent (MapMouseFlags (mf), pos);
+            }
+
+            return;
+        }
+
+        if (seqReqStatus is { })
+        {
+            //HandleRequestResponseEvent (c1Control, code, values, terminating);
+
+            var ckiString = EscSeqUtils.ToString (cki);
+
+            lock (seqReqStatus.AnsiRequest._responseLock)
+            {
+                seqReqStatus.AnsiRequest.Response = ckiString;
+                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, ckiString);
+            }
+
+            return;
+        }
+
+        if (!string.IsNullOrEmpty (EscSeqUtils.InvalidRequestTerminator))
+        {
+            if (EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus result))
+            {
+                lock (result.AnsiRequest._responseLock)
+                {
+                    result.AnsiRequest.Response = EscSeqUtils.InvalidRequestTerminator;
+                    result.AnsiRequest.RaiseResponseFromInput (result.AnsiRequest, EscSeqUtils.InvalidRequestTerminator);
+
+                    EscSeqUtils.InvalidRequestTerminator = null;
+                }
+            }
+
+            return;
+        }
+
+        if (newConsoleKeyInfo != default)
+        {
+            HandleKeyboardEvent (newConsoleKeyInfo);
+        }
+    }
+
+    [UnconditionalSuppressMessage ("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
+    private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
+    {
+        MouseButtonState mbs = default;
+
+        foreach (object flag in Enum.GetValues (mouseFlags.GetType ()))
+        {
+            if (mouseFlags.HasFlag ((MouseFlags)flag))
+            {
+                switch (flag)
+                {
+                    case MouseFlags.Button1Pressed:
+                        mbs |= MouseButtonState.Button1Pressed;
+
+                        break;
+                    case MouseFlags.Button1Released:
+                        mbs |= MouseButtonState.Button1Released;
+
+                        break;
+                    case MouseFlags.Button1Clicked:
+                        mbs |= MouseButtonState.Button1Clicked;
+
+                        break;
+                    case MouseFlags.Button1DoubleClicked:
+                        mbs |= MouseButtonState.Button1DoubleClicked;
+
+                        break;
+                    case MouseFlags.Button1TripleClicked:
+                        mbs |= MouseButtonState.Button1TripleClicked;
+
+                        break;
+                    case MouseFlags.Button2Pressed:
+                        mbs |= MouseButtonState.Button2Pressed;
+
+                        break;
+                    case MouseFlags.Button2Released:
+                        mbs |= MouseButtonState.Button2Released;
+
+                        break;
+                    case MouseFlags.Button2Clicked:
+                        mbs |= MouseButtonState.Button2Clicked;
+
+                        break;
+                    case MouseFlags.Button2DoubleClicked:
+                        mbs |= MouseButtonState.Button2DoubleClicked;
+
+                        break;
+                    case MouseFlags.Button2TripleClicked:
+                        mbs |= MouseButtonState.Button2TripleClicked;
+
+                        break;
+                    case MouseFlags.Button3Pressed:
+                        mbs |= MouseButtonState.Button3Pressed;
+
+                        break;
+                    case MouseFlags.Button3Released:
+                        mbs |= MouseButtonState.Button3Released;
+
+                        break;
+                    case MouseFlags.Button3Clicked:
+                        mbs |= MouseButtonState.Button3Clicked;
+
+                        break;
+                    case MouseFlags.Button3DoubleClicked:
+                        mbs |= MouseButtonState.Button3DoubleClicked;
+
+                        break;
+                    case MouseFlags.Button3TripleClicked:
+                        mbs |= MouseButtonState.Button3TripleClicked;
+
+                        break;
+                    case MouseFlags.WheeledUp:
+                        mbs |= MouseButtonState.ButtonWheeledUp;
+
+                        break;
+                    case MouseFlags.WheeledDown:
+                        mbs |= MouseButtonState.ButtonWheeledDown;
+
+                        break;
+                    case MouseFlags.WheeledLeft:
+                        mbs |= MouseButtonState.ButtonWheeledLeft;
+
+                        break;
+                    case MouseFlags.WheeledRight:
+                        mbs |= MouseButtonState.ButtonWheeledRight;
+
+                        break;
+                    case MouseFlags.Button4Pressed:
+                        mbs |= MouseButtonState.Button4Pressed;
+
+                        break;
+                    case MouseFlags.Button4Released:
+                        mbs |= MouseButtonState.Button4Released;
+
+                        break;
+                    case MouseFlags.Button4Clicked:
+                        mbs |= MouseButtonState.Button4Clicked;
+
+                        break;
+                    case MouseFlags.Button4DoubleClicked:
+                        mbs |= MouseButtonState.Button4DoubleClicked;
+
+                        break;
+                    case MouseFlags.Button4TripleClicked:
+                        mbs |= MouseButtonState.Button4TripleClicked;
+
+                        break;
+                    case MouseFlags.ButtonShift:
+                        mbs |= MouseButtonState.ButtonShift;
+
+                        break;
+                    case MouseFlags.ButtonCtrl:
+                        mbs |= MouseButtonState.ButtonCtrl;
+
+                        break;
+                    case MouseFlags.ButtonAlt:
+                        mbs |= MouseButtonState.ButtonAlt;
+
+                        break;
+                    case MouseFlags.ReportMousePosition:
+                        mbs |= MouseButtonState.ReportMousePosition;
+
+                        break;
+                    case MouseFlags.AllEvents:
+                        mbs |= MouseButtonState.AllEvents;
+
+                        break;
+                }
+            }
+        }
+
+        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 void HandleMouseEvent (MouseButtonState buttonState, Point pos)
+    {
+        var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
+
+        _inputQueue.Enqueue (
+                             new InputResult { EventType = EventType.Mouse, MouseEvent = mouseEvent }
+                            );
+
+        _inputReady.Set ();
+    }
+
+    public enum EventType
+    {
+        Key = 1,
+        Mouse = 2,
+        WindowSize = 3,
+        WindowPosition = 4,
+        RequestResponse = 5
+    }
+
+    [Flags]
+    public enum MouseButtonState
+    {
+        Button1Pressed = 0x1,
+        Button1Released = 0x2,
+        Button1Clicked = 0x4,
+        Button1DoubleClicked = 0x8,
+        Button1TripleClicked = 0x10,
+        Button2Pressed = 0x20,
+        Button2Released = 0x40,
+        Button2Clicked = 0x80,
+        Button2DoubleClicked = 0x100,
+        Button2TripleClicked = 0x200,
+        Button3Pressed = 0x400,
+        Button3Released = 0x800,
+        Button3Clicked = 0x1000,
+        Button3DoubleClicked = 0x2000,
+        Button3TripleClicked = 0x4000,
+        ButtonWheeledUp = 0x8000,
+        ButtonWheeledDown = 0x10000,
+        ButtonWheeledLeft = 0x20000,
+        ButtonWheeledRight = 0x40000,
+        Button4Pressed = 0x80000,
+        Button4Released = 0x100000,
+        Button4Clicked = 0x200000,
+        Button4DoubleClicked = 0x400000,
+        Button4TripleClicked = 0x800000,
+        ButtonShift = 0x1000000,
+        ButtonCtrl = 0x2000000,
+        ButtonAlt = 0x4000000,
+        ReportMousePosition = 0x8000000,
+        AllEvents = -1
+    }
+
+    public struct MouseEvent
+    {
+        public Point Position;
+        public MouseButtonState ButtonState;
+    }
+
+    public struct WindowSizeEvent
+    {
+        public Size Size;
+    }
+
+    public struct WindowPositionEvent
+    {
+        public int Top;
+        public int Left;
+        public Point CursorPosition;
+    }
+
+    public struct RequestResponseEvent
+    {
+        public (string c1Control, string code, string [] values, string terminating) ResultTuple;
+    }
+
+    public struct InputResult
+    {
+        public EventType EventType;
+        public ConsoleKeyInfo ConsoleKeyInfo;
+        public MouseEvent MouseEvent;
+        public WindowSizeEvent WindowSizeEvent;
+        public WindowPositionEvent WindowPositionEvent;
+        public RequestResponseEvent RequestResponseEvent;
+
+        public readonly override string ToString ()
+        {
+            return EventType switch
+                   {
+                       EventType.Key => ToString (ConsoleKeyInfo),
+                       EventType.Mouse => MouseEvent.ToString (),
+
+                       //EventType.WindowSize => WindowSize.ToString (),
+                       //EventType.RequestResponse => RequestResponse.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})]";
+        }
+    }
+
+    private void HandleKeyboardEvent (ConsoleKeyInfo cki)
+    {
+        var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
+
+        _inputQueue.Enqueue (inputResult);
+    }
+
+    public void Dispose ()
+    {
+        _inputReadyCancellationTokenSource?.Cancel ();
+        _inputReadyCancellationTokenSource?.Dispose ();
+        _inputReadyCancellationTokenSource = null;
+
+        try
+        {
+            // throws away any typeahead that has been typed by
+            // the user and has not yet been read by the program.
+            while (Console.KeyAvailable)
+            {
+                Console.ReadKey (true);
+            }
+        }
+        catch (InvalidOperationException)
+        {
+            // Ignore - Console input has already been closed
+        }
+    }
+}

+ 173 - 0
Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs

@@ -0,0 +1,173 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Mainloop intended to be used with the .NET System.Console API, and can be used on Windows and Unix, it is
+///     cross-platform but lacks things like file descriptor monitoring.
+/// </summary>
+/// <remarks>This implementation is used for NetDriver.</remarks>
+internal class NetMainLoop : IMainLoopDriver
+{
+    internal NetEvents _netEvents;
+
+    /// <summary>Invoked when a Key is pressed.</summary>
+    internal Action<NetEvents.InputResult> ProcessInput;
+
+    private readonly ManualResetEventSlim _eventReady = new (false);
+    private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
+    private readonly ConcurrentQueue<NetEvents.InputResult?> _resultQueue = new ();
+    internal readonly ManualResetEventSlim _waitForProbe = new (false);
+    private readonly CancellationTokenSource _eventReadyTokenSource = new ();
+    private MainLoop _mainLoop;
+
+    /// <summary>Initializes the class with the console driver.</summary>
+    /// <remarks>Passing a consoleDriver is provided to capture windows resizing.</remarks>
+    /// <param name="consoleDriver">The console driver used by this Net main loop.</param>
+    /// <exception cref="ArgumentNullException"></exception>
+    public NetMainLoop (ConsoleDriver consoleDriver = null)
+    {
+        if (consoleDriver is null)
+        {
+            throw new ArgumentNullException (nameof (consoleDriver));
+        }
+
+        _netEvents = new NetEvents (consoleDriver);
+    }
+
+    void IMainLoopDriver.Setup (MainLoop mainLoop)
+    {
+        _mainLoop = mainLoop;
+
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
+        Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
+    }
+
+    void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
+
+    bool IMainLoopDriver.EventsPending ()
+    {
+        _waitForProbe.Set ();
+
+        if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout))
+        {
+            return true;
+        }
+
+        try
+        {
+            if (!_eventReadyTokenSource.IsCancellationRequested)
+            {
+                // 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);
+            }
+        }
+        catch (OperationCanceledException)
+        {
+            return true;
+        }
+        finally
+        {
+            _eventReady.Reset ();
+        }
+
+        _eventReadyTokenSource.Token.ThrowIfCancellationRequested ();
+
+        if (!_eventReadyTokenSource.IsCancellationRequested)
+        {
+            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
+        }
+
+        return true;
+    }
+
+    void IMainLoopDriver.Iteration ()
+    {
+        while (_resultQueue.Count > 0)
+        {
+            // Always dequeue even if it's null and invoke if isn't null
+            if (_resultQueue.TryDequeue (out NetEvents.InputResult? dequeueResult))
+            {
+                if (dequeueResult is { })
+                {
+                    ProcessInput?.Invoke (dequeueResult.Value);
+                }
+            }
+        }
+    }
+
+    void IMainLoopDriver.TearDown ()
+    {
+        _inputHandlerTokenSource?.Cancel ();
+        _inputHandlerTokenSource?.Dispose ();
+        _eventReadyTokenSource?.Cancel ();
+        _eventReadyTokenSource?.Dispose ();
+
+        _eventReady?.Dispose ();
+
+        _resultQueue?.Clear ();
+        _waitForProbe?.Dispose ();
+        _netEvents?.Dispose ();
+        _netEvents = null;
+
+        _mainLoop = null;
+    }
+
+    private void NetInputHandler ()
+    {
+        while (_mainLoop is { })
+        {
+            try
+            {
+                if (!_netEvents._forceRead && !_inputHandlerTokenSource.IsCancellationRequested)
+                {
+                    _waitForProbe.Wait (_inputHandlerTokenSource.Token);
+                }
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+            finally
+            {
+                if (_waitForProbe.IsSet)
+                {
+                    _waitForProbe.Reset ();
+                }
+            }
+
+            if (_inputHandlerTokenSource.IsCancellationRequested)
+            {
+                return;
+            }
+
+            _inputHandlerTokenSource.Token.ThrowIfCancellationRequested ();
+
+            if (_resultQueue.Count == 0)
+            {
+                _resultQueue.Enqueue (_netEvents.DequeueInput ());
+            }
+
+            try
+            {
+                while (_resultQueue.Count > 0 && _resultQueue.TryPeek (out NetEvents.InputResult? result) && result is null)
+                {
+                    // Dequeue null values
+                    _resultQueue.TryDequeue (out _);
+                }
+            }
+            catch (InvalidOperationException) // Peek can raise an exception
+            { }
+
+            if (_resultQueue.Count > 0)
+            {
+                _eventReady.Set ();
+            }
+        }
+    }
+}

+ 125 - 0
Terminal.Gui/ConsoleDrivers/NetDriver/NetWinVTConsole.cs

@@ -0,0 +1,125 @@
+using System.Runtime.InteropServices;
+
+namespace Terminal.Gui;
+
+internal class NetWinVTConsole
+{
+    private const uint DISABLE_NEWLINE_AUTO_RETURN = 8;
+    private const uint ENABLE_ECHO_INPUT = 4;
+    private const uint ENABLE_EXTENDED_FLAGS = 128;
+    private const uint ENABLE_INSERT_MODE = 32;
+    private const uint ENABLE_LINE_INPUT = 2;
+    private const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
+    private const uint ENABLE_MOUSE_INPUT = 16;
+
+    // Input modes.
+    private const uint ENABLE_PROCESSED_INPUT = 1;
+
+    // Output modes.
+    private const uint ENABLE_PROCESSED_OUTPUT = 1;
+    private const uint ENABLE_QUICK_EDIT_MODE = 64;
+    private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
+    private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
+    private const uint ENABLE_WINDOW_INPUT = 8;
+    private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2;
+    private const int STD_ERROR_HANDLE = -12;
+    private const int STD_INPUT_HANDLE = -10;
+    private const int STD_OUTPUT_HANDLE = -11;
+
+    private readonly nint _errorHandle;
+    private readonly nint _inputHandle;
+    private readonly uint _originalErrorConsoleMode;
+    private readonly uint _originalInputConsoleMode;
+    private readonly uint _originalOutputConsoleMode;
+    private readonly nint _outputHandle;
+
+    public NetWinVTConsole ()
+    {
+        _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+
+        if (!GetConsoleMode (_inputHandle, out uint mode))
+        {
+            throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}.");
+        }
+
+        _originalInputConsoleMode = mode;
+
+        if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT)
+        {
+            mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+
+            if (!SetConsoleMode (_inputHandle, mode))
+            {
+                throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}.");
+            }
+        }
+
+        _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+
+        if (!GetConsoleMode (_outputHandle, out mode))
+        {
+            throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}.");
+        }
+
+        _originalOutputConsoleMode = mode;
+
+        if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN)
+        {
+            mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
+
+            if (!SetConsoleMode (_outputHandle, mode))
+            {
+                throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}.");
+            }
+        }
+
+        _errorHandle = GetStdHandle (STD_ERROR_HANDLE);
+
+        if (!GetConsoleMode (_errorHandle, out mode))
+        {
+            throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
+        }
+
+        _originalErrorConsoleMode = mode;
+
+        if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN)
+        {
+            mode |= DISABLE_NEWLINE_AUTO_RETURN;
+
+            if (!SetConsoleMode (_errorHandle, mode))
+            {
+                throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
+            }
+        }
+    }
+
+    public void Cleanup ()
+    {
+        if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode))
+        {
+            throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
+        }
+
+        if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode))
+        {
+            throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}.");
+        }
+
+        if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode))
+        {
+            throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}.");
+        }
+    }
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
+
+    [DllImport ("kernel32.dll")]
+    private static extern uint GetLastError ();
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern nint GetStdHandle (int nStdHandle);
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+}

+ 1109 - 0
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs

@@ -0,0 +1,1109 @@
+// TODO: #nullable enable
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using Terminal.Gui.ConsoleDrivers;
+
+namespace Terminal.Gui;
+
+internal class WindowsConsole
+{
+    internal WindowsMainLoop _mainLoop;
+
+    public const int STD_OUTPUT_HANDLE = -11;
+    public const int STD_INPUT_HANDLE = -10;
+
+    private readonly nint _inputHandle;
+    private nint _outputHandle;
+    //private nint _screenBuffer;
+    private readonly uint _originalConsoleMode;
+    private CursorVisibility? _initialCursorVisibility;
+    private CursorVisibility? _currentCursorVisibility;
+    private CursorVisibility? _pendingCursorVisibility;
+    private readonly StringBuilder _stringBuilder = new (256 * 1024);
+    private string _lastWrite = string.Empty;
+
+    public WindowsConsole ()
+    {
+        _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+        _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+        _originalConsoleMode = ConsoleMode;
+        uint newConsoleMode = _originalConsoleMode;
+        newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
+        newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
+        newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
+        ConsoleMode = newConsoleMode;
+    }
+
+    private CharInfo [] _originalStdOutChars;
+
+    public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
+    {
+        //Debug.WriteLine ("WriteToConsole");
+
+        //if (_screenBuffer == nint.Zero)
+        //{
+        //    ReadFromConsoleOutput (size, bufferSize, ref window);
+        //}
+
+        var result = false;
+
+        if (force16Colors)
+        {
+            var i = 0;
+            CharInfo [] ci = new CharInfo [charInfoBuffer.Length];
+
+            foreach (ExtendedCharInfo info in charInfoBuffer)
+            {
+                ci [i++] = new CharInfo
+                {
+                    Char = new CharUnion { UnicodeChar = info.Char },
+                    Attributes =
+                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
+                };
+            }
+
+            result = WriteConsoleOutput (_outputHandle, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window);
+        }
+        else
+        {
+            _stringBuilder.Clear ();
+
+            _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
+            _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0));
+
+            Attribute? prev = null;
+
+            foreach (ExtendedCharInfo info in charInfoBuffer)
+            {
+                Attribute attr = info.Attribute;
+
+                if (attr != prev)
+                {
+                    prev = attr;
+                    _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')
+                {
+                    if (!info.Empty)
+                    {
+                        _stringBuilder.Append (info.Char);
+                    }
+                }
+                else
+                {
+                    _stringBuilder.Append (' ');
+                }
+            }
+
+            _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
+            _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
+
+            var s = _stringBuilder.ToString ();
+
+            // TODO: requires extensive testing if we go down this route
+            // If console output has changed
+            if (s != _lastWrite)
+            {
+                // supply console with the new content
+                result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
+            }
+
+            _lastWrite = s;
+
+            foreach (var sixel in Application.Sixel)
+            {
+                SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y));
+                WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
+            }
+        }
+
+        if (!result)
+        {
+            int err = Marshal.GetLastWin32Error ();
+
+            if (err != 0)
+            {
+                throw new Win32Exception (err);
+            }
+        }
+
+        return result;
+    }
+
+    internal bool WriteANSI (string ansi)
+    {
+        if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero))
+        {
+            // Flush the output to make sure it's sent immediately
+            return FlushFileBuffers (_outputHandle);
+        }
+
+        return false;
+    }
+
+    public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
+    {
+        //_screenBuffer = CreateConsoleScreenBuffer (
+        //                                           DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
+        //                                           ShareMode.FileShareRead | ShareMode.FileShareWrite,
+        //                                           nint.Zero,
+        //                                           1,
+        //                                           nint.Zero
+        //                                          );
+
+        //if (_screenBuffer == INVALID_HANDLE_VALUE)
+        //{
+        //    int err = Marshal.GetLastWin32Error ();
+
+        //    if (err != 0)
+        //    {
+        //        throw new Win32Exception (err);
+        //    }
+        //}
+
+        SetInitialCursorVisibility ();
+
+        //if (!SetConsoleActiveScreenBuffer (_screenBuffer))
+        //{
+        //    throw new Win32Exception (Marshal.GetLastWin32Error ());
+        //}
+
+        _originalStdOutChars = new CharInfo [size.Height * size.Width];
+
+        if (!ReadConsoleOutput (_outputHandle, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window))
+        {
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+    }
+
+    public bool SetCursorPosition (Coord position)
+    {
+        return SetConsoleCursorPosition (_outputHandle, position);
+    }
+
+    public void SetInitialCursorVisibility ()
+    {
+        if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility))
+        {
+            _initialCursorVisibility = visibility;
+        }
+    }
+
+    public bool GetCursorVisibility (out CursorVisibility visibility)
+    {
+        if (_outputHandle == nint.Zero)
+        {
+            visibility = CursorVisibility.Invisible;
+
+            return false;
+        }
+
+        if (!GetConsoleCursorInfo (_outputHandle, out ConsoleCursorInfo info))
+        {
+            int err = Marshal.GetLastWin32Error ();
+
+            if (err != 0)
+            {
+                throw new Win32Exception (err);
+            }
+
+            visibility = CursorVisibility.Default;
+
+            return false;
+        }
+
+        if (!info.bVisible)
+        {
+            visibility = CursorVisibility.Invisible;
+        }
+        else if (info.dwSize > 50)
+        {
+            visibility = CursorVisibility.Default;
+        }
+        else
+        {
+            visibility = CursorVisibility.Default;
+        }
+
+        return true;
+    }
+
+    public bool EnsureCursorVisibility ()
+    {
+        if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value))
+        {
+            _pendingCursorVisibility = null;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    public void ForceRefreshCursorVisibility ()
+    {
+        if (_currentCursorVisibility.HasValue)
+        {
+            _pendingCursorVisibility = _currentCursorVisibility;
+            _currentCursorVisibility = null;
+        }
+    }
+
+    public bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        if (_initialCursorVisibility.HasValue == false)
+        {
+            _pendingCursorVisibility = visibility;
+
+            return false;
+        }
+
+        if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility)
+        {
+            var info = new ConsoleCursorInfo
+            {
+                dwSize = (uint)visibility & 0x00FF,
+                bVisible = ((uint)visibility & 0xFF00) != 0
+            };
+
+            if (!SetConsoleCursorInfo (_outputHandle, ref info))
+            {
+                return false;
+            }
+
+            _currentCursorVisibility = visibility;
+        }
+
+        return true;
+    }
+
+    public void Cleanup ()
+    {
+        if (_initialCursorVisibility.HasValue)
+        {
+            SetCursorVisibility (_initialCursorVisibility.Value);
+        }
+
+        //SetConsoleOutputWindow (out _);
+
+        ConsoleMode = _originalConsoleMode;
+
+        _outputHandle = CreateConsoleScreenBuffer (
+                                                   DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
+                                                   ShareMode.FileShareRead | ShareMode.FileShareWrite,
+                                                   nint.Zero,
+                                                   1,
+                                                   nint.Zero
+                                                  );
+
+        if (!SetConsoleActiveScreenBuffer (_outputHandle))
+        {
+            int err = Marshal.GetLastWin32Error ();
+            Console.WriteLine ("Error: {0}", err);
+        }
+
+        //if (_screenBuffer != nint.Zero)
+        //{
+        //    CloseHandle (_screenBuffer);
+        //}
+
+        //_screenBuffer = nint.Zero;
+    }
+
+    //internal Size GetConsoleBufferWindow (out Point position)
+    //{
+    //    if (_screenBuffer == nint.Zero)
+    //    {
+    //        position = Point.Empty;
+
+    //        return Size.Empty;
+    //    }
+
+    //    var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
+    //    csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+    //    if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+    //    {
+    //        //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+    //        position = Point.Empty;
+
+    //        return Size.Empty;
+    //    }
+
+    //    Size sz = new (
+    //                   csbi.srWindow.Right - csbi.srWindow.Left + 1,
+    //                   csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
+    //    position = new (csbi.srWindow.Left, csbi.srWindow.Top);
+
+    //    return sz;
+    //}
+
+    internal Size GetConsoleOutputWindow (out Point position)
+    {
+        var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
+        csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+        if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
+        {
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+
+        Size sz = new (
+                       csbi.srWindow.Right - csbi.srWindow.Left + 1,
+                       csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
+        position = new (csbi.srWindow.Left, csbi.srWindow.Top);
+
+        return sz;
+    }
+
+    //internal Size SetConsoleWindow (short cols, short rows)
+    //{
+    //    var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
+    //    csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+    //    if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+    //    {
+    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
+    //    }
+
+    //    Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer);
+    //    short newCols = Math.Min (cols, maxWinSize.X);
+    //    short newRows = Math.Min (rows, maxWinSize.Y);
+    //    csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1));
+    //    csbi.srWindow = new SmallRect (0, 0, newCols, newRows);
+    //    csbi.dwMaximumWindowSize = new Coord (newCols, newRows);
+
+    //    if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+    //    {
+    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
+    //    }
+
+    //    var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
+
+    //    if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
+    //    {
+    //        //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
+    //        return new (cols, rows);
+    //    }
+
+    //    SetConsoleOutputWindow (csbi);
+
+    //    return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
+    //}
+
+    //private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
+    //{
+    //    if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+    //    {
+    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
+    //    }
+    //}
+
+    //internal Size SetConsoleOutputWindow (out Point position)
+    //{
+    //    if (_screenBuffer == nint.Zero)
+    //    {
+    //        position = Point.Empty;
+
+    //        return Size.Empty;
+    //    }
+
+    //    var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
+    //    csbi.cbSize = (uint)Marshal.SizeOf (csbi);
+
+    //    if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
+    //    {
+    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
+    //    }
+
+    //    Size sz = new (
+    //                       csbi.srWindow.Right - csbi.srWindow.Left + 1,
+    //                       Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0));
+    //    position = new (csbi.srWindow.Left, csbi.srWindow.Top);
+    //    SetConsoleOutputWindow (csbi);
+    //    var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
+
+    //    if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
+    //    {
+    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
+    //    }
+
+    //    if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
+    //    {
+    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
+    //    }
+
+    //    return sz;
+    //}
+
+    private uint ConsoleMode
+    {
+        get
+        {
+            GetConsoleMode (_inputHandle, out uint v);
+
+            return v;
+        }
+        set => SetConsoleMode (_inputHandle, value);
+    }
+
+    [Flags]
+    public enum ConsoleModes : uint
+    {
+        EnableProcessedInput = 1,
+        EnableMouseInput = 16,
+        EnableQuickEditMode = 64,
+        EnableExtendedFlags = 128
+    }
+
+    [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
+    public struct KeyEventRecord
+    {
+        [FieldOffset (0)]
+        [MarshalAs (UnmanagedType.Bool)]
+        public bool bKeyDown;
+
+        [FieldOffset (4)]
+        [MarshalAs (UnmanagedType.U2)]
+        public ushort wRepeatCount;
+
+        [FieldOffset (6)]
+        [MarshalAs (UnmanagedType.U2)]
+        public ConsoleKeyMapping.VK wVirtualKeyCode;
+
+        [FieldOffset (8)]
+        [MarshalAs (UnmanagedType.U2)]
+        public ushort wVirtualScanCode;
+
+        [FieldOffset (10)]
+        public char UnicodeChar;
+
+        [FieldOffset (12)]
+        [MarshalAs (UnmanagedType.U4)]
+        public ControlKeyState dwControlKeyState;
+
+        public readonly override string ToString ()
+        {
+            return
+                $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]";
+        }
+    }
+
+    [Flags]
+    public enum ButtonState
+    {
+        NoButtonPressed = 0,
+        Button1Pressed = 1,
+        Button2Pressed = 4,
+        Button3Pressed = 8,
+        Button4Pressed = 16,
+        RightmostButtonPressed = 2
+    }
+
+    [Flags]
+    public enum ControlKeyState
+    {
+        NoControlKeyPressed = 0,
+        RightAltPressed = 1,
+        LeftAltPressed = 2,
+        RightControlPressed = 4,
+        LeftControlPressed = 8,
+        ShiftPressed = 16,
+        NumlockOn = 32,
+        ScrolllockOn = 64,
+        CapslockOn = 128,
+        EnhancedKey = 256
+    }
+
+    [Flags]
+    public enum EventFlags
+    {
+        NoEvent = 0,
+        MouseMoved = 1,
+        DoubleClick = 2,
+        MouseWheeled = 4,
+        MouseHorizontalWheeled = 8
+    }
+
+    [StructLayout (LayoutKind.Explicit)]
+    public struct MouseEventRecord
+    {
+        [FieldOffset (0)]
+        public Coord MousePosition;
+
+        [FieldOffset (4)]
+        public ButtonState ButtonState;
+
+        [FieldOffset (8)]
+        public ControlKeyState ControlKeyState;
+
+        [FieldOffset (12)]
+        public EventFlags EventFlags;
+
+        public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; }
+    }
+
+    public struct WindowBufferSizeRecord
+    {
+        public Coord _size;
+
+        public WindowBufferSizeRecord (short x, short y) { _size = new Coord (x, y); }
+
+        public readonly override string ToString () { return $"[WindowBufferSize{_size}"; }
+    }
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct MenuEventRecord
+    {
+        public uint dwCommandId;
+    }
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct FocusEventRecord
+    {
+        public uint bSetFocus;
+    }
+
+    public enum EventType : ushort
+    {
+        Focus = 0x10,
+        Key = 0x1,
+        Menu = 0x8,
+        Mouse = 2,
+        WindowBufferSize = 4
+    }
+
+    [StructLayout (LayoutKind.Explicit)]
+    public struct InputRecord
+    {
+        [FieldOffset (0)]
+        public EventType EventType;
+
+        [FieldOffset (4)]
+        public KeyEventRecord KeyEvent;
+
+        [FieldOffset (4)]
+        public MouseEventRecord MouseEvent;
+
+        [FieldOffset (4)]
+        public WindowBufferSizeRecord WindowBufferSizeEvent;
+
+        [FieldOffset (4)]
+        public MenuEventRecord MenuEvent;
+
+        [FieldOffset (4)]
+        public FocusEventRecord FocusEvent;
+
+        public readonly override string ToString ()
+        {
+            return EventType switch
+                   {
+                       EventType.Focus => FocusEvent.ToString (),
+                       EventType.Key => KeyEvent.ToString (),
+                       EventType.Menu => MenuEvent.ToString (),
+                       EventType.Mouse => MouseEvent.ToString (),
+                       EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
+                       _ => "Unknown event type: " + EventType
+                   };
+        }
+    }
+
+    [Flags]
+    private enum ShareMode : uint
+    {
+        FileShareRead = 1,
+        FileShareWrite = 2
+    }
+
+    [Flags]
+    private enum DesiredAccess : uint
+    {
+        GenericRead = 2147483648,
+        GenericWrite = 1073741824
+    }
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct ConsoleScreenBufferInfo
+    {
+        public Coord dwSize;
+        public Coord dwCursorPosition;
+        public ushort wAttributes;
+        public SmallRect srWindow;
+        public Coord dwMaximumWindowSize;
+    }
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct Coord
+    {
+        public short X;
+        public short Y;
+
+        public Coord (short x, short y)
+        {
+            X = x;
+            Y = y;
+        }
+
+        public readonly override string ToString () { return $"({X},{Y})"; }
+    }
+
+    [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
+    public struct CharUnion
+    {
+        [FieldOffset (0)]
+        public char UnicodeChar;
+
+        [FieldOffset (0)]
+        public byte AsciiChar;
+    }
+
+    [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
+    public struct CharInfo
+    {
+        [FieldOffset (0)]
+        public CharUnion Char;
+
+        [FieldOffset (2)]
+        public ushort Attributes;
+    }
+
+    public struct ExtendedCharInfo
+    {
+        public char Char { get; set; }
+        public Attribute Attribute { get; set; }
+        public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences
+
+        public ExtendedCharInfo (char character, Attribute attribute)
+        {
+            Char = character;
+            Attribute = attribute;
+            Empty = false;
+        }
+    }
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct SmallRect
+    {
+        public short Left;
+        public short Top;
+        public short Right;
+        public short Bottom;
+
+        public SmallRect (short left, short top, short right, short bottom)
+        {
+            Left = left;
+            Top = top;
+            Right = right;
+            Bottom = bottom;
+        }
+
+        public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; }
+
+        public static void Update (ref SmallRect rect, short col, short row)
+        {
+            if (rect.Left == -1)
+            {
+                rect.Left = rect.Right = col;
+                rect.Bottom = rect.Top = row;
+
+                return;
+            }
+
+            if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom)
+            {
+                return;
+            }
+
+            if (col < rect.Left)
+            {
+                rect.Left = col;
+            }
+
+            if (col > rect.Right)
+            {
+                rect.Right = col;
+            }
+
+            if (row < rect.Top)
+            {
+                rect.Top = row;
+            }
+
+            if (row > rect.Bottom)
+            {
+                rect.Bottom = row;
+            }
+        }
+
+        public readonly override string ToString () { return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; }
+    }
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct ConsoleKeyInfoEx
+    {
+        public ConsoleKeyInfo ConsoleKeyInfo;
+        public bool CapsLock;
+        public bool NumLock;
+        public bool ScrollLock;
+
+        public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock)
+        {
+            ConsoleKeyInfo = consoleKeyInfo;
+            CapsLock = capslock;
+            NumLock = numlock;
+            ScrollLock = scrolllock;
+        }
+
+        /// <summary>
+        ///     Prints a ConsoleKeyInfoEx structure
+        /// </summary>
+        /// <param name="ex"></param>
+        /// <returns></returns>
+        public readonly string ToString (ConsoleKeyInfoEx ex)
+        {
+            var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar);
+            var sb = new StringBuilder ();
+            sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})");
+            sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
+            sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
+            sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
+            sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) ");
+            sb.Append (ex.CapsLock ? "caps," : string.Empty);
+            sb.Append (ex.NumLock ? "num," : string.Empty);
+            sb.Append (ex.ScrollLock ? "scroll," : string.Empty);
+            string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
+
+            return $"[ConsoleKeyInfoEx({s})]";
+        }
+    }
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern nint GetStdHandle (int nStdHandle);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool CloseHandle (nint handle);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead);
+
+    [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
+    public static extern bool ReadConsoleInput (
+        nint hConsoleInput,
+        out InputRecord lpBuffer,
+        uint nLength,
+        out uint lpNumberOfEventsRead
+    );
+
+    [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+    private static extern bool ReadConsoleOutput (
+        nint hConsoleOutput,
+        [Out] CharInfo [] lpBuffer,
+        Coord dwBufferSize,
+        Coord dwBufferCoord,
+        ref SmallRect lpReadRegion
+    );
+
+    // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput
+    [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)]
+    private static extern bool WriteConsoleOutput (
+        nint hConsoleOutput,
+        CharInfo [] lpBuffer,
+        Coord dwBufferSize,
+        Coord dwBufferCoord,
+        ref SmallRect lpWriteRegion
+    );
+
+    [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
+    private static extern bool WriteConsole (
+        nint hConsoleOutput,
+        string lpbufer,
+        uint NumberOfCharsToWriten,
+        out uint lpNumberOfCharsWritten,
+        nint lpReserved
+    );
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    static extern bool FlushFileBuffers (nint hFile);
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct ConsoleCursorInfo
+    {
+        /// <summary>
+        /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100.
+        /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal
+        /// line at the bottom of the cell.
+        /// </summary>
+        public uint dwSize;
+        public bool bVisible;
+    }
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool GetConsoleCursorInfo (nint hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo);
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern nint CreateConsoleScreenBuffer (
+        DesiredAccess dwDesiredAccess,
+        ShareMode dwShareMode,
+        nint secutiryAttributes,
+        uint flags,
+        nint screenBufferData
+    );
+
+    internal static nint INVALID_HANDLE_VALUE = new (-1);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool SetConsoleActiveScreenBuffer (nint Handle);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents);
+
+    internal uint GetNumberOfConsoleInputEvents ()
+    {
+        if (!GetNumberOfConsoleInputEvents (_inputHandle, out uint numOfEvents))
+        {
+            Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
+
+            return 0;
+        }
+
+        return numOfEvents;
+    }
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool FlushConsoleInputBuffer (nint handle);
+
+    internal void FlushConsoleInputBuffer ()
+    {
+        if (!FlushConsoleInputBuffer (_inputHandle))
+        {
+            Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
+        }
+    }
+
+    private int _retries;
+
+    public InputRecord [] ReadConsoleInput ()
+    {
+        const int bufferSize = 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 _, bufferSize, out uint eventsRead) && eventsRead > 0)
+                {
+                    // Read the input since it is available
+                    ReadConsoleInput (
+                                      _inputHandle,
+                                      out inputRecord,
+                                      bufferSize,
+                                      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, bufferSize, 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 EscSeqReqStatus 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 && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
+                {
+                    _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
+
+                    lock (seqReqStatus!.AnsiRequest._responseLock)
+                    {
+                        seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
+                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
+                    }
+
+                    _retries = 0;
+                }
+                else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
+                {
+                    if (_retries > 1)
+                    {
+                        if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
+                        {
+                            lock (seqReqStatus!.AnsiRequest._responseLock)
+                            {
+                                _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _);
+
+                                seqReqStatus.AnsiRequest.Response = string.Empty;
+                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
+                            }
+                        }
+
+                        _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 ();
+
+		[DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+		static extern bool ShowWindow (IntPtr hWnd, int nCmdShow);
+
+		public const int HIDE = 0;
+		public const int MAXIMIZE = 3;
+		public const int MINIMIZE = 6;
+		public const int RESTORE = 9;
+
+		internal void ShowWindow (int state)
+		{
+			IntPtr thisConsole = GetConsoleWindow ();
+			ShowWindow (thisConsole, state);
+		}
+#endif
+
+    // See: https://github.com/gui-cs/Terminal.Gui/issues/357
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct CONSOLE_SCREEN_BUFFER_INFOEX
+    {
+        public uint cbSize;
+        public Coord dwSize;
+        public Coord dwCursorPosition;
+        public ushort wAttributes;
+        public SmallRect srWindow;
+        public Coord dwMaximumWindowSize;
+        public ushort wPopupAttributes;
+        public bool bFullscreenSupported;
+
+        [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
+        public COLORREF [] ColorTable;
+    }
+
+    [StructLayout (LayoutKind.Explicit, Size = 4)]
+    public struct COLORREF
+    {
+        public COLORREF (byte r, byte g, byte b)
+        {
+            Value = 0;
+            R = r;
+            G = g;
+            B = b;
+        }
+
+        public COLORREF (uint value)
+        {
+            R = 0;
+            G = 0;
+            B = 0;
+            Value = value & 0x00FFFFFF;
+        }
+
+        [FieldOffset (0)]
+        public byte R;
+
+        [FieldOffset (1)]
+        public byte G;
+
+        [FieldOffset (2)]
+        public byte B;
+
+        [FieldOffset (0)]
+        public uint Value;
+    }
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool SetConsoleWindowInfo (
+        nint hConsoleOutput,
+        bool bAbsolute,
+        [In] ref SmallRect lpConsoleWindow
+    );
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern Coord GetLargestConsoleWindowSize (
+        nint hConsoleOutput
+    );
+}

+ 2 - 1104
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs → Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs

@@ -1,4 +1,5 @@
-//
+// TODO: #nullable enable
+// 
 // WindowsDriver.cs: Windows specific driver
 //
 
@@ -23,1109 +24,6 @@ using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui;
 
-internal class WindowsConsole
-{
-    internal WindowsMainLoop _mainLoop;
-
-    public const int STD_OUTPUT_HANDLE = -11;
-    public const int STD_INPUT_HANDLE = -10;
-
-    private readonly nint _inputHandle;
-    private nint _outputHandle;
-    //private nint _screenBuffer;
-    private readonly uint _originalConsoleMode;
-    private CursorVisibility? _initialCursorVisibility;
-    private CursorVisibility? _currentCursorVisibility;
-    private CursorVisibility? _pendingCursorVisibility;
-    private readonly StringBuilder _stringBuilder = new (256 * 1024);
-    private string _lastWrite = string.Empty;
-
-    public WindowsConsole ()
-    {
-        _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
-        _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
-        _originalConsoleMode = ConsoleMode;
-        uint newConsoleMode = _originalConsoleMode;
-        newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
-        newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
-        newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
-        ConsoleMode = newConsoleMode;
-    }
-
-    private CharInfo [] _originalStdOutChars;
-
-    public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
-    {
-        //Debug.WriteLine ("WriteToConsole");
-
-        //if (_screenBuffer == nint.Zero)
-        //{
-        //    ReadFromConsoleOutput (size, bufferSize, ref window);
-        //}
-
-        var result = false;
-
-        if (force16Colors)
-        {
-            var i = 0;
-            CharInfo [] ci = new CharInfo [charInfoBuffer.Length];
-
-            foreach (ExtendedCharInfo info in charInfoBuffer)
-            {
-                ci [i++] = new CharInfo
-                {
-                    Char = new CharUnion { UnicodeChar = info.Char },
-                    Attributes =
-                        (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
-                };
-            }
-
-            result = WriteConsoleOutput (_outputHandle, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window);
-        }
-        else
-        {
-            _stringBuilder.Clear ();
-
-            _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
-            _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0));
-
-            Attribute? prev = null;
-
-            foreach (ExtendedCharInfo info in charInfoBuffer)
-            {
-                Attribute attr = info.Attribute;
-
-                if (attr != prev)
-                {
-                    prev = attr;
-                    _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')
-                {
-                    if (!info.Empty)
-                    {
-                        _stringBuilder.Append (info.Char);
-                    }
-                }
-                else
-                {
-                    _stringBuilder.Append (' ');
-                }
-            }
-
-            _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
-            _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
-
-            var s = _stringBuilder.ToString ();
-
-            // TODO: requires extensive testing if we go down this route
-            // If console output has changed
-            if (s != _lastWrite)
-            {
-                // supply console with the new content
-                result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
-            }
-
-            _lastWrite = s;
-
-            foreach (var sixel in Application.Sixel)
-            {
-                SetCursorPosition (new Coord ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y));
-                WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
-            }
-        }
-
-        if (!result)
-        {
-            int err = Marshal.GetLastWin32Error ();
-
-            if (err != 0)
-            {
-                throw new Win32Exception (err);
-            }
-        }
-
-        return result;
-    }
-
-    internal bool WriteANSI (string ansi)
-    {
-        if (WriteConsole (_outputHandle, ansi, (uint)ansi.Length, out uint _, nint.Zero))
-        {
-            // Flush the output to make sure it's sent immediately
-            return FlushFileBuffers (_outputHandle);
-        }
-
-        return false;
-    }
-
-    public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window)
-    {
-        //_screenBuffer = CreateConsoleScreenBuffer (
-        //                                           DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
-        //                                           ShareMode.FileShareRead | ShareMode.FileShareWrite,
-        //                                           nint.Zero,
-        //                                           1,
-        //                                           nint.Zero
-        //                                          );
-
-        //if (_screenBuffer == INVALID_HANDLE_VALUE)
-        //{
-        //    int err = Marshal.GetLastWin32Error ();
-
-        //    if (err != 0)
-        //    {
-        //        throw new Win32Exception (err);
-        //    }
-        //}
-
-        SetInitialCursorVisibility ();
-
-        //if (!SetConsoleActiveScreenBuffer (_screenBuffer))
-        //{
-        //    throw new Win32Exception (Marshal.GetLastWin32Error ());
-        //}
-
-        _originalStdOutChars = new CharInfo [size.Height * size.Width];
-
-        if (!ReadConsoleOutput (_outputHandle, _originalStdOutChars, coords, new Coord { X = 0, Y = 0 }, ref window))
-        {
-            throw new Win32Exception (Marshal.GetLastWin32Error ());
-        }
-    }
-
-    public bool SetCursorPosition (Coord position)
-    {
-        return SetConsoleCursorPosition (_outputHandle, position);
-    }
-
-    public void SetInitialCursorVisibility ()
-    {
-        if (_initialCursorVisibility.HasValue == false && GetCursorVisibility (out CursorVisibility visibility))
-        {
-            _initialCursorVisibility = visibility;
-        }
-    }
-
-    public bool GetCursorVisibility (out CursorVisibility visibility)
-    {
-        if (_outputHandle == nint.Zero)
-        {
-            visibility = CursorVisibility.Invisible;
-
-            return false;
-        }
-
-        if (!GetConsoleCursorInfo (_outputHandle, out ConsoleCursorInfo info))
-        {
-            int err = Marshal.GetLastWin32Error ();
-
-            if (err != 0)
-            {
-                throw new Win32Exception (err);
-            }
-
-            visibility = CursorVisibility.Default;
-
-            return false;
-        }
-
-        if (!info.bVisible)
-        {
-            visibility = CursorVisibility.Invisible;
-        }
-        else if (info.dwSize > 50)
-        {
-            visibility = CursorVisibility.Default;
-        }
-        else
-        {
-            visibility = CursorVisibility.Default;
-        }
-
-        return true;
-    }
-
-    public bool EnsureCursorVisibility ()
-    {
-        if (_initialCursorVisibility.HasValue && _pendingCursorVisibility.HasValue && SetCursorVisibility (_pendingCursorVisibility.Value))
-        {
-            _pendingCursorVisibility = null;
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public void ForceRefreshCursorVisibility ()
-    {
-        if (_currentCursorVisibility.HasValue)
-        {
-            _pendingCursorVisibility = _currentCursorVisibility;
-            _currentCursorVisibility = null;
-        }
-    }
-
-    public bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        if (_initialCursorVisibility.HasValue == false)
-        {
-            _pendingCursorVisibility = visibility;
-
-            return false;
-        }
-
-        if (_currentCursorVisibility.HasValue == false || _currentCursorVisibility.Value != visibility)
-        {
-            var info = new ConsoleCursorInfo
-            {
-                dwSize = (uint)visibility & 0x00FF,
-                bVisible = ((uint)visibility & 0xFF00) != 0
-            };
-
-            if (!SetConsoleCursorInfo (_outputHandle, ref info))
-            {
-                return false;
-            }
-
-            _currentCursorVisibility = visibility;
-        }
-
-        return true;
-    }
-
-    public void Cleanup ()
-    {
-        if (_initialCursorVisibility.HasValue)
-        {
-            SetCursorVisibility (_initialCursorVisibility.Value);
-        }
-
-        //SetConsoleOutputWindow (out _);
-
-        ConsoleMode = _originalConsoleMode;
-
-        _outputHandle = CreateConsoleScreenBuffer (
-                                                   DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
-                                                   ShareMode.FileShareRead | ShareMode.FileShareWrite,
-                                                   nint.Zero,
-                                                   1,
-                                                   nint.Zero
-                                                  );
-
-        if (!SetConsoleActiveScreenBuffer (_outputHandle))
-        {
-            int err = Marshal.GetLastWin32Error ();
-            Console.WriteLine ("Error: {0}", err);
-        }
-
-        //if (_screenBuffer != nint.Zero)
-        //{
-        //    CloseHandle (_screenBuffer);
-        //}
-
-        //_screenBuffer = nint.Zero;
-    }
-
-    //internal Size GetConsoleBufferWindow (out Point position)
-    //{
-    //    if (_screenBuffer == nint.Zero)
-    //    {
-    //        position = Point.Empty;
-
-    //        return Size.Empty;
-    //    }
-
-    //    var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
-    //    csbi.cbSize = (uint)Marshal.SizeOf (csbi);
-
-    //    if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
-    //    {
-    //        //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
-    //        position = Point.Empty;
-
-    //        return Size.Empty;
-    //    }
-
-    //    Size sz = new (
-    //                   csbi.srWindow.Right - csbi.srWindow.Left + 1,
-    //                   csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
-    //    position = new (csbi.srWindow.Left, csbi.srWindow.Top);
-
-    //    return sz;
-    //}
-
-    internal Size GetConsoleOutputWindow (out Point position)
-    {
-        var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
-        csbi.cbSize = (uint)Marshal.SizeOf (csbi);
-
-        if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
-        {
-            throw new Win32Exception (Marshal.GetLastWin32Error ());
-        }
-
-        Size sz = new (
-                       csbi.srWindow.Right - csbi.srWindow.Left + 1,
-                       csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
-        position = new (csbi.srWindow.Left, csbi.srWindow.Top);
-
-        return sz;
-    }
-
-    //internal Size SetConsoleWindow (short cols, short rows)
-    //{
-    //    var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
-    //    csbi.cbSize = (uint)Marshal.SizeOf (csbi);
-
-    //    if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
-    //    {
-    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
-    //    }
-
-    //    Coord maxWinSize = GetLargestConsoleWindowSize (_screenBuffer);
-    //    short newCols = Math.Min (cols, maxWinSize.X);
-    //    short newRows = Math.Min (rows, maxWinSize.Y);
-    //    csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1));
-    //    csbi.srWindow = new SmallRect (0, 0, newCols, newRows);
-    //    csbi.dwMaximumWindowSize = new Coord (newCols, newRows);
-
-    //    if (!SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
-    //    {
-    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
-    //    }
-
-    //    var winRect = new SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0));
-
-    //    if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
-    //    {
-    //        //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
-    //        return new (cols, rows);
-    //    }
-
-    //    SetConsoleOutputWindow (csbi);
-
-    //    return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1);
-    //}
-
-    //private void SetConsoleOutputWindow (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
-    //{
-    //    if (_screenBuffer != nint.Zero && !SetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
-    //    {
-    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
-    //    }
-    //}
-
-    //internal Size SetConsoleOutputWindow (out Point position)
-    //{
-    //    if (_screenBuffer == nint.Zero)
-    //    {
-    //        position = Point.Empty;
-
-    //        return Size.Empty;
-    //    }
-
-    //    var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
-    //    csbi.cbSize = (uint)Marshal.SizeOf (csbi);
-
-    //    if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
-    //    {
-    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
-    //    }
-
-    //    Size sz = new (
-    //                       csbi.srWindow.Right - csbi.srWindow.Left + 1,
-    //                       Math.Max (csbi.srWindow.Bottom - csbi.srWindow.Top + 1, 0));
-    //    position = new (csbi.srWindow.Left, csbi.srWindow.Top);
-    //    SetConsoleOutputWindow (csbi);
-    //    var winRect = new SmallRect (0, 0, (short)(sz.Width - 1), (short)Math.Max (sz.Height - 1, 0));
-
-    //    if (!SetConsoleScreenBufferInfoEx (_outputHandle, ref csbi))
-    //    {
-    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
-    //    }
-
-    //    if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect))
-    //    {
-    //        throw new Win32Exception (Marshal.GetLastWin32Error ());
-    //    }
-
-    //    return sz;
-    //}
-
-    private uint ConsoleMode
-    {
-        get
-        {
-            GetConsoleMode (_inputHandle, out uint v);
-
-            return v;
-        }
-        set => SetConsoleMode (_inputHandle, value);
-    }
-
-    [Flags]
-    public enum ConsoleModes : uint
-    {
-        EnableProcessedInput = 1,
-        EnableMouseInput = 16,
-        EnableQuickEditMode = 64,
-        EnableExtendedFlags = 128
-    }
-
-    [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
-    public struct KeyEventRecord
-    {
-        [FieldOffset (0)]
-        [MarshalAs (UnmanagedType.Bool)]
-        public bool bKeyDown;
-
-        [FieldOffset (4)]
-        [MarshalAs (UnmanagedType.U2)]
-        public ushort wRepeatCount;
-
-        [FieldOffset (6)]
-        [MarshalAs (UnmanagedType.U2)]
-        public VK wVirtualKeyCode;
-
-        [FieldOffset (8)]
-        [MarshalAs (UnmanagedType.U2)]
-        public ushort wVirtualScanCode;
-
-        [FieldOffset (10)]
-        public char UnicodeChar;
-
-        [FieldOffset (12)]
-        [MarshalAs (UnmanagedType.U4)]
-        public ControlKeyState dwControlKeyState;
-
-        public readonly override string ToString ()
-        {
-            return
-                $"[KeyEventRecord({(bKeyDown ? "down" : "up")},{wRepeatCount},{wVirtualKeyCode},{wVirtualScanCode},{new Rune (UnicodeChar).MakePrintable ()},{dwControlKeyState})]";
-        }
-    }
-
-    [Flags]
-    public enum ButtonState
-    {
-        NoButtonPressed = 0,
-        Button1Pressed = 1,
-        Button2Pressed = 4,
-        Button3Pressed = 8,
-        Button4Pressed = 16,
-        RightmostButtonPressed = 2
-    }
-
-    [Flags]
-    public enum ControlKeyState
-    {
-        NoControlKeyPressed = 0,
-        RightAltPressed = 1,
-        LeftAltPressed = 2,
-        RightControlPressed = 4,
-        LeftControlPressed = 8,
-        ShiftPressed = 16,
-        NumlockOn = 32,
-        ScrolllockOn = 64,
-        CapslockOn = 128,
-        EnhancedKey = 256
-    }
-
-    [Flags]
-    public enum EventFlags
-    {
-        NoEvent = 0,
-        MouseMoved = 1,
-        DoubleClick = 2,
-        MouseWheeled = 4,
-        MouseHorizontalWheeled = 8
-    }
-
-    [StructLayout (LayoutKind.Explicit)]
-    public struct MouseEventRecord
-    {
-        [FieldOffset (0)]
-        public Coord MousePosition;
-
-        [FieldOffset (4)]
-        public ButtonState ButtonState;
-
-        [FieldOffset (8)]
-        public ControlKeyState ControlKeyState;
-
-        [FieldOffset (12)]
-        public EventFlags EventFlags;
-
-        public readonly override string ToString () { return $"[Mouse{MousePosition},{ButtonState},{ControlKeyState},{EventFlags}]"; }
-    }
-
-    public struct WindowBufferSizeRecord
-    {
-        public Coord _size;
-
-        public WindowBufferSizeRecord (short x, short y) { _size = new Coord (x, y); }
-
-        public readonly override string ToString () { return $"[WindowBufferSize{_size}"; }
-    }
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct MenuEventRecord
-    {
-        public uint dwCommandId;
-    }
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct FocusEventRecord
-    {
-        public uint bSetFocus;
-    }
-
-    public enum EventType : ushort
-    {
-        Focus = 0x10,
-        Key = 0x1,
-        Menu = 0x8,
-        Mouse = 2,
-        WindowBufferSize = 4
-    }
-
-    [StructLayout (LayoutKind.Explicit)]
-    public struct InputRecord
-    {
-        [FieldOffset (0)]
-        public EventType EventType;
-
-        [FieldOffset (4)]
-        public KeyEventRecord KeyEvent;
-
-        [FieldOffset (4)]
-        public MouseEventRecord MouseEvent;
-
-        [FieldOffset (4)]
-        public WindowBufferSizeRecord WindowBufferSizeEvent;
-
-        [FieldOffset (4)]
-        public MenuEventRecord MenuEvent;
-
-        [FieldOffset (4)]
-        public FocusEventRecord FocusEvent;
-
-        public readonly override string ToString ()
-        {
-            return EventType switch
-            {
-                EventType.Focus => FocusEvent.ToString (),
-                EventType.Key => KeyEvent.ToString (),
-                EventType.Menu => MenuEvent.ToString (),
-                EventType.Mouse => MouseEvent.ToString (),
-                EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (),
-                _ => "Unknown event type: " + EventType
-            };
-        }
-    }
-
-    [Flags]
-    private enum ShareMode : uint
-    {
-        FileShareRead = 1,
-        FileShareWrite = 2
-    }
-
-    [Flags]
-    private enum DesiredAccess : uint
-    {
-        GenericRead = 2147483648,
-        GenericWrite = 1073741824
-    }
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct ConsoleScreenBufferInfo
-    {
-        public Coord dwSize;
-        public Coord dwCursorPosition;
-        public ushort wAttributes;
-        public SmallRect srWindow;
-        public Coord dwMaximumWindowSize;
-    }
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct Coord
-    {
-        public short X;
-        public short Y;
-
-        public Coord (short x, short y)
-        {
-            X = x;
-            Y = y;
-        }
-
-        public readonly override string ToString () { return $"({X},{Y})"; }
-    }
-
-    [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
-    public struct CharUnion
-    {
-        [FieldOffset (0)]
-        public char UnicodeChar;
-
-        [FieldOffset (0)]
-        public byte AsciiChar;
-    }
-
-    [StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
-    public struct CharInfo
-    {
-        [FieldOffset (0)]
-        public CharUnion Char;
-
-        [FieldOffset (2)]
-        public ushort Attributes;
-    }
-
-    public struct ExtendedCharInfo
-    {
-        public char Char { get; set; }
-        public Attribute Attribute { get; set; }
-        public bool Empty { get; set; } // TODO: Temp hack until virtual terminal sequences
-
-        public ExtendedCharInfo (char character, Attribute attribute)
-        {
-            Char = character;
-            Attribute = attribute;
-            Empty = false;
-        }
-    }
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct SmallRect
-    {
-        public short Left;
-        public short Top;
-        public short Right;
-        public short Bottom;
-
-        public SmallRect (short left, short top, short right, short bottom)
-        {
-            Left = left;
-            Top = top;
-            Right = right;
-            Bottom = bottom;
-        }
-
-        public static void MakeEmpty (ref SmallRect rect) { rect.Left = -1; }
-
-        public static void Update (ref SmallRect rect, short col, short row)
-        {
-            if (rect.Left == -1)
-            {
-                rect.Left = rect.Right = col;
-                rect.Bottom = rect.Top = row;
-
-                return;
-            }
-
-            if (col >= rect.Left && col <= rect.Right && row >= rect.Top && row <= rect.Bottom)
-            {
-                return;
-            }
-
-            if (col < rect.Left)
-            {
-                rect.Left = col;
-            }
-
-            if (col > rect.Right)
-            {
-                rect.Right = col;
-            }
-
-            if (row < rect.Top)
-            {
-                rect.Top = row;
-            }
-
-            if (row > rect.Bottom)
-            {
-                rect.Bottom = row;
-            }
-        }
-
-        public readonly override string ToString () { return $"Left={Left},Top={Top},Right={Right},Bottom={Bottom}"; }
-    }
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct ConsoleKeyInfoEx
-    {
-        public ConsoleKeyInfo ConsoleKeyInfo;
-        public bool CapsLock;
-        public bool NumLock;
-        public bool ScrollLock;
-
-        public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock)
-        {
-            ConsoleKeyInfo = consoleKeyInfo;
-            CapsLock = capslock;
-            NumLock = numlock;
-            ScrollLock = scrolllock;
-        }
-
-        /// <summary>
-        ///     Prints a ConsoleKeyInfoEx structure
-        /// </summary>
-        /// <param name="ex"></param>
-        /// <returns></returns>
-        public readonly string ToString (ConsoleKeyInfoEx ex)
-        {
-            var ke = new Key ((KeyCode)ex.ConsoleKeyInfo.KeyChar);
-            var sb = new StringBuilder ();
-            sb.Append ($"Key: {(KeyCode)ex.ConsoleKeyInfo.Key} ({ex.ConsoleKeyInfo.Key})");
-            sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
-            sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
-            sb.Append ((ex.ConsoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
-            sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)ex.ConsoleKeyInfo.KeyChar}) ");
-            sb.Append (ex.CapsLock ? "caps," : string.Empty);
-            sb.Append (ex.NumLock ? "num," : string.Empty);
-            sb.Append (ex.ScrollLock ? "scroll," : string.Empty);
-            string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
-
-            return $"[ConsoleKeyInfoEx({s})]";
-        }
-    }
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern nint GetStdHandle (int nStdHandle);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool CloseHandle (nint handle);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    public static extern bool PeekConsoleInput (nint hConsoleInput, out InputRecord lpBuffer, uint nLength, out uint lpNumberOfEventsRead);
-
-    [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
-    public static extern bool ReadConsoleInput (
-        nint hConsoleInput,
-        out InputRecord lpBuffer,
-        uint nLength,
-        out uint lpNumberOfEventsRead
-    );
-
-    [DllImport ("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
-    private static extern bool ReadConsoleOutput (
-        nint hConsoleOutput,
-        [Out] CharInfo [] lpBuffer,
-        Coord dwBufferSize,
-        Coord dwBufferCoord,
-        ref SmallRect lpReadRegion
-    );
-
-    // TODO: This API is obsolete. See https://learn.microsoft.com/en-us/windows/console/writeconsoleoutput
-    [DllImport ("kernel32.dll", EntryPoint = "WriteConsoleOutputW", SetLastError = true, CharSet = CharSet.Unicode)]
-    private static extern bool WriteConsoleOutput (
-        nint hConsoleOutput,
-        CharInfo [] lpBuffer,
-        Coord dwBufferSize,
-        Coord dwBufferCoord,
-        ref SmallRect lpWriteRegion
-    );
-
-    [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
-    private static extern bool WriteConsole (
-        nint hConsoleOutput,
-        string lpbufer,
-        uint NumberOfCharsToWriten,
-        out uint lpNumberOfCharsWritten,
-        nint lpReserved
-    );
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    static extern bool FlushFileBuffers (nint hFile);
-
-    [DllImport ("kernel32.dll")]
-    private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct ConsoleCursorInfo
-    {
-        /// <summary>
-        /// The percentage of the character cell that is filled by the cursor.This value is between 1 and 100.
-        /// The cursor appearance varies, ranging from completely filling the cell to showing up as a horizontal
-        /// line at the bottom of the cell.
-        /// </summary>
-        public uint dwSize;
-        public bool bVisible;
-    }
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref ConsoleCursorInfo lpConsoleCursorInfo);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool GetConsoleCursorInfo (nint hConsoleOutput, out ConsoleCursorInfo lpConsoleCursorInfo);
-
-    [DllImport ("kernel32.dll")]
-    private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
-
-    [DllImport ("kernel32.dll")]
-    private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern nint CreateConsoleScreenBuffer (
-        DesiredAccess dwDesiredAccess,
-        ShareMode dwShareMode,
-        nint secutiryAttributes,
-        uint flags,
-        nint screenBufferData
-    );
-
-    internal static nint INVALID_HANDLE_VALUE = new (-1);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleActiveScreenBuffer (nint Handle);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool GetNumberOfConsoleInputEvents (nint handle, out uint lpcNumberOfEvents);
-
-    internal uint GetNumberOfConsoleInputEvents ()
-    {
-        if (!GetNumberOfConsoleInputEvents (_inputHandle, out uint numOfEvents))
-        {
-            Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
-
-            return 0;
-        }
-
-        return numOfEvents;
-    }
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool FlushConsoleInputBuffer (nint handle);
-
-    internal void FlushConsoleInputBuffer ()
-    {
-        if (!FlushConsoleInputBuffer (_inputHandle))
-        {
-            Console.WriteLine ($"Error: {Marshal.GetLastWin32Error ()}");
-        }
-    }
-
-    private int _retries;
-
-    public InputRecord [] ReadConsoleInput ()
-    {
-        const int bufferSize = 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 _, bufferSize, out uint eventsRead) && eventsRead > 0)
-                {
-                    // Read the input since it is available
-                    ReadConsoleInput (
-                                      _inputHandle,
-                                      out inputRecord,
-                                      bufferSize,
-                                      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, bufferSize, 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 EscSeqReqStatus 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 && EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
-                {
-                    _mainLoop.EscSeqRequests.Statuses.TryDequeue (out EscSeqReqStatus seqReqStatus);
-
-                    lock (seqReqStatus!.AnsiRequest._responseLock)
-                    {
-                        seqReqStatus.AnsiRequest.Response = ansiSequence.ToString ();
-                        seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, seqReqStatus.AnsiRequest.Response);
-                    }
-
-                    _retries = 0;
-                }
-                else if (EscSeqUtils.IncompleteCkInfos is null && _mainLoop.EscSeqRequests is { Statuses.Count: > 0 })
-                {
-                    if (_retries > 1)
-                    {
-                        if (_mainLoop.EscSeqRequests.Statuses.TryPeek (out EscSeqReqStatus seqReqStatus) && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.Response))
-                        {
-                            lock (seqReqStatus!.AnsiRequest._responseLock)
-                            {
-                                _mainLoop.EscSeqRequests.Statuses.TryDequeue (out _);
-
-                                seqReqStatus.AnsiRequest.Response = string.Empty;
-                                seqReqStatus.AnsiRequest.RaiseResponseFromInput (seqReqStatus.AnsiRequest, string.Empty);
-                            }
-                        }
-
-                        _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 ();
-
-		[DllImport ("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
-		static extern bool ShowWindow (IntPtr hWnd, int nCmdShow);
-
-		public const int HIDE = 0;
-		public const int MAXIMIZE = 3;
-		public const int MINIMIZE = 6;
-		public const int RESTORE = 9;
-
-		internal void ShowWindow (int state)
-		{
-			IntPtr thisConsole = GetConsoleWindow ();
-			ShowWindow (thisConsole, state);
-		}
-#endif
-
-    // See: https://github.com/gui-cs/Terminal.Gui/issues/357
-
-    [StructLayout (LayoutKind.Sequential)]
-    public struct CONSOLE_SCREEN_BUFFER_INFOEX
-    {
-        public uint cbSize;
-        public Coord dwSize;
-        public Coord dwCursorPosition;
-        public ushort wAttributes;
-        public SmallRect srWindow;
-        public Coord dwMaximumWindowSize;
-        public ushort wPopupAttributes;
-        public bool bFullscreenSupported;
-
-        [MarshalAs (UnmanagedType.ByValArray, SizeConst = 16)]
-        public COLORREF [] ColorTable;
-    }
-
-    [StructLayout (LayoutKind.Explicit, Size = 4)]
-    public struct COLORREF
-    {
-        public COLORREF (byte r, byte g, byte b)
-        {
-            Value = 0;
-            R = r;
-            G = g;
-            B = b;
-        }
-
-        public COLORREF (uint value)
-        {
-            R = 0;
-            G = 0;
-            B = 0;
-            Value = value & 0x00FFFFFF;
-        }
-
-        [FieldOffset (0)]
-        public byte R;
-
-        [FieldOffset (1)]
-        public byte G;
-
-        [FieldOffset (2)]
-        public byte B;
-
-        [FieldOffset (0)]
-        public uint Value;
-    }
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX ConsoleScreenBufferInfo);
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleWindowInfo (
-        nint hConsoleOutput,
-        bool bAbsolute,
-        [In] ref SmallRect lpConsoleWindow
-    );
-
-    [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern Coord GetLargestConsoleWindowSize (
-        nint hConsoleOutput
-    );
-}
-
 internal class WindowsDriver : ConsoleDriver
 {
     private readonly bool _isWindowsTerminal;

+ 1 - 2
Terminal.Gui/Terminal.Gui.csproj

@@ -164,8 +164,7 @@
 	  <Message Text="Found packages: @(NuGetPackages)" Importance="high" />
 
 	  <!-- Copy files only if found -->
-	  <Copy SourceFiles="@(NuGetPackages)" DestinationFolder="$(LocalPackagesPath)" SkipUnchangedFiles="false"
-	        Condition="@(NuGetPackages) != ''" />
+	  <Copy SourceFiles="@(NuGetPackages)" DestinationFolder="$(LocalPackagesPath)" SkipUnchangedFiles="false" Condition="@(NuGetPackages) != ''" />
 
 	  <!-- Log success -->
 	  <Message Text="Copy completed successfully." Importance="high" />

+ 103 - 97
UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
 using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
@@ -12,14 +11,13 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
 {
     private GraphView _graphView;
 
-    private DateTime start = DateTime.Now;
     private ScatterSeries _sentSeries;
     private ScatterSeries _answeredSeries;
 
-    private List<DateTime> sends = new ();
+    private readonly List<DateTime> _sends = new ();
 
-    private object lockAnswers = new object ();
-    private Dictionary<DateTime, string> answers = new ();
+    private readonly object _lockAnswers = new ();
+    private readonly Dictionary<DateTime, string> _answers = new ();
     private Label _lblSummary;
 
     public override void Main ()
@@ -27,18 +25,18 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
         // Init
         Application.Init ();
 
-        TabView tv = new TabView
+        var tv = new TabView
         {
             Width = Dim.Fill (),
             Height = Dim.Fill ()
         };
 
-        Tab single = new Tab ();
-        single.DisplayText = "Single";
+        var single = new Tab ();
+        single.DisplayText = "_Single";
         single.View = BuildSingleTab ();
 
         Tab bulk = new ();
-        bulk.DisplayText = "Multi";
+        bulk.DisplayText = "_Multi";
         bulk.View = BuildBulkTab ();
 
         tv.AddTab (single, true);
@@ -47,7 +45,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
         // Setup - Create a top-level application window and configure it.
         Window appWindow = new ()
         {
-            Title = GetQuitKeyAndName (),
+            Title = GetQuitKeyAndName ()
         };
 
         appWindow.Add (tv);
@@ -61,18 +59,20 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
         // Shutdown - Calling Application.Shutdown is required.
         Application.Shutdown ();
     }
+
     private View BuildSingleTab ()
     {
-        View w = new View ()
+        var w = new View
         {
-            Width = Dim.Fill(),
+            Width = Dim.Fill (),
             Height = Dim.Fill (),
             CanFocus = true
         };
 
         w.Padding.Thickness = new (1);
 
-        var scrRequests = new List<string>
+        // 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",
@@ -80,18 +80,19 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
             "CSI_SendDeviceAttributes2"
         };
 
-        var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper<string> (new (scrRequests)) };
+        var cbRequests = new ComboBox { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper<string> (new (scrRequests)) };
         w.Add (cbRequests);
 
-        var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:" };
+        // 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 Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "Value:" };
+        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 Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "Terminator:" };
+        label = new () { X = Pos.Right (tfValue) + 1, 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);
 
@@ -102,101 +103,111 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
                                                   return;
                                               }
 
-                                              var selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem];
+                                              string selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem];
                                               AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null;
+
                                               switch (selAnsiEscapeSequenceRequestName)
                                               {
                                                   case "CSI_SendDeviceAttributes":
                                                       selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes;
+
                                                       break;
                                                   case "CSI_ReportTerminalSizeInChars":
                                                       selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportTerminalSizeInChars;
+
                                                       break;
                                                   case "CSI_RequestCursorPositionReport":
                                                       selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_RequestCursorPositionReport;
+
                                                       break;
                                                   case "CSI_SendDeviceAttributes2":
                                                       selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes2;
+
                                                       break;
                                               }
 
                                               tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : "";
-                                              tfValue.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Value ?? "" : "";
+
+                                              tfValue.Text = selAnsiEscapeSequenceRequest is { }
+                                                                 ? selAnsiEscapeSequenceRequest.ExpectedResponseValue ?? ""
+                                                                 : "";
                                               tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : "";
                                           };
+
         // Forces raise cbRequests.SelectedItemChanged to update TextFields
         cbRequests.SelectedItem = 0;
 
-        label = new Label { Y = Pos.Bottom (tfRequest) + 2, Text = "Response:" };
+        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 Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "Error:" };
+        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 Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "Value:" };
+        label = new () { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "E_xpectedResponseValue:" };
         var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true };
         w.Add (label, tvValue);
 
-        label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "Terminator:" };
+        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 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,
-                                      Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
-                                  };
-
-                                  var success = AnsiEscapeSequenceRequest.TryExecuteAnsiRequest (
-                                       ansiEscapeSequenceRequest,
-                                       out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse
-                                      );
-
-                                  tvResponse.Text = ansiEscapeSequenceResponse.Response;
-                                  tvError.Text = ansiEscapeSequenceResponse.Error;
-                                  tvValue.Text = ansiEscapeSequenceResponse.Value ?? "";
-                                  tvTerminator.Text = ansiEscapeSequenceResponse.Terminator;
-
-                                  if (success)
-                                  {
-                                      lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"];
-                                      lblSuccess.Text = "Successful";
-                                  }
-                                  else
-                                  {
-                                      lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"];
-                                      lblSuccess.Text = "Error";
-                                  }
-                              };
+                                 {
+                                     var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest
+                                     {
+                                         Request = tfRequest.Text,
+                                         Terminator = tfTerminator.Text,
+                                         ExpectedResponseValue = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
+                                     };
+
+                                     bool success = AnsiEscapeSequenceRequest.TryRequest (
+                                                                                          ansiEscapeSequenceRequest,
+                                                                                          out AnsiEscapeSequenceResponse ansiEscapeSequenceResponse
+                                                                                         );
+
+                                     tvResponse.Text = ansiEscapeSequenceResponse.Response;
+                                     tvError.Text = ansiEscapeSequenceResponse.Error;
+                                     tvValue.Text = ansiEscapeSequenceResponse.ExpectedResponseValue ?? "";
+                                     tvTerminator.Text = 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 = "You can send other requests by editing the TextFields." });
+        w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "Send other requests by editing the TextFields." });
 
         return w;
     }
 
     private View BuildBulkTab ()
     {
-        View w = new View ()
+        var w = new View
         {
             Width = Dim.Fill (),
             Height = Dim.Fill (),
             CanFocus = true
         };
 
-        var lbl = new Label ()
+        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.",
+            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 ()
         };
@@ -205,50 +216,49 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
                                 TimeSpan.FromMilliseconds (1000),
                                 () =>
                                 {
-                                    lock (lockAnswers)
+                                    lock (_lockAnswers)
                                     {
                                         UpdateGraph ();
 
                                         UpdateResponses ();
                                     }
 
-
-
                                     return true;
                                 });
 
-        var tv = new TextView ()
+        var tv = new TextView
         {
             Y = Pos.Bottom (lbl),
             Width = Dim.Percent (50),
             Height = Dim.Fill ()
         };
 
-
-        var lblDar = new Label ()
+        var lblDar = new Label
         {
             Y = Pos.Bottom (lbl),
             X = Pos.Right (tv) + 1,
-            Text = "DAR per second",
+            Text = "_DAR per second: "
         };
-        var cbDar = new NumericUpDown ()
+
+        var cbDar = new NumericUpDown
         {
             X = Pos.Right (lblDar),
             Y = Pos.Bottom (lbl),
-            Value = 0,
+            Value = 0
         };
 
         cbDar.ValueChanging += (s, e) =>
-        {
-            if (e.NewValue < 0 || e.NewValue > 20)
-            {
-                e.Cancel = true;
-            }
-        };
+                               {
+                                   if (e.NewValue < 0 || e.NewValue > 20)
+                                   {
+                                       e.Cancel = true;
+                                   }
+                               };
         w.Add (cbDar);
 
         int lastSendTime = Environment.TickCount;
-        object lockObj = new object ();
+        var lockObj = new object ();
+
         Application.AddTimeout (
                                 TimeSpan.FromMilliseconds (50),
                                 () =>
@@ -272,8 +282,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
                                     return true;
                                 });
 
-
-        _graphView = new GraphView ()
+        _graphView = new ()
         {
             Y = Pos.Bottom (cbDar),
             X = Pos.Right (tv),
@@ -281,7 +290,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
             Height = Dim.Fill (1)
         };
 
-        _lblSummary = new Label ()
+        _lblSummary = new ()
         {
             Y = Pos.Bottom (_graphView),
             X = Pos.Right (tv),
@@ -299,6 +308,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
 
         return w;
     }
+
     private void UpdateResponses ()
     {
         _lblSummary.Text = GetSummary ();
@@ -307,32 +317,31 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
 
     private string GetSummary ()
     {
-        if (answers.Count == 0)
+        if (_answers.Count == 0)
         {
             return "No requests sent yet";
         }
 
-        var last = answers.Last ().Value;
+        string last = _answers.Last ().Value;
 
-        var unique = answers.Values.Distinct ().Count ();
-        var total = answers.Count;
+        int unique = _answers.Values.Distinct ().Count ();
+        int total = _answers.Count;
 
         return $"Last:{last} U:{unique} T:{total}";
     }
 
     private void SetupGraph ()
     {
+        _graphView.Series.Add (_sentSeries = new ());
+        _graphView.Series.Add (_answeredSeries = new ());
 
-        _graphView.Series.Add (_sentSeries = new ScatterSeries ());
-        _graphView.Series.Add (_answeredSeries = new ScatterSeries ());
-
-        _sentSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightGreen, ColorName16.Black));
-        _answeredSeries.Fill = new GraphCellToRender (new Rune ('.'), new Attribute (ColorName16.BrightRed, ColorName16.Black));
+        _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 PointF (1, 1);
+        _graphView.CellSize = new (1, 1);
         _graphView.MarginBottom = 2;
         _graphView.AxisX.Increment = 1;
         _graphView.AxisX.Text = "Seconds";
@@ -341,40 +350,37 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
 
     private void UpdateGraph ()
     {
-        _sentSeries.Points = sends
+        _sentSeries.Points = _sends
                              .GroupBy (ToSeconds)
                              .Select (g => new PointF (g.Key, g.Count ()))
                              .ToList ();
 
-        _answeredSeries.Points = answers.Keys
+        _answeredSeries.Points = _answers.Keys
                                         .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.SetNeedsDisplay ();
         }
-
     }
 
-    private int ToSeconds (DateTime t)
-    {
-        return (int)(DateTime.Now - t).TotalSeconds;
-    }
+    private int ToSeconds (DateTime t) { return (int)(DateTime.Now - t).TotalSeconds; }
 
     private void SendDar ()
     {
-        sends.Add (DateTime.Now);
-        var result = Application.Driver.WriteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes);
+        _sends.Add (DateTime.Now);
+        string result = Application.Driver.WriteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes);
         HandleResponse (result);
     }
 
     private void HandleResponse (string response)
     {
-        lock (lockAnswers)
+        lock (_lockAnswers)
         {
-            answers.Add (DateTime.Now, response);
+            _answers.Add (DateTime.Now, response);
         }
     }
 }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff